tandem-editor 0.11.2 → 0.13.0
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +3 -3
- package/CHANGELOG.md +201 -72
- package/README.md +141 -238
- package/dist/channel/index.js +211 -81
- package/dist/channel/index.js.map +1 -1
- package/dist/cli/index.js +749 -170
- package/dist/cli/index.js.map +1 -1
- package/dist/client/assets/CoworkSettings-BOYbyKul.js +3 -0
- package/dist/client/assets/event-CNdo2oXa.js +1 -0
- package/dist/client/assets/index-D8uS4cj7.css +1 -0
- package/dist/client/assets/index-Dm_QtxGQ.js +1 -0
- package/dist/client/assets/index-g-KwmRn9.js +271 -0
- package/dist/client/assets/webview-KiZyy_pC.js +1 -0
- package/dist/client/assets/window-DePn7tLG.js +1 -0
- package/dist/client/fonts/OFL-Hanuman.txt +93 -0
- package/dist/client/fonts/OFL-InterTight.txt +93 -0
- package/dist/client/fonts/OFL-JetBrainsMono.txt +93 -0
- package/dist/client/fonts/OFL-SNPro.txt +93 -0
- package/dist/client/fonts/OFL-Sono.txt +93 -0
- package/dist/client/fonts/OFL-SourceSerif4.txt +93 -0
- package/dist/client/fonts/hanuman-latin.woff2 +0 -0
- package/dist/client/fonts/jetbrains-mono-latin.woff2 +0 -0
- package/dist/client/fonts/sn-pro-latin.woff2 +0 -0
- package/dist/client/fonts/sono-latin.woff2 +0 -0
- package/dist/client/fonts/source-serif-4-latin.woff2 +0 -0
- package/dist/client/index.html +206 -17
- package/dist/client/logo.png +0 -0
- package/dist/monitor/index.js +241 -160
- package/dist/monitor/index.js.map +1 -1
- package/dist/server/index.js +22828 -19659
- package/dist/server/index.js.map +1 -1
- package/package.json +12 -4
- package/sample/welcome.md +6 -6
- package/skills/tandem/SKILL.md +15 -0
- package/dist/client/assets/CoworkSettings-DK3jjdwK.js +0 -3
- package/dist/client/assets/index-CfT503n4.js +0 -297
- package/dist/client/assets/index-DeJe09pn.css +0 -1
- package/dist/client/assets/webview-Ben21ZLJ.js +0 -1
- package/dist/client/assets/window-BxBvHL5k.js +0 -1
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/win-path-guard.ts","../../src/cli/uninstall-scrub.ts","../../src/shared/constants.ts","../../src/cli/skill-content.ts","../../src/cli/setup.ts","../../src/shared/cli-runtime.ts","../../src/cli/preflight.ts","../../src/cli/mcp-stdio.ts","../../src/shared/fetch-with-timeout.ts","../../src/shared/utils.ts","../../src/shared/events/types.ts","../../src/channel/event-bridge.ts","../../src/channel/run.ts","../../src/cli/channel.ts","../../src/shared/auth/token-file.ts","../../src/cli/rotate-token.ts","../../src/cli/start.ts","../../src/cli/index.ts"],"sourcesContent":["/**\n * Windows workspace path guard — mirrors the Rust §3 invariant for TypeScript callers.\n *\n * Four steps (in order):\n * a. lstat each ancestor; reject any path whose chain contains a symlink.\n * b. fs.realpath() to canonicalize (safe: symlinks already rejected in (a)).\n * c. Reject UNC paths (\\\\server\\share or \\\\?\\UNC\\...; allow \\\\?\\C:\\...).\n * d. Component-wise containment check under realpath'd %LOCALAPPDATA%\n * (case-insensitive on Windows).\n *\n * Extracted into its own module so it can be unit-tested via vi.mock(\"node:fs\", ...).\n */\n\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\ntype Logger = { warn: (msg: string) => void };\n\n/**\n * Validate that `candidate` is a safe workspace path contained within `realLocalAppData`.\n *\n * @returns the realpath'd canonical path string on success, or null if rejected.\n * Callers are responsible for supplying a realpath'd `realLocalAppData`.\n */\nexport async function assertSafeWorkspacePath(\n candidate: string,\n realLocalAppData: string,\n logger?: Logger,\n): Promise<string | null> {\n const warn = (msg: string) => logger?.warn(`[path-guard] ${msg}`);\n\n // (a) lstat-walk: reject any component that is a symlink.\n if (await hasSymlinkInChain(candidate, warn)) {\n warn(`symlink/reparse point in chain: ${candidate}`);\n return null;\n }\n\n // (b) Canonicalize via realpath (safe now — symlinks already rejected).\n let real: string;\n try {\n real = await fs.realpath(candidate);\n } catch (err) {\n warn(`realpath failed for ${candidate}: ${(err as Error).message}`);\n return null;\n }\n\n // (c) Reject UNC paths.\n if (isUncPath(real)) {\n warn(`UNC path rejected: ${real}`);\n return null;\n }\n\n // (d) Component-wise containment under realLocalAppData (case-insensitive).\n if (!isComponentWiseChild(real, realLocalAppData)) {\n warn(`path outside %LOCALAPPDATA%: ${real}`);\n return null;\n }\n\n return real;\n}\n\n/** Returns true if any ancestor (inclusive) of `p` is a symbolic link. */\nasync function hasSymlinkInChain(p: string, warn: (m: string) => void): Promise<boolean> {\n // Walk from candidate up through all ancestors.\n let current = path.resolve(p);\n const visited = new Set<string>();\n\n while (true) {\n if (visited.has(current)) break;\n visited.add(current);\n\n try {\n const stat = await fs.lstat(current);\n if (stat.isSymbolicLink()) {\n return true;\n }\n } catch (err) {\n // lstat failed — fail closed for safety.\n warn(`lstat failed for ${current}: ${(err as Error).message}`);\n return true;\n }\n\n const parent = path.dirname(current);\n if (parent === current) break; // reached root\n current = parent;\n }\n\n return false;\n}\n\n/** Returns true if the path is a UNC path (\\\\server\\share or \\\\?\\UNC\\...). */\nfunction isUncPath(p: string): boolean {\n // Allow extended-length local paths (\\\\?\\C:\\...) but reject:\n // \\\\?\\UNC\\server\\share — extended UNC\n // \\\\server\\share — classic UNC\n if (p.startsWith(\"\\\\\\\\?\\\\UNC\\\\\") || p.startsWith(\"//?/UNC/\")) return true;\n if (\n (p.startsWith(\"\\\\\\\\\") && !p.startsWith(\"\\\\\\\\?\\\\\")) ||\n (p.startsWith(\"//\") && !p.startsWith(\"//?/\"))\n )\n return true;\n return false;\n}\n\n/**\n * Returns true if `child` is strictly within `root` on a component-wise basis\n * (case-insensitive on Windows).\n */\nfunction isComponentWiseChild(child: string, root: string): boolean {\n // Normalize separators and split on path.sep.\n const normalize = (p: string) => p.replace(/[\\\\/]+/g, path.sep).replace(/[/\\\\]$/, \"\");\n\n const rootNorm = normalize(root);\n const childNorm = normalize(child);\n\n const rootParts = rootNorm.split(path.sep);\n const childParts = childNorm.split(path.sep);\n\n if (childParts.length <= rootParts.length) return false;\n\n for (let i = 0; i < rootParts.length; i++) {\n // Case-insensitive on Windows.\n if (rootParts[i].toLowerCase() !== childParts[i].toLowerCase()) return false;\n }\n return true;\n}\n","/**\n * Tandem `--uninstall-scrub` subcommand.\n *\n * Invoked by the Tauri NSIS installer hook during uninstall. Walks every\n * Cowork workspace under `%LOCALAPPDATA%\\Packages\\Claude_*\\LocalCache\\\n * Roaming\\Claude\\local-agent-mode-sessions\\` and removes the Tandem plugin\n * entry from:\n * - `installed_plugins.json` (remove `mcpServers.tandem`)\n * - `known_marketplaces.json` (remove `marketplaces.tandem`)\n * - `cowork_settings.json` (remove `tandem@tandem` from `enabledPlugins`)\n *\n * Then removes the `Tandem Cowork*` Windows Firewall rules via `netsh`.\n *\n * **Security invariant §10 (ADR):** this runs INSIDE the already-signed\n * `tandem.exe` binary — NOT as a separate `uninstall_scrub.exe`. That\n * prevents binary-planting attacks during uninstall.\n *\n * **Failure policy:** logs every error, exits 0 on clean-or-not-installed,\n * non-zero only on unrecoverable I/O failures. NSIS logs the exit code but\n * does NOT block uninstall — Tandem must always uninstall even if a\n * workspace scrub partially fails.\n *\n * **Token safety:** this scrub READS JSON to find Tandem entries but the\n * removed-entry contents (including the auth token) are NEVER logged.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { promises as fsPromises } from \"node:fs\";\nimport path from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { assertSafeWorkspacePath } from \"./win-path-guard.js\";\n\nconst execFileAsync = promisify(execFile);\n\nconst TANDEM_PLUGIN_ID = \"tandem\";\nconst TANDEM_ENABLED_KEY = \"tandem@tandem\";\nconst FIREWALL_ALLOW_RULE = \"Tandem Cowork\";\nconst FIREWALL_DENY_RULE = \"Tandem Cowork \\u2014 Deny (elevation refused)\";\n\ntype ScrubLogger = {\n info: (msg: string) => void;\n warn: (msg: string) => void;\n error: (msg: string) => void;\n close: () => Promise<void>;\n};\n\nasync function openLogger(): Promise<ScrubLogger> {\n const localAppData = process.env.LOCALAPPDATA;\n if (!localAppData) {\n // No log file — just use stderr.\n const write = (level: string, msg: string): void => {\n process.stderr.write(`[tandem uninstall-scrub ${level}] ${msg}\\n`);\n };\n return {\n info: (m) => write(\"info\", m),\n warn: (m) => write(\"warn\", m),\n error: (m) => write(\"error\", m),\n close: async () => {},\n };\n }\n\n const logDir = path.join(localAppData, \"tandem\", \"Logs\");\n await fsPromises.mkdir(logDir, { recursive: true }).catch(() => {});\n const logPath = path.join(logDir, \"uninstall.log\");\n const stream = await fsPromises.open(logPath, \"a\").catch(() => null);\n\n const write = (level: string, msg: string): void => {\n const line = `[${new Date().toISOString()}] [${level}] ${msg}\\n`;\n process.stderr.write(line);\n if (stream) {\n stream.write(line).catch(() => {});\n }\n };\n\n return {\n info: (m) => write(\"info\", m),\n warn: (m) => write(\"warn\", m),\n error: (m) => write(\"error\", m),\n close: async () => {\n if (stream) await stream.close().catch(() => {});\n },\n };\n}\n\n/**\n * Find all Cowork workspace directories.\n *\n * Matches `%LOCALAPPDATA%\\Packages\\Claude_*\\LocalCache\\Roaming\\Claude\\\n * local-agent-mode-sessions\\<ws-id>\\<vm-id>\\`.\n *\n * Each candidate vm-path is validated by the 4-step Windows path guard before\n * being included — callers receive only safe, realpath'd paths.\n *\n * Returns an empty array on any error (e.g. Cowork not installed).\n */\nexport async function findCoworkWorkspaces(logger: ScrubLogger): Promise<string[]> {\n const localAppData = process.env.LOCALAPPDATA;\n if (!localAppData) {\n logger.info(\"%LOCALAPPDATA% not set — skipping workspace scan\");\n return [];\n }\n\n // Realpath %LOCALAPPDATA% once for use by the path guard.\n let realLad: string;\n try {\n realLad = await fsPromises.realpath(localAppData);\n } catch {\n realLad = localAppData; // best-effort fallback\n }\n\n const packagesDir = path.join(localAppData, \"Packages\");\n let packageEntries: string[];\n try {\n packageEntries = await fsPromises.readdir(packagesDir);\n } catch (err) {\n logger.info(`cannot read Packages dir: ${(err as Error).message}`);\n return [];\n }\n\n const claudePackages = packageEntries.filter((name) => name.startsWith(\"Claude_\"));\n if (claudePackages.length === 0) {\n logger.info(\"no Claude_* package directories found\");\n return [];\n }\n\n const workspaces: string[] = [];\n for (const pkg of claudePackages) {\n const sessionsRoot = path.join(\n packagesDir,\n pkg,\n \"LocalCache\",\n \"Roaming\",\n \"Claude\",\n \"local-agent-mode-sessions\",\n );\n\n let wsEntries: string[];\n try {\n wsEntries = await fsPromises.readdir(sessionsRoot);\n } catch (err) {\n logger.warn(`cannot read sessions root ${sessionsRoot}: ${(err as Error).message}`);\n continue;\n }\n\n for (const ws of wsEntries) {\n const wsPath = path.join(sessionsRoot, ws);\n let vmEntries: string[];\n try {\n vmEntries = await fsPromises.readdir(wsPath);\n } catch (err) {\n logger.warn(`cannot read workspace dir ${wsPath}: ${(err as Error).message}`);\n continue;\n }\n\n for (const vm of vmEntries) {\n const vmPath = path.join(wsPath, vm);\n try {\n const stat = await fsPromises.stat(vmPath);\n if (!stat.isDirectory()) continue;\n\n // 4-step path guard — only include validated, realpath'd paths.\n const safePath = await assertSafeWorkspacePath(vmPath, realLad, logger);\n if (safePath !== null) {\n workspaces.push(safePath);\n }\n } catch (err) {\n logger.warn(`cannot stat ${vmPath}: ${(err as Error).message}`);\n }\n }\n }\n }\n\n logger.info(`found ${workspaces.length} workspace(s)`);\n return workspaces;\n}\n\n/**\n * Atomically rewrite a JSON file, invoking `mutate` on the parsed object.\n *\n * Precondition: `filePath` has already been validated by the path guard.\n * This function trusts its callers (consistent with `with_locked_json` on the Rust side).\n *\n * Returns true if the mutation changed something and was written, false if\n * the file was absent or unchanged.\n */\nexport async function rewriteJson(\n filePath: string,\n mutate: (obj: Record<string, unknown>) => boolean,\n logger: ScrubLogger,\n): Promise<boolean> {\n let content: string;\n try {\n content = await fsPromises.readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return false;\n }\n logger.warn(`cannot read ${filePath}: ${(err as Error).message}`);\n return false;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch (err) {\n logger.warn(`invalid JSON in ${filePath}: ${(err as Error).message}`);\n return false;\n }\n\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n logger.warn(`${filePath} is not a JSON object — skipping`);\n return false;\n }\n\n const changed = mutate(parsed as Record<string, unknown>);\n if (!changed) {\n return false;\n }\n\n const dir = path.dirname(filePath);\n const tmpName = `.tandem-scrub-tmp-${Math.random().toString(36).slice(2, 10)}`;\n const tmpPath = path.join(dir, tmpName);\n\n try {\n await fsPromises.writeFile(tmpPath, JSON.stringify(parsed, null, 2), \"utf8\");\n await fsPromises.rename(tmpPath, filePath);\n } catch (err) {\n await fsPromises.unlink(tmpPath).catch(() => {});\n throw err;\n }\n return true;\n}\n\n/**\n * Remove the Tandem entry from `installed_plugins.json`.\n */\nexport function removeInstalledPlugins(obj: Record<string, unknown>): boolean {\n let changed = false;\n for (const key of [\"mcpServers\", \"servers\"]) {\n const servers = obj[key];\n if (typeof servers === \"object\" && servers !== null && !Array.isArray(servers)) {\n const map = servers as Record<string, unknown>;\n if (TANDEM_PLUGIN_ID in map) {\n delete map[TANDEM_PLUGIN_ID];\n changed = true;\n }\n }\n }\n return changed;\n}\n\n/**\n * Remove the Tandem marketplace entry from `known_marketplaces.json`.\n */\nexport function removeKnownMarketplaces(obj: Record<string, unknown>): boolean {\n const mp = obj.marketplaces;\n if (typeof mp === \"object\" && mp !== null && !Array.isArray(mp)) {\n const map = mp as Record<string, unknown>;\n if (TANDEM_PLUGIN_ID in map) {\n delete map[TANDEM_PLUGIN_ID];\n return true;\n }\n }\n return false;\n}\n\n/**\n * Remove `tandem@tandem` from `enabledPlugins` in `cowork_settings.json`.\n */\nexport function removeCoworkSettings(obj: Record<string, unknown>): boolean {\n const enabled = obj.enabledPlugins;\n if (Array.isArray(enabled)) {\n const before = enabled.length;\n obj.enabledPlugins = enabled.filter((v) => v !== TANDEM_ENABLED_KEY);\n return (obj.enabledPlugins as unknown[]).length < before;\n }\n if (typeof enabled === \"object\" && enabled !== null) {\n const map = enabled as Record<string, unknown>;\n if (TANDEM_ENABLED_KEY in map) {\n delete map[TANDEM_ENABLED_KEY];\n return true;\n }\n }\n return false;\n}\n\n/**\n * Delete a Windows Firewall rule by name. Non-existent rules are not an error.\n */\nasync function deleteFirewallRule(name: string, logger: ScrubLogger): Promise<void> {\n try {\n await execFileAsync(\"netsh\", [\"advfirewall\", \"firewall\", \"delete\", \"rule\", `name=${name}`]);\n logger.info(`deleted firewall rule: ${name}`);\n } catch (err) {\n const e = err as NodeJS.ErrnoException & { stdout?: string; stderr?: string };\n // netsh returns exit code 1 when no rule matches — not fatal.\n const stdoutStr = e.stdout ?? \"\";\n if (stdoutStr.includes(\"No rules match\")) {\n logger.info(`no firewall rule to delete: ${name}`);\n return;\n }\n logger.warn(\n `failed to delete firewall rule ${name}: ${e.message ?? String(err)} ` +\n `(stdout: ${stdoutStr.trim().slice(0, 200)})`,\n );\n }\n}\n\n/**\n * Main entry — Windows-only.\n */\nexport async function runUninstallScrub(): Promise<number> {\n const logger = await openLogger();\n\n logger.info(\"Tandem uninstall scrub starting\");\n\n if (process.platform !== \"win32\") {\n logger.info(`platform ${process.platform} is not win32 — skipping Cowork scrub`);\n await logger.close();\n return 0;\n }\n\n let failures = 0;\n\n try {\n const workspaces = await findCoworkWorkspaces(logger);\n for (const ws of workspaces) {\n const pluginsDir = path.join(ws, \"cowork_plugins\");\n try {\n await rewriteJson(\n path.join(pluginsDir, \"installed_plugins.json\"),\n removeInstalledPlugins,\n logger,\n );\n await rewriteJson(\n path.join(pluginsDir, \"known_marketplaces.json\"),\n removeKnownMarketplaces,\n logger,\n );\n await rewriteJson(\n path.join(pluginsDir, \"cowork_settings.json\"),\n removeCoworkSettings,\n logger,\n );\n } catch (err) {\n logger.error(`scrub failed for ${ws}: ${(err as Error).message}`);\n failures++;\n }\n }\n\n await deleteFirewallRule(FIREWALL_ALLOW_RULE, logger);\n await deleteFirewallRule(FIREWALL_DENY_RULE, logger);\n\n logger.info(`scrub complete: ${workspaces.length} workspace(s), ${failures} failure(s)`);\n } catch (err) {\n logger.error(`scrub fatal error: ${(err as Error).message}`);\n failures++;\n }\n\n await logger.close();\n\n // Exit 0 on clean-or-not-installed. Non-zero only on unrecoverable failures —\n // and even then, NSIS does NOT block uninstall; it just records the code.\n return failures > 0 ? 1 : 0;\n}\n","export const DEFAULT_WS_PORT = 3478;\nexport const DEFAULT_MCP_PORT = 3479;\n\nexport const TANDEM_REPO_URL = \"https://github.com/bloknayrb/tandem\";\nexport const TANDEM_ISSUES_NEW_URL = `${TANDEM_REPO_URL}/issues/new`;\n\n/** File extensions the server accepts for opening. */\nexport const SUPPORTED_EXTENSIONS = new Set([\".md\", \".txt\", \".html\", \".htm\", \".docx\"]);\nexport const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB\nexport const MAX_WS_PAYLOAD = 10 * 1024 * 1024; // 10MB\nexport const MAX_WS_CONNECTIONS = 4;\nexport const IDLE_TIMEOUT = 30 * 60 * 1000; // 30 minutes\nexport const SESSION_MAX_AGE = 30 * 24 * 60 * 60 * 1000; // 30 days\nexport const TYPING_DEBOUNCE = 3000; // 3 seconds\nexport const DISCONNECT_DEBOUNCE_MS = 3000; // 3 seconds before showing \"server not reachable\"\nexport const PROLONGED_DISCONNECT_MS = 30_000; // 30 seconds before showing App-level disconnect banner\nexport const OVERLAY_STALE_DEBOUNCE = 200; // 200ms\n\nimport type { HighlightColor } from \"./types.js\";\n\nexport const HIGHLIGHT_COLORS: Record<HighlightColor, string> = {\n yellow: \"rgba(255, 235, 59, 0.3)\",\n green: \"rgba(76, 175, 80, 0.3)\",\n blue: \"rgba(33, 150, 243, 0.3)\",\n pink: \"rgba(236, 72, 153, 0.3)\",\n};\n\nexport const HIGHLIGHT_COLOR_VARS: Record<HighlightColor, string> = {\n yellow: \"var(--tandem-highlight-yellow)\",\n green: \"var(--tandem-highlight-green)\",\n blue: \"var(--tandem-highlight-blue)\",\n pink: \"var(--tandem-highlight-pink)\",\n};\n\nexport function normalizeHighlightColor(color: string | null | undefined): HighlightColor {\n return color && color in HIGHLIGHT_COLORS ? (color as HighlightColor) : \"yellow\";\n}\n\nexport const TANDEM_MODE_DEFAULT = \"tandem\" as const;\nexport const TANDEM_MODE_KEY = \"tandem:mode\";\nexport const TANDEM_SETTINGS_KEY = \"tandem:settings\";\n// Panel-width localStorage keys.\n//\n// NOTE: these use legacy hyphen naming (vs the neighboring colon convention\n// `tandem:mode`/`tandem:settings`) because they predate the colon scheme and\n// changing the strings would invalidate every existing user's saved widths.\n// Do not \"fix\" the style — the key string is the persistence contract.\n//\n// Right-side panel width is shared between the tabbed layout and the\n// three-panel right panel. The left key only applies in three-panel mode.\nexport const PANEL_WIDTH_KEY = \"tandem-panel-width\";\nexport const LEFT_PANEL_WIDTH_KEY = \"tandem-left-panel-width\";\n\nexport type PanelSide = \"left\" | \"right\";\n\n/**\n * Maps a panel side to its localStorage key. Using a Record instead of two\n * bare constants makes the \"both handles write to the same key\" regression\n * (#228) structurally impossible — you can't accidentally map both sides to\n * the same value at a callsite.\n *\n * Uses `as const satisfies Record<PanelSide, string>` so the value type stays\n * as the literal strings rather than widening to `string` — this preserves\n * the persistence-key identity at every callsite while still enforcing\n * exhaustive coverage of `PanelSide`.\n */\nexport const PANEL_WIDTH_KEYS = {\n left: LEFT_PANEL_WIDTH_KEY,\n right: PANEL_WIDTH_KEY,\n} as const satisfies Record<PanelSide, string>;\nexport const SELECTION_DWELL_DEFAULT_MS = 1000;\nexport const SELECTION_DWELL_MIN_MS = 500;\nexport const SELECTION_DWELL_MAX_MS = 3000;\n\n// Large file thresholds\nexport const CHARS_PER_PAGE = 3_000;\nexport const LARGE_FILE_PAGE_THRESHOLD = 50;\nexport const VERY_LARGE_FILE_PAGE_THRESHOLD = 100;\n\nexport const CTRL_ROOM = \"__tandem_ctrl__\";\n\n/** Y.Map key constants — centralized to prevent silent bugs from string typos. */\nexport const Y_MAP_ANNOTATIONS = \"annotations\";\nexport const Y_MAP_AWARENESS = \"awareness\";\nexport const Y_MAP_USER_AWARENESS = \"userAwareness\";\nexport const Y_MAP_MODE = \"mode\";\nexport const Y_MAP_DWELL_MS = \"selectionDwellMs\";\nexport const Y_MAP_CHAT = \"chat\";\nexport const Y_MAP_DOCUMENT_META = \"documentMeta\";\nexport const Y_MAP_ANNOTATION_REPLIES = \"annotationReplies\";\nexport const Y_MAP_SAVED_AT_VERSION = \"savedAtVersion\";\nexport const Y_MAP_AUTHORSHIP = \"authorship\";\n// Y.Map sub-keys: userAwareness\nexport const Y_MAP_SELECTION = \"selection\";\nexport const Y_MAP_ACTIVITY = \"activity\";\n// Y.Map sub-keys: awareness (Claude focus)\nexport const Y_MAP_CLAUDE = \"claude\";\n// Y.Map sub-keys: documentMeta\nexport const Y_MAP_OPEN_DOCUMENTS = \"openDocuments\";\nexport const Y_MAP_ACTIVE_DOCUMENT_ID = \"activeDocumentId\";\nexport const Y_MAP_GENERATION_ID = \"generationId\";\nexport const Y_MAP_READ_ONLY = \"readOnly\";\nexport const Y_MAP_STORE_READ_ONLY = \"storeReadOnly\";\n\nexport const AUTHORSHIP_TOGGLE_KEY = \"tandem:showAuthorship\";\n\nexport const SERVER_INFO_DIR = \".tandem\";\nexport const SERVER_INFO_FILE = \".tandem/.server-info\";\n\nexport const RECENT_FILES_KEY = \"tandem:recentFiles\";\nexport const RECENT_FILES_CAP = 20;\n\nexport const USER_NAME_KEY = \"tandem:userName\";\nexport const USER_NAME_DEFAULT = \"You\";\nexport const USER_NAME_EVENT = \"tandem:user-name-changed\";\nexport const USER_NAME_MAX_LEN = 40;\n\n// Toast notifications\nexport const TOAST_DISMISS_MS = { error: 8000, warning: 6000, info: 4000 } as const;\nexport const MAX_VISIBLE_TOASTS = 5;\nexport const NOTIFICATION_BUFFER_SIZE = 50;\n\n// Onboarding tutorial\nexport const TUTORIAL_COMPLETED_KEY = \"tandem:tutorialCompleted\";\nexport const TUTORIAL_ANNOTATION_PREFIX = \"tutorial-\";\n/** Persists \"user skipped the Cowork onboarding step\" across sessions. */\nexport const COWORK_ONBOARDING_SKIPPED_KEY = \"tandem:coworkOnboardingSkipped\";\n/** Polling interval for `cowork_get_status` while the consumer is active. */\nexport const COWORK_STATUS_POLL_MS = 30_000;\n/** Debounce interval for the \"Re-scan workspaces\" button. */\nexport const COWORK_RESCAN_DEBOUNCE_MS = 2_000;\n\n// Editor layout\nexport const EDITOR_WIDTH_MODE_KEY = \"tandem:editorWidthMode\";\n\n// Channel / event queue\nexport const CHANNEL_EVENT_BUFFER_SIZE = 200;\nexport const CHANNEL_EVENT_BUFFER_AGE_MS = 60_000; // 60 seconds\nexport const CHANNEL_SSE_KEEPALIVE_MS = 15_000; // 15 seconds\nexport const CHANNEL_MAX_RETRIES = 5;\nexport const CHANNEL_RETRY_DELAY_MS = 2_000;\n\n// Channel shim per-request timeouts. Mirror the monitor pattern (#364) so a\n// half-open Tandem server can't wedge `tandem_reply`, the permission relay,\n// or the event-bridge SSE handshake / awareness / mode / error-report POSTs.\n// Intentionally separate constants per endpoint so a slow endpoint doesn't\n// hold up a faster one — and so log lines name a meaningful threshold.\nexport const CHANNEL_CONNECT_FETCH_TIMEOUT_MS = 10_000; // /api/events handshake\nexport const CHANNEL_SSE_INACTIVITY_TIMEOUT_MS = 60_000; // No-bytes watchdog on SSE body\nexport const CHANNEL_MODE_FETCH_TIMEOUT_MS = 2_000; // /api/mode cache refresh\nexport const CHANNEL_AWARENESS_FETCH_TIMEOUT_MS = 5_000; // /api/channel-awareness POST\nexport const CHANNEL_ERROR_REPORT_TIMEOUT_MS = 3_000; // /api/channel-error POST on exit\nexport const CHANNEL_REPLY_FETCH_TIMEOUT_MS = 5_000; // /api/channel-reply (tandem_reply)\nexport const CHANNEL_PERMISSION_FETCH_TIMEOUT_MS = 5_000; // /api/channel-permission relay\n// Bound the SSE buffer so a misbehaving server that never emits frame\n// boundaries can't wedge the bridge with unbounded string growth.\nexport const CHANNEL_MAX_SSE_BUFFER_BYTES = 1_000_000;\n\n/** Auth token filename inside the app-data directory. */\nexport const TOKEN_FILE_NAME = \"auth-token\";\n\n/** Default MCP bind host — loopback only by default. */\nexport const DEFAULT_BIND_HOST = \"127.0.0.1\";\n\n/** Env var name to opt in to unauthenticated LAN binding. */\nexport const TANDEM_ALLOW_UNAUTHENTICATED_LAN_ENV = \"TANDEM_ALLOW_UNAUTHENTICATED_LAN\";\n\n/** Tauri WebView origin hostname — must be accepted alongside localhost. */\nexport const TAURI_HOSTNAME = \"tauri.localhost\";\n\n// Zoom persistence (Tauri desktop)\nexport const ZOOM_STORAGE_KEY = \"tandem:zoomLevel\";\nexport const ZOOM_MIN = 0.5;\nexport const ZOOM_MAX = 2.0;\nexport const ZOOM_DEFAULT = 1.0;\n","/**\n * SKILL.md content installed to ~/.claude/skills/tandem/ by `tandem setup`.\n * Single source of truth lives at `skills/tandem/SKILL.md`. This module\n * reads that file at module load so the plugin install path and the\n * `tandem setup` install path always deliver byte-identical content.\n *\n * The file is shipped via package.json `files: [\"skills/\", ...]`, and the\n * CLI entry (dist/cli/index.js) is not self-contained — so at runtime the\n * relative path `../../skills/tandem/SKILL.md` resolves from either\n * dist/cli/ (tsx dev) or dist/cli/ (npm install) to the package-root\n * `skills/tandem/SKILL.md`.\n */\nimport { readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst SKILL_PATH = resolve(__dirname, \"../../skills/tandem/SKILL.md\");\n\nexport const SKILL_CONTENT = readFileSync(SKILL_PATH, \"utf-8\");\n","import { randomUUID } from \"node:crypto\";\nimport { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { copyFile, mkdir, rename, unlink, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { DEFAULT_MCP_PORT } from \"../shared/constants.js\";\nimport { SKILL_CONTENT } from \"./skill-content.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// Paths are anchored to the package root (dist/cli/ resolves up two levels).\nconst PACKAGE_ROOT = resolve(__dirname, \"../..\");\nconst CHANNEL_DIST = resolve(PACKAGE_ROOT, \"dist/channel/index.js\");\n\nconst MCP_URL = `http://localhost:${DEFAULT_MCP_PORT}`;\n\nexport interface McpEntry {\n type?: \"http\";\n url?: string;\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n headers?: Record<string, string>;\n}\n\nexport interface McpEntries {\n tandem: McpEntry;\n \"tandem-channel\"?: McpEntry;\n}\n\nexport interface BuildMcpEntriesOptions {\n /** Include the legacy stdio channel shim. Defaults to false — the plugin\n * monitor handles event push for modern installs. Users on older setups\n * can run `tandem setup --with-channel-shim` to preserve the shim. */\n withChannelShim?: boolean;\n nodeBinary?: string;\n /** Auth token to embed in HTTP entry headers and stdio shim env.\n * When omitted (first-run before token provisioned), headers/env are omitted\n * and backward compatibility is preserved. */\n token?: string;\n /** Target kind controls entry shape. Claude Code uses HTTP (direct);\n * Claude Desktop uses stdio (npx bridge) because Cowork sessions can\n * only surface stdio MCP servers. */\n targetKind?: TargetKind;\n}\n\nexport function buildMcpEntries(\n channelPath: string,\n opts: BuildMcpEntriesOptions = {},\n): McpEntries {\n const isDesktop = opts.targetKind === \"claude-desktop\";\n\n let tandemEntry: McpEntry;\n if (isDesktop) {\n const env: Record<string, string> = { TANDEM_URL: MCP_URL };\n if (opts.token) {\n env.TANDEM_AUTH_TOKEN = opts.token;\n }\n tandemEntry = {\n command: \"npx\",\n args: [\"-y\", \"tandem-editor\", \"mcp-stdio\"],\n env,\n };\n } else {\n tandemEntry = { type: \"http\", url: `${MCP_URL}/mcp` };\n if (opts.token) {\n tandemEntry.headers = { Authorization: `Bearer ${opts.token}` };\n }\n }\n const entries: McpEntries = { tandem: tandemEntry };\n\n if (opts.withChannelShim) {\n const shimEnv: Record<string, string> = { TANDEM_URL: MCP_URL };\n if (opts.token) {\n shimEnv.TANDEM_AUTH_TOKEN = opts.token;\n }\n entries[\"tandem-channel\"] = {\n command: opts.nodeBinary ?? \"node\",\n args: [channelPath],\n env: shimEnv,\n };\n }\n return entries;\n}\n\nexport type TargetKind = \"claude-code\" | \"claude-desktop\";\n\nexport interface DetectedTarget {\n label: string;\n configPath: string;\n kind: TargetKind;\n}\n\nexport interface DetectOptions {\n homeOverride?: string;\n localAppDataOverride?: string;\n force?: boolean;\n}\n\nexport function detectTargets(opts: DetectOptions = {}): DetectedTarget[] {\n const home = opts.homeOverride ?? homedir();\n const targets: DetectedTarget[] = [];\n\n // Claude Code — cross-platform.\n // MCP servers are configured in ~/.claude.json under the \"mcpServers\" key.\n // Detect if the file exists OR if ~/.claude directory exists (Claude Code is installed).\n // With --force, always include regardless.\n const claudeCodeConfig = join(home, \".claude.json\");\n const claudeCodeDir = join(home, \".claude\");\n if (opts.force || existsSync(claudeCodeConfig) || existsSync(claudeCodeDir)) {\n targets.push({ label: \"Claude Code\", configPath: claudeCodeConfig, kind: \"claude-code\" });\n }\n\n // Claude Desktop — platform-specific.\n // Only detect if the config file already exists (user has launched Desktop at least once).\n // With --force, always include.\n let desktopConfig: string | null = null;\n if (process.platform === \"win32\") {\n const appdata = process.env.APPDATA ?? join(home, \"AppData\", \"Roaming\");\n desktopConfig = join(appdata, \"Claude\", \"claude_desktop_config.json\");\n } else if (process.platform === \"darwin\") {\n desktopConfig = join(\n home,\n \"Library\",\n \"Application Support\",\n \"Claude\",\n \"claude_desktop_config.json\",\n );\n } else {\n desktopConfig = join(home, \".config\", \"claude\", \"claude_desktop_config.json\");\n }\n\n if (desktopConfig && (opts.force || existsSync(desktopConfig))) {\n targets.push({ label: \"Claude Desktop\", configPath: desktopConfig, kind: \"claude-desktop\" });\n }\n\n // Claude Desktop (MSIX) — Windows only.\n // MSIX-packaged installs (Microsoft Store) redirect %APPDATA% to a per-package\n // LocalCache dir. The config lives under %LOCALAPPDATA%\\Packages\\Claude_*\\\n // LocalCache\\Roaming\\Claude\\. Multiple package families may exist.\n if (process.platform === \"win32\") {\n const localAppData =\n opts.localAppDataOverride ?? process.env.LOCALAPPDATA ?? join(home, \"AppData\", \"Local\");\n const packagesDir = join(localAppData, \"Packages\");\n try {\n const entries = readdirSync(packagesDir);\n for (const pkg of entries.filter((n) => n.startsWith(\"Claude_\"))) {\n const msixConfig = join(\n packagesDir,\n pkg,\n \"LocalCache\",\n \"Roaming\",\n \"Claude\",\n \"claude_desktop_config.json\",\n );\n if (opts.force || existsSync(msixConfig)) {\n const suffix =\n entries.filter((n) => n.startsWith(\"Claude_\")).length > 1\n ? ` (${pkg.slice(0, 12)}…)`\n : \"\";\n targets.push({\n label: `Claude Desktop MSIX${suffix}`,\n configPath: msixConfig,\n kind: \"claude-desktop\",\n });\n }\n }\n } catch {\n // %LOCALAPPDATA%\\Packages doesn't exist or isn't readable — not an MSIX install\n }\n }\n\n return targets;\n}\n\n/**\n * Atomic write: write to a temp file in the SAME directory as the destination,\n * then rename. Using the same directory avoids EXDEV errors on Windows when\n * %TEMP% and %APPDATA% are on different drives.\n */\nasync function atomicWrite(content: string, dest: string): Promise<void> {\n const tmp = join(dirname(dest), `.tandem-setup-${randomUUID()}.tmp`);\n await writeFile(tmp, content, \"utf-8\");\n try {\n await rename(tmp, dest);\n } catch (err) {\n // EXDEV: cross-device link — fall back to copy + delete\n if ((err as NodeJS.ErrnoException).code === \"EXDEV\") {\n await copyFile(tmp, dest);\n await unlink(tmp).catch((cleanupErr: Error) => {\n console.error(` Warning: could not remove temp file ${tmp}: ${cleanupErr.message}`);\n });\n } else {\n await unlink(tmp).catch((cleanupErr: Error) => {\n console.error(` Warning: could not remove temp file ${tmp}: ${cleanupErr.message}`);\n });\n throw err;\n }\n }\n}\n\nexport async function applyConfig(configPath: string, entries: McpEntries): Promise<void> {\n // Read existing config or start fresh — no existsSync guard needed.\n // ENOENT and malformed JSON start fresh; other errors (permissions, disk) propagate.\n let existing: { mcpServers?: Record<string, McpEntry> } = {};\n try {\n existing = JSON.parse(readFileSync(configPath, \"utf-8\"));\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n // File doesn't exist yet — start fresh\n } else if (err instanceof SyntaxError) {\n // Don't silently wipe the user's other mcpServers. Copy the malformed\n // file to a .broken-<ts> sibling first so they can recover it. If the\n // backup itself fails, refuse to overwrite — runSetup's per-target\n // try/catch reports the partial failure.\n const backupPath = `${configPath}.broken-${Date.now()}`;\n try {\n await copyFile(configPath, backupPath);\n console.error(\n ` Warning: ${configPath} contains malformed JSON — backed up to ${basename(backupPath)}, replacing with fresh config`,\n );\n } catch (copyErr) {\n console.error(\n ` Warning: ${configPath} contains malformed JSON and backup failed (${\n copyErr instanceof Error ? copyErr.message : copyErr\n }) — refusing to overwrite. Fix the JSON manually and rerun 'tandem setup'.`,\n );\n throw copyErr;\n }\n } else {\n throw err; // Permission errors, disk errors, etc. should not be silently swallowed\n }\n }\n\n const merged = {\n ...(existing.mcpServers ?? {}),\n ...entries,\n };\n // Remove stale tandem-channel entry left by older Tauri installers.\n // The channel shim is Claude Code-only; Cowork can't use it.\n if (!entries[\"tandem-channel\"]) {\n if (merged[\"tandem-channel\"]) {\n console.error(\n ` Warning: removed stale tandem-channel entry from ${configPath} (legacy Tauri install artifact)`,\n );\n }\n delete merged[\"tandem-channel\"];\n }\n const updated = { ...existing, mcpServers: merged };\n\n await mkdir(dirname(configPath), { recursive: true });\n await atomicWrite(JSON.stringify(updated, null, 2) + \"\\n\", configPath);\n}\n\n/**\n * Install the Tandem skill to ~/.claude/skills/tandem/SKILL.md.\n * Claude Code auto-discovers skills in this directory and uses the description\n * field to trigger them when tandem_* tools are present.\n */\nexport async function installSkill(opts: { homeOverride?: string } = {}): Promise<void> {\n const home = opts.homeOverride ?? homedir();\n const skillPath = join(home, \".claude\", \"skills\", \"tandem\", \"SKILL.md\");\n await mkdir(dirname(skillPath), { recursive: true });\n await atomicWrite(SKILL_CONTENT, skillPath);\n}\n\n/**\n * Returns true if the channel-shim build artifact exists at the given path.\n * Exported so the prereq check can be tested without spawning runSetup.\n */\nexport function validateChannelShimPrereq(channelPath: string): boolean {\n return existsSync(channelPath);\n}\n\n/**\n * Write the given token into all detected Claude MCP config files.\n * Returns the number of configs successfully updated and any per-target errors.\n */\nexport async function applyConfigWithToken(\n token: string | null,\n opts: { force?: boolean; withChannelShim?: boolean } = {},\n): Promise<{ updated: number; errors: string[] }> {\n const targets = detectTargets({ force: opts.force });\n\n let updated = 0;\n const errors: string[] = [];\n for (const t of targets) {\n const entries = buildMcpEntries(CHANNEL_DIST, {\n withChannelShim: opts.withChannelShim,\n token: token ?? undefined,\n targetKind: t.kind,\n });\n try {\n await applyConfig(t.configPath, entries);\n updated++;\n } catch (err) {\n errors.push(`${t.label}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n return { updated, errors };\n}\n\n/** Run the setup command. Writes MCP config to all detected Claude installs. */\nexport async function runSetup(\n opts: { force?: boolean; withChannelShim?: boolean } = {},\n): Promise<void> {\n console.error(\"\\nTandem Setup\\n\");\n\n if (opts.withChannelShim && !validateChannelShimPrereq(CHANNEL_DIST)) {\n console.error(\n `Error: --with-channel-shim requires dist/channel/index.js at ${CHANNEL_DIST}\\n` +\n `Run 'npm run build' first, or drop --with-channel-shim to use the plugin monitor.`,\n );\n process.exit(1);\n }\n\n console.error(\"Detecting Claude installations...\");\n\n const targets = detectTargets({ force: opts.force });\n\n if (targets.length === 0) {\n console.error(\n \" No Claude installations detected.\\n\" +\n \" If Claude Code is installed, ensure ~/.claude exists.\\n\" +\n \" You can force configuration to default paths with: tandem setup --force\",\n );\n return;\n }\n\n for (const t of targets) {\n console.error(` Found: ${t.label} (${t.configPath})`);\n }\n\n console.error(\"\\nWriting MCP configuration...\");\n\n let failures = 0;\n for (const t of targets) {\n const entries = buildMcpEntries(CHANNEL_DIST, {\n withChannelShim: opts.withChannelShim,\n targetKind: t.kind,\n });\n try {\n await applyConfig(t.configPath, entries);\n console.error(` \\x1b[32m✓\\x1b[0m ${t.label}`);\n } catch (err) {\n failures++;\n console.error(\n ` \\x1b[31m✗\\x1b[0m ${t.label}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n if (failures === targets.length) {\n console.error(\"\\nSetup failed — could not write any configuration. Check file permissions.\");\n process.exit(1);\n } else if (failures > 0) {\n console.error(\n `\\nSetup partially complete (${failures} target(s) failed). Start Tandem with: tandem`,\n );\n } else {\n console.error(\"\\nSetup complete! Start Tandem with: tandem\");\n console.error(\"Then in Claude, your tandem_* tools will be available.\");\n }\n\n // Install Claude Code skill (best-effort — doesn't block MCP setup)\n console.error(\"\\nInstalling Claude Code skill...\");\n try {\n await installSkill();\n console.error(\" \\x1b[32m✓\\x1b[0m ~/.claude/skills/tandem/SKILL.md\");\n } catch (err) {\n console.error(\n ` \\x1b[33m⚠\\x1b[0m Could not install skill: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Plugin install instructions (shown on all successful setups)\n if (failures < targets.length) {\n const pluginManifest = join(PACKAGE_ROOT, \".claude-plugin\", \"plugin.json\");\n const devInstructions = existsSync(pluginManifest)\n ? ` Or for development, load directly from this package:\\n\\n` +\n ` claude --plugin-dir ${PACKAGE_ROOT}\\n\\n`\n : ` (Development plugin dir not found at ${pluginManifest}; skipping local-plugin instructions.)\\n\\n`;\n\n console.error(\n \"\\n\\x1b[1mReal-time push notifications (recommended):\\x1b[0m\\n\" +\n \" Install the Tandem plugin for instant events (one-time):\\n\\n\" +\n \" claude plugin marketplace add bloknayrb/tandem\\n\" +\n \" claude plugin install tandem@tandem-editor\\n\\n\" +\n devInstructions +\n \" Without the plugin, Claude still works but relies on tandem_checkInbox polling.\\n\",\n );\n }\n}\n","/**\n * Helpers shared by CLI stdio entry points (`mcp-stdio`, `channel`) and the\n * standalone server / monitor binaries that all speak MCP over stdout.\n */\n\nimport { DEFAULT_MCP_PORT } from \"./constants.js\";\n\n/**\n * In stdio MCP mode, stdout is the JSON-RPC wire — any stray library write\n * corrupts the protocol. Redirect `console.log/warn/info` to stderr so\n * incidental logging is safe. Callers that also run in-process tests (e.g.\n * `src/monitor/index.ts`) can gate this behind `!process.env.VITEST`.\n */\nexport function redirectConsoleToStderr(): void {\n console.log = console.error;\n console.warn = console.error;\n console.info = console.error;\n}\n\n/**\n * Resolve the Tandem HTTP base URL used by stdio subcommands. Precedence:\n * (1) explicit override (programmatic, e.g. from tests)\n * (2) CLAUDE_PLUGIN_OPTION_SERVER_URL — injected by plugin host from userConfig\n * (3) TANDEM_URL — explicit env override\n * (4) localhost default\n * Blank values are treated as absent so a blank plugin option does not mask an\n * explicit TANDEM_URL or the localhost default.\n * The returned string has no trailing slash so callers can concatenate\n * `/health`, `/mcp`, etc. without double-slash. One or more trailing slashes\n * are stripped, so both `http://x/` and `http://x//` resolve to `http://x`.\n */\nexport function resolveTandemUrl(override?: string): string {\n return resolveTandemUrlCandidate(override).replace(/\\/+$/, \"\");\n}\n\nfunction resolveTandemUrlCandidate(override?: string): string {\n const candidates = [\n override,\n process.env.CLAUDE_PLUGIN_OPTION_SERVER_URL,\n process.env.TANDEM_URL,\n ];\n for (const url of candidates) {\n if (url !== undefined && url.trim() !== \"\") return url.trim();\n }\n return `http://localhost:${DEFAULT_MCP_PORT}`;\n}\n\n/**\n * Resolve the Tandem auth token. Precedence:\n * (1) explicit override (programmatic, e.g. from tests)\n * (2) CLAUDE_PLUGIN_OPTION_AUTH_TOKEN — injected by plugin host from userConfig\n * (3) TANDEM_AUTH_TOKEN — explicit env override\n * Blank values are treated as absent so a blank plugin option does not mask an\n * explicit TANDEM_AUTH_TOKEN. Returns undefined when all absent (loopback mode,\n * no Authorization header sent).\n */\nexport function resolveAuthToken(override?: string): string | undefined {\n return resolveAuthTokenCandidate(override).token;\n}\n\nexport type AuthTokenSource =\n | \"explicit override\"\n | \"CLAUDE_PLUGIN_OPTION_AUTH_TOKEN\"\n | \"TANDEM_AUTH_TOKEN\";\n\nexport function resolveAuthTokenCandidate(\n override?: string,\n): { token: string; source: AuthTokenSource } | { token: undefined; source: undefined } {\n const candidates: Array<[AuthTokenSource, string | undefined]> = [\n [\"explicit override\", override],\n [\"CLAUDE_PLUGIN_OPTION_AUTH_TOKEN\", process.env.CLAUDE_PLUGIN_OPTION_AUTH_TOKEN],\n [\"TANDEM_AUTH_TOKEN\", process.env.TANDEM_AUTH_TOKEN],\n ];\n for (const [source, token] of candidates) {\n if (token !== undefined && token.trim() !== \"\") return { token, source };\n }\n return { token: undefined, source: undefined };\n}\n\n/** Regex for a valid Tandem auth token (32+ URL-safe alphanumeric chars). */\nconst VALID_TOKEN_RE = /^[A-Za-z0-9_\\-]{32,}$/;\n\n/** Guard so we only warn once per process (not on every SSE reconnect). */\nlet _warnedInvalidToken = false;\n\n/**\n * Fetch wrapper that automatically injects `Authorization: Bearer <token>`\n * when a resolved Tandem auth token is set and valid.\n *\n * This is the forgiving variant — used by monitor/channel which may run in\n * loopback-only mode without a token. Invalid or absent tokens are silently\n * ignored (no exit-1). The strict validation lives in mcp-stdio.ts only.\n * When the token is set but fails validation, a one-time warning is emitted\n * so operators know why auth headers are absent.\n */\nexport async function authFetch(url: string, init?: RequestInit): Promise<Response> {\n const { token, source } = resolveAuthTokenCandidate();\n if (token !== undefined) {\n const trimmed = token.trim();\n if (VALID_TOKEN_RE.test(trimmed)) {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", `Bearer ${trimmed}`);\n return fetch(url, { ...init, headers });\n }\n // Token is set but invalid — warn once so operators know why auth fails\n if (!_warnedInvalidToken) {\n _warnedInvalidToken = true;\n console.error(\n `[tandem] authFetch: ${source} is set but invalid (must be 32+ alphanumeric chars [A-Za-z0-9_-]); sending without Authorization header`,\n );\n }\n }\n return fetch(url, init);\n}\n","/**\n * Shared preflight check for stdio MCP subcommands.\n *\n * Both `tandem mcp-stdio` and `tandem channel` need a live Tandem server on\n * localhost before they can do anything useful. Two flavors:\n *\n * - `ensureTandemServer` — fail fast via stderr + exit(1) when the server\n * isn't reachable. Used by `tandem channel`, whose stdio transport can't\n * meaningfully respond on its own.\n * - `probeTandemServer` — returns a result without side effects. Used by\n * `tandem mcp-stdio`, which starts its stdio transport before preflight\n * so it can synthesize -32000 JSON-RPC errors for any in-flight request\n * before exiting (issue #336).\n */\n\nimport { resolveTandemUrl } from \"../shared/cli-runtime.js\";\n\nconst DEFAULT_TIMEOUT_MS = 2000;\n\nexport interface PreflightOptions {\n url?: string;\n timeoutMs?: number;\n}\n\n// Note: \"unreachable\" is a catch-all for any non-HTTP-status failure —\n// DNS, TLS, timeout, ECONNREFUSED, RST all land here. \"unhealthy\" is\n// strictly non-2xx responses from /health.\nexport type PreflightProbe =\n | { ok: true }\n | { ok: false; url: string; reason: string; kind: \"unreachable\" | \"unhealthy\" };\n\nexport async function probeTandemServer(opts: PreflightOptions = {}): Promise<PreflightProbe> {\n const url = resolveTandemUrl(opts.url);\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const res = await fetch(`${url}/health`, { signal: controller.signal });\n if (!res.ok) {\n return {\n ok: false,\n url,\n reason: `health endpoint returned HTTP ${res.status}`,\n kind: \"unhealthy\",\n };\n }\n return { ok: true };\n } catch (err) {\n return {\n ok: false,\n url,\n reason: err instanceof Error ? err.message : String(err),\n kind: \"unreachable\",\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function ensureTandemServer(opts: PreflightOptions = {}): Promise<void> {\n const probe = await probeTandemServer(opts);\n if (!probe.ok) {\n const guidance =\n probe.kind === \"unreachable\"\n ? \"Start the Tauri app or run `tandem start` on the host, then retry.\"\n : \"The Tandem server is running but unhealthy — check the host logs.\";\n process.stderr.write(\n `[tandem] Tandem server preflight failed at ${probe.url} (${probe.reason}).\\n` +\n `[tandem] ${guidance}\\n`,\n );\n process.exit(1);\n }\n}\n","/**\n * Tandem mcp-stdio subcommand — stdio ↔ Streamable HTTP JSON-RPC proxy.\n *\n * Claude Desktop's plugin loader bridges stdio MCP servers into sandboxed\n * sessions but not HTTP MCP servers, so plugin-cached stdio entries that\n * forward to the local HTTP MCP endpoint are the only supported way to\n * surface tandem_* tools into those sessions.\n *\n * Raw message forwarding: no handler registrations, no per-method logic.\n * Any message the upstream emits (tool results, notifications, future\n * methods we haven't heard of) reaches the stdio client unchanged.\n *\n * Error surfacing (issue #336): the stdio transport is started before\n * preflight and http.start(), and early messages are buffered until the\n * upstream is ready. If the upstream never becomes ready — or dies mid-\n * session — every in-flight request ID is answered with a synthesized\n * `-32000` JSON-RPC error instead of a silent stdio close. Plugin hosts\n * surface `-32000` as actionable; a silent close is what produces \"tools\n * never appear in Cowork\" with nothing diagnosable in the logs.\n *\n * Intentional: no reconnection logic. If the upstream HTTP server dies\n * mid-session, we synthesize errors for pending requests and exit 1.\n * The plugin loader will respawn us on the next tool call and preflight\n * will re-run with a fresh, accurate error if the server is still down.\n */\n\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport type { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\nimport {\n redirectConsoleToStderr,\n resolveAuthTokenCandidate,\n resolveTandemUrl,\n} from \"../shared/cli-runtime.js\";\nimport { probeTandemServer } from \"./preflight.js\";\n\nredirectConsoleToStderr();\n\n// After preflight or http.start() fails we wait ~1.5s for any already-in-\n// flight `initialize` from the plugin loader to land on stdin and receive\n// a -32000 reply before tear-down. Sizing covers stdin-read lag between\n// preflight resolution and first message arrival — independent of\n// preflight's own fetch timeout.\nconst PREFLIGHT_GRACE_MS = 1500;\n\n// Per-request timeout. Node's setTimeout uses a 32-bit signed integer\n// internally — values above this constant are silently clamped to 1ms,\n// which would make every request immediately synthesize -32000.\nconst MAX_TIMEOUT_MS = 2_147_483_647; // 2^31 - 1\n\nexport function parseTimeoutMs(raw: string | undefined): number {\n if (raw !== undefined) {\n const parsed = parseInt(raw, 10);\n if (Number.isFinite(parsed) && parsed > 0 && parsed <= MAX_TIMEOUT_MS) {\n return parsed;\n }\n // Note: parseInt(\"3e4\", 10) returns 3 (stops at 'e'), which passes validation.\n // Scientific notation lands here only when the leading integer is invalid.\n process.stderr.write(\n `[tandem mcp-stdio] TANDEM_REQUEST_TIMEOUT_MS must be a positive integer ≤ ${MAX_TIMEOUT_MS}; ignoring \"${raw}\", using 30000ms default\\n`,\n );\n }\n return 30_000;\n}\n\nconst STDIO_REQUEST_TIMEOUT_MS = parseTimeoutMs(process.env.TANDEM_REQUEST_TIMEOUT_MS);\n\n// Last-gasp handlers for truly unexpected crashes: write one diagnostic to\n// stderr before exit. Installed at module load; process.once bounds each\n// handler to a single fire. No -32000 synthesis here because pendingRequests\n// lives inside runMcpStdio()'s closure.\nprocess.once(\"uncaughtException\", (err: Error) => {\n process.stderr.write(\n `[tandem mcp-stdio] uncaughtException: ${err.message}\\n${err.stack ?? \"\"}\\n`,\n );\n process.exit(1);\n});\nprocess.once(\"unhandledRejection\", (reason: unknown) => {\n const detail = reason instanceof Error ? reason.message : String(reason);\n process.stderr.write(`[tandem mcp-stdio] unhandledRejection: ${detail}\\n`);\n process.exit(1);\n});\n\n/** Regex for a valid Tandem auth token (32+ URL-safe alphanumeric chars). */\nconst VALID_TOKEN_RE = /^[A-Za-z0-9_\\-]{32,}$/;\n\n/**\n * Validate the configured auth token if present.\n * Rules (invariant 4):\n * - If not set at all, or if empty/whitespace-only after trim → return null (loopback-only mode).\n * - \"Bearer \" prefix → exit 1 with \"double-prefix\" message.\n * - Must match /^[A-Za-z0-9_-]{32,}$/ (no whitespace, no newlines, no Bearer prefix).\n */\nexport function readAndValidateAuthToken(): string | null {\n const { token, source } = resolveAuthTokenCandidate();\n // Token not set at all, or empty/whitespace-only → loopback-only mode, no auth header, no exit.\n if (token === undefined) return null;\n const trimmed = token.trim();\n if (trimmed === \"\") return null;\n\n if (trimmed.startsWith(\"Bearer \")) {\n process.stderr.write(\n `[tandem mcp-stdio] ${source} is invalid (double-prefix: do not include 'Bearer ' prefix — supply the raw token only)\\n`,\n );\n process.exit(1);\n }\n\n if (!VALID_TOKEN_RE.test(trimmed)) {\n process.stderr.write(\n `[tandem mcp-stdio] ${source} is malformed (must be 32+ URL-safe characters: [A-Za-z0-9_-])\\n`,\n );\n process.exit(1);\n }\n\n return trimmed;\n}\n\nexport async function runMcpStdio(): Promise<void> {\n const baseUrl = resolveTandemUrl();\n const authToken = readAndValidateAuthToken();\n\n const http = new StreamableHTTPClientTransport(new URL(`${baseUrl}/mcp`), {\n requestInit: authToken ? { headers: { Authorization: `Bearer ${authToken}` } } : undefined,\n });\n const stdio = new StdioServerTransport();\n\n // On upstream failure we synthesize -32000 for every entry before exit.\n // Value is the per-request timeout handle so we can cancel it on response.\n const pendingRequests = new Map<string | number, ReturnType<typeof setTimeout>>();\n // Messages arriving before httpReady flips; either drained and forwarded\n // on success, or each request answered with -32000 on preflight/http-start\n // failure.\n const preReadyBuffer: JSONRPCMessage[] = [];\n let shuttingDown = false;\n let httpReady = false;\n\n async function sendErrorResponse(\n id: string | number,\n message: string,\n detail?: string,\n ): Promise<void> {\n const errorResponse: JSONRPCMessage = {\n jsonrpc: \"2.0\",\n id,\n error: {\n // -32000 is the implementation-defined server error range per\n // JSON-RPC 2.0 §5.1 — upstream unavailability is an application-\n // level condition, not a generic Internal Error.\n code: -32000,\n message,\n ...(detail !== undefined ? { data: { detail } } : {}),\n },\n };\n try {\n await stdio.send(errorResponse);\n } catch (err) {\n // stdio already torn down; log so synth failures during shutdown\n // (e.g., http.onclose racing stdio.onclose) aren't silently dropped —\n // a silent drop here would recreate exactly the failure mode this\n // module exists to prevent.\n const detail = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n `[tandem mcp-stdio] failed to send synthesized error for id ${id}: ${detail}\\n`,\n );\n }\n }\n\n function forwardToUpstream(msg: JSONRPCMessage): void {\n if (shuttingDown) return;\n const requestId = getRequestId(msg);\n if (requestId !== undefined) {\n // Clear any existing timer for this id (duplicate/retry scenario).\n const existing = pendingRequests.get(requestId);\n if (existing) clearTimeout(existing);\n\n const timeoutHandle = setTimeout(() => {\n // Atomic: delete returns false if synthesizePending already drained the map.\n if (!pendingRequests.delete(requestId)) return;\n void sendErrorResponse(\n requestId,\n \"Tandem HTTP upstream not responding (half-open)\",\n `No response after ${STDIO_REQUEST_TIMEOUT_MS}ms`,\n );\n }, STDIO_REQUEST_TIMEOUT_MS);\n pendingRequests.set(requestId, timeoutHandle);\n }\n http.send(msg).catch((err: unknown) => {\n const detail = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[tandem mcp-stdio] upstream send failed: ${detail}\\n`);\n if (requestId !== undefined) {\n const handle = pendingRequests.get(requestId);\n if (handle !== undefined) {\n pendingRequests.delete(requestId);\n clearTimeout(handle);\n void sendErrorResponse(requestId, \"Tandem HTTP upstream unreachable\", detail);\n }\n }\n });\n }\n\n async function synthesizeBuffered(message: string, detail?: string): Promise<void> {\n const buffered = preReadyBuffer.splice(0);\n const ids = buffered\n .map((msg) => getRequestId(msg))\n .filter((id): id is string | number => id !== undefined);\n for (const id of ids) {\n await sendErrorResponse(id, message, detail);\n }\n }\n\n async function synthesizePending(message: string, detail?: string): Promise<void> {\n if (pendingRequests.size === 0) return;\n const ids = [...pendingRequests.keys()];\n // Synchronous before any await: clear all timers + drain map atomically.\n // Timer callbacks that are already queued observe empty map and return early.\n for (const handle of pendingRequests.values()) clearTimeout(handle);\n pendingRequests.clear();\n await Promise.all(ids.map((id) => sendErrorResponse(id, message, detail)));\n }\n\n const shutdown = async (\n code = 0,\n synth?: { message: string; detail?: string },\n ): Promise<never> => {\n if (!shuttingDown) {\n shuttingDown = true;\n // Hard deadline: if http.close() or stdio.close() hang (e.g., a half-\n // open upstream holding an SSE GET open), don't let cleanup block the\n // process exit indefinitely. .unref() means this timer doesn't itself\n // keep the event loop alive — fast paths resolve and call process.exit\n // below before the deadline fires; hung paths are forcibly terminated.\n setTimeout(() => process.exit(code), 2_000).unref();\n // Unconditionally drain all pending timers before any await — prevents\n // orphan timers from firing into the half-closed transport during\n // http.close() / stdio.close() awaits. synthesizePending will also\n // drain the map if synth is provided; the clearTimeout calls here are\n // defensive for the synth=undefined path (e.g., clean stdio.onclose).\n for (const handle of pendingRequests.values()) clearTimeout(handle);\n if (synth) {\n await synthesizeBuffered(synth.message, synth.detail);\n await synthesizePending(synth.message, synth.detail);\n }\n await http.close().catch((err: unknown) => {\n const detail = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[tandem mcp-stdio] http.close failed: ${detail}\\n`);\n });\n await stdio.close().catch((err: unknown) => {\n const detail = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[tandem mcp-stdio] stdio.close failed: ${detail}\\n`);\n });\n }\n process.exit(code);\n };\n\n // Plugin hosts typically send `initialize` immediately after spawn (MCP\n // lifecycle §initialization). Deferring shutdown by PREFLIGHT_GRACE_MS\n // lets that request land during the preflight/start window and receive\n // a -32000 reply rather than a silent stdio close. stdio.onclose\n // short-circuits this if the loader closes stdin first.\n function deferredShutdown(synth: { message: string; detail?: string }): void {\n setTimeout(() => void shutdown(1, synth), PREFLIGHT_GRACE_MS);\n }\n\n stdio.onmessage = (msg: JSONRPCMessage) => {\n if (!httpReady) {\n preReadyBuffer.push(msg);\n return;\n }\n forwardToUpstream(msg);\n };\n\n http.onmessage = (msg: JSONRPCMessage) => {\n if (shuttingDown) return;\n // Synchronous delete+clear FIRST — before stdio.send — to prevent the\n // per-request timer from firing in the window between response arrival\n // and stdio write completion (which would synthesize a false -32000 for\n // an id that already has a real response in flight).\n const responseId = getResponseId(msg);\n if (responseId !== undefined) {\n const handle = pendingRequests.get(responseId);\n if (handle !== undefined) {\n clearTimeout(handle);\n pendingRequests.delete(responseId);\n }\n }\n const sendHandler = (err: unknown) => {\n const detail = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n `[tandem mcp-stdio] stdio write failed for id ${responseId ?? \"<notification>\"}: ${detail}\\n`,\n );\n // Map entry already deleted above. Synthesize directly for this id,\n // then tear down — stdio is broken so other pending requests can't\n // be delivered either.\n if (responseId !== undefined) {\n void sendErrorResponse(responseId, \"Tandem stdio write failed\", detail);\n }\n void shutdown(1, {\n message: \"Tandem stdio write failed\",\n detail,\n });\n };\n try {\n stdio.send(msg).catch(sendHandler);\n } catch (err) {\n sendHandler(err);\n }\n };\n\n stdio.onerror = (err) => {\n process.stderr.write(`[tandem mcp-stdio] stdio error: ${err.message}\\n${err.stack ?? \"\"}\\n`);\n };\n http.onerror = (err) => {\n const cause = (err as { cause?: unknown }).cause;\n process.stderr.write(\n `[tandem mcp-stdio] http error: ${err.message}\\n${err.stack ?? \"\"}${cause !== undefined ? `\\ncause: ${cause}` : \"\"}\\n`,\n );\n };\n\n stdio.onclose = () => {\n void shutdown(0);\n };\n http.onclose = () => {\n // We've observed the current @modelcontextprotocol/sdk (0.20.x) only\n // firing onclose from inside its own close() method — i.e., as a\n // consequence of *our* shutdown. The synth branch below is defensive\n // for future SDK versions that may propagate socket-death as onclose.\n // The `shuttingDown` guard prevents double-synth when shutdown() calls\n // http.close() itself.\n if (shuttingDown) return;\n void shutdown(1, {\n message: \"Tandem HTTP upstream closed unexpectedly\",\n detail: \"upstream connection dropped mid-session\",\n });\n };\n\n // Start stdio BEFORE preflight so any `initialize` that arrives during\n // the preflight window is captured and either forwarded once upstream is\n // ready, or answered with -32000 if upstream never comes up.\n await stdio.start();\n\n // The SDK's StdioServerTransport watches stdin for 'data' and 'error' only —\n // it does not call onclose when the plugin host closes stdin (EOF). Register\n // our own one-shot listener so that plugin-host close (stdin.end() / HUP)\n // triggers the same clean shutdown path as stdio.onclose does.\n process.stdin.once(\"end\", () => {\n void shutdown(0);\n });\n\n const probe = await probeTandemServer({ url: baseUrl });\n if (!probe.ok) {\n const guidance =\n probe.kind === \"unreachable\"\n ? \"Start the Tauri app or run `tandem start` on the host, then retry.\"\n : \"The Tandem server is running but unhealthy — check the host logs.\";\n process.stderr.write(\n `[tandem mcp-stdio] Tandem server preflight failed at ${probe.url} (${probe.reason}).\\n` +\n `[tandem mcp-stdio] ${guidance}\\n`,\n );\n const synthMessage =\n probe.kind === \"unreachable\"\n ? \"Tandem server not running. Start the Tauri app or run `tandem start`.\"\n : \"Tandem server unhealthy (check host logs).\";\n deferredShutdown({ message: synthMessage, detail: probe.reason });\n return;\n }\n\n // The current @modelcontextprotocol/sdk's StreamableHTTPClientTransport.start()\n // only creates an AbortController and returns synchronously — this catch is\n // defensive for future SDK versions that may perform real I/O during start().\n try {\n await http.start();\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[tandem mcp-stdio] upstream http start failed: ${detail}\\n`);\n deferredShutdown({ message: \"Tandem HTTP upstream failed to start\", detail });\n return;\n }\n httpReady = true;\n\n // Held to preserve forwarding semantics — push through the normal path\n // now that upstream is ready. Note: forwardToUpstream does not await the\n // http.send, so buffered requests POST in parallel. Plugin hosts wait\n // for `initialize` to resolve before sending follow-ups per MCP spec, so\n // the buffer is usually ≤1 entry; we don't enforce serial ordering here.\n const buffered = preReadyBuffer.splice(0);\n for (const msg of buffered) forwardToUpstream(msg);\n}\n\nexport function getRequestId(msg: JSONRPCMessage): string | number | undefined {\n const m = msg as { id?: unknown; method?: unknown };\n if (typeof m.method !== \"string\") return undefined;\n if (typeof m.id === \"string\" || typeof m.id === \"number\") return m.id;\n return undefined;\n}\n\nexport function getResponseId(msg: JSONRPCMessage): string | number | undefined {\n const m = msg as { id?: unknown; method?: unknown };\n if (typeof m.method === \"string\") return undefined;\n if (typeof m.id === \"string\" || typeof m.id === \"number\") return m.id;\n return undefined;\n}\n","/**\n * Shared fetch-with-timeout helper.\n *\n * Used by `src/monitor/index.ts` and `src/channel/` (event-bridge + run) to\n * give every outbound HTTP call a bounded deadline. Without this, a half-open\n * upstream wedges the caller silently — see #336 (silent failures) and #364\n * (event-bridge transport timeout symmetry).\n *\n * Pure native `fetch` + `AbortSignal.timeout` so it can be imported from any\n * surface without dragging in server deps. Routes through `authFetch` so the\n * resolved Tandem auth token is forwarded automatically when set.\n *\n * `describeFetchError` formats timeout aborts as `<endpoint> timed out after\n * <ms>ms` so logs name the hung endpoint instead of the generic\n * \"operation was aborted\" string from AbortError/TimeoutError.\n */\n\nimport { authFetch } from \"./cli-runtime.js\";\n\n/**\n * Fetch with a per-request deadline.\n *\n * **Do not use for SSE handshake-then-stream patterns** — applying a fetch-level\n * timeout to a streaming response also aborts the body `ReadableStream` when\n * the timeout fires, killing the stream at `timeoutMs`. Use a local\n * `AbortController` cleared after the handshake settles for that case.\n */\nexport async function fetchWithTimeout(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<Response> {\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;\n return authFetch(url, { ...init, signal });\n}\n\n/**\n * Format a fetch error for logs. Recognizes `TimeoutError` / `AbortError`\n * (both names that `AbortSignal.timeout` and manual aborts produce) and tags\n * them with the endpoint + threshold so log lines name the hung request.\n */\nexport function describeFetchError(err: unknown, endpoint: string, timeoutMs: number): string {\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n return `${endpoint} timed out after ${timeoutMs}ms`;\n }\n return err instanceof Error ? err.message : String(err);\n}\n\n/** True iff `err` is an AbortError or TimeoutError (the names produced by\n * AbortSignal.timeout and manual aborts). Channel callers re-throw these\n * through broad catches so timeouts surface as structured errors instead of\n * being swallowed as \"non-JSON response\" fake-success. */\nexport function isAbortOrTimeoutError(err: unknown): boolean {\n return err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\");\n}\n","function generateId(prefix: string): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n}\n\nexport function generateAnnotationId(): string {\n return generateId(\"ann\");\n}\n\nexport function generateMessageId(): string {\n return generateId(\"msg\");\n}\n\nexport function generateEventId(): string {\n return generateId(\"evt\");\n}\n\nexport function generateReplyId(): string {\n return generateId(\"rpl\");\n}\n\nexport function generateNotificationId(): string {\n return generateId(\"ntf\");\n}\n\nexport function generateAuthorshipId(author: \"user\" | \"claude\"): string {\n return generateId(author);\n}\n","/**\n * Event types for the Tandem → Claude Code channel.\n *\n * These events flow from browser-originated Y.Map changes through an SSE\n * endpoint to the channel shim, which pushes them into Claude Code as\n * `notifications/claude/channel` messages.\n *\n * This module lives in `src/shared/` so that `src/channel/` and\n * `src/monitor/` can import wire-protocol types without crossing the\n * server layer boundary.\n */\n\nimport type { AnnotationType, ReplyAuthor } from \"../types.js\";\n\n// --- Per-event payload interfaces ---\n\nexport interface AnnotationCreatedPayload {\n annotationId: string;\n annotationType: AnnotationType;\n content: string;\n textSnippet: string;\n hasSuggestedText?: boolean;\n}\n\nexport interface AnnotationAcceptedPayload {\n annotationId: string;\n textSnippet: string;\n}\n\nexport interface AnnotationDismissedPayload {\n annotationId: string;\n textSnippet: string;\n}\n\nexport interface ChatMessagePayload {\n messageId: string;\n text: string;\n replyTo: string | null;\n anchor: { from: number; to: number; textSnapshot: string } | null;\n /** Buffered selection context at the time the chat message was sent. */\n selection?: { from: number; to: number; selectedText: string } | { selectedText: string };\n}\n\nexport interface DocumentOpenedPayload {\n fileName: string;\n format: string;\n}\n\nexport interface DocumentClosedPayload {\n fileName: string;\n}\n\nexport interface AnnotationReplyPayload {\n annotationId: string;\n replyId: string;\n replyText: string;\n replyAuthor: ReplyAuthor;\n textSnippet: string;\n}\n\nexport interface DocumentSwitchedPayload {\n fileName: string;\n}\n\nexport interface AnnotationEditedPayload {\n annotationId: string;\n content: string;\n textSnippet: string;\n editedAt: number;\n}\n\n// --- Discriminated union ---\n\ninterface TandemEventBase {\n /** Timestamp-based unique ID for SSE `Last-Event-ID` reconnection. Format: `evt_<timestamp>_<rand>`. Roughly ordered but not strictly monotonic. */\n id: string;\n timestamp: number;\n /** Which document this event relates to (absent for global events). */\n documentId?: string;\n}\n\nexport type TandemEvent =\n | (TandemEventBase & { type: \"annotation:created\"; payload: AnnotationCreatedPayload })\n | (TandemEventBase & { type: \"annotation:accepted\"; payload: AnnotationAcceptedPayload })\n | (TandemEventBase & { type: \"annotation:dismissed\"; payload: AnnotationDismissedPayload })\n | (TandemEventBase & { type: \"annotation:reply\"; payload: AnnotationReplyPayload })\n | (TandemEventBase & { type: \"chat:message\"; payload: ChatMessagePayload })\n | (TandemEventBase & { type: \"document:opened\"; payload: DocumentOpenedPayload })\n | (TandemEventBase & { type: \"document:closed\"; payload: DocumentClosedPayload })\n | (TandemEventBase & { type: \"document:switched\"; payload: DocumentSwitchedPayload })\n | (TandemEventBase & { type: \"annotation:edited\"; payload: AnnotationEditedPayload });\n\n/** Union of all event type discriminants. */\nexport type TandemEventType = TandemEvent[\"type\"];\n\n// Re-export from shared utils (single ID generation pattern)\nexport { generateEventId } from \"../utils.js\";\n\n// --- Parse guard for SSE consumers ---\n\nconst VALID_EVENT_TYPES = new Set<TandemEventType>([\n \"annotation:created\",\n \"annotation:accepted\",\n \"annotation:dismissed\",\n \"annotation:edited\",\n \"annotation:reply\",\n \"chat:message\",\n \"document:opened\",\n \"document:closed\",\n \"document:switched\",\n]);\n\n/**\n * Validate a JSON-parsed value as a TandemEvent.\n * Used by the event-bridge to safely consume SSE data.\n */\nexport function parseTandemEvent(raw: unknown): TandemEvent | null {\n if (\n typeof raw !== \"object\" ||\n raw === null ||\n !(\"id\" in raw) ||\n typeof (raw as Record<string, unknown>).id !== \"string\" ||\n !(\"type\" in raw) ||\n !VALID_EVENT_TYPES.has((raw as Record<string, unknown>).type as TandemEventType) ||\n !(\"timestamp\" in raw) ||\n typeof (raw as Record<string, unknown>).timestamp !== \"number\" ||\n !(\"payload\" in raw) ||\n typeof (raw as Record<string, unknown>).payload !== \"object\"\n ) {\n return null;\n }\n return raw as TandemEvent;\n}\n\n/**\n * Convert a TandemEvent into a human-readable string for the channel `content` field.\n * Claude sees this text inside `<channel source=\"tandem-channel\">` tags.\n */\nexport function formatEventContent(event: TandemEvent): string {\n const doc = event.documentId ? ` [doc: ${event.documentId}]` : \"\";\n\n switch (event.type) {\n case \"annotation:created\": {\n const { annotationType, content, textSnippet, hasSuggestedText } = event.payload;\n const snippet = textSnippet ? ` on \"${textSnippet}\"` : \"\";\n const label = hasSuggestedText ? \"replacement\" : annotationType;\n return `User created ${label}${snippet}: ${content || \"(no content)\"}${doc}`;\n }\n case \"annotation:accepted\": {\n const { annotationId, textSnippet } = event.payload;\n return `User accepted annotation ${annotationId}${textSnippet ? ` (\"${textSnippet}\")` : \"\"}${doc}`;\n }\n case \"annotation:dismissed\": {\n const { annotationId, textSnippet } = event.payload;\n return `User dismissed annotation ${annotationId}${textSnippet ? ` (\"${textSnippet}\")` : \"\"}${doc}`;\n }\n case \"annotation:edited\": {\n const { content } = event.payload;\n return `User edited annotation: \"${content}\"${doc}`;\n }\n case \"annotation:reply\": {\n const { annotationId, replyAuthor, replyText, textSnippet } = event.payload;\n const who = replyAuthor === \"claude\" ? \"Claude\" : \"User\";\n const snippet = textSnippet ? ` (on \"${textSnippet}\")` : \"\";\n return `${who} replied to annotation ${annotationId}${snippet}: ${replyText}${doc}`;\n }\n case \"chat:message\": {\n const { text, replyTo, selection } = event.payload;\n const reply = replyTo ? ` (replying to ${replyTo})` : \"\";\n const sel =\n selection && selection.selectedText\n ? ` [selection: \"${selection.selectedText}\"${\"from\" in selection ? ` (${selection.from}-${selection.to})` : \"\"}]`\n : \"\";\n return `User says${reply}: ${text}${sel}${doc}`;\n }\n case \"document:opened\": {\n const { fileName, format } = event.payload;\n return `User opened document: ${fileName} (${format})${doc}`;\n }\n case \"document:closed\": {\n const { fileName } = event.payload;\n return `User closed document: ${fileName}${doc}`;\n }\n case \"document:switched\": {\n const { fileName } = event.payload;\n return `User switched to document: ${fileName}${doc}`;\n }\n default: {\n const _exhaustive: never = event;\n void _exhaustive;\n return `Unknown event${doc}`;\n }\n }\n}\n\n/**\n * Build the `meta` record for a channel notification.\n * Keys use underscores only (Channels API silently drops hyphenated keys).\n */\nexport function formatEventMeta(event: TandemEvent): Record<string, string> {\n const meta: Record<string, string> = {\n event_type: event.type,\n };\n if (event.documentId) meta.document_id = event.documentId;\n\n switch (event.type) {\n case \"annotation:created\":\n case \"annotation:accepted\":\n case \"annotation:dismissed\":\n meta.annotation_id = event.payload.annotationId;\n break;\n case \"annotation:edited\":\n meta.annotation_id = event.payload.annotationId;\n meta.edited_at = String(event.payload.editedAt);\n break;\n case \"annotation:reply\":\n meta.annotation_id = event.payload.annotationId;\n meta.reply_id = event.payload.replyId;\n break;\n case \"chat:message\":\n meta.message_id = event.payload.messageId;\n if (event.payload.selection?.selectedText) meta.has_selection = \"true\";\n break;\n case \"document:opened\":\n case \"document:closed\":\n case \"document:switched\":\n break;\n default: {\n const _exhaustive: never = event;\n void _exhaustive;\n break;\n }\n }\n\n return meta;\n}\n","/**\n * SSE event bridge: connects to Tandem server's /api/events endpoint\n * and pushes received events to Claude Code as channel notifications.\n *\n * Per-request timeouts (#364) mirror the monitor pattern: every outbound\n * fetch has a bounded deadline, the SSE body has an inactivity watchdog,\n * and the parse buffer is capped so a malformed upstream can't OOM us.\n * Without these, a half-open Tandem server wedges the bridge silently.\n */\n\nimport type { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { authFetch } from \"../shared/cli-runtime.js\";\nimport {\n CHANNEL_AWARENESS_FETCH_TIMEOUT_MS,\n CHANNEL_CONNECT_FETCH_TIMEOUT_MS,\n CHANNEL_ERROR_REPORT_TIMEOUT_MS,\n CHANNEL_MAX_RETRIES,\n CHANNEL_MAX_SSE_BUFFER_BYTES,\n CHANNEL_MODE_FETCH_TIMEOUT_MS,\n CHANNEL_RETRY_DELAY_MS,\n CHANNEL_SSE_INACTIVITY_TIMEOUT_MS,\n} from \"../shared/constants.js\";\nimport type { TandemEvent } from \"../shared/events/types.js\";\nimport { formatEventContent, formatEventMeta, parseTandemEvent } from \"../shared/events/types.js\";\nimport { describeFetchError, fetchWithTimeout } from \"../shared/fetch-with-timeout.js\";\n\nconst AWARENESS_DEBOUNCE_MS = 500;\nconst MODE_CACHE_TTL_MS = 2000;\n\n/**\n * Stdio-mode SSE bridge. New push-path work should target src/monitor/.\n * This path remains active for stdio-mode Claude Code connections.\n */\nexport async function startEventBridge(mcp: Server, tandemUrl: string): Promise<void> {\n let retries = 0;\n let lastEventId: string | undefined;\n\n while (retries < CHANNEL_MAX_RETRIES) {\n try {\n await connectAndStream(mcp, tandemUrl, lastEventId, (id) => {\n lastEventId = id;\n retries = 0;\n });\n } catch (err) {\n retries++;\n console.error(\n `[Channel] SSE connection failed (${retries}/${CHANNEL_MAX_RETRIES}):`,\n err instanceof Error ? err.message : err,\n );\n\n if (retries >= CHANNEL_MAX_RETRIES) {\n console.error(\"[Channel] SSE connection exhausted, reporting error and exiting\");\n try {\n await fetchWithTimeout(\n `${tandemUrl}/api/channel-error`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n error: \"CHANNEL_CONNECT_FAILED\",\n message: `Channel shim lost connection after ${CHANNEL_MAX_RETRIES} retries.`,\n }),\n },\n CHANNEL_ERROR_REPORT_TIMEOUT_MS,\n );\n } catch (reportErr) {\n console.error(\n \"[Channel] Could not report failure to server:\",\n describeFetchError(reportErr, \"/api/channel-error\", CHANNEL_ERROR_REPORT_TIMEOUT_MS),\n );\n }\n process.exit(1);\n }\n\n await new Promise((r) => setTimeout(r, CHANNEL_RETRY_DELAY_MS));\n }\n }\n}\n\nasync function connectAndStream(\n mcp: Server,\n tandemUrl: string,\n lastEventId: string | undefined,\n onEventId: (id: string) => void,\n): Promise<void> {\n const headers: Record<string, string> = { Accept: \"text/event-stream\" };\n if (lastEventId) headers[\"Last-Event-ID\"] = lastEventId;\n\n // Split handshake timeout from body lifetime. Using AbortSignal.timeout on\n // the fetch would kill the response body ReadableStream when the timeout\n // fires — every SSE stream would abort at CHANNEL_CONNECT_FETCH_TIMEOUT_MS.\n // A local AbortController cleared in `finally` after the handshake settles\n // means the body stream is no longer governed by it.\n const connectCtrl = new AbortController();\n const connectTimer = setTimeout(\n () => connectCtrl.abort(new Error(\"handshake timeout\")),\n CHANNEL_CONNECT_FETCH_TIMEOUT_MS,\n );\n let res: Response;\n try {\n res = await authFetch(`${tandemUrl}/api/events`, { headers, signal: connectCtrl.signal });\n } finally {\n clearTimeout(connectTimer);\n }\n if (!res.ok) throw new Error(`SSE endpoint returned ${res.status}`);\n if (!res.body) throw new Error(\"SSE endpoint returned no body\");\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n // Inactivity watchdog. A healthy stream emits keepalive comments\n // periodically; if no bytes arrive for CHANNEL_SSE_INACTIVITY_TIMEOUT_MS,\n // cancel the reader. reader.cancel() resolves a pending read() with\n // {done: true} (does not reject), so we surface the cause via a flag.\n let lastActivityAt = Date.now();\n let inactivityTimedOut = false;\n const watchdog = setInterval(() => {\n if (Date.now() - lastActivityAt > CHANNEL_SSE_INACTIVITY_TIMEOUT_MS) {\n inactivityTimedOut = true;\n reader.cancel(new Error(\"SSE inactivity timeout\")).catch(() => {});\n }\n }, CHANNEL_SSE_INACTIVITY_TIMEOUT_MS / 4);\n\n // Debounced awareness: only send the latest status after a quiet period\n let awarenessTimer: ReturnType<typeof setTimeout> | null = null;\n let clearAwarenessTimer: ReturnType<typeof setTimeout> | null = null;\n let pendingAwareness: TandemEvent | null = null;\n const AWARENESS_CLEAR_MS = 3000; // Reset active state after 3s of no new events\n\n function clearAwareness(documentId?: string) {\n fetchWithTimeout(\n `${tandemUrl}/api/channel-awareness`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n documentId: documentId ?? null,\n status: \"idle\",\n active: false,\n }),\n },\n CHANNEL_AWARENESS_FETCH_TIMEOUT_MS,\n ).catch((err) => {\n console.error(\n \"[Channel] clearAwareness failed (non-fatal):\",\n describeFetchError(err, \"/api/channel-awareness clear\", CHANNEL_AWARENESS_FETCH_TIMEOUT_MS),\n );\n });\n }\n\n function flushAwareness() {\n if (!pendingAwareness) return;\n const event = pendingAwareness;\n pendingAwareness = null;\n fetchWithTimeout(\n `${tandemUrl}/api/channel-awareness`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n documentId: event.documentId,\n status: `processing: ${event.type}`,\n active: true,\n }),\n },\n CHANNEL_AWARENESS_FETCH_TIMEOUT_MS,\n ).catch((err) => {\n console.error(\n \"[Channel] Awareness update failed:\",\n describeFetchError(\n err,\n \"/api/channel-awareness update\",\n CHANNEL_AWARENESS_FETCH_TIMEOUT_MS,\n ),\n );\n });\n\n // Auto-clear after timeout so the indicator doesn't stick\n if (clearAwarenessTimer) clearTimeout(clearAwarenessTimer);\n clearAwarenessTimer = setTimeout(() => clearAwareness(event.documentId), AWARENESS_CLEAR_MS);\n }\n\n function scheduleAwareness(event: TandemEvent) {\n pendingAwareness = event;\n if (awarenessTimer) clearTimeout(awarenessTimer);\n awarenessTimer = setTimeout(flushAwareness, AWARENESS_DEBOUNCE_MS);\n }\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n if (inactivityTimedOut) throw new Error(\"SSE inactivity timeout\");\n throw new Error(\"SSE stream ended\");\n }\n lastActivityAt = Date.now();\n\n buffer += decoder.decode(value, { stream: true });\n\n if (buffer.length > CHANNEL_MAX_SSE_BUFFER_BYTES) {\n throw new Error(\n `SSE buffer exceeded ${CHANNEL_MAX_SSE_BUFFER_BYTES} bytes without a frame boundary`,\n );\n }\n\n let boundary: number;\n while ((boundary = buffer.indexOf(\"\\n\\n\")) !== -1) {\n const frame = buffer.slice(0, boundary);\n buffer = buffer.slice(boundary + 2);\n\n if (frame.startsWith(\":\")) continue;\n\n let eventId: string | undefined;\n let data: string | undefined;\n\n for (const line of frame.split(\"\\n\")) {\n if (line.startsWith(\"id: \")) eventId = line.slice(4);\n else if (line.startsWith(\"data: \")) data = line.slice(6);\n }\n\n if (!data) continue;\n\n let event: TandemEvent | null;\n try {\n event = parseTandemEvent(JSON.parse(data));\n } catch {\n console.error(\n \"[Channel] Malformed SSE event data (skipping), eventId=%s:\",\n eventId,\n data.slice(0, 200),\n );\n // Permanently unparseable — advance past it to prevent infinite re-delivery on reconnect.\n if (eventId) onEventId(eventId);\n continue;\n }\n if (!event) {\n console.error(\n \"[Channel] Invalid SSE event structure (skipping), eventId=%s:\",\n eventId,\n data.slice(0, 200),\n );\n if (eventId) onEventId(eventId);\n continue;\n }\n\n // Solo mode: intentionally suppressed (not a delivery failure) — advance past it.\n if (event.type !== \"chat:message\") {\n const mode = await getCachedMode(tandemUrl);\n if (mode === \"solo\") {\n console.error(`[Channel] Solo mode: suppressed ${event.type} event`);\n if (eventId) onEventId(eventId);\n continue;\n }\n }\n\n try {\n await mcp.notification({\n method: \"notifications/claude/channel\",\n params: {\n content: formatEventContent(event),\n meta: formatEventMeta(event),\n },\n });\n } catch (err) {\n console.error(\"[Channel] MCP notification failed (transport broken?):\", err);\n throw err;\n }\n\n // Advance only after notification succeeds so a transport failure\n // doesn't silently skip events on reconnect.\n if (eventId) onEventId(eventId);\n\n scheduleAwareness(event);\n }\n }\n } finally {\n // Single source of truth for timer cleanup — every exit path (success,\n // throw, reader.cancel) runs through here so awareness/inactivity\n // timers can't leak across reconnects.\n clearInterval(watchdog);\n if (awarenessTimer) clearTimeout(awarenessTimer);\n if (clearAwarenessTimer) clearTimeout(clearAwarenessTimer);\n }\n}\n\n// Cached mode lookup — avoids an HTTP fetch per event\nlet cachedMode: string = \"tandem\";\nlet cachedModeAt = 0;\n\nasync function getCachedMode(tandemUrl: string): Promise<string> {\n const now = Date.now();\n if (now - cachedModeAt < MODE_CACHE_TTL_MS) return cachedMode;\n try {\n const res = await fetchWithTimeout(`${tandemUrl}/api/mode`, {}, CHANNEL_MODE_FETCH_TIMEOUT_MS);\n if (res.ok) {\n const { mode } = (await res.json()) as { mode: string };\n cachedMode = mode;\n } else {\n console.error(`[Channel] Mode check returned ${res.status}, using cached: \"${cachedMode}\"`);\n }\n cachedModeAt = now;\n } catch (err) {\n console.error(\n \"[Channel] Mode check failed, delivering event (fail-open):\",\n describeFetchError(err, \"/api/mode\", CHANNEL_MODE_FETCH_TIMEOUT_MS),\n );\n cachedModeAt = now;\n }\n return cachedMode;\n}\n","/**\n * Tandem Channel Shim — core runtime, shared by:\n * - src/channel/index.ts (standalone binary, used by the Desktop sidecar)\n * - src/cli/channel.ts (npm-delivered entry for the plugin `tandem-channel`)\n *\n * Bridges Tandem's SSE event stream → Claude Code channel notifications,\n * and exposes a `tandem_reply` tool for Claude to respond to chat messages.\n *\n * Uses the low-level MCP `Server` class (not `McpServer`) as required by\n * the Channels API spec.\n */\n\nimport { createConnection } from \"node:net\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { CallToolRequestSchema, ListToolsRequestSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\nimport { redirectConsoleToStderr, resolveTandemUrl } from \"../shared/cli-runtime.js\";\nimport {\n CHANNEL_PERMISSION_FETCH_TIMEOUT_MS,\n CHANNEL_REPLY_FETCH_TIMEOUT_MS,\n DEFAULT_MCP_PORT,\n} from \"../shared/constants.js\";\nimport {\n describeFetchError,\n fetchWithTimeout,\n isAbortOrTimeoutError,\n} from \"../shared/fetch-with-timeout.js\";\nimport { startEventBridge } from \"./event-bridge.js\";\n\nexport interface RunChannelOptions {\n /** Skip the non-fatal reachability probe. The CLI wrapper runs a strict\n * preflight upstream and we don't want to double-log \"server not reachable\"\n * noise. Defaults to false. */\n skipReachabilityLog?: boolean;\n}\n\nexport async function runChannel(opts: RunChannelOptions = {}): Promise<void> {\n redirectConsoleToStderr();\n\n const tandemUrl = resolveTandemUrl();\n\n const mcp = new Server(\n { name: \"tandem-channel\", version: \"0.1.0\" },\n {\n capabilities: {\n experimental: {\n \"claude/channel\": {},\n \"claude/channel/permission\": {},\n },\n tools: {},\n },\n instructions: [\n 'Events from Tandem arrive as <channel source=\"tandem-channel\" event_type=\"...\" document_id=\"...\">.',\n \"These are real-time push notifications of user actions in the collaborative document editor.\",\n \"Event types: annotation:created, annotation:accepted, annotation:dismissed, annotation:reply,\",\n \"chat:message, document:opened, document:closed, document:switched.\",\n \"Chat messages may include a 'selection' field with buffered selection context.\",\n \"Use your tandem MCP tools (tandem_getTextContent, tandem_comment, tandem_edit, etc.) to act on them.\",\n \"Reply to chat messages using tandem_reply. Pass document_id from the tag attributes.\",\n \"Do not reply to non-chat events — just act on them using tools.\",\n \"If you haven't received channel notifications recently, call tandem_checkInbox as a fallback.\",\n ].join(\" \"),\n },\n );\n\n mcp.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [\n {\n name: \"tandem_reply\",\n description: \"Reply to a chat message in Tandem\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n text: { type: \"string\", description: \"The reply message\" },\n documentId: {\n type: \"string\",\n description: \"Document ID from the channel event (optional)\",\n },\n replyTo: {\n type: \"string\",\n description: \"Message ID being replied to (optional)\",\n },\n },\n required: [\"text\"],\n },\n },\n ],\n }));\n\n mcp.setRequestHandler(CallToolRequestSchema, async (req) => {\n if (req.params.name === \"tandem_reply\") {\n const args = req.params.arguments as Record<string, unknown>;\n try {\n const res = await fetchWithTimeout(\n `${tandemUrl}/api/channel-reply`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(args),\n },\n CHANNEL_REPLY_FETCH_TIMEOUT_MS,\n );\n let data: unknown;\n try {\n data = await res.json();\n } catch (parseErr) {\n // Re-throw timeout/abort errors so they surface as structured\n // failures to Claude. AbortSignal.timeout fires DURING `res.json()`\n // (headers landed but body hung); without this re-throw, the bare\n // catch swallows AbortError and reports a fake-success \"Non-JSON\n // response\" payload — exactly the silent-failure pattern #364\n // exists to prevent.\n if (isAbortOrTimeoutError(parseErr)) throw parseErr;\n data = { message: \"Non-JSON response\" };\n }\n if (!res.ok) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Reply failed (${res.status}): ${JSON.stringify(data)}`,\n },\n ],\n isError: true,\n };\n }\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data) }] };\n } catch (err) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Failed to send reply: ${describeFetchError(\n err,\n \"/api/channel-reply\",\n CHANNEL_REPLY_FETCH_TIMEOUT_MS,\n )}`,\n },\n ],\n isError: true,\n };\n }\n }\n throw new Error(`Unknown tool: ${req.params.name}`);\n });\n\n const PermissionRequestSchema = z.object({\n method: z.literal(\"notifications/claude/channel/permission_request\"),\n params: z.object({\n request_id: z.string(),\n tool_name: z.string(),\n description: z.string(),\n input_preview: z.string(),\n }),\n });\n\n mcp.setNotificationHandler(PermissionRequestSchema, async ({ params }) => {\n try {\n const res = await fetchWithTimeout(\n `${tandemUrl}/api/channel-permission`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n requestId: params.request_id,\n toolName: params.tool_name,\n description: params.description,\n inputPreview: params.input_preview,\n }),\n },\n CHANNEL_PERMISSION_FETCH_TIMEOUT_MS,\n );\n if (!res.ok) {\n console.error(\n `[Channel] Permission relay got HTTP ${res.status} — browser may not see prompt`,\n );\n }\n } catch (err) {\n console.error(\n \"[Channel] Failed to forward permission request:\",\n describeFetchError(err, \"/api/channel-permission\", CHANNEL_PERMISSION_FETCH_TIMEOUT_MS),\n );\n }\n });\n\n console.error(`[Channel] Tandem channel shim starting (server: ${tandemUrl})`);\n\n if (!opts.skipReachabilityLog) {\n const reachable = await checkServerReachable(tandemUrl);\n if (!reachable) {\n console.error(`[Channel] Cannot reach Tandem server at ${tandemUrl}`);\n console.error(\"[Channel] Start it with: tandem start\");\n // Continue anyway — the event bridge will retry, and the server may start later\n }\n }\n\n const transport = new StdioServerTransport();\n await mcp.connect(transport);\n console.error(\"[Channel] Connected to Claude Code via stdio\");\n\n startEventBridge(mcp, tandemUrl).catch((err) => {\n console.error(\"[Channel] Event bridge failed unexpectedly:\", err);\n process.exit(1);\n });\n}\n\nasync function checkServerReachable(url: string, timeoutMs = 2000): Promise<boolean> {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n console.error(\n `[Channel] Invalid TANDEM_URL: \"${url}\" — expected format: http://localhost:3479`,\n );\n return false;\n }\n const port = parseInt(parsed.port || String(DEFAULT_MCP_PORT), 10);\n return new Promise((resolve) => {\n const socket = createConnection({ port, host: parsed.hostname }, () => {\n socket.destroy();\n resolve(true);\n });\n socket.setTimeout(timeoutMs);\n socket.on(\"timeout\", () => {\n socket.destroy();\n resolve(false);\n });\n socket.on(\"error\", (err) => {\n console.error(`[Channel] Server probe failed: ${err.message}`);\n socket.destroy();\n resolve(false);\n });\n });\n}\n","/**\n * Tandem channel subcommand — npm-delivered entry for the plugin\n * `tandem-channel` MCP server. Runs the unified preflight, then hands off to\n * the shared channel shim runtime in src/channel/run.ts.\n */\n\nimport { runChannel } from \"../channel/run.js\";\nimport { ensureTandemServer } from \"./preflight.js\";\n\nexport async function runChannelCli(): Promise<void> {\n await ensureTandemServer();\n await runChannel({ skipReachabilityLog: true });\n}\n","import envPaths from \"env-paths\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { TOKEN_FILE_NAME } from \"../constants.js\";\n\nexport function getTokenFilePath(): string {\n return path.join(envPaths(\"tandem\", { suffix: \"\" }).data, TOKEN_FILE_NAME);\n}\n\nexport async function readTokenFromFile(): Promise<string | null> {\n const filePath = getTokenFilePath();\n try {\n const content = await fs.promises.readFile(filePath, \"utf8\");\n // Remediate insecure permissions if a previous chmod failed (e.g., process crashed).\n if (process.platform !== \"win32\") {\n try {\n const stat = await fs.promises.stat(filePath);\n if ((stat.mode & 0o077) !== 0) {\n console.error(\"[tandem] auth token file has insecure permissions; attempting chmod 0600\");\n await fs.promises.chmod(filePath, 0o600);\n }\n } catch {\n // Non-fatal: stat/chmod failure doesn't invalidate the token we already read\n }\n }\n const trimmed = content.trim();\n return trimmed.length > 0 ? trimmed : null;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { promises as fsPromises } from \"node:fs\";\nimport path from \"node:path\";\nimport { getTokenFilePath, readTokenFromFile } from \"../shared/auth/token-file.js\";\nimport { resolveAuthTokenCandidate, resolveTandemUrl } from \"../shared/cli-runtime.js\";\nimport { applyConfigWithToken } from \"./setup.js\";\n\n/** SHA-256 fingerprint — first 8 hex chars. Never logs the full token value. */\nfunction fingerprint(token: string): string {\n return createHash(\"sha256\").update(token, \"utf8\").digest(\"hex\").slice(0, 8);\n}\n\nfunction generateToken(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\nexport async function rotateToken(): Promise<void> {\n console.error(\"\\n[tandem] Rotating auth token...\\n\");\n\n // Refuse to rotate when token comes from env — Tauri injects TANDEM_AUTH_TOKEN\n // before sidecar spawn, and Claude Code's plugin host injects\n // CLAUDE_PLUGIN_OPTION_AUTH_TOKEN from userConfig. In either case we have no\n // way to update the launcher; rotating the file would desync with what's\n // re-injected on the next launch.\n const { source: envAuthSource } = resolveAuthTokenCandidate();\n if (\n envAuthSource === \"TANDEM_AUTH_TOKEN\" ||\n envAuthSource === \"CLAUDE_PLUGIN_OPTION_AUTH_TOKEN\"\n ) {\n console.error(\n `[tandem] Error: ${envAuthSource} is set in the environment.\\n` +\n \" Token rotation is not supported in env-token mode (used by Tauri\\n\" +\n \" and Claude Code's plugin host). Unset the variable and let Tandem\\n\" +\n \" manage the token file, or rotate via the launcher's token management.\",\n );\n process.exit(1);\n }\n\n const oldToken = await readTokenFromFile();\n if (!oldToken) {\n console.error(\n \"[tandem] Error: no token file found. Run `tandem setup` first to initialize the token.\",\n );\n process.exit(1);\n }\n\n // writeTokenToFile uses O_EXCL; bypass it here — rotation is an intentional overwrite.\n // Use atomic write: write to a temp file first, then rename() into place.\n // rename() is atomic on the same filesystem — power-loss mid-write cannot leave an empty file.\n const newToken = generateToken();\n const tokenPath = getTokenFilePath();\n const dir = path.dirname(tokenPath);\n const tmpPath = path.join(dir, `.auth-token-tmp-${randomBytes(4).toString(\"hex\")}`);\n try {\n await fsPromises.writeFile(tmpPath, newToken, { encoding: \"utf8\", mode: 0o600 });\n await fsPromises.rename(tmpPath, tokenPath);\n } catch (err) {\n await fsPromises.unlink(tmpPath).catch(() => {});\n throw err;\n }\n\n const serverUrl = resolveTandemUrl();\n\n // Three distinct outcomes:\n // graceWindowActive = true → server accepted the rotation; grace window is live\n // serverRejected = true → server reachable but returned non-2xx\n // (neither) → fetch threw; server was not running\n let graceWindowActive = false;\n let serverRejected = false;\n let serverRejectedStatus = 0;\n try {\n const resp = await fetch(`${serverUrl}/api/rotate-token`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${oldToken}`,\n },\n body: JSON.stringify({}),\n signal: AbortSignal.timeout(5000),\n });\n if (resp.ok) {\n graceWindowActive = true;\n } else {\n serverRejected = true;\n serverRejectedStatus = resp.status;\n }\n } catch {\n console.error(\n \"[tandem] Warning: server is not reachable. The new token is written to disk.\\n\" +\n \" Restart the server to activate the grace window; reconnect Claude Code after.\",\n );\n }\n\n let updatedCount = 0;\n let configErrors: string[] = [];\n try {\n const result = await applyConfigWithToken(newToken);\n updatedCount = result.updated;\n configErrors = result.errors;\n } catch (err) {\n console.error(\n `[tandem] Warning: failed to update MCP configs: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // TODO(v0.8.1): After rotation, re-walk Cowork workspaces to rewrite\n // env.TANDEM_AUTH_TOKEN so post-rotation Cowork sessions don't 401\n // (security invariant §6 — silent-failure H1). The Tauri IPC dynamic import\n // approach is inert here: this CLI runs as a Node subprocess with no WebView,\n // so `@tauri-apps/api/core`'s `invoke()` has no bridge to Rust. The fix is\n // an HTTP bridge — add a POST /api/cowork-apply-token endpoint in the server\n // (guarded by the auth middleware) and call it from here after the server\n // accepts the rotation.\n\n if (serverRejected) {\n // Configs now reference the new token but the server still holds the old one.\n // Print a strong warning — do NOT print \"Rotated auth token\" as that implies success.\n console.error(\n `[tandem] WARNING: server rejected the rotation request (status: ${serverRejectedStatus}).`,\n );\n if (updatedCount > 0) {\n console.error(\n ` ${updatedCount} config file(s) updated to the new token, but the server still\\n` +\n \" holds the old token. Restart the server to complete rotation.\",\n );\n }\n console.error(` Old fingerprint: ${fingerprint(oldToken)}`);\n console.error(` New fingerprint: ${fingerprint(newToken)}`);\n for (const e of configErrors) {\n console.error(` Warning: could not update config — ${e}`);\n }\n console.error(\"\");\n return;\n }\n\n console.error(\"[tandem] Rotated auth token.\");\n console.error(` Old fingerprint: ${fingerprint(oldToken)}`);\n console.error(` New fingerprint: ${fingerprint(newToken)}`);\n console.error(` Updated ${updatedCount} config file(s).`);\n\n for (const e of configErrors) {\n console.error(` Warning: could not update config — ${e}`);\n }\n\n if (graceWindowActive) {\n console.error(\n \" Old token remains valid for 60 seconds; reconnect Claude Code within that window.\",\n );\n } else {\n console.error(\n \" Server was not running — start it with `tandem` and reconnect Claude Code with the new token.\",\n );\n }\n\n console.error(\"\");\n}\n","import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst SERVER_DIST = resolve(__dirname, \"../server/index.js\");\n\nexport function runStart(): void {\n if (!existsSync(SERVER_DIST)) {\n console.error(`[Tandem] Server not found at ${SERVER_DIST}`);\n console.error(\"[Tandem] The installation may be corrupted. Try: npm install -g tandem-editor\");\n process.exit(1);\n }\n\n console.error(\"[Tandem] Starting server...\");\n\n const proc = spawn(\"node\", [SERVER_DIST], {\n stdio: \"inherit\",\n env: { ...process.env, TANDEM_OPEN_BROWSER: \"1\" },\n });\n\n proc.on(\"error\", (err) => {\n console.error(`[Tandem] Failed to start server: ${err.message}`);\n process.exit(1);\n });\n\n proc.on(\"exit\", (code) => {\n process.exit(code ?? 0);\n });\n\n // Forward signals — proc.kill() with no argument uses SIGTERM on Unix\n // and TerminateProcess on Windows (correct cross-platform behavior).\n // On Windows SIGTERM is not emitted by the OS, but SIGINT (Ctrl+C) works.\n // Both are listed for Unix compatibility.\n for (const sig of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.once(sig, () => proc.kill());\n }\n}\n","/**\n * Tandem CLI — entry point for the `tandem` global command.\n * Shebang is added by tsup banner at build time.\n *\n * Usage:\n * tandem Start the Tandem server and open the editor\n * tandem setup Register Tandem MCP tools with Claude Code / Claude Desktop\n * tandem setup --force Register even if no Claude install is auto-detected\n * tandem --help Show this help\n * tandem --version Show version\n */\n\nimport updateNotifier from \"update-notifier\";\n\nprocess.once(\"uncaughtException\", (err: unknown) => {\n const msg = err instanceof Error ? (err.stack ?? err.message) : String(err);\n try {\n process.stderr.write(`[tandem cli] uncaughtException: ${msg}\\n`);\n } catch {\n /* EPIPE */\n }\n process.exit(1);\n});\nprocess.once(\"unhandledRejection\", (reason: unknown) => {\n const detail = reason instanceof Error ? reason.message : String(reason);\n process.stderr.write(`[tandem cli] unhandledRejection: ${detail}\\n`);\n process.exit(1);\n});\n\n// Injected at build time by tsup define; declared here for TypeScript\ndeclare const __TANDEM_VERSION__: string;\nconst version = typeof __TANDEM_VERSION__ !== \"undefined\" ? __TANDEM_VERSION__ : \"0.0.0-dev\";\n\nconst args = process.argv.slice(2);\n\n// Skip the update notifier for stdio subcommands — the output is machine-consumed\n// by Claude Desktop's plugin loader, and any incidental write risks corrupting\n// the MCP wire or producing log noise no human will ever read.\nconst isStdioMode = args[0] === \"mcp-stdio\" || args[0] === \"channel\";\nif (!isStdioMode) {\n updateNotifier({ pkg: { name: \"tandem-editor\", version } }).notify();\n}\n\nif (args.includes(\"--help\") || args.includes(\"-h\")) {\n console.log(`tandem v${version}\n\nUsage:\n tandem Start Tandem server and open the editor\n tandem setup Register MCP tools with Claude Code / Claude Desktop\n tandem setup --force Register to default paths regardless of detection\n tandem setup --with-channel-shim Also register the stdio channel shim (legacy opt-in)\n tandem rotate-token Rotate the auth token with a 60-second grace window\n tandem mcp-stdio Run as a stdio MCP server proxying to local HTTP\n (used by the plugin's Cowork bridge; requires\n tandem server running on the host)\n tandem channel Run the Tandem channel shim (stdio MCP)\n (used by the plugin's tandem-channel entry)\n tandem --version\n tandem --help\n`);\n process.exit(0);\n}\n\nif (args.includes(\"--version\") || args.includes(\"-v\")) {\n console.log(version);\n process.exit(0);\n}\n\ntry {\n if (args[0] === \"--uninstall-scrub\") {\n // Hidden subcommand invoked by the Tauri NSIS uninstaller hook. Walks\n // Cowork workspaces and removes Tandem plugin entries + firewall rules.\n // Runs inside the already-signed tandem.exe (security invariant §10 —\n // prevents binary-planting during uninstall).\n const { runUninstallScrub } = await import(\"./uninstall-scrub.js\");\n const exitCode = await runUninstallScrub();\n process.exit(exitCode);\n } else if (args[0] === \"setup\") {\n const { runSetup } = await import(\"./setup.js\");\n await runSetup({\n force: args.includes(\"--force\"),\n withChannelShim: args.includes(\"--with-channel-shim\"),\n });\n } else if (args[0] === \"mcp-stdio\") {\n const { runMcpStdio } = await import(\"./mcp-stdio.js\");\n await runMcpStdio();\n } else if (args[0] === \"channel\") {\n const { runChannelCli } = await import(\"./channel.js\");\n await runChannelCli();\n } else if (args[0] === \"rotate-token\") {\n const { rotateToken } = await import(\"./rotate-token.js\");\n await rotateToken();\n } else if (!args[0] || args[0] === \"start\") {\n const { runStart } = await import(\"./start.js\");\n runStart();\n } else {\n console.error(`Unknown command: ${args[0]}`);\n console.error(\"Run 'tandem --help' for usage.\");\n process.exit(1);\n }\n} catch (err) {\n console.error(`\\n[Tandem] Fatal error: ${err instanceof Error ? err.message : String(err)}`);\n console.error(\"If this persists, try reinstalling: npm install -g tandem-editor\\n\");\n process.exit(1);\n}\n"],"mappings":"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","names":["path","write","readFileSync","dirname","resolve","fileURLToPath","__dirname","VALID_TOKEN_RE","detail","buffered","StdioServerTransport","args","resolve","fs","path","fsPromises","path","existsSync","dirname","resolve","fileURLToPath","__dirname","runUninstallScrub","runSetup","runMcpStdio","runChannelCli","rotateToken","runStart"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/win-path-guard.ts","../../src/cli/uninstall-scrub.ts","../../src/cli/skill-content.ts","../../src/shared/constants.ts","../../src/server/platform.ts","../../src/server/integrations/acl-win.ts","../../src/server/integrations/backup.ts","../../src/server/integrations/apply.ts","../../src/cli/setup.ts","../../src/shared/cli-runtime.ts","../../src/cli/preflight.ts","../../src/cli/mcp-stdio.ts","../../src/shared/api-paths.ts","../../src/shared/fetch-with-timeout.ts","../../src/shared/utils.ts","../../src/shared/events/types.ts","../../src/shared/positions/types.ts","../../src/shared/types.ts","../../src/shared/sse-consumer.ts","../../src/channel/event-bridge.ts","../../src/channel/run.ts","../../src/cli/channel.ts","../../src/shared/auth/token-file.ts","../../src/cli/rotate-token.ts","../../src/cli/start.ts","../../src/cli/index.ts"],"sourcesContent":["/**\n * Windows workspace path guard — mirrors the Rust §3 invariant for TypeScript callers.\n *\n * Four steps (in order):\n * a. lstat each ancestor; reject any path whose chain contains a symlink.\n * b. fs.realpath() to canonicalize (safe: symlinks already rejected in (a)).\n * c. Reject UNC paths (\\\\server\\share or \\\\?\\UNC\\...; allow \\\\?\\C:\\...).\n * d. Component-wise containment check under realpath'd %LOCALAPPDATA%\n * (case-insensitive on Windows).\n *\n * Extracted into its own module so it can be unit-tested via vi.mock(\"node:fs\", ...).\n */\n\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\ntype Logger = { warn: (msg: string) => void };\n\n/**\n * Validate that `candidate` is a safe workspace path contained within `realLocalAppData`.\n *\n * @returns the realpath'd canonical path string on success, or null if rejected.\n * Callers are responsible for supplying a realpath'd `realLocalAppData`.\n */\nexport async function assertSafeWorkspacePath(\n candidate: string,\n realLocalAppData: string,\n logger?: Logger,\n): Promise<string | null> {\n const warn = (msg: string) => logger?.warn(`[path-guard] ${msg}`);\n\n // (a) lstat-walk: reject any component that is a symlink.\n if (await hasSymlinkInChain(candidate, warn)) {\n warn(`symlink/reparse point in chain: ${candidate}`);\n return null;\n }\n\n // (b) Canonicalize via realpath (safe now — symlinks already rejected).\n let real: string;\n try {\n real = await fs.realpath(candidate);\n } catch (err) {\n warn(`realpath failed for ${candidate}: ${(err as Error).message}`);\n return null;\n }\n\n // (c) Reject UNC paths.\n if (isUncPath(real)) {\n warn(`UNC path rejected: ${real}`);\n return null;\n }\n\n // (d) Component-wise containment under realLocalAppData (case-insensitive).\n if (!isComponentWiseChild(real, realLocalAppData)) {\n warn(`path outside %LOCALAPPDATA%: ${real}`);\n return null;\n }\n\n return real;\n}\n\n/** Returns true if any ancestor (inclusive) of `p` is a symbolic link. */\nasync function hasSymlinkInChain(p: string, warn: (m: string) => void): Promise<boolean> {\n // Walk from candidate up through all ancestors.\n let current = path.resolve(p);\n const visited = new Set<string>();\n\n while (true) {\n if (visited.has(current)) break;\n visited.add(current);\n\n try {\n const stat = await fs.lstat(current);\n if (stat.isSymbolicLink()) {\n return true;\n }\n } catch (err) {\n // lstat failed — fail closed for safety.\n warn(`lstat failed for ${current}: ${(err as Error).message}`);\n return true;\n }\n\n const parent = path.dirname(current);\n if (parent === current) break; // reached root\n current = parent;\n }\n\n return false;\n}\n\n/** Returns true if the path is a UNC path (\\\\server\\share or \\\\?\\UNC\\...). */\nfunction isUncPath(p: string): boolean {\n // Allow extended-length local paths (\\\\?\\C:\\...) but reject:\n // \\\\?\\UNC\\server\\share — extended UNC\n // \\\\server\\share — classic UNC\n if (p.startsWith(\"\\\\\\\\?\\\\UNC\\\\\") || p.startsWith(\"//?/UNC/\")) return true;\n if (\n (p.startsWith(\"\\\\\\\\\") && !p.startsWith(\"\\\\\\\\?\\\\\")) ||\n (p.startsWith(\"//\") && !p.startsWith(\"//?/\"))\n )\n return true;\n return false;\n}\n\n/**\n * Returns true if `child` is strictly within `root` on a component-wise basis\n * (case-insensitive on Windows).\n */\nfunction isComponentWiseChild(child: string, root: string): boolean {\n // Normalize separators and split on path.sep.\n const normalize = (p: string) => p.replace(/[\\\\/]+/g, path.sep).replace(/[/\\\\]$/, \"\");\n\n const rootNorm = normalize(root);\n const childNorm = normalize(child);\n\n const rootParts = rootNorm.split(path.sep);\n const childParts = childNorm.split(path.sep);\n\n if (childParts.length <= rootParts.length) return false;\n\n for (let i = 0; i < rootParts.length; i++) {\n // Case-insensitive on Windows.\n if (rootParts[i].toLowerCase() !== childParts[i].toLowerCase()) return false;\n }\n return true;\n}\n","/**\n * Tandem `--uninstall-scrub` subcommand.\n *\n * Invoked by the Tauri NSIS installer hook during uninstall. Walks every\n * Cowork workspace under `%LOCALAPPDATA%\\Packages\\Claude_*\\LocalCache\\\n * Roaming\\Claude\\local-agent-mode-sessions\\` and removes the Tandem plugin\n * entry from:\n * - `installed_plugins.json` (remove `mcpServers.tandem`)\n * - `known_marketplaces.json` (remove `marketplaces.tandem`)\n * - `cowork_settings.json` (remove `tandem@tandem` from `enabledPlugins`)\n *\n * Then removes the `Tandem Cowork*` Windows Firewall rules via `netsh`.\n *\n * **Security invariant §10 (ADR):** this runs INSIDE the already-signed\n * `tandem.exe` binary — NOT as a separate `uninstall_scrub.exe`. That\n * prevents binary-planting attacks during uninstall.\n *\n * **Failure policy:** logs every error, exits 0 on clean-or-not-installed,\n * non-zero only on unrecoverable I/O failures. NSIS logs the exit code but\n * does NOT block uninstall — Tandem must always uninstall even if a\n * workspace scrub partially fails.\n *\n * **Token safety:** this scrub READS JSON to find Tandem entries but the\n * removed-entry contents (including the auth token) are NEVER logged.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { promises as fsPromises } from \"node:fs\";\nimport path from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { assertSafeWorkspacePath } from \"./win-path-guard.js\";\n\nconst execFileAsync = promisify(execFile);\n\nconst TANDEM_PLUGIN_ID = \"tandem\";\nconst TANDEM_ENABLED_KEY = \"tandem@tandem\";\nconst FIREWALL_ALLOW_RULE = \"Tandem Cowork\";\nconst FIREWALL_DENY_RULE = \"Tandem Cowork \\u2014 Deny (elevation refused)\";\n\ntype ScrubLogger = {\n info: (msg: string) => void;\n warn: (msg: string) => void;\n error: (msg: string) => void;\n close: () => Promise<void>;\n};\n\nasync function openLogger(): Promise<ScrubLogger> {\n const localAppData = process.env.LOCALAPPDATA;\n if (!localAppData) {\n // No log file — just use stderr.\n const write = (level: string, msg: string): void => {\n process.stderr.write(`[tandem uninstall-scrub ${level}] ${msg}\\n`);\n };\n return {\n info: (m) => write(\"info\", m),\n warn: (m) => write(\"warn\", m),\n error: (m) => write(\"error\", m),\n close: async () => {},\n };\n }\n\n const logDir = path.join(localAppData, \"tandem\", \"Logs\");\n await fsPromises.mkdir(logDir, { recursive: true }).catch(() => {});\n const logPath = path.join(logDir, \"uninstall.log\");\n const stream = await fsPromises.open(logPath, \"a\").catch(() => null);\n\n const write = (level: string, msg: string): void => {\n const line = `[${new Date().toISOString()}] [${level}] ${msg}\\n`;\n process.stderr.write(line);\n if (stream) {\n stream.write(line).catch(() => {});\n }\n };\n\n return {\n info: (m) => write(\"info\", m),\n warn: (m) => write(\"warn\", m),\n error: (m) => write(\"error\", m),\n close: async () => {\n if (stream) await stream.close().catch(() => {});\n },\n };\n}\n\n/**\n * Find all Cowork workspace directories.\n *\n * Matches `%LOCALAPPDATA%\\Packages\\Claude_*\\LocalCache\\Roaming\\Claude\\\n * local-agent-mode-sessions\\<ws-id>\\<vm-id>\\`.\n *\n * Each candidate vm-path is validated by the 4-step Windows path guard before\n * being included — callers receive only safe, realpath'd paths.\n *\n * Returns an empty array on any error (e.g. Cowork not installed).\n */\nexport async function findCoworkWorkspaces(logger: ScrubLogger): Promise<string[]> {\n const localAppData = process.env.LOCALAPPDATA;\n if (!localAppData) {\n logger.info(\"%LOCALAPPDATA% not set — skipping workspace scan\");\n return [];\n }\n\n // Realpath %LOCALAPPDATA% once for use by the path guard.\n let realLad: string;\n try {\n realLad = await fsPromises.realpath(localAppData);\n } catch {\n realLad = localAppData; // best-effort fallback\n }\n\n const packagesDir = path.join(localAppData, \"Packages\");\n let packageEntries: string[];\n try {\n packageEntries = await fsPromises.readdir(packagesDir);\n } catch (err) {\n logger.info(`cannot read Packages dir: ${(err as Error).message}`);\n return [];\n }\n\n const claudePackages = packageEntries.filter((name) => name.startsWith(\"Claude_\"));\n if (claudePackages.length === 0) {\n logger.info(\"no Claude_* package directories found\");\n return [];\n }\n\n const workspaces: string[] = [];\n for (const pkg of claudePackages) {\n const sessionsRoot = path.join(\n packagesDir,\n pkg,\n \"LocalCache\",\n \"Roaming\",\n \"Claude\",\n \"local-agent-mode-sessions\",\n );\n\n let wsEntries: string[];\n try {\n wsEntries = await fsPromises.readdir(sessionsRoot);\n } catch (err) {\n logger.warn(`cannot read sessions root ${sessionsRoot}: ${(err as Error).message}`);\n continue;\n }\n\n for (const ws of wsEntries) {\n const wsPath = path.join(sessionsRoot, ws);\n let vmEntries: string[];\n try {\n vmEntries = await fsPromises.readdir(wsPath);\n } catch (err) {\n logger.warn(`cannot read workspace dir ${wsPath}: ${(err as Error).message}`);\n continue;\n }\n\n for (const vm of vmEntries) {\n const vmPath = path.join(wsPath, vm);\n try {\n const stat = await fsPromises.stat(vmPath);\n if (!stat.isDirectory()) continue;\n\n // 4-step path guard — only include validated, realpath'd paths.\n const safePath = await assertSafeWorkspacePath(vmPath, realLad, logger);\n if (safePath !== null) {\n workspaces.push(safePath);\n }\n } catch (err) {\n logger.warn(`cannot stat ${vmPath}: ${(err as Error).message}`);\n }\n }\n }\n }\n\n logger.info(`found ${workspaces.length} workspace(s)`);\n return workspaces;\n}\n\n/**\n * Atomically rewrite a JSON file, invoking `mutate` on the parsed object.\n *\n * Precondition: `filePath` has already been validated by the path guard.\n * This function trusts its callers (consistent with `with_locked_json` on the Rust side).\n *\n * Returns true if the mutation changed something and was written, false if\n * the file was absent or unchanged.\n */\nexport async function rewriteJson(\n filePath: string,\n mutate: (obj: Record<string, unknown>) => boolean,\n logger: ScrubLogger,\n): Promise<boolean> {\n let content: string;\n try {\n content = await fsPromises.readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return false;\n }\n logger.warn(`cannot read ${filePath}: ${(err as Error).message}`);\n return false;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch (err) {\n logger.warn(`invalid JSON in ${filePath}: ${(err as Error).message}`);\n return false;\n }\n\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n logger.warn(`${filePath} is not a JSON object — skipping`);\n return false;\n }\n\n const changed = mutate(parsed as Record<string, unknown>);\n if (!changed) {\n return false;\n }\n\n const dir = path.dirname(filePath);\n const tmpName = `.tandem-scrub-tmp-${Math.random().toString(36).slice(2, 10)}`;\n const tmpPath = path.join(dir, tmpName);\n\n try {\n await fsPromises.writeFile(tmpPath, JSON.stringify(parsed, null, 2), \"utf8\");\n await fsPromises.rename(tmpPath, filePath);\n } catch (err) {\n await fsPromises.unlink(tmpPath).catch(() => {});\n throw err;\n }\n return true;\n}\n\n/**\n * Remove the Tandem entry from `installed_plugins.json`.\n */\nexport function removeInstalledPlugins(obj: Record<string, unknown>): boolean {\n let changed = false;\n for (const key of [\"mcpServers\", \"servers\"]) {\n const servers = obj[key];\n if (typeof servers === \"object\" && servers !== null && !Array.isArray(servers)) {\n const map = servers as Record<string, unknown>;\n if (TANDEM_PLUGIN_ID in map) {\n delete map[TANDEM_PLUGIN_ID];\n changed = true;\n }\n }\n }\n return changed;\n}\n\n/**\n * Remove the Tandem marketplace entry from `known_marketplaces.json`.\n */\nexport function removeKnownMarketplaces(obj: Record<string, unknown>): boolean {\n const mp = obj.marketplaces;\n if (typeof mp === \"object\" && mp !== null && !Array.isArray(mp)) {\n const map = mp as Record<string, unknown>;\n if (TANDEM_PLUGIN_ID in map) {\n delete map[TANDEM_PLUGIN_ID];\n return true;\n }\n }\n return false;\n}\n\n/**\n * Remove `tandem@tandem` from `enabledPlugins` in `cowork_settings.json`.\n */\nexport function removeCoworkSettings(obj: Record<string, unknown>): boolean {\n const enabled = obj.enabledPlugins;\n if (Array.isArray(enabled)) {\n const before = enabled.length;\n obj.enabledPlugins = enabled.filter((v) => v !== TANDEM_ENABLED_KEY);\n return (obj.enabledPlugins as unknown[]).length < before;\n }\n if (typeof enabled === \"object\" && enabled !== null) {\n const map = enabled as Record<string, unknown>;\n if (TANDEM_ENABLED_KEY in map) {\n delete map[TANDEM_ENABLED_KEY];\n return true;\n }\n }\n return false;\n}\n\n/**\n * Delete a Windows Firewall rule by name. Non-existent rules are not an error.\n */\nasync function deleteFirewallRule(name: string, logger: ScrubLogger): Promise<void> {\n try {\n await execFileAsync(\"netsh\", [\"advfirewall\", \"firewall\", \"delete\", \"rule\", `name=${name}`]);\n logger.info(`deleted firewall rule: ${name}`);\n } catch (err) {\n const e = err as NodeJS.ErrnoException & { stdout?: string; stderr?: string };\n // netsh returns exit code 1 when no rule matches — not fatal.\n const stdoutStr = e.stdout ?? \"\";\n if (stdoutStr.includes(\"No rules match\")) {\n logger.info(`no firewall rule to delete: ${name}`);\n return;\n }\n logger.warn(\n `failed to delete firewall rule ${name}: ${e.message ?? String(err)} ` +\n `(stdout: ${stdoutStr.trim().slice(0, 200)})`,\n );\n }\n}\n\n/**\n * Main entry — Windows-only.\n */\nexport async function runUninstallScrub(): Promise<number> {\n const logger = await openLogger();\n\n logger.info(\"Tandem uninstall scrub starting\");\n\n if (process.platform !== \"win32\") {\n logger.info(`platform ${process.platform} is not win32 — skipping Cowork scrub`);\n await logger.close();\n return 0;\n }\n\n let failures = 0;\n\n try {\n const workspaces = await findCoworkWorkspaces(logger);\n for (const ws of workspaces) {\n const pluginsDir = path.join(ws, \"cowork_plugins\");\n try {\n await rewriteJson(\n path.join(pluginsDir, \"installed_plugins.json\"),\n removeInstalledPlugins,\n logger,\n );\n await rewriteJson(\n path.join(pluginsDir, \"known_marketplaces.json\"),\n removeKnownMarketplaces,\n logger,\n );\n await rewriteJson(\n path.join(pluginsDir, \"cowork_settings.json\"),\n removeCoworkSettings,\n logger,\n );\n } catch (err) {\n logger.error(`scrub failed for ${ws}: ${(err as Error).message}`);\n failures++;\n }\n }\n\n await deleteFirewallRule(FIREWALL_ALLOW_RULE, logger);\n await deleteFirewallRule(FIREWALL_DENY_RULE, logger);\n\n logger.info(`scrub complete: ${workspaces.length} workspace(s), ${failures} failure(s)`);\n } catch (err) {\n logger.error(`scrub fatal error: ${(err as Error).message}`);\n failures++;\n }\n\n await logger.close();\n\n // Exit 0 on clean-or-not-installed. Non-zero only on unrecoverable failures —\n // and even then, NSIS does NOT block uninstall; it just records the code.\n return failures > 0 ? 1 : 0;\n}\n","/**\n * SKILL.md content installed to ~/.claude/skills/tandem/ by `tandem setup`.\n * Single source of truth lives at `skills/tandem/SKILL.md`. This module\n * reads that file at module load so the plugin install path and the\n * `tandem setup` install path always deliver byte-identical content.\n *\n * The file is shipped via package.json `files: [\"skills/\", ...]`, and the\n * CLI entry (dist/cli/index.js) is not self-contained — so at runtime the\n * relative path `../../skills/tandem/SKILL.md` resolves from either\n * dist/cli/ (tsx dev) or dist/cli/ (npm install) to the package-root\n * `skills/tandem/SKILL.md`.\n */\nimport { readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst SKILL_PATH = resolve(__dirname, \"../../skills/tandem/SKILL.md\");\n\nexport const SKILL_CONTENT = readFileSync(SKILL_PATH, \"utf-8\");\n","export const DEFAULT_WS_PORT = 3478;\nexport const DEFAULT_MCP_PORT = 3479;\n\nexport const TANDEM_REPO_URL = \"https://github.com/bloknayrb/tandem\";\nexport const TANDEM_ISSUES_NEW_URL = `${TANDEM_REPO_URL}/issues/new`;\n\n/** File extensions the server accepts for opening. */\nexport const SUPPORTED_EXTENSIONS = new Set([\".md\", \".txt\", \".html\", \".htm\", \".docx\"]);\nexport const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB\nexport const SESSION_MAX_AGE = 30 * 24 * 60 * 60 * 1000; // 30 days\nexport const TYPING_DEBOUNCE = 3000; // 3 seconds\nexport const DISCONNECT_DEBOUNCE_MS = 3000; // 3 seconds before showing \"server not reachable\"\nexport const PROLONGED_DISCONNECT_MS = 30_000; // 30 seconds before showing App-level disconnect banner\n\nimport type { HighlightColor } from \"./types.js\";\n\nexport const HIGHLIGHT_COLORS: Record<HighlightColor, string> = {\n yellow: \"rgba(255, 235, 59, 0.3)\",\n green: \"rgba(76, 175, 80, 0.3)\",\n blue: \"rgba(33, 150, 243, 0.3)\",\n pink: \"rgba(236, 72, 153, 0.3)\",\n};\n\nexport const HIGHLIGHT_COLOR_VARS: Record<HighlightColor, string> = {\n yellow: \"var(--tandem-highlight-yellow)\",\n green: \"var(--tandem-highlight-green)\",\n blue: \"var(--tandem-highlight-blue)\",\n pink: \"var(--tandem-highlight-pink)\",\n};\n\nexport function normalizeHighlightColor(color: string | null | undefined): HighlightColor {\n return color && color in HIGHLIGHT_COLORS ? (color as HighlightColor) : \"yellow\";\n}\n\nexport const TANDEM_MODE_DEFAULT = \"tandem\" as const;\nexport const TANDEM_MODE_KEY = \"tandem:mode\";\nexport const TANDEM_SETTINGS_KEY = \"tandem:settings\";\n// Panel-width localStorage keys.\n//\n// NOTE: these use legacy hyphen naming (vs the neighboring colon convention\n// `tandem:mode`/`tandem:settings`) because they predate the colon scheme and\n// changing the strings would invalidate every existing user's saved widths.\n// Do not \"fix\" the style — the key string is the persistence contract.\n//\n// Right-side panel width is shared between the tabbed layout and any\n// future left-panel variant. The left key only applies when the left\n// panel is visible.\nexport const PANEL_WIDTH_KEY = \"tandem-panel-width\";\nexport const LEFT_PANEL_WIDTH_KEY = \"tandem-left-panel-width\";\n\nexport type PanelSide = \"left\" | \"right\";\n\n/**\n * Maps a panel side to its localStorage key. Using a Record instead of two\n * bare constants makes the \"both handles write to the same key\" regression\n * (#228) structurally impossible — you can't accidentally map both sides to\n * the same value at a callsite.\n *\n * Uses `as const satisfies Record<PanelSide, string>` so the value type stays\n * as the literal strings rather than widening to `string` — this preserves\n * the persistence-key identity at every callsite while still enforcing\n * exhaustive coverage of `PanelSide`.\n */\nexport const PANEL_WIDTH_KEYS = {\n left: LEFT_PANEL_WIDTH_KEY,\n right: PANEL_WIDTH_KEY,\n} as const satisfies Record<PanelSide, string>;\nexport const SELECTION_DWELL_DEFAULT_MS = 1000;\nexport const SELECTION_DWELL_MIN_MS = 500;\nexport const SELECTION_DWELL_MAX_MS = 3000;\n\n// Large file thresholds\nexport const CHARS_PER_PAGE = 3_000;\nexport const LARGE_FILE_PAGE_THRESHOLD = 50;\nexport const VERY_LARGE_FILE_PAGE_THRESHOLD = 100;\n\nexport const CTRL_ROOM = \"__tandem_ctrl__\";\n\n/** Y.Map key constants — centralized to prevent silent bugs from string typos. */\nexport const Y_MAP_ANNOTATIONS = \"annotations\";\nexport const Y_MAP_AWARENESS = \"awareness\";\nexport const Y_MAP_USER_AWARENESS = \"userAwareness\";\nexport const Y_MAP_MODE = \"mode\";\nexport const Y_MAP_DWELL_MS = \"selectionDwellMs\";\nexport const Y_MAP_CHAT = \"chat\";\nexport const Y_MAP_DOCUMENT_META = \"documentMeta\";\nexport const Y_MAP_ANNOTATION_REPLIES = \"annotationReplies\";\nexport const Y_MAP_SAVED_AT_VERSION = \"savedAtVersion\";\nexport const Y_MAP_AUTHORSHIP = \"authorship\";\n// Y.Map sub-keys: userAwareness\nexport const Y_MAP_SELECTION = \"selection\";\nexport const Y_MAP_ACTIVITY = \"activity\";\n// Y.Map sub-keys: awareness (Claude focus)\nexport const Y_MAP_CLAUDE = \"claude\";\n// Y.Map sub-keys: documentMeta\nexport const Y_MAP_OPEN_DOCUMENTS = \"openDocuments\";\nexport const Y_MAP_ACTIVE_DOCUMENT_ID = \"activeDocumentId\";\nexport const Y_MAP_ACTIVE_DOCUMENT_EPOCH = \"activeDocumentEpoch\";\nexport const Y_MAP_GENERATION_ID = \"generationId\";\nexport const Y_MAP_READ_ONLY = \"readOnly\";\nexport const Y_MAP_STORE_READ_ONLY = \"storeReadOnly\";\n\nexport const AUTHORSHIP_TOGGLE_KEY = \"tandem:showAuthorship\";\nexport const ANNOTATION_DECORATIONS_TOGGLE_KEY = \"tandem:showAnnotationDecorations\";\n\nexport const RECENT_FILES_KEY = \"tandem:recentFiles\";\nexport const RECENT_FILES_CAP = 20;\n\nexport const USER_NAME_KEY = \"tandem:userName\";\nexport const USER_NAME_DEFAULT = \"You\";\nexport const USER_NAME_EVENT = \"tandem:user-name-changed\";\nexport const USER_NAME_MAX_LEN = 40;\n\n// Toast notifications\nexport const TOAST_DISMISS_MS = { error: 8000, warning: 6000, info: 4000 } as const;\nexport const MAX_VISIBLE_TOASTS = 5;\nexport const NOTIFICATION_BUFFER_SIZE = 50;\n\n// Onboarding tutorial\nexport const TUTORIAL_COMPLETED_KEY = \"tandem:tutorialCompleted\";\n// Load-bearing: useTutorial.svelte.ts uses this prefix to exclude tutorial\n// SEEDS from \"user-authored annotation\" detection. Tutorial NOTES carry\n// author=\"user\" (ADR-027: only the user can author notes), so the prefix\n// is the ONLY thing distinguishing a seed from a real user note. Renaming\n// this constant without updating useTutorial.svelte.ts would silently\n// re-introduce the step-1 auto-advance bug from PR #621 PR-A2b.\nexport const TUTORIAL_ANNOTATION_PREFIX = \"tutorial-\";\n/** Persists \"user skipped the Cowork onboarding step\" across sessions. */\nexport const COWORK_ONBOARDING_SKIPPED_KEY = \"tandem:coworkOnboardingSkipped\";\n/** Polling interval for `cowork_get_status` while the consumer is active. */\nexport const COWORK_STATUS_POLL_MS = 30_000;\n/** Debounce interval for the \"Re-scan workspaces\" button. */\nexport const COWORK_RESCAN_DEBOUNCE_MS = 2_000;\n\n// Channel / event queue\nexport const CHANNEL_EVENT_BUFFER_SIZE = 200;\nexport const CHANNEL_EVENT_BUFFER_AGE_MS = 60_000; // 60 seconds\nexport const CHANNEL_SSE_KEEPALIVE_MS = 15_000; // 15 seconds\nexport const CHANNEL_MAX_RETRIES = 5;\nexport const CHANNEL_RETRY_DELAY_MS = 2_000;\n\n// Channel shim per-request timeouts. Mirror the monitor pattern (#364) so a\n// half-open Tandem server can't wedge `tandem_reply`, the permission relay,\n// or the event-bridge SSE handshake / awareness / mode / error-report POSTs.\n// Intentionally separate constants per endpoint so a slow endpoint doesn't\n// hold up a faster one — and so log lines name a meaningful threshold.\nexport const CHANNEL_CONNECT_FETCH_TIMEOUT_MS = 10_000; // /api/events handshake\nexport const CHANNEL_SSE_INACTIVITY_TIMEOUT_MS = 60_000; // No-bytes watchdog on SSE body\nexport const CHANNEL_MODE_FETCH_TIMEOUT_MS = 2_000; // /api/mode cache refresh\nexport const CHANNEL_AWARENESS_FETCH_TIMEOUT_MS = 5_000; // /api/channel-awareness POST\nexport const CHANNEL_ERROR_REPORT_TIMEOUT_MS = 3_000; // /api/channel-error POST on exit\nexport const CHANNEL_REPLY_FETCH_TIMEOUT_MS = 5_000; // /api/channel-reply (tandem_reply)\nexport const CHANNEL_PERMISSION_FETCH_TIMEOUT_MS = 5_000; // /api/channel-permission relay\n// Bound the SSE buffer so a misbehaving server that never emits frame\n// boundaries can't wedge the bridge with unbounded string growth.\nexport const CHANNEL_MAX_SSE_BUFFER_BYTES = 1_000_000;\n\n/** Auth token filename inside the app-data directory. */\nexport const TOKEN_FILE_NAME = \"auth-token\";\n\n/** Default MCP bind host — loopback only by default. */\nexport const DEFAULT_BIND_HOST = \"127.0.0.1\";\n\n/** Env var name to opt in to unauthenticated LAN binding. */\nexport const TANDEM_ALLOW_UNAUTHENTICATED_LAN_ENV = \"TANDEM_ALLOW_UNAUTHENTICATED_LAN\";\n\n/**\n * Env var name to suppress the integration-wizard first-run auto-open.\n * Used by the E2E harness (Playwright) so wizard modals don't cover\n * unrelated editor surfaces. The integration-wizard.spec.ts test itself\n * does NOT set this — it exercises the manual-reopen affordance.\n */\nexport const TANDEM_DISABLE_FIRST_RUN_WIZARD_ENV = \"TANDEM_DISABLE_FIRST_RUN_WIZARD\";\n\n/** Tauri WebView origin hostname — must be accepted alongside localhost. */\nexport const TAURI_HOSTNAME = \"tauri.localhost\";\n\n// Zoom persistence (Tauri desktop)\nexport const ZOOM_STORAGE_KEY = \"tandem:zoomLevel\";\nexport const ZOOM_MIN = 0.5;\nexport const ZOOM_MAX = 2.0;\nexport const ZOOM_DEFAULT = 1.0;\n","import { execFileSync, execSync } from \"child_process\";\nimport envPaths from \"env-paths\";\nimport net from \"net\";\nimport path from \"path\";\n\n/**\n * Resolve the Tandem app-data root directory. `TANDEM_APP_DATA_DIR` overrides\n * the `env-paths` default. Not memoised so tests can swap tempdirs mid-run.\n */\nexport function resolveAppDataDir(): string {\n const envOverride = process.env.TANDEM_APP_DATA_DIR;\n if (envOverride && envOverride.length > 0) return envOverride;\n return envPaths(\"tandem\", { suffix: \"\" }).data;\n}\n\nconst APP_DATA_DIR = resolveAppDataDir();\n\n/** Platform-appropriate session storage directory. */\nexport const SESSION_DIR = path.join(APP_DATA_DIR, \"sessions\");\n\n/** Path to the file tracking the last version the user ran. */\nexport const LAST_SEEN_VERSION_FILE = path.join(APP_DATA_DIR, \"last-seen-version\");\n\n/**\n * Kill any process currently listening on the given TCP port.\n * Best-effort — swallows all errors so startup always proceeds.\n */\nexport function freePort(port: number): void {\n try {\n if (process.platform === \"win32\") {\n freePortWindows(port);\n } else {\n freePortUnix(port);\n }\n } catch (err) {\n console.error(`[Tandem] freePort(${port}): ${err instanceof Error ? err.message : err}`);\n }\n}\n\n/**\n * Poll until a TCP port is available for binding.\n * Replaces the fixed 300ms sleep after freePort() — the OS may need\n * longer to release a killed process's socket (especially on Windows).\n */\nexport async function waitForPort(port: number, timeoutMs = 5000): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n if (await tryBind(port)) return;\n await new Promise((r) => setTimeout(r, 200));\n }\n throw new Error(`Port ${port} still not available after ${timeoutMs}ms`);\n}\n\n/** Attempt to bind a port and immediately release it. Returns true if available. */\nfunction tryBind(port: number): Promise<boolean> {\n return new Promise((resolve, reject) => {\n const srv = net.createServer();\n srv.once(\"error\", (err: NodeJS.ErrnoException) => {\n srv.close(() => {\n if (err.code === \"EADDRINUSE\") {\n resolve(false);\n } else {\n reject(err); // EACCES, etc. — don't mask as \"port in use\"\n }\n });\n });\n srv.listen(port, \"127.0.0.1\", () => {\n srv.close(() => resolve(true));\n });\n });\n}\n\n/** Parse PIDs from lsof output (one PID per line). */\nexport function parseLsofPids(output: string): number[] {\n return output\n .trim()\n .split(\"\\n\")\n .map((line) => parseInt(line.trim(), 10))\n .filter((pid) => Number.isFinite(pid) && pid > 0);\n}\n\n/** Parse a PID from ss output (e.g. `pid=1234`). */\nexport function parseSsPid(output: string): number | null {\n const match = output.match(/pid=(\\d+)/);\n return match ? parseInt(match[1], 10) : null;\n}\n\nfunction freePortWindows(port: number): void {\n const out = execSync(`netstat -ano | findstr \":${port}.*LISTENING\"`, {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n });\n const pid = out.trim().split(/\\s+/).at(-1);\n if (pid && /^\\d+$/.test(pid)) {\n try {\n const killOut = execFileSync(\"taskkill\", [\"/PID\", pid, \"/F\"], {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n // Some taskkill paths exit 0 with empty stdout — avoid a dangling\n // \": \" in the log when there's no message to append.\n const trimmed = killOut.trim();\n console.error(\n `[Tandem] Killed stale PID ${pid} holding port ${port}${trimmed ? `: ${trimmed}` : \"\"}`,\n );\n } catch (err) {\n // Surface taskkill failure (permission denied, cross-session, race) so a\n // subsequent port-bind EADDRINUSE is diagnosable. The prior `stdio: \"ignore\"`\n // masked these and left the user with a silent \"Disconnected\" state.\n const stderr =\n err && typeof err === \"object\" && \"stderr\" in err\n ? String((err as { stderr: unknown }).stderr ?? \"\")\n : \"\";\n const message = err instanceof Error ? err.message : String(err);\n console.error(\n `[Tandem] taskkill failed for PID ${pid} on port ${port}: ${message}${\n stderr ? ` — stderr: ${stderr.trim()}` : \"\"\n }`,\n );\n }\n }\n}\n\nfunction freePortUnix(port: number): void {\n let pids: number[] = [];\n\n try {\n const out = execSync(`lsof -ti TCP:${port} -sTCP:LISTEN`, {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n });\n pids = parseLsofPids(out);\n } catch {\n // lsof not available — try ss (Linux)\n try {\n const out = execSync(`ss -tlnp sport = :${port}`, {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n });\n const pid = parseSsPid(out);\n if (pid) pids = [pid];\n } catch {\n // ss also unavailable — give up\n }\n }\n\n for (const pid of pids) {\n try {\n process.kill(pid, \"SIGKILL\");\n console.error(`[Tandem] Killed stale PID ${pid} holding port ${port}`);\n } catch {\n // Process already gone\n }\n }\n}\n","/**\n * Windows DACL hardening for files that contain bearer tokens (`~/.claude.json`\n * and similar). Every function in this module is a no-op on non-Windows\n * platforms — POSIX paths use `chmod 0o600` instead.\n *\n * Security contract:\n * - `icacls`, `whoami`, and `powershell` are always invoked via `execFile`\n * with an argv array, never via a shell. Paths flow from `homedir()`\n * which is user-controlled via `HOME`/`USERPROFILE`; any shell-style\n * invocation is a command-injection bug.\n * - Broad-principal detection (`assertNoBroadAce`) reads the file's SDDL\n * via PowerShell `(Get-Acl).Sddl`. SDDL uses locale-independent 2-letter\n * shortcuts for well-known SIDs (`WD`=Everyone, `AU`=Authenticated\n * Users, `BU`=BUILTIN\\Users). Parsing `icacls` default output instead\n * would false-negative on non-English Windows (where the resolver\n * returns \"Utilisateurs\", \"Benutzer\", etc.) and on partial-success\n * exit-0 cases where icacls's \"Failed processing N files\" summary is\n * also localised.\n * - `setRestrictiveAcl` grants by **SID** (resolved once per process via\n * `whoami /user`), not by `process.env.USERNAME`. USERNAME is\n * in-process spoofable; a poisoned USERNAME could direct the grant at\n * a different local user whose name happens to collide, or contain\n * characters icacls parses unexpectedly (`:`, `/`, embedded UPNs).\n * - `setRestrictiveAcl` calls `/inheritance:r` THEN `/grant:r`. Without\n * `:r` on both flags, inherited ACEs from the parent dir survive and\n * `/grant` adds the user as an additional principal instead of\n * replacing the ACL.\n * - The tempfile self-verify (`assertNoBroadAce` at the end of\n * `setRestrictiveAcl`) is load-bearing — icacls is documented to exit\n * 0 on partial failure (silent no-op). The SDDL re-read is the only\n * trustworthy signal that the DACL is actually correct.\n * - PowerShell paths are passed via the `TANDEM_ACL_PATH` env var, not\n * interpolated into the script string. This avoids every quoting /\n * escaping pitfall for paths that contain spaces, apostrophes, or\n * backticks.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Resolve a Windows system binary by absolute path under `%SystemRoot%`.\n * Bypasses `PATH` so a git-bash / MSYS / Cygwin shadow (e.g. their own\n * `whoami` that doesn't understand Windows flags) can't intercept us.\n * `icacls` and `whoami` both live in `System32`.\n */\nfunction systemBin(name: string): string {\n return join(process.env.SystemRoot ?? \"C:\\\\Windows\", \"System32\", name);\n}\n\n/**\n * Run a PowerShell script, preferring PowerShell 7 (`pwsh.exe`) and falling\n * back to Windows PowerShell 5.1 (`powershell.exe`). pwsh is more reliable —\n * 5.1's auto-module-load has been observed to fail intermittently for\n * `Microsoft.PowerShell.Security` in CI sandboxes and on some user setups.\n *\n * Fallback is gated on `ENOENT` only. A spawn error of `EACCES` or `EPERM`\n * almost always reflects an AppLocker / WDAC policy denial — silently\n * routing around it via the legacy shell is the wrong defence posture\n * (admin's policy is a real security boundary). The original error is\n * preserved as `cause` on the fallback's failure for log forensics.\n */\nasync function runPowerShell(script: string, env: NodeJS.ProcessEnv): Promise<{ stdout: string }> {\n const args = [\"-NoProfile\", \"-NonInteractive\", \"-Command\", script];\n try {\n return await execFileAsync(\"pwsh.exe\", args, { env });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\") throw err;\n try {\n return await execFileAsync(\"powershell.exe\", args, { env });\n } catch (fallbackErr) {\n throw new Error(\n `runPowerShell: both pwsh.exe and powershell.exe failed (pwsh: ${(err as Error).message})`,\n { cause: fallbackErr },\n );\n }\n }\n}\n\n/**\n * SDDL ACE-string fragments that flag a broad-principal grant. These\n * shortcuts are locale-independent — they appear verbatim in the SDDL\n * regardless of Windows display language.\n *\n * Each pattern matches the principal-SID position of an SDDL ACE,\n * which is the last token before the closing `)`. Example ACE:\n * `(A;;FA;;;WD)` ← Everyone has Full Access\n *\n * Reference: https://learn.microsoft.com/windows/win32/secauthz/sid-strings\n */\nconst BROAD_SDDL_FRAGMENTS = [\n \";WD)\", // Everyone (S-1-1-0)\n \";AU)\", // Authenticated Users (S-1-5-11)\n \";BU)\", // BUILTIN\\Users (S-1-5-32-545)\n] as const;\n\n/**\n * Cached SID of the current Windows user. `whoami /user` is the cheapest\n * locale-independent way to get this. Resolved once per process; cleared\n * to `null` only on test reset.\n */\nlet cachedCurrentUserSid: string | null = null;\n\n/** Reset for tests only. Production callers MUST NOT depend on resetting. */\nexport function _resetCurrentUserSidForTests(): void {\n cachedCurrentUserSid = null;\n}\n\n/**\n * Resolve the SID of the current user via `whoami /user /fo csv /nh`. CSV\n * output is locale-independent and parses cleanly without PowerShell.\n */\nasync function getCurrentUserSid(): Promise<string> {\n if (cachedCurrentUserSid !== null) return cachedCurrentUserSid;\n const { stdout } = await execFileAsync(systemBin(\"whoami.exe\"), [\"/user\", \"/fo\", \"csv\", \"/nh\"]);\n // CSV row format: `\"DOMAIN\\user\",\"S-1-5-21-...\"` — extract the second column.\n const match = stdout.match(/\"(S-[\\d-]+)\"\\s*$/m);\n if (!match) {\n throw new Error(`getCurrentUserSid: could not parse SID from whoami output: ${stdout.trim()}`);\n }\n cachedCurrentUserSid = match[1];\n return cachedCurrentUserSid;\n}\n\n/**\n * Set a restrictive DACL on `path`: break inheritance, then grant Full\n * Control to the current user (by SID, not name) only. On non-Windows,\n * no-op.\n *\n * Includes a post-set SDDL re-verify on `path` because icacls is\n * documented to exit 0 even when \"Failed processing N files\" — the SDDL\n * read is the only trustworthy signal that the DACL is actually correct.\n *\n * @throws if any step (SID resolve, icacls, verify) fails.\n */\nexport async function setRestrictiveAcl(path: string): Promise<void> {\n if (process.platform !== \"win32\") return;\n\n const sid = await getCurrentUserSid();\n\n // icacls processes flags left-to-right in one invocation:\n // /inheritance:r — remove ALL inherited entries (`:d` would copy them\n // as explicit, defeating the purpose)\n // /grant:r — replace any existing explicit ACE for the user\n // rather than ADD a new one (no `:r` → duplicates\n // accumulate over repeated runs).\n // *<SID>:F — grant Full Control by SID. The `*` prefix tells\n // icacls to interpret the principal as a raw SID\n // rather than a name to look up.\n try {\n await execFileAsync(systemBin(\"icacls.exe\"), [path, \"/inheritance:r\", \"/grant:r\", `*${sid}:F`]);\n } catch (err) {\n throw new Error(`setRestrictiveAcl: icacls failed on ${path}: ${(err as Error).message}`, {\n cause: err,\n });\n }\n\n await assertNoBroadAce(path);\n}\n\n/**\n * Read the SDDL of `path` and assert no well-known broad principal is\n * granted access. Uses PowerShell `Get-Acl` so the principal check works\n * regardless of Windows display language.\n *\n * @throws if any broad-principal SDDL shortcut appears in the file's ACL.\n */\nexport async function assertNoBroadAce(path: string): Promise<void> {\n if (process.platform !== \"win32\") return;\n\n // `-LiteralPath` ensures wildcards in the path are not expanded. The\n // explicit `Import-Module` is defensive — `Get-Acl` lives in\n // `Microsoft.PowerShell.Security` which usually auto-loads, but the\n // auto-load fails in some sandboxed shells (e.g. CI runners with\n // restricted module paths).\n const script =\n \"Import-Module Microsoft.PowerShell.Security; (Get-Acl -LiteralPath $env:TANDEM_ACL_PATH).Sddl\";\n\n const { stdout } = await runPowerShell(script, {\n ...process.env,\n TANDEM_ACL_PATH: path,\n });\n\n const sddl = stdout.trim();\n for (const fragment of BROAD_SDDL_FRAGMENTS) {\n if (sddl.includes(fragment)) {\n throw new Error(\n `assertNoBroadAce: ${path} has a broad-principal ACE (SDDL fragment ${fragment}). SDDL:\\n${sddl}`,\n );\n }\n }\n}\n","/**\n * Conditional backup of `~/.claude.json` before Tandem overwrites a\n * user-customised `mcpServers.tandem` entry.\n *\n * The threat we close: a user with a hand-crafted tandem entry pointing\n * at a non-default port, a custom URL, or extra fields runs `tandem\n * setup` (or the auto-launcher) and Tandem replaces the entry with the\n * default `http://127.0.0.1:3479/mcp` shape. The user's customisation\n * is silently destroyed.\n *\n * The backup gives them a recovery path. We only write a backup when\n * the existing entry is **non-default** — token-rotation and fresh-\n * install runs would otherwise generate backup churn that buries the\n * one backup the user actually needs.\n *\n * Storage layout: `${appDataDir}/.backups/claude-json-YYYYMMDD-HHMMSS-\n * ${UUID8}.json`. Dir mode 0o700, file mode 0o600. The UUID suffix\n * defeats predictable-path symlink attacks; `wx` (exclusive create)\n * is the second layer.\n *\n * Atomicity invariant: `writeBackup` MUST complete (or throw) before\n * the caller invokes `atomicWrite` on the destination. If the backup\n * write fails, the destination MUST NOT be touched. `applyConfig`\n * sequences this contract explicitly.\n *\n * Cross-platform note: on Windows, `wx` + `mode: 0o600` doesn't honour\n * POSIX modes, so we apply `setRestrictiveAcl` from `acl-win.ts` to the\n * backup file after write. The same SDDL contract that protects\n * `~/.claude.json` itself now also protects the backup copy.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { open, readdir, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { setRestrictiveAcl } from \"./acl-win.js\";\n\n/** Directory name (under appDataDir) for non-default-config backups. */\nconst BACKUP_DIR_NAME = \".backups\";\n\n/** Filename prefix — present in every backup we create. */\nconst BACKUP_PREFIX = \"claude-json-\";\n\n/** Filename suffix — present in every backup we create. */\nconst BACKUP_SUFFIX = \".json\";\n\n/**\n * Keep at most this many backups. Older ones are pruned on every write\n * and on every server startup (covers crash-mid-prune cases).\n */\nexport const MAX_BACKUPS = 3;\n\n/**\n * Resolve the backup directory under `appDataDir`. Callers MUST pass the\n * dir as a parameter (not read `resolveAppDataDir()` internally) so tests\n * can inject a tmpdir without env-var stubbing.\n */\nexport function backupDir(appDataDir: string): string {\n return join(appDataDir, BACKUP_DIR_NAME);\n}\n\n/**\n * Format a timestamp as `YYYYMMDD-HHMMSS` in the local timezone. Local\n * time is what the user sees in their file manager — useful for\n * forensics.\n */\nfunction formatTimestamp(d: Date): string {\n const pad = (n: number): string => String(n).padStart(2, \"0\");\n return (\n `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-` +\n `${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`\n );\n}\n\n/**\n * Build a backup filename. UUID suffix defeats predictable-path attacks\n * where an attacker pre-creates a symlink at the predicted name.\n */\nexport function backupFilename(now: Date = new Date()): string {\n const ts = formatTimestamp(now);\n const uuid8 = randomUUID().slice(0, 8);\n return `${BACKUP_PREFIX}${ts}-${uuid8}${BACKUP_SUFFIX}`;\n}\n\n/**\n * Write `content` as a backup under `backupDir(appDataDir)`. Caller\n * passes the directory (already validated via `assertPathSafe` and\n * created with mode 0o700) and the bytes to write.\n *\n * Returns the full path of the written backup.\n *\n * Atomicity contract: this function either completes successfully and\n * the backup is on disk, or it throws and the caller MUST NOT proceed\n * with the rewrite. The atomic-failure-leaves-original-intact guarantee\n * depends on this.\n */\nexport async function writeBackup(dir: string, content: Buffer): Promise<string> {\n const backupPath = join(dir, backupFilename());\n\n // `wx` is exclusive-create: fails if the path already exists (UUID\n // collision is astronomically rare but a symlinked predictable path\n // is the real concern). `0o600` is honoured on POSIX; on Windows\n // we apply setRestrictiveAcl below.\n const fd = await open(backupPath, \"wx\", 0o600);\n let writeFailed = false;\n try {\n try {\n await fd.write(content);\n } catch (writeErr) {\n writeFailed = true;\n throw writeErr;\n }\n } finally {\n await fd.close();\n if (writeFailed) {\n // Remove the partial/zero-byte file so it doesn't count against\n // MAX_BACKUPS or mislead a forensic read. Cleanup errors are\n // swallowed — the write failure is what the caller needs to see.\n await rm(backupPath, { force: true }).catch(() => {});\n }\n }\n\n if (process.platform === \"win32\") {\n try {\n await setRestrictiveAcl(backupPath);\n } catch (aclErr) {\n // ACL failure leaves a bearer-token-bearing file on disk at the\n // dir-inherited permissions. Remove it — the caller treats the\n // throw as \"abort\", so an orphan would be invisible.\n await rm(backupPath, { force: true }).catch(() => {});\n throw aclErr;\n }\n }\n\n return backupPath;\n}\n\n/**\n * List backups currently in `dir`, newest first. Files are matched by\n * the supplied prefix/suffix (defaults match the `.claude.json` backup\n * scheme) so stray non-backup files in the dir aren't touched.\n *\n * The prefix/suffix parameters let a second caller (the broken-\n * integrations.json backup sweep in `storage.ts`) share this\n * implementation. Both call sites filter by a constant pair.\n *\n * Sorting is by filename, which works because the timestamp segment is\n * lexicographically monotonic (`YYYYMMDD-HHMMSS` or `Date.now()`). The\n * UUID suffix is a tiebreaker within the same millisecond.\n */\nexport async function listBackups(\n dir: string,\n prefix: string = BACKUP_PREFIX,\n suffix: string = BACKUP_SUFFIX,\n): Promise<string[]> {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw err;\n }\n return entries\n .filter((e) => e.startsWith(prefix) && e.endsWith(suffix))\n .sort()\n .reverse();\n}\n\n/**\n * Delete backups beyond the `max` newest. Returns the list of paths\n * removed (best-effort — entries that failed to remove are still\n * reported in the return list and logged via `console.error`, but the\n * loop never throws).\n *\n * The aggregate-failure shape exists because this runs at server\n * startup: an AV-locked file on Windows or a transient EACCES MUST NOT\n * abandon the rest of the sweep. The previous shape threw on the first\n * `rm` failure and silently skipped the rest.\n *\n * Defaults match the `claude-json-...json` scheme; the broken-\n * integrations sweep passes its own constants.\n */\nexport async function pruneOldBackups(\n dir: string,\n prefix: string = BACKUP_PREFIX,\n suffix: string = BACKUP_SUFFIX,\n max: number = MAX_BACKUPS,\n): Promise<string[]> {\n const all = await listBackups(dir, prefix, suffix);\n const toRemove = all.slice(max);\n const failures: Array<{ path: string; err: unknown }> = [];\n for (const name of toRemove) {\n const fullPath = join(dir, name);\n try {\n await rm(fullPath, { force: true });\n } catch (err) {\n failures.push({ path: fullPath, err });\n }\n }\n if (failures.length > 0) {\n const summary = failures\n .map((f) => `${f.path}: ${f.err instanceof Error ? f.err.message : String(f.err)}`)\n .join(\"; \");\n console.error(\n `[tandem] backup sweep: ${failures.length} entries could not be removed (${summary})`,\n );\n }\n return toRemove.map((name) => join(dir, name));\n}\n\n/**\n * Server-startup hook. Idempotent. Bounded — sweeps only the backup dir.\n *\n * Callers pass `appDataDir` so tests can inject a tmpdir; the production\n * call site uses `resolveAppDataDir()`.\n */\nexport async function sweepBackupsOnStartup(appDataDir: string): Promise<void> {\n await pruneOldBackups(backupDir(appDataDir));\n}\n\n/**\n * Decide whether overwriting `existing` with `newEntry` could lose\n * information the user might care about. The check is content-based,\n * not shape-based: shape-only checks miss the case where a user\n * hand-crafted `headers.Authorization` with a custom Bearer token —\n * Tandem's overwrite would silently destroy it because the entry's\n * SHAPE matches the default (URL identical, only known keys present).\n *\n * Returns `true` when:\n * - existing entry exists, AND\n * - existing != newEntry under canonical-JSON equality.\n *\n * This trades off: token rotation now triggers a backup (the bytes\n * change). That's acceptable churn — `MAX_BACKUPS=3` caps disk usage\n * and the user gets a strict history of recent token-bearing configs\n * as a side benefit. The alternative (shape-only check) had a\n * security review-flagged silent-destruction gap that we cannot close\n * without state we don't have (Tandem can't distinguish \"token Tandem\n * itself wrote 5 minutes ago\" from \"token the user added by hand\").\n */\nexport function shouldBackup(existing: unknown, newEntry: unknown): boolean {\n if (existing == null) return false;\n return canonicalJson(existing) !== canonicalJson(newEntry);\n}\n\n/**\n * Deterministic JSON-string with sorted object keys. Defeats key-order\n * false-positives (`{a:1,b:2}` vs `{b:2,a:1}` would otherwise\n * stringify differently).\n */\nfunction canonicalJson(value: unknown): string {\n if (value === null || typeof value !== \"object\") return JSON.stringify(value);\n if (Array.isArray(value)) return `[${value.map(canonicalJson).join(\",\")}]`;\n const keys = Object.keys(value as Record<string, unknown>).sort();\n const parts = keys.map(\n (k) => `${JSON.stringify(k)}:${canonicalJson((value as Record<string, unknown>)[k])}`,\n );\n return `{${parts.join(\",\")}}`;\n}\n","/**\n * Library helpers for applying Tandem's MCP entries to a Claude (or other\n * MCP-capable) client's config file. Shared by the wizard's apply route,\n * `tandem setup`, and `tandem rotate-token`.\n *\n * Hardening invariants enforced here (callers MUST NOT bypass):\n * - `assertPathSafe()` rejects symlinks and any path whose realpath falls\n * outside `[homedir(), tmpdir()]`. Prevents a compromised account from\n * replacing `~/.claude.json` with a symlink to `/etc/shadow` or a\n * Windows junction redirecting `~/.claude` into a protected dir.\n * - MSIX detection is anchored to `/^Claude_[A-Za-z0-9]+$/` and the\n * `%LOCALAPPDATA%` realpath must resolve under home (defeats an\n * attacker who controls env).\n * - Malformed JSON backups land in `${appDataDir}/.broken-backups/` at\n * `0o600` rather than next to `~/.claude.json` (which may inherit\n * world-readable perms and would leak co-tenant API keys).\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport {\n chmodSync,\n existsSync,\n constants as fsConstants,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n realpathSync,\n statSync,\n} from \"node:fs\";\nimport {\n chmod,\n copyFile,\n mkdir,\n open,\n readFile,\n rename,\n unlink,\n writeFile,\n} from \"node:fs/promises\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { basename, dirname, join, resolve, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { SKILL_CONTENT } from \"../../cli/skill-content.js\";\nimport { DEFAULT_MCP_PORT } from \"../../shared/constants.js\";\nimport { resolveAppDataDir } from \"../platform.js\";\nimport { setRestrictiveAcl } from \"./acl-win.js\";\nimport { backupDir, pruneOldBackups, shouldBackup, writeBackup } from \"./backup.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// Paths are anchored to the package root. The CLI bundle (`dist/cli/`) and\n// the server bundle (`dist/server/`) both sit one level below `dist/`, so\n// `../..` resolves to the package root in either bundle. In tsx dev,\n// `__dirname` is `src/server/integrations/`, so we need `../../..`.\nconst PACKAGE_ROOT = (() => {\n const fromBundle = resolve(__dirname, \"../..\");\n if (existsSync(join(fromBundle, \"package.json\"))) return fromBundle;\n return resolve(__dirname, \"../../..\");\n})();\nconst CHANNEL_DIST = resolve(PACKAGE_ROOT, \"dist/channel/index.js\");\n\nconst MCP_URL = `http://127.0.0.1:${DEFAULT_MCP_PORT}`;\n\n/**\n * Refuse to read a config larger than 5 MiB. The realistic `.claude.json` is\n * single-digit kilobytes; anything beyond that is either accidental corruption\n * (log files dropped in) or a deliberate DoS aimed at making the wizard's\n * read-parse-rewrite path exhaust memory. The cap is generous enough that no\n * legitimate user hits it.\n */\nconst MAX_CONFIG_BYTES = 5 * 1024 * 1024;\n\nexport interface McpEntry {\n type?: \"http\";\n url?: string;\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n headers?: Record<string, string>;\n}\n\nexport interface McpEntries {\n tandem: McpEntry;\n \"tandem-channel\"?: McpEntry;\n}\n\n/** MCP entry keys Tandem owns and may remove from an existing config. */\nexport type RemovableEntry = \"tandem\" | \"tandem-channel\";\n\n/**\n * Explicit apply intent. Callers state both `create` and `remove` so\n * absence of a key never implies removal — that distinction matters when\n * the wizard's user-confirmed diff differs from CLI's stale-cleanup\n * default. `applyOpsForCli` builds the CLI-shaped value.\n */\nexport interface ApplyOps {\n /** Entries to create or update in `mcpServers`. */\n create: McpEntries;\n /** Entries to remove if present in `mcpServers`. */\n remove: RemovableEntry[];\n /**\n * Optional callback invoked with the backup path when `applyConfig`\n * preserves a non-default existing `mcpServers.tandem` entry before\n * overwriting it. CLI callers pass a `console.error` printer; the\n * wizard pushes the path onto its structured apply response so the\n * user sees a recovery hint. Callback is NOT invoked on fresh-install\n * or pure token-rotation runs (those don't trigger a backup).\n */\n onBackup?: (backupPath: string) => void;\n}\n\n/**\n * Construct `ApplyOps` with the legacy \"remove tandem-channel unless using\n * the shim\" semantics. CLI callers (`tandem setup`, `tandem rotate-token`)\n * use this to preserve back-compat without re-implementing the diff. The\n * wizard never calls this — it builds `ApplyOps` from the user's diff\n * confirmation.\n *\n * Options object intentional: the boolean-flag `withChannelShim` is the\n * only state, but exposing it as a named field at every call site avoids\n * the boolean-trap antipattern (the previous positional signature read as\n * `applyOpsForCli(entries, true)` with no clue what the boolean meant).\n */\nexport function applyOpsForCli(create: McpEntries, opts: { withChannelShim: boolean }): ApplyOps {\n return {\n create,\n remove: opts.withChannelShim ? [] : [\"tandem-channel\"],\n };\n}\n\n/** Error thrown when a target path fails realpath/symlink validation. */\nexport class PathRejectedError extends Error {\n override readonly name = \"PathRejectedError\";\n constructor(\n public readonly path: string,\n public readonly reason: \"symlink\" | \"outside-home\" | \"unreadable\",\n message: string,\n ) {\n super(message);\n }\n}\n\nexport interface BuildMcpEntriesOptions {\n /** Include the legacy stdio channel shim. Defaults to false — the plugin\n * monitor handles event push for modern installs. Users on older setups\n * can run `tandem setup --with-channel-shim` to preserve the shim. */\n withChannelShim?: boolean;\n nodeBinary?: string;\n /** Auth token to embed in HTTP entry headers and stdio shim env.\n * When omitted (first-run before token provisioned), headers/env are omitted\n * and backward compatibility is preserved. */\n token?: string;\n /** Target kind controls entry shape. Claude Code uses HTTP (direct);\n * Claude Desktop uses stdio (npx bridge) because Cowork sessions can\n * only surface stdio MCP servers. */\n targetKind?: TargetKind;\n}\n\nexport function buildMcpEntries(\n channelPath: string,\n opts: BuildMcpEntriesOptions = {},\n): McpEntries {\n const isDesktop = opts.targetKind === \"claude-desktop\";\n\n let tandemEntry: McpEntry;\n if (isDesktop) {\n const env: Record<string, string> = { TANDEM_URL: MCP_URL };\n if (opts.token) {\n env.TANDEM_AUTH_TOKEN = opts.token;\n }\n tandemEntry = {\n command: \"npx\",\n args: [\"-y\", \"tandem-editor\", \"mcp-stdio\"],\n env,\n };\n } else {\n tandemEntry = { type: \"http\", url: `${MCP_URL}/mcp` };\n if (opts.token) {\n tandemEntry.headers = { Authorization: `Bearer ${opts.token}` };\n }\n }\n const entries: McpEntries = { tandem: tandemEntry };\n\n if (opts.withChannelShim) {\n const shimEnv: Record<string, string> = { TANDEM_URL: MCP_URL };\n if (opts.token) {\n shimEnv.TANDEM_AUTH_TOKEN = opts.token;\n }\n entries[\"tandem-channel\"] = {\n command: opts.nodeBinary ?? \"node\",\n args: [channelPath],\n env: shimEnv,\n };\n }\n return entries;\n}\n\nexport type TargetKind = \"claude-code\" | \"claude-desktop\";\n\nexport interface DetectedTarget {\n label: string;\n configPath: string;\n kind: TargetKind;\n}\n\n// Realpath of OS roots changes only across process restart (and tmpdir is\n// stable for test fixtures). Memoize so per-integration apply loops and\n// MSIX detection don't re-syscall for every call.\n//\n// Lifetime: process. Tandem doesn't drop privileges and never mutates HOME\n// mid-process; if a future caller does either, the cache is stale and must\n// be invalidated.\nconst DEFAULT_ROOTS_CACHE = new Map<string, string>();\nfunction realpathCached(p: string): string {\n const cached = DEFAULT_ROOTS_CACHE.get(p);\n if (cached !== undefined) return cached;\n try {\n const r = realpathSync(p);\n DEFAULT_ROOTS_CACHE.set(p, r);\n return r;\n } catch {\n return p;\n }\n}\n\n/**\n * Verify the target directory (a) is not a symlink/junction and (b) its\n * realpath resolves under one of the allowed roots. Throws\n * `PathRejectedError` otherwise.\n *\n * Threat model: prior compromised account replaces `~/.claude.json` with a\n * symlink to `/etc/shadow`; on Windows, a junction at `~/.claude`\n * redirecting to `C:\\Windows\\System32\\config\\` could let `mkdir(...\n * recursive)` walk into a protected dir. We reject both cases before any\n * read or write. The ancestor check is the closed-set boundary — any path\n * outside the allowed roots is refused even if it's not a symlink.\n *\n * Residual TOCTOU: the lstat-then-write window admits a same-uid attacker\n * who swaps a regular file for a symlink between check and write. Node's\n * `fs` doesn't expose `O_NOFOLLOW` / `openat`-style per-component\n * traversal, so the window is unavoidable without native bindings. The\n * realpath check shrinks the practical attack but doesn't close it.\n *\n * Defaults to `[homedir(), tmpdir()]`. Callers may nominate alternate\n * roots (e.g., test fixtures that operate inside a specific tmpdir).\n */\nexport function assertPathSafe(targetPath: string, opts: { allowedRoots?: string[] } = {}): void {\n const allowedRoots = (opts.allowedRoots ?? [homedir(), tmpdir()]).map(realpathCached);\n\n // `lstat` a path that may not exist yet (e.g., apply will create the\n // parent dir). Walk up until we find an existing ancestor and validate\n // there — if any walked-through component is a symlink, fail.\n let cursor = targetPath;\n let existing: string | null = null;\n while (true) {\n if (existsSync(cursor)) {\n const st = lstatSync(cursor);\n if (st.isSymbolicLink()) {\n throw new PathRejectedError(\n targetPath,\n \"symlink\",\n `Refusing to operate on symlinked path: ${cursor}`,\n );\n }\n existing = cursor;\n break;\n }\n const parent = dirname(cursor);\n if (parent === cursor) break; // reached filesystem root\n cursor = parent;\n }\n\n const resolved = existing ? realpathSync(existing) : targetPath;\n const ok = allowedRoots.some((root) => {\n if (resolved === root) return true;\n const normRoot = root.endsWith(sep) ? root : `${root}${sep}`;\n return resolved.startsWith(normRoot);\n });\n if (!ok) {\n throw new PathRejectedError(\n targetPath,\n \"outside-home\",\n `Refusing path outside allowed roots: realpath=${resolved}`,\n );\n }\n}\n\n/** MSIX package families that match Claude's installer. Anchored on both ends. */\nexport const MSIX_PACKAGE_PATTERN = /^Claude_[A-Za-z0-9]+$/;\n\nexport interface DetectOptions {\n homeOverride?: string;\n localAppDataOverride?: string;\n force?: boolean;\n}\n\nexport function detectTargets(opts: DetectOptions = {}): DetectedTarget[] {\n const home = opts.homeOverride ?? homedir();\n const targets: DetectedTarget[] = [];\n\n // Claude Code — cross-platform.\n // MCP servers are configured in ~/.claude.json under the \"mcpServers\" key.\n // Detect if the file exists OR if ~/.claude directory exists (Claude Code is installed).\n // With --force, always include regardless.\n const claudeCodeConfig = join(home, \".claude.json\");\n const claudeCodeDir = join(home, \".claude\");\n if (opts.force || existsSync(claudeCodeConfig) || existsSync(claudeCodeDir)) {\n targets.push({ label: \"Claude Code\", configPath: claudeCodeConfig, kind: \"claude-code\" });\n }\n\n // Claude Desktop — platform-specific.\n // Only detect if the config file already exists (user has launched Desktop at least once).\n // With --force, always include.\n let desktopConfig: string | null = null;\n if (process.platform === \"win32\") {\n const appdata = process.env.APPDATA ?? join(home, \"AppData\", \"Roaming\");\n desktopConfig = join(appdata, \"Claude\", \"claude_desktop_config.json\");\n } else if (process.platform === \"darwin\") {\n desktopConfig = join(\n home,\n \"Library\",\n \"Application Support\",\n \"Claude\",\n \"claude_desktop_config.json\",\n );\n } else {\n desktopConfig = join(home, \".config\", \"claude\", \"claude_desktop_config.json\");\n }\n\n if (desktopConfig && (opts.force || existsSync(desktopConfig))) {\n targets.push({ label: \"Claude Desktop\", configPath: desktopConfig, kind: \"claude-desktop\" });\n }\n\n // Claude Desktop (MSIX) — Windows only.\n // MSIX-packaged installs (Microsoft Store) redirect %APPDATA% to a per-package\n // LocalCache dir. The config lives under %LOCALAPPDATA%\\Packages\\Claude_*\\\n // LocalCache\\Roaming\\Claude\\. Multiple package families may exist.\n // Package name is constrained to `Claude_[A-Za-z0-9]+` so an attacker can't\n // smuggle a write target through a hand-crafted package directory like\n // `Claude_../../Windows/System32` (the path constructor would reject such\n // a name on most platforms but we belt-and-suspender).\n if (process.platform === \"win32\") {\n const localAppData =\n opts.localAppDataOverride ?? process.env.LOCALAPPDATA ?? join(home, \"AppData\", \"Local\");\n // Verify LOCALAPPDATA resolves under home. An attacker who controls env\n // could otherwise redirect us to write under any directory.\n try {\n assertPathSafe(localAppData, { allowedRoots: [home] });\n } catch {\n return targets;\n }\n const packagesDir = join(localAppData, \"Packages\");\n try {\n const entries = readdirSync(packagesDir);\n const matching = entries.filter((n) => MSIX_PACKAGE_PATTERN.test(n));\n for (const pkg of matching) {\n const msixConfig = join(\n packagesDir,\n pkg,\n \"LocalCache\",\n \"Roaming\",\n \"Claude\",\n \"claude_desktop_config.json\",\n );\n if (opts.force || existsSync(msixConfig)) {\n const suffix = matching.length > 1 ? ` (${pkg.slice(0, 12)}…)` : \"\";\n targets.push({\n label: `Claude Desktop MSIX${suffix}`,\n configPath: msixConfig,\n kind: \"claude-desktop\",\n });\n }\n }\n } catch {\n // %LOCALAPPDATA%\\Packages doesn't exist or isn't readable — not an MSIX install\n }\n }\n\n return targets;\n}\n\n/**\n * Atomic write: write to a temp file in the SAME directory as the destination,\n * tighten its mode/DACL, then rename. Same-directory tempfile avoids EXDEV\n * errors when `%TEMP%` and `%APPDATA%` are on different drives.\n *\n * Sequence (security-load-bearing):\n * 1. Write tempfile. On Windows the tempfile inherits its parent dir's\n * DACL (`homedir()` is OS-restricted by default to user + SYSTEM +\n * Administrators); on POSIX it lands at `0o666 & ~umask` (typically\n * `0o644` — world-readable, hence step 2's chmod).\n * 2. Tighten on the tempfile:\n * - Windows: `setRestrictiveAcl` breaks inheritance and grants Full\n * Control to the current user's SID only, then self-verifies via\n * SDDL read. The verify is load-bearing — icacls exits 0 even when\n * \"Failed processing N files\".\n * - POSIX: `chmod(tmp, 0o600)` — user-only read/write.\n * 3. Rename tempfile to dest. The kernel preserves mode (POSIX) and DACL\n * (Windows `MoveFileEx`) across the rename, so the tightened\n * permissions survive into the destination.\n * 4. On cleanup failure after a write/ACL/rename error, the original\n * failure propagates with the cleanup error attached via `cause` so\n * operators can diagnose a leaked tempfile or dest from a single log\n * line rather than chasing a stray `console.error`.\n */\nasync function atomicWrite(content: string, dest: string): Promise<void> {\n const tmp = join(dirname(dest), `.tandem-setup-${randomUUID()}.tmp`);\n await writeFile(tmp, content, \"utf-8\");\n\n try {\n if (process.platform === \"win32\") {\n await setRestrictiveAcl(tmp);\n } else {\n await chmod(tmp, 0o600);\n }\n } catch (tightenErr) {\n await unlinkOrLeak(tmp, tightenErr);\n throw tightenErr;\n }\n\n try {\n await rename(tmp, dest);\n } catch (err) {\n // EXDEV: cross-device link — fall back to copy + delete. The copy\n // preserves the source mode on POSIX (file is created via copyFile's\n // default which honors the source's mode bits); on Windows the\n // destination DACL is recomputed from the dest dir, so we re-run\n // setRestrictiveAcl on the dest after the cross-device copy.\n if ((err as NodeJS.ErrnoException).code === \"EXDEV\") {\n await copyFile(tmp, dest);\n await unlinkOrLeak(tmp, err);\n if (process.platform === \"win32\") await setRestrictiveAcl(dest);\n else await chmod(dest, 0o600);\n } else {\n await unlinkOrLeak(tmp, err);\n throw err;\n }\n }\n}\n\n/**\n * Attempt to delete `path` after a write/ACL/rename failure. On cleanup\n * success we just return; on cleanup failure we attach the cleanup error\n * as `cause` to the original failure (mutating `originalErr`) so operators\n * see a single composite log line rather than a free-floating\n * `console.error`. Bearer-token-bearing files that fail cleanup MUST be\n * surfaced, not silently logged.\n */\nasync function unlinkOrLeak(path: string, originalErr: unknown): Promise<void> {\n try {\n await unlink(path);\n } catch (cleanupErr) {\n if (originalErr instanceof Error && originalErr.cause === undefined) {\n (originalErr as { cause?: unknown }).cause = cleanupErr;\n }\n console.error(\n ` Warning: could not remove ${path} after a previous failure: ${\n (cleanupErr as Error).message\n }`,\n );\n }\n}\n\n/**\n * Apply explicit create/remove operations to a Claude config file.\n *\n * **Security gates (run before any read or write):**\n * - `assertPathSafe(configPath)` — symlink / outside-home rejection.\n * - Malformed JSON is backed up under Tandem's data dir with mode `0o600`\n * (avoids leaking other vendors' API keys via a world-readable\n * `~/.claude.json.broken-<ts>` sibling).\n *\n * `applyConfig` writes both `ops.create` entries (merging into the existing\n * `mcpServers` object) and removes any key listed in `ops.remove`. Removal\n * is silent if the key was already absent. Callers state both intents\n * explicitly — no implicit \"absent key implies remove\".\n */\nexport async function applyConfig(configPath: string, ops: ApplyOps): Promise<void> {\n // Security gate: refuse symlinks, refuse paths outside home/tmpdir.\n assertPathSafe(configPath);\n\n // Size guard runs before any read — fail closed on oversized files. ENOENT\n // falls through so fresh-install (no .claude.json yet) stays the common path.\n try {\n const { size } = statSync(configPath);\n if (size > MAX_CONFIG_BYTES) {\n throw new Error(\n `${configPath} is ${size} bytes; refusing to read (cap: ${MAX_CONFIG_BYTES}).`,\n );\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n\n // Read existing config or start fresh — no existsSync guard needed.\n // ENOENT and malformed JSON start fresh; other errors (permissions, disk) propagate.\n let existing: { mcpServers?: Record<string, McpEntry> } = {};\n try {\n // Strip a leading UTF-8 BOM (``) before JSON.parse. Some editors\n // (legacy Windows tooling, certain VS Code configs) write `.claude.json`\n // with a BOM; without this strip, `JSON.parse` throws `SyntaxError`\n // and the file would be pushed into `.broken-backups/` as if it were\n // malformed. The BOM is encoded as the literal three bytes\n // `EF BB BF` which Node's \"utf-8\" decoder surfaces as a leading\n // U+FEFF code point.\n let raw = readFileSync(configPath, \"utf-8\");\n if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1);\n const parsed: unknown = JSON.parse(raw);\n\n // Shape gate: the rewrite path spreads `existing.mcpServers` and\n // `existing` itself into the new config. If either is the wrong\n // shape, the spread produces a corrupted output (string-spread\n // yields `{0:'a',1:'b',...}`, array-spread yields numeric keys,\n // null-spread throws). Reject up-front so a legitimate-looking\n // config-shape mismatch never silently corrupts the user's file.\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(`${configPath} root is not a JSON object — refusing to rewrite`);\n }\n const maybeServers = (parsed as Record<string, unknown>).mcpServers;\n if (\n maybeServers !== undefined &&\n (maybeServers === null || typeof maybeServers !== \"object\" || Array.isArray(maybeServers))\n ) {\n throw new Error(`${configPath} mcpServers is not an object — refusing to rewrite`);\n }\n existing = parsed as { mcpServers?: Record<string, McpEntry> };\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n // File doesn't exist yet — start fresh\n } else if (err instanceof SyntaxError) {\n // Don't silently wipe the user's other mcpServers. Copy the malformed\n // file under Tandem's data dir (NOT next to ~/.claude.json — that\n // location may inherit world-readable perms and leak co-tenant API\n // keys via the backup). Mode 0o600 hardens against the same.\n const brokenBackupDir = join(resolveAppDataDir(), \".broken-backups\");\n // Validate against default roots [homedir(), tmpdir()] rather than\n // scoping to resolveAppDataDir() — the latter is tautological, and\n // an XDG_DATA_HOME-poisoning attacker can otherwise redirect the\n // backup target outside the home tree.\n assertPathSafe(brokenBackupDir);\n // mode: 0o700 on dir creation — the file mode is 0o600, but a\n // world-readable parent dir lists sibling filenames (older backups\n // carry other vendors' keys). Mode applies only when the dir is\n // newly created; existing dirs retain their mode.\n mkdirSync(brokenBackupDir, { recursive: true, mode: 0o700 });\n // randomUUID() in the path defeats path prediction by an attacker\n // who might pre-create a file at the predicted location and have\n // our mode-at-open inherit world-readable bits. `wx` (exclusive\n // create) below is the second layer.\n const backupPath = join(\n brokenBackupDir,\n `${basename(configPath)}.broken-${Date.now()}-${randomUUID()}`,\n );\n try {\n if (process.platform === \"win32\") {\n // Windows ignores the POSIX `mode` arg on mkdir, so harden the\n // dir with an explicit DACL BEFORE writing the backup file.\n // Mirrors the ordering invariant in\n // `storage.ts#backupBrokenFile`: dir-level ACL closes the\n // TOCTOU window that a per-file ACL would otherwise open\n // between copyFile and the ACL set. Fail loud — orphaning a\n // half-hardened dir is worse than aborting the backup\n // outright. The inner `catch (copyErr)` below surfaces a\n // named error and refuses to overwrite the malformed config.\n try {\n await setRestrictiveAcl(brokenBackupDir);\n } catch (aclErr) {\n throw new Error(\n `failed to apply restrictive ACL to broken-backups dir ${brokenBackupDir}: ${\n aclErr instanceof Error ? aclErr.message : String(aclErr)\n }`,\n { cause: aclErr },\n );\n }\n // Windows doesn't honor POSIX modes — fall back to plain copy.\n // The randomUUID-suffixed path makes collisions effectively\n // impossible. COPYFILE_EXCL refuses to overwrite an existing\n // target, defeating any predictable-path symlink/pre-create\n // attack the UUID suffix might still leave reachable. NOTE:\n // `setRestrictiveAcl` (acl-win.ts) calls `icacls /grant:r\n // *<SID>:F` without (OI)(CI) inheritance flags, so the new\n // file does NOT inherit the parent dir's SID-only ACE.\n // Instead it receives the DACL synthesized from the process\n // token's default (typically user + SYSTEM + Administrators),\n // which is narrow enough to prevent cross-tenant leak in\n // standard contexts. If broader access is observed, the dir\n // ACE should be made inheritable in acl-win.ts (this would\n // also benefit storage.ts which uses the same helper).\n await copyFile(configPath, backupPath, fsConstants.COPYFILE_EXCL);\n } else {\n // Open with mode 0o600 + `wx` so the file is created exclusively\n // at the right mode (no copyFile + chmodSync race window where\n // the backup was briefly 0o644 with another vendor's API keys\n // inside).\n const data = await readFile(configPath);\n const fd = await open(backupPath, \"wx\", 0o600);\n try {\n await fd.write(data);\n } finally {\n await fd.close();\n }\n }\n console.error(\n ` Warning: ${configPath} contains malformed JSON — backed up to ${backupPath}, replacing with fresh config`,\n );\n } catch (copyErr) {\n console.error(\n ` Warning: ${configPath} contains malformed JSON and backup failed (${\n copyErr instanceof Error ? copyErr.message : copyErr\n }) — refusing to overwrite. Fix the JSON manually and rerun 'tandem setup'.`,\n );\n throw copyErr;\n }\n } else {\n throw err; // Permission errors, disk errors, etc. should not be silently swallowed\n }\n }\n\n // Backup the existing config IFF the existing `mcpServers.tandem` entry\n // is non-default (user-customised URL or extra keys). Token rotation and\n // fresh-install runs would otherwise generate backup churn that buries\n // the one backup the user actually needs. The write happens BEFORE the\n // rewrite — atomicity invariant: if backup throws, the original is\n // untouched.\n const backupPath = await maybeBackupExistingConfig(configPath, existing, ops);\n if (backupPath && ops.onBackup) {\n // The onBackup callback is observational — a wizard push, a CLI\n // print, a telemetry hop. If the consumer throws, log it but DO\n // NOT abort the rewrite: doing so would orphan the just-written\n // backup file and leave the user's config un-applied, with no\n // user-visible explanation.\n try {\n ops.onBackup(backupPath);\n } catch (cbErr) {\n console.error(\n ` Warning: onBackup callback threw — continuing with rewrite: ${\n cbErr instanceof Error ? cbErr.message : cbErr\n }`,\n );\n }\n }\n\n const merged: Record<string, McpEntry> = {\n ...(existing.mcpServers ?? {}),\n ...ops.create,\n };\n // Explicit removals — silent if the key was already absent.\n for (const key of ops.remove) {\n if (merged[key]) {\n console.error(` Note: removed mcpServers.${key} from ${configPath}`);\n }\n delete merged[key];\n }\n const updated = { ...existing, mcpServers: merged };\n\n await mkdir(dirname(configPath), { recursive: true });\n await atomicWrite(JSON.stringify(updated, null, 2) + \"\\n\", configPath);\n}\n\n/**\n * Conditionally back up `configPath` before `applyConfig` overwrites a\n * user-customised tandem entry. Returns the backup path on write, or\n * `undefined` when no backup was needed (fresh install, token rotation,\n * non-tandem-key changes only).\n *\n * Atomicity contract: if this throws, the caller MUST abort before\n * touching the destination. The original config and any prior backup\n * remain intact.\n */\nasync function maybeBackupExistingConfig(\n configPath: string,\n existing: { mcpServers?: Record<string, McpEntry> },\n ops: ApplyOps,\n): Promise<string | undefined> {\n const existingTandem = existing.mcpServers?.tandem;\n if (!shouldBackup(existingTandem, ops.create.tandem)) return undefined;\n\n const dir = backupDir(resolveAppDataDir());\n // assertPathSafe defeats XDG_DATA_HOME poisoning — same hardening as\n // the broken-JSON backup path above.\n assertPathSafe(dir);\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n\n // mkdirSync's `mode` only applies on creation. If the dir already\n // existed at a more permissive mode (older umask, manual creation,\n // user fiddling), tighten it now. POSIX-only; on Windows the file's\n // own ACL — set by setRestrictiveAcl inside writeBackup — is the\n // protection. Filenames in a broader dir leak timestamps, not\n // bytes; failing closed here would block legit setups for that.\n if (process.platform !== \"win32\") {\n try {\n const dirStat = statSync(dir);\n if ((dirStat.mode & 0o777) !== 0o700) chmodSync(dir, 0o700);\n } catch {\n // stat/chmod failure is non-fatal — proceeds with the wider\n // protection on the file itself.\n }\n }\n\n const content = readFileSync(configPath);\n const backupPath = await writeBackup(dir, content);\n // Prune AFTER successful write so we never delete the previous backup\n // before its replacement is on disk.\n await pruneOldBackups(dir);\n return backupPath;\n}\n\n/**\n * Install the Tandem skill to ~/.claude/skills/tandem/SKILL.md.\n * Claude Code auto-discovers skills in this directory and uses the description\n * field to trigger them when tandem_* tools are present.\n *\n * `homeOverride` is supported for tests only — the apply HTTP handler\n * MUST NOT thread this from the request body. Same symlink/realpath\n * hardening as `applyConfig`.\n */\nexport async function installSkill(opts: { homeOverride?: string } = {}): Promise<void> {\n const home = opts.homeOverride ?? homedir();\n const skillPath = join(home, \".claude\", \"skills\", \"tandem\", \"SKILL.md\");\n assertPathSafe(skillPath, { allowedRoots: opts.homeOverride ? [opts.homeOverride] : undefined });\n await mkdir(dirname(skillPath), { recursive: true });\n await atomicWrite(SKILL_CONTENT, skillPath);\n}\n\n/** Parse the integer `version:` from a skill front-matter block. Returns 0\n * if the file doesn't exist, has no version, or fails to parse — older\n * bundled skills predate the version stamp, so 0 means \"definitely upgrade\". */\nfunction readSkillVersion(skillContent: string): number {\n const match = skillContent.match(/^version:\\s*(\\d+)\\s*$/m);\n if (!match) return 0;\n const n = parseInt(match[1], 10);\n return Number.isFinite(n) ? n : 0;\n}\n\nconst BUNDLED_SKILL_VERSION = readSkillVersion(SKILL_CONTENT);\n\n/** Module-scoped last-failure record. Cleared on successful refresh; set on\n * read/write failure. Surfaced via `GET /api/launcher/status` (loopback only)\n * so the palette/settings UI can warn the user that the bundled skill is\n * out of date. `null` when last refresh succeeded or was a no-op. */\nlet lastSkillRefreshError: { code: \"write-failed\" | \"read-failed\"; message: string } | null = null;\nexport function getSkillRefreshError(): {\n code: \"write-failed\" | \"read-failed\";\n message: string;\n} | null {\n return lastSkillRefreshError;\n}\n/** Test-only — reset module state between tests. */\nexport function _resetSkillRefreshErrorForTests(): void {\n if (process.env.VITEST !== \"true\") return;\n lastSkillRefreshError = null;\n}\n\n/**\n * Idempotent skill refresh — called from supervisor startup so existing\n * users (who already ran `tandem setup` once) pick up bundled-skill\n * updates without re-running the wizard.\n *\n * Compares the bundled `version:` against the on-disk file. Writes only\n * if bundled > on-disk. Silently no-ops on any error (read failure,\n * write failure, missing parent dir) — this is a best-effort refresh,\n * not a critical path. The wizard-driven `installSkill()` remains the\n * authoritative installer.\n */\nexport async function refreshSkillIfStale(opts: { homeOverride?: string } = {}): Promise<void> {\n if (BUNDLED_SKILL_VERSION === 0) return; // Bundled skill has no version stamp — nothing to compare.\n const home = opts.homeOverride ?? homedir();\n const skillPath = join(home, \".claude\", \"skills\", \"tandem\", \"SKILL.md\");\n try {\n assertPathSafe(skillPath, {\n allowedRoots: opts.homeOverride ? [opts.homeOverride] : undefined,\n });\n } catch {\n return;\n }\n let onDiskVersion = -1; // -1 = file missing (treat as needing install)\n let readErr: unknown;\n try {\n const fs = await import(\"node:fs/promises\");\n const current = await fs.readFile(skillPath, \"utf8\");\n onDiskVersion = readSkillVersion(current);\n } catch (err) {\n // ENOENT is expected (first run); any other error is a real read failure.\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") readErr = err;\n }\n if (onDiskVersion >= BUNDLED_SKILL_VERSION) {\n // No-op path — clear any prior failure only if read succeeded.\n if (readErr === undefined) lastSkillRefreshError = null;\n else\n lastSkillRefreshError = {\n code: \"read-failed\",\n message: readErr instanceof Error ? readErr.message : String(readErr),\n };\n return;\n }\n try {\n await mkdir(dirname(skillPath), { recursive: true });\n await atomicWrite(SKILL_CONTENT, skillPath);\n lastSkillRefreshError = null;\n console.error(\n `[Tandem] Refreshed bundled skill at ${skillPath} (v${onDiskVersion} → v${BUNDLED_SKILL_VERSION}).`,\n );\n } catch (err) {\n lastSkillRefreshError = {\n code: \"write-failed\",\n message: err instanceof Error ? err.message : String(err),\n };\n console.error(\n `[Tandem] Skill refresh failed (non-fatal): ${err instanceof Error ? err.message : err}`,\n );\n }\n}\n\n/**\n * Returns true if the channel-shim build artifact exists at the given path.\n * Exported so the prereq check can be tested without spawning runSetup.\n */\nexport function validateChannelShimPrereq(channelPath: string): boolean {\n return existsSync(channelPath);\n}\n\n/** Re-exported for `tandem setup` orchestration in `src/cli/setup.ts`. */\nexport { CHANNEL_DIST, PACKAGE_ROOT };\n\n/**\n * Write the given token into all detected Claude MCP config files.\n * Returns the number of configs successfully updated and any per-target errors.\n *\n * CLI-shaped semantics: when `withChannelShim` is false, any existing\n * `tandem-channel` entry is removed (legacy install artifact). The\n * wizard's apply endpoint uses an explicit-confirmation code path\n * instead; this helper is for `tandem rotate-token` / `tandem setup`\n * where the flag already captures user intent.\n */\nexport async function applyConfigWithToken(\n token: string | null,\n opts: { force?: boolean; withChannelShim?: boolean } = {},\n): Promise<{ updated: number; errors: string[] }> {\n const targets = detectTargets({ force: opts.force });\n\n let updated = 0;\n const errors: string[] = [];\n for (const t of targets) {\n const entries = buildMcpEntries(CHANNEL_DIST, {\n withChannelShim: opts.withChannelShim,\n token: token ?? undefined,\n targetKind: t.kind,\n });\n try {\n await applyConfig(\n t.configPath,\n applyOpsForCli(entries, { withChannelShim: !!opts.withChannelShim }),\n );\n updated++;\n } catch (err) {\n errors.push(`${t.label}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n return { updated, errors };\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport {\n applyConfig,\n applyOpsForCli,\n buildMcpEntries,\n CHANNEL_DIST,\n detectTargets,\n installSkill,\n PACKAGE_ROOT,\n validateChannelShimPrereq,\n} from \"../server/integrations/apply.js\";\n\n/**\n * Back-compat re-exports.\n *\n * The helpers below now live in `src/server/integrations/apply.ts`\n * (#477 PR 3c-ii-a — server library factor). External consumers\n * (`tandem rotate-token`, tests, anything that bundles against the\n * CLI surface) keep importing from `src/cli/setup.js` for now. The\n * re-export goes away in PR 3c-ii-c when `runSetup` is rewritten as\n * a non-interactive `--apply` wrapper and the CLI surface is reduced.\n */\nexport {\n type ApplyOps,\n applyConfig,\n applyConfigWithToken,\n applyOpsForCli,\n type BuildMcpEntriesOptions,\n buildMcpEntries,\n type DetectedTarget,\n type DetectOptions,\n detectTargets,\n installSkill,\n type McpEntries,\n type McpEntry,\n PathRejectedError,\n type RemovableEntry,\n type TargetKind,\n validateChannelShimPrereq,\n} from \"../server/integrations/apply.js\";\n\n/** Run the setup command. Writes MCP config to all detected Claude installs. */\nexport async function runSetup(\n opts: { force?: boolean; withChannelShim?: boolean } = {},\n): Promise<void> {\n console.error(\"\\nTandem Setup\\n\");\n\n if (opts.withChannelShim && !validateChannelShimPrereq(CHANNEL_DIST)) {\n console.error(\n `Error: --with-channel-shim requires dist/channel/index.js at ${CHANNEL_DIST}\\n` +\n `Run 'npm run build' first, or drop --with-channel-shim to use the plugin monitor.`,\n );\n process.exit(1);\n }\n\n console.error(\"Detecting Claude installations...\");\n\n const targets = detectTargets({ force: opts.force });\n\n if (targets.length === 0) {\n console.error(\n \" No Claude installations detected.\\n\" +\n \" If Claude Code is installed, ensure ~/.claude exists.\\n\" +\n \" You can force configuration to default paths with: tandem setup --force\",\n );\n return;\n }\n\n for (const t of targets) {\n console.error(` Found: ${t.label} (${t.configPath})`);\n }\n\n console.error(\"\\nWriting MCP configuration...\");\n\n let failures = 0;\n for (const t of targets) {\n const entries = buildMcpEntries(CHANNEL_DIST, {\n withChannelShim: opts.withChannelShim,\n targetKind: t.kind,\n });\n try {\n await applyConfig(\n t.configPath,\n applyOpsForCli(entries, { withChannelShim: !!opts.withChannelShim }),\n );\n console.error(` \\x1b[32m✓\\x1b[0m ${t.label}`);\n } catch (err) {\n failures++;\n console.error(\n ` \\x1b[31m✗\\x1b[0m ${t.label}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n if (failures === targets.length) {\n console.error(\"\\nSetup failed — could not write any configuration. Check file permissions.\");\n process.exit(1);\n } else if (failures > 0) {\n console.error(\n `\\nSetup partially complete (${failures} target(s) failed). Start Tandem with: tandem`,\n );\n } else {\n console.error(\"\\nSetup complete! Start Tandem with: tandem\");\n console.error(\"Then in Claude, your tandem_* tools will be available.\");\n }\n\n // Install Claude Code skill (best-effort — doesn't block MCP setup)\n console.error(\"\\nInstalling Claude Code skill...\");\n try {\n await installSkill();\n console.error(\" \\x1b[32m✓\\x1b[0m ~/.claude/skills/tandem/SKILL.md\");\n } catch (err) {\n console.error(\n ` \\x1b[33m⚠\\x1b[0m Could not install skill: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Plugin install instructions (shown on all successful setups)\n if (failures < targets.length) {\n const pluginManifest = join(PACKAGE_ROOT, \".claude-plugin\", \"plugin.json\");\n const devInstructions = existsSync(pluginManifest)\n ? ` Or for development, load directly from this package:\\n\\n` +\n ` claude --plugin-dir ${PACKAGE_ROOT}\\n\\n`\n : ` (Development plugin dir not found at ${pluginManifest}; skipping local-plugin instructions.)\\n\\n`;\n\n console.error(\n \"\\n\\x1b[1mReal-time push notifications (recommended):\\x1b[0m\\n\" +\n \" Install the Tandem plugin for instant events (one-time):\\n\\n\" +\n \" claude plugin marketplace add bloknayrb/tandem\\n\" +\n \" claude plugin install tandem@tandem-editor\\n\\n\" +\n devInstructions +\n \" Without the plugin, Claude still works but relies on tandem_checkInbox polling.\\n\",\n );\n }\n}\n","/**\n * Helpers shared by CLI stdio entry points (`mcp-stdio`, `channel`) and the\n * standalone server / monitor binaries that all speak MCP over stdout.\n */\n\nimport { DEFAULT_MCP_PORT } from \"./constants.js\";\n\n/**\n * In stdio MCP mode, stdout is the JSON-RPC wire — any stray library write\n * corrupts the protocol. Redirect `console.log/warn/info` to stderr so\n * incidental logging is safe. Callers that also run in-process tests (e.g.\n * `src/monitor/index.ts`) can gate this behind `!process.env.VITEST`.\n */\nexport function redirectConsoleToStderr(): void {\n console.log = console.error;\n console.warn = console.error;\n console.info = console.error;\n}\n\n/**\n * Resolve the Tandem HTTP base URL used by stdio subcommands. Precedence:\n * (1) explicit override (programmatic, e.g. from tests)\n * (2) CLAUDE_PLUGIN_OPTION_SERVER_URL — injected by plugin host from userConfig\n * (3) TANDEM_URL — explicit env override\n * (4) 127.0.0.1 default (apiMiddleware narrowed out bare 'localhost' in PR #477 PR 2)\n * Blank values are treated as absent so a blank plugin option does not mask an\n * explicit TANDEM_URL or the 127.0.0.1 default.\n * The returned string has no trailing slash so callers can concatenate\n * `/health`, `/mcp`, etc. without double-slash. One or more trailing slashes\n * are stripped, so both `http://x/` and `http://x//` resolve to `http://x`.\n */\nexport function resolveTandemUrl(override?: string): string {\n return resolveTandemUrlCandidate(override).replace(/\\/+$/, \"\");\n}\n\nfunction resolveTandemUrlCandidate(override?: string): string {\n const candidates = [\n override,\n process.env.CLAUDE_PLUGIN_OPTION_SERVER_URL,\n process.env.TANDEM_URL,\n ];\n for (const url of candidates) {\n if (url !== undefined && url.trim() !== \"\") return url.trim();\n }\n return `http://127.0.0.1:${DEFAULT_MCP_PORT}`;\n}\n\n/**\n * Resolve the Tandem auth token. Precedence:\n * (1) explicit override (programmatic, e.g. from tests)\n * (2) CLAUDE_PLUGIN_OPTION_AUTH_TOKEN — injected by plugin host from userConfig\n * (3) TANDEM_AUTH_TOKEN — explicit env override\n * Blank values are treated as absent so a blank plugin option does not mask an\n * explicit TANDEM_AUTH_TOKEN. Returns undefined when all absent (loopback mode,\n * no Authorization header sent).\n */\nexport function resolveAuthToken(override?: string): string | undefined {\n return resolveAuthTokenCandidate(override).token;\n}\n\nexport type AuthTokenSource =\n | \"explicit override\"\n | \"CLAUDE_PLUGIN_OPTION_AUTH_TOKEN\"\n | \"TANDEM_AUTH_TOKEN\";\n\nexport function resolveAuthTokenCandidate(\n override?: string,\n): { token: string; source: AuthTokenSource } | { token: undefined; source: undefined } {\n const candidates: Array<[AuthTokenSource, string | undefined]> = [\n [\"explicit override\", override],\n [\"CLAUDE_PLUGIN_OPTION_AUTH_TOKEN\", process.env.CLAUDE_PLUGIN_OPTION_AUTH_TOKEN],\n [\"TANDEM_AUTH_TOKEN\", process.env.TANDEM_AUTH_TOKEN],\n ];\n for (const [source, token] of candidates) {\n if (token !== undefined && token.trim() !== \"\") return { token, source };\n }\n return { token: undefined, source: undefined };\n}\n\n/** Regex for a valid Tandem auth token (32+ URL-safe alphanumeric chars). */\nconst VALID_TOKEN_RE = /^[A-Za-z0-9_\\-]{32,}$/;\n\n/** Guard so we only warn once per process (not on every SSE reconnect). */\nlet _warnedInvalidToken = false;\n\n/**\n * Fetch wrapper that automatically injects `Authorization: Bearer <token>`\n * when a resolved Tandem auth token is set and valid.\n *\n * This is the forgiving variant — used by monitor/channel which may run in\n * loopback-only mode without a token. Invalid or absent tokens are silently\n * ignored (no exit-1). The strict validation lives in mcp-stdio.ts only.\n * When the token is set but fails validation, a one-time warning is emitted\n * so operators know why auth headers are absent.\n */\nexport async function authFetch(url: string, init?: RequestInit): Promise<Response> {\n const { token, source } = resolveAuthTokenCandidate();\n if (token !== undefined) {\n const trimmed = token.trim();\n if (VALID_TOKEN_RE.test(trimmed)) {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", `Bearer ${trimmed}`);\n return fetch(url, { ...init, headers });\n }\n // Token is set but invalid — warn once so operators know why auth fails\n if (!_warnedInvalidToken) {\n _warnedInvalidToken = true;\n console.error(\n `[tandem] authFetch: ${source} is set but invalid (must be 32+ alphanumeric chars [A-Za-z0-9_-]); sending without Authorization header`,\n );\n }\n }\n return fetch(url, init);\n}\n","/**\n * Shared preflight check for stdio MCP subcommands.\n *\n * Both `tandem mcp-stdio` and `tandem channel` need a live Tandem server on\n * localhost before they can do anything useful. Two flavors:\n *\n * - `ensureTandemServer` — fail fast via stderr + exit(1) when the server\n * isn't reachable. Used by `tandem channel`, whose stdio transport can't\n * meaningfully respond on its own.\n * - `probeTandemServer` — returns a result without side effects. Used by\n * `tandem mcp-stdio`, which starts its stdio transport before preflight\n * so it can synthesize -32000 JSON-RPC errors for any in-flight request\n * before exiting (issue #336).\n */\n\nimport { resolveTandemUrl } from \"../shared/cli-runtime.js\";\n\nconst DEFAULT_TIMEOUT_MS = 2000;\n\nexport interface PreflightOptions {\n url?: string;\n timeoutMs?: number;\n}\n\n// Note: \"unreachable\" is a catch-all for any non-HTTP-status failure —\n// DNS, TLS, timeout, ECONNREFUSED, RST all land here. \"unhealthy\" is\n// strictly non-2xx responses from /health.\nexport type PreflightProbe =\n | { ok: true }\n | { ok: false; url: string; reason: string; kind: \"unreachable\" | \"unhealthy\" };\n\nexport async function probeTandemServer(opts: PreflightOptions = {}): Promise<PreflightProbe> {\n const url = resolveTandemUrl(opts.url);\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const res = await fetch(`${url}/health`, { signal: controller.signal });\n if (!res.ok) {\n return {\n ok: false,\n url,\n reason: `health endpoint returned HTTP ${res.status}`,\n kind: \"unhealthy\",\n };\n }\n return { ok: true };\n } catch (err) {\n return {\n ok: false,\n url,\n reason: err instanceof Error ? err.message : String(err),\n kind: \"unreachable\",\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport async function ensureTandemServer(opts: PreflightOptions = {}): Promise<void> {\n const probe = await probeTandemServer(opts);\n if (!probe.ok) {\n const guidance =\n probe.kind === \"unreachable\"\n ? \"Start the Tauri app or run `tandem start` on the host, then retry.\"\n : \"The Tandem server is running but unhealthy — check the host logs.\";\n process.stderr.write(\n `[tandem] Tandem server preflight failed at ${probe.url} (${probe.reason}).\\n` +\n `[tandem] ${guidance}\\n`,\n );\n process.exit(1);\n }\n}\n","/**\n * Tandem mcp-stdio subcommand — stdio ↔ Streamable HTTP JSON-RPC proxy.\n *\n * Claude Desktop's plugin loader bridges stdio MCP servers into sandboxed\n * sessions but not HTTP MCP servers, so plugin-cached stdio entries that\n * forward to the local HTTP MCP endpoint are the only supported way to\n * surface tandem_* tools into those sessions.\n *\n * Raw message forwarding: no handler registrations, no per-method logic.\n * Any message the upstream emits (tool results, notifications, future\n * methods we haven't heard of) reaches the stdio client unchanged.\n *\n * Error surfacing (issue #336): the stdio transport is started before\n * preflight and http.start(), and early messages are buffered until the\n * upstream is ready. If the upstream never becomes ready — or dies mid-\n * session — every in-flight request ID is answered with a synthesized\n * `-32000` JSON-RPC error instead of a silent stdio close. Plugin hosts\n * surface `-32000` as actionable; a silent close is what produces \"tools\n * never appear in Cowork\" with nothing diagnosable in the logs.\n *\n * Intentional: no reconnection logic. If the upstream HTTP server dies\n * mid-session, we synthesize errors for pending requests and exit 1.\n * The plugin loader will respawn us on the next tool call and preflight\n * will re-run with a fresh, accurate error if the server is still down.\n */\n\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport type { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\nimport {\n redirectConsoleToStderr,\n resolveAuthTokenCandidate,\n resolveTandemUrl,\n} from \"../shared/cli-runtime.js\";\nimport { probeTandemServer } from \"./preflight.js\";\n\nredirectConsoleToStderr();\n\n// After preflight or http.start() fails we wait ~1.5s for any already-in-\n// flight `initialize` from the plugin loader to land on stdin and receive\n// a -32000 reply before tear-down. Sizing covers stdin-read lag between\n// preflight resolution and first message arrival — independent of\n// preflight's own fetch timeout.\nconst PREFLIGHT_GRACE_MS = 1500;\n\n// Per-request timeout. Node's setTimeout uses a 32-bit signed integer\n// internally — values above this constant are silently clamped to 1ms,\n// which would make every request immediately synthesize -32000.\nconst MAX_TIMEOUT_MS = 2_147_483_647; // 2^31 - 1\n\nexport function parseTimeoutMs(raw: string | undefined): number {\n if (raw !== undefined) {\n const parsed = parseInt(raw, 10);\n if (Number.isFinite(parsed) && parsed > 0 && parsed <= MAX_TIMEOUT_MS) {\n return parsed;\n }\n // Note: parseInt(\"3e4\", 10) returns 3 (stops at 'e'), which passes validation.\n // Scientific notation lands here only when the leading integer is invalid.\n process.stderr.write(\n `[tandem mcp-stdio] TANDEM_REQUEST_TIMEOUT_MS must be a positive integer ≤ ${MAX_TIMEOUT_MS}; ignoring \"${raw}\", using 30000ms default\\n`,\n );\n }\n return 30_000;\n}\n\nconst STDIO_REQUEST_TIMEOUT_MS = parseTimeoutMs(process.env.TANDEM_REQUEST_TIMEOUT_MS);\n\n// Last-gasp handlers for truly unexpected crashes: write one diagnostic to\n// stderr before exit. Installed at module load; process.once bounds each\n// handler to a single fire. No -32000 synthesis here because pendingRequests\n// lives inside runMcpStdio()'s closure.\nprocess.once(\"uncaughtException\", (err: Error) => {\n process.stderr.write(\n `[tandem mcp-stdio] uncaughtException: ${err.message}\\n${err.stack ?? \"\"}\\n`,\n );\n process.exit(1);\n});\nprocess.once(\"unhandledRejection\", (reason: unknown) => {\n const detail = reason instanceof Error ? reason.message : String(reason);\n process.stderr.write(`[tandem mcp-stdio] unhandledRejection: ${detail}\\n`);\n process.exit(1);\n});\n\n/** Regex for a valid Tandem auth token (32+ URL-safe alphanumeric chars). */\nconst VALID_TOKEN_RE = /^[A-Za-z0-9_\\-]{32,}$/;\n\n/**\n * Validate the configured auth token if present.\n * Rules (invariant 4):\n * - If not set at all, or if empty/whitespace-only after trim → return null (loopback-only mode).\n * - \"Bearer \" prefix → exit 1 with \"double-prefix\" message.\n * - Must match /^[A-Za-z0-9_-]{32,}$/ (no whitespace, no newlines, no Bearer prefix).\n */\nexport function readAndValidateAuthToken(): string | null {\n const { token, source } = resolveAuthTokenCandidate();\n // Token not set at all, or empty/whitespace-only → loopback-only mode, no auth header, no exit.\n if (token === undefined) return null;\n const trimmed = token.trim();\n if (trimmed === \"\") return null;\n\n if (trimmed.startsWith(\"Bearer \")) {\n process.stderr.write(\n `[tandem mcp-stdio] ${source} is invalid (double-prefix: do not include 'Bearer ' prefix — supply the raw token only)\\n`,\n );\n process.exit(1);\n }\n\n if (!VALID_TOKEN_RE.test(trimmed)) {\n process.stderr.write(\n `[tandem mcp-stdio] ${source} is malformed (must be 32+ URL-safe characters: [A-Za-z0-9_-])\\n`,\n );\n process.exit(1);\n }\n\n return trimmed;\n}\n\nexport async function runMcpStdio(): Promise<void> {\n const baseUrl = resolveTandemUrl();\n const authToken = readAndValidateAuthToken();\n\n const http = new StreamableHTTPClientTransport(new URL(`${baseUrl}/mcp`), {\n requestInit: authToken ? { headers: { Authorization: `Bearer ${authToken}` } } : undefined,\n });\n const stdio = new StdioServerTransport();\n\n // On upstream failure we synthesize -32000 for every entry before exit.\n // Value is the per-request timeout handle so we can cancel it on response.\n const pendingRequests = new Map<string | number, ReturnType<typeof setTimeout>>();\n // Messages arriving before httpReady flips; either drained and forwarded\n // on success, or each request answered with -32000 on preflight/http-start\n // failure.\n const preReadyBuffer: JSONRPCMessage[] = [];\n let shuttingDown = false;\n let httpReady = false;\n\n async function sendErrorResponse(\n id: string | number,\n message: string,\n detail?: string,\n ): Promise<void> {\n const errorResponse: JSONRPCMessage = {\n jsonrpc: \"2.0\",\n id,\n error: {\n // -32000 is the implementation-defined server error range per\n // JSON-RPC 2.0 §5.1 — upstream unavailability is an application-\n // level condition, not a generic Internal Error.\n code: -32000,\n message,\n ...(detail !== undefined ? { data: { detail } } : {}),\n },\n };\n try {\n await stdio.send(errorResponse);\n } catch (err) {\n // stdio already torn down; log so synth failures during shutdown\n // (e.g., http.onclose racing stdio.onclose) aren't silently dropped —\n // a silent drop here would recreate exactly the failure mode this\n // module exists to prevent.\n const detail = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n `[tandem mcp-stdio] failed to send synthesized error for id ${id}: ${detail}\\n`,\n );\n }\n }\n\n function forwardToUpstream(msg: JSONRPCMessage): void {\n if (shuttingDown) return;\n const requestId = getRequestId(msg);\n if (requestId !== undefined) {\n // Clear any existing timer for this id (duplicate/retry scenario).\n const existing = pendingRequests.get(requestId);\n if (existing) clearTimeout(existing);\n\n const timeoutHandle = setTimeout(() => {\n // Atomic: delete returns false if synthesizePending already drained the map.\n if (!pendingRequests.delete(requestId)) return;\n void sendErrorResponse(\n requestId,\n \"Tandem HTTP upstream not responding (half-open)\",\n `No response after ${STDIO_REQUEST_TIMEOUT_MS}ms`,\n );\n }, STDIO_REQUEST_TIMEOUT_MS);\n pendingRequests.set(requestId, timeoutHandle);\n }\n http.send(msg).catch((err: unknown) => {\n const detail = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[tandem mcp-stdio] upstream send failed: ${detail}\\n`);\n if (requestId !== undefined) {\n const handle = pendingRequests.get(requestId);\n if (handle !== undefined) {\n pendingRequests.delete(requestId);\n clearTimeout(handle);\n void sendErrorResponse(requestId, \"Tandem HTTP upstream unreachable\", detail);\n }\n }\n });\n }\n\n async function synthesizeBuffered(message: string, detail?: string): Promise<void> {\n const buffered = preReadyBuffer.splice(0);\n const ids = buffered\n .map((msg) => getRequestId(msg))\n .filter((id): id is string | number => id !== undefined);\n for (const id of ids) {\n await sendErrorResponse(id, message, detail);\n }\n }\n\n async function synthesizePending(message: string, detail?: string): Promise<void> {\n if (pendingRequests.size === 0) return;\n const ids = [...pendingRequests.keys()];\n // Synchronous before any await: clear all timers + drain map atomically.\n // Timer callbacks that are already queued observe empty map and return early.\n for (const handle of pendingRequests.values()) clearTimeout(handle);\n pendingRequests.clear();\n await Promise.all(ids.map((id) => sendErrorResponse(id, message, detail)));\n }\n\n const shutdown = async (\n code = 0,\n synth?: { message: string; detail?: string },\n ): Promise<never> => {\n if (!shuttingDown) {\n shuttingDown = true;\n // Hard deadline: if http.close() or stdio.close() hang (e.g., a half-\n // open upstream holding an SSE GET open), don't let cleanup block the\n // process exit indefinitely. .unref() means this timer doesn't itself\n // keep the event loop alive — fast paths resolve and call process.exit\n // below before the deadline fires; hung paths are forcibly terminated.\n setTimeout(() => process.exit(code), 2_000).unref();\n // Unconditionally drain all pending timers before any await — prevents\n // orphan timers from firing into the half-closed transport during\n // http.close() / stdio.close() awaits. synthesizePending will also\n // drain the map if synth is provided; the clearTimeout calls here are\n // defensive for the synth=undefined path (e.g., clean stdio.onclose).\n for (const handle of pendingRequests.values()) clearTimeout(handle);\n if (synth) {\n await synthesizeBuffered(synth.message, synth.detail);\n await synthesizePending(synth.message, synth.detail);\n }\n await http.close().catch((err: unknown) => {\n const detail = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[tandem mcp-stdio] http.close failed: ${detail}\\n`);\n });\n await stdio.close().catch((err: unknown) => {\n const detail = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[tandem mcp-stdio] stdio.close failed: ${detail}\\n`);\n });\n }\n process.exit(code);\n };\n\n // Plugin hosts typically send `initialize` immediately after spawn (MCP\n // lifecycle §initialization). Deferring shutdown by PREFLIGHT_GRACE_MS\n // lets that request land during the preflight/start window and receive\n // a -32000 reply rather than a silent stdio close. stdio.onclose\n // short-circuits this if the loader closes stdin first.\n function deferredShutdown(synth: { message: string; detail?: string }): void {\n setTimeout(() => void shutdown(1, synth), PREFLIGHT_GRACE_MS);\n }\n\n stdio.onmessage = (msg: JSONRPCMessage) => {\n if (!httpReady) {\n preReadyBuffer.push(msg);\n return;\n }\n forwardToUpstream(msg);\n };\n\n http.onmessage = (msg: JSONRPCMessage) => {\n if (shuttingDown) return;\n // Synchronous delete+clear FIRST — before stdio.send — to prevent the\n // per-request timer from firing in the window between response arrival\n // and stdio write completion (which would synthesize a false -32000 for\n // an id that already has a real response in flight).\n const responseId = getResponseId(msg);\n if (responseId !== undefined) {\n const handle = pendingRequests.get(responseId);\n if (handle !== undefined) {\n clearTimeout(handle);\n pendingRequests.delete(responseId);\n }\n }\n const sendHandler = (err: unknown) => {\n const detail = err instanceof Error ? err.message : String(err);\n process.stderr.write(\n `[tandem mcp-stdio] stdio write failed for id ${responseId ?? \"<notification>\"}: ${detail}\\n`,\n );\n // Map entry already deleted above. Synthesize directly for this id,\n // then tear down — stdio is broken so other pending requests can't\n // be delivered either.\n if (responseId !== undefined) {\n void sendErrorResponse(responseId, \"Tandem stdio write failed\", detail);\n }\n void shutdown(1, {\n message: \"Tandem stdio write failed\",\n detail,\n });\n };\n try {\n stdio.send(msg).catch(sendHandler);\n } catch (err) {\n sendHandler(err);\n }\n };\n\n stdio.onerror = (err) => {\n process.stderr.write(`[tandem mcp-stdio] stdio error: ${err.message}\\n${err.stack ?? \"\"}\\n`);\n };\n http.onerror = (err) => {\n const cause = (err as { cause?: unknown }).cause;\n process.stderr.write(\n `[tandem mcp-stdio] http error: ${err.message}\\n${err.stack ?? \"\"}${cause !== undefined ? `\\ncause: ${cause}` : \"\"}\\n`,\n );\n };\n\n stdio.onclose = () => {\n void shutdown(0);\n };\n http.onclose = () => {\n // We've observed the current @modelcontextprotocol/sdk (0.20.x) only\n // firing onclose from inside its own close() method — i.e., as a\n // consequence of *our* shutdown. The synth branch below is defensive\n // for future SDK versions that may propagate socket-death as onclose.\n // The `shuttingDown` guard prevents double-synth when shutdown() calls\n // http.close() itself.\n if (shuttingDown) return;\n void shutdown(1, {\n message: \"Tandem HTTP upstream closed unexpectedly\",\n detail: \"upstream connection dropped mid-session\",\n });\n };\n\n // Start stdio BEFORE preflight so any `initialize` that arrives during\n // the preflight window is captured and either forwarded once upstream is\n // ready, or answered with -32000 if upstream never comes up.\n await stdio.start();\n\n // The SDK's StdioServerTransport watches stdin for 'data' and 'error' only —\n // it does not call onclose when the plugin host closes stdin (EOF). Register\n // our own one-shot listener so that plugin-host close (stdin.end() / HUP)\n // triggers the same clean shutdown path as stdio.onclose does.\n process.stdin.once(\"end\", () => {\n void shutdown(0);\n });\n\n const probe = await probeTandemServer({ url: baseUrl });\n if (!probe.ok) {\n const guidance =\n probe.kind === \"unreachable\"\n ? \"Start the Tauri app or run `tandem start` on the host, then retry.\"\n : \"The Tandem server is running but unhealthy — check the host logs.\";\n process.stderr.write(\n `[tandem mcp-stdio] Tandem server preflight failed at ${probe.url} (${probe.reason}).\\n` +\n `[tandem mcp-stdio] ${guidance}\\n`,\n );\n const synthMessage =\n probe.kind === \"unreachable\"\n ? \"Tandem server not running. Start the Tauri app or run `tandem start`.\"\n : \"Tandem server unhealthy (check host logs).\";\n deferredShutdown({ message: synthMessage, detail: probe.reason });\n return;\n }\n\n // The current @modelcontextprotocol/sdk's StreamableHTTPClientTransport.start()\n // only creates an AbortController and returns synchronously — this catch is\n // defensive for future SDK versions that may perform real I/O during start().\n try {\n await http.start();\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[tandem mcp-stdio] upstream http start failed: ${detail}\\n`);\n deferredShutdown({ message: \"Tandem HTTP upstream failed to start\", detail });\n return;\n }\n httpReady = true;\n\n // Held to preserve forwarding semantics — push through the normal path\n // now that upstream is ready. Note: forwardToUpstream does not await the\n // http.send, so buffered requests POST in parallel. Plugin hosts wait\n // for `initialize` to resolve before sending follow-ups per MCP spec, so\n // the buffer is usually ≤1 entry; we don't enforce serial ordering here.\n const buffered = preReadyBuffer.splice(0);\n for (const msg of buffered) forwardToUpstream(msg);\n}\n\nexport function getRequestId(msg: JSONRPCMessage): string | number | undefined {\n const m = msg as { id?: unknown; method?: unknown };\n if (typeof m.method !== \"string\") return undefined;\n if (typeof m.id === \"string\" || typeof m.id === \"number\") return m.id;\n return undefined;\n}\n\nexport function getResponseId(msg: JSONRPCMessage): string | number | undefined {\n const m = msg as { id?: unknown; method?: unknown };\n if (typeof m.method === \"string\") return undefined;\n if (typeof m.id === \"string\" || typeof m.id === \"number\") return m.id;\n return undefined;\n}\n","/**\n * Single source of truth for Tandem's internal HTTP path strings.\n *\n * Server route registration (`src/server/mcp/{api,channel}-routes.ts`) and every\n * client/CLI/channel-shim/monitor caller import from here so a rename hits one file.\n */\n\n// --- Channel / event stream (SSE + push-back) -------------------------------\nexport const API_EVENTS = \"/api/events\";\nexport const API_NOTIFY_STREAM = \"/api/notify-stream\";\nexport const API_CHANNEL_AWARENESS = \"/api/channel-awareness\";\nexport const API_CHANNEL_ERROR = \"/api/channel-error\";\nexport const API_CHANNEL_REPLY = \"/api/channel-reply\";\nexport const API_CHANNEL_PERMISSION = \"/api/channel-permission\";\nexport const API_CHANNEL_PERMISSION_VERDICT = \"/api/channel-permission-verdict\";\nexport const API_LAUNCH_CLAUDE = \"/api/launch-claude\";\n\n// --- Mode / metadata --------------------------------------------------------\nexport const API_MODE = \"/api/mode\";\nexport const API_INFO = \"/api/info\";\n\n// --- Document lifecycle -----------------------------------------------------\nexport const API_OPEN = \"/api/open\";\nexport const API_CLOSE = \"/api/close\";\nexport const API_SAVE = \"/api/save\";\nexport const API_UPLOAD = \"/api/upload\";\nexport const API_SCRATCHPAD = \"/api/scratchpad\";\nexport const API_CONVERT = \"/api/convert\";\nexport const API_APPLY_CHANGES = \"/api/apply-changes\";\n\n// --- Annotations ------------------------------------------------------------\nexport const API_ANNOTATION_REPLY = \"/api/annotation-reply\";\nexport const API_REMOVE_ANNOTATION = \"/api/remove-annotation\";\n\n// --- Chat -------------------------------------------------------------------\nexport const API_CHAT = \"/api/chat\";\n\n// --- Setup / auth -----------------------------------------------------------\nexport const API_SETUP = \"/api/setup\";\nexport const API_ROTATE_TOKEN = \"/api/rotate-token\";\n\n// --- Auto-launcher (Claude Code supervisor, #477 PR 4b) ---------------------\nexport const API_LAUNCHER_STATUS = \"/api/launcher/status\";\nexport const API_LAUNCHER_NONCE = \"/api/launcher/nonce\";\nexport const API_LAUNCHER_RELAUNCH = \"/api/launcher/relaunch\";\nexport const API_LAUNCHER_START_FRESH = \"/api/launcher/start-fresh\";\nexport const API_LAUNCHER_WORKING_DIRECTORY = \"/api/launcher/working-directory\";\n","/**\n * Shared fetch-with-timeout helper.\n *\n * Used by `src/monitor/index.ts` and `src/channel/` (event-bridge + run) to\n * give every outbound HTTP call a bounded deadline. Without this, a half-open\n * upstream wedges the caller silently — see #336 (silent failures) and #364\n * (event-bridge transport timeout symmetry).\n *\n * Pure native `fetch` + `AbortSignal.timeout` so it can be imported from any\n * surface without dragging in server deps. Routes through `authFetch` so the\n * resolved Tandem auth token is forwarded automatically when set.\n *\n * `describeFetchError` formats timeout aborts as `<endpoint> timed out after\n * <ms>ms` so logs name the hung endpoint instead of the generic\n * \"operation was aborted\" string from AbortError/TimeoutError.\n */\n\nimport { authFetch } from \"./cli-runtime.js\";\n\n/**\n * Fetch with a per-request deadline.\n *\n * **Do not use for SSE handshake-then-stream patterns** — applying a fetch-level\n * timeout to a streaming response also aborts the body `ReadableStream` when\n * the timeout fires, killing the stream at `timeoutMs`. Use a local\n * `AbortController` cleared after the handshake settles for that case.\n */\nexport async function fetchWithTimeout(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<Response> {\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;\n return authFetch(url, { ...init, signal });\n}\n\n/**\n * Format a fetch error for logs. Recognizes `TimeoutError` / `AbortError`\n * (both names that `AbortSignal.timeout` and manual aborts produce) and tags\n * them with the endpoint + threshold so log lines name the hung request.\n */\nexport function describeFetchError(err: unknown, endpoint: string, timeoutMs: number): string {\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n return `${endpoint} timed out after ${timeoutMs}ms`;\n }\n return err instanceof Error ? err.message : String(err);\n}\n\n/** True iff `err` is an AbortError or TimeoutError (the names produced by\n * AbortSignal.timeout and manual aborts). Channel callers re-throw these\n * through broad catches so timeouts surface as structured errors instead of\n * being swallowed as \"non-JSON response\" fake-success. */\nexport function isAbortOrTimeoutError(err: unknown): boolean {\n return err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\");\n}\n","function generateId(prefix: string): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n}\n\nexport function generateAnnotationId(): string {\n return generateId(\"ann\");\n}\n\nexport function generateMessageId(): string {\n return generateId(\"msg\");\n}\n\nexport function generateEventId(): string {\n return generateId(\"evt\");\n}\n\nexport function generateReplyId(): string {\n return generateId(\"rpl\");\n}\n\nexport function generateNotificationId(): string {\n return generateId(\"ntf\");\n}\n\nexport function generateAuthorshipId(author: \"user\" | \"claude\"): string {\n return generateId(author);\n}\n","/**\n * Event types for the Tandem → Claude Code channel.\n *\n * These events flow from browser-originated Y.Map changes through an SSE\n * endpoint to the channel shim, which pushes them into Claude Code as\n * `notifications/claude/channel` messages.\n *\n * This module lives in `src/shared/` so that `src/channel/` and\n * `src/monitor/` can import wire-protocol types without crossing the\n * server layer boundary.\n */\n\nimport type { AnnotationType, ReplyAuthor } from \"../types.js\";\n\n// --- Per-event payload interfaces ---\n\nexport interface AnnotationCreatedPayload {\n annotationId: string;\n annotationType: AnnotationType;\n content: string;\n textSnippet: string;\n hasSuggestedText?: boolean;\n}\n\nexport interface AnnotationAcceptedPayload {\n annotationId: string;\n textSnippet: string;\n}\n\nexport interface AnnotationDismissedPayload {\n annotationId: string;\n textSnippet: string;\n}\n\nexport interface ChatMessagePayload {\n messageId: string;\n text: string;\n replyTo: string | null;\n anchor: { from: number; to: number; textSnapshot: string } | null;\n /** Buffered selection context at the time the chat message was sent. */\n selection?: { from: number; to: number; selectedText: string } | { selectedText: string };\n}\n\nexport interface DocumentOpenedPayload {\n fileName: string;\n format: string;\n}\n\nexport interface DocumentClosedPayload {\n fileName: string;\n}\n\nexport interface AnnotationReplyPayload {\n annotationId: string;\n replyId: string;\n replyText: string;\n replyAuthor: ReplyAuthor;\n textSnippet: string;\n}\n\nexport interface DocumentSwitchedPayload {\n fileName: string;\n}\n\nexport interface AnnotationEditedPayload {\n annotationId: string;\n content: string;\n textSnippet: string;\n editedAt: number;\n}\n\n// --- Discriminated union ---\n\ninterface TandemEventBase {\n /** Timestamp-based unique ID for SSE `Last-Event-ID` reconnection. Format: `evt_<timestamp>_<rand>`. Roughly ordered but not strictly monotonic. */\n id: string;\n timestamp: number;\n /** Which document this event relates to (absent for global events). */\n documentId?: string;\n}\n\nexport type TandemEvent =\n | (TandemEventBase & { type: \"annotation:created\"; payload: AnnotationCreatedPayload })\n | (TandemEventBase & { type: \"annotation:accepted\"; payload: AnnotationAcceptedPayload })\n | (TandemEventBase & { type: \"annotation:dismissed\"; payload: AnnotationDismissedPayload })\n | (TandemEventBase & { type: \"annotation:reply\"; payload: AnnotationReplyPayload })\n | (TandemEventBase & { type: \"chat:message\"; payload: ChatMessagePayload })\n | (TandemEventBase & { type: \"document:opened\"; payload: DocumentOpenedPayload })\n | (TandemEventBase & { type: \"document:closed\"; payload: DocumentClosedPayload })\n | (TandemEventBase & { type: \"document:switched\"; payload: DocumentSwitchedPayload })\n | (TandemEventBase & { type: \"annotation:edited\"; payload: AnnotationEditedPayload });\n\n/** Union of all event type discriminants. */\nexport type TandemEventType = TandemEvent[\"type\"];\n\n// Re-export from shared utils (single ID generation pattern)\nexport { generateEventId } from \"../utils.js\";\n\n// --- Parse guard for SSE consumers ---\n\nconst VALID_EVENT_TYPES = new Set<TandemEventType>([\n \"annotation:created\",\n \"annotation:accepted\",\n \"annotation:dismissed\",\n \"annotation:edited\",\n \"annotation:reply\",\n \"chat:message\",\n \"document:opened\",\n \"document:closed\",\n \"document:switched\",\n]);\n\n/**\n * Validate a JSON-parsed value as a TandemEvent.\n * Used by the event-bridge to safely consume SSE data.\n */\nexport function parseTandemEvent(raw: unknown): TandemEvent | null {\n if (\n typeof raw !== \"object\" ||\n raw === null ||\n !(\"id\" in raw) ||\n typeof (raw as Record<string, unknown>).id !== \"string\" ||\n !(\"type\" in raw) ||\n !VALID_EVENT_TYPES.has((raw as Record<string, unknown>).type as TandemEventType) ||\n !(\"timestamp\" in raw) ||\n typeof (raw as Record<string, unknown>).timestamp !== \"number\" ||\n !(\"payload\" in raw) ||\n typeof (raw as Record<string, unknown>).payload !== \"object\"\n ) {\n return null;\n }\n return raw as TandemEvent;\n}\n\n/**\n * Convert a TandemEvent into a human-readable string for the channel `content` field.\n * Claude sees this text inside `<channel source=\"tandem-channel\">` tags.\n */\nexport function formatEventContent(event: TandemEvent): string {\n const doc = event.documentId ? ` [doc: ${event.documentId}]` : \"\";\n\n switch (event.type) {\n case \"annotation:created\": {\n const { annotationType, content, textSnippet, hasSuggestedText } = event.payload;\n const snippet = textSnippet ? ` on \"${textSnippet}\"` : \"\";\n const label = hasSuggestedText ? \"replacement\" : annotationType;\n return `User created ${label}${snippet}: ${content || \"(no content)\"}${doc}`;\n }\n case \"annotation:accepted\": {\n const { annotationId, textSnippet } = event.payload;\n return `User accepted annotation ${annotationId}${textSnippet ? ` (\"${textSnippet}\")` : \"\"}${doc}`;\n }\n case \"annotation:dismissed\": {\n const { annotationId, textSnippet } = event.payload;\n return `User dismissed annotation ${annotationId}${textSnippet ? ` (\"${textSnippet}\")` : \"\"}${doc}`;\n }\n case \"annotation:edited\": {\n const { content } = event.payload;\n return `User edited annotation: \"${content}\"${doc}`;\n }\n case \"annotation:reply\": {\n const { annotationId, replyAuthor, replyText, textSnippet } = event.payload;\n const who = replyAuthor === \"claude\" ? \"Claude\" : \"User\";\n const snippet = textSnippet ? ` (on \"${textSnippet}\")` : \"\";\n return `${who} replied to annotation ${annotationId}${snippet}: ${replyText}${doc}`;\n }\n case \"chat:message\": {\n const { text, replyTo, selection } = event.payload;\n const reply = replyTo ? ` (replying to ${replyTo})` : \"\";\n const sel =\n selection && selection.selectedText\n ? ` [selection: \"${selection.selectedText}\"${\"from\" in selection ? ` (${selection.from}-${selection.to})` : \"\"}]`\n : \"\";\n return `User says${reply}: ${text}${sel}${doc}`;\n }\n case \"document:opened\": {\n const { fileName, format } = event.payload;\n return `User opened document: ${fileName} (${format})${doc}`;\n }\n case \"document:closed\": {\n const { fileName } = event.payload;\n return `User closed document: ${fileName}${doc}`;\n }\n case \"document:switched\": {\n const { fileName } = event.payload;\n return `User switched to document: ${fileName}${doc}`;\n }\n default: {\n const _exhaustive: never = event;\n void _exhaustive;\n return `Unknown event${doc}`;\n }\n }\n}\n\n/**\n * Build the `meta` record for a channel notification.\n * Keys use underscores only (Channels API silently drops hyphenated keys).\n */\nexport function formatEventMeta(event: TandemEvent): Record<string, string> {\n const meta: Record<string, string> = {\n event_type: event.type,\n };\n if (event.documentId) meta.document_id = event.documentId;\n\n switch (event.type) {\n case \"annotation:created\":\n case \"annotation:accepted\":\n case \"annotation:dismissed\":\n meta.annotation_id = event.payload.annotationId;\n break;\n case \"annotation:edited\":\n meta.annotation_id = event.payload.annotationId;\n meta.edited_at = String(event.payload.editedAt);\n break;\n case \"annotation:reply\":\n meta.annotation_id = event.payload.annotationId;\n meta.reply_id = event.payload.replyId;\n break;\n case \"chat:message\":\n meta.message_id = event.payload.messageId;\n if (event.payload.selection?.selectedText) meta.has_selection = \"true\";\n break;\n case \"document:opened\":\n case \"document:closed\":\n case \"document:switched\":\n break;\n default: {\n const _exhaustive: never = event;\n void _exhaustive;\n break;\n }\n }\n\n return meta;\n}\n","/**\n * Shared types for the position/coordinate system.\n *\n * Three coordinate systems exist in Tandem:\n * 1. Flat text offsets — server-side, includes heading prefixes and \\n separators\n * 2. ProseMirror positions — client-side, structural node positions\n * 3. Yjs RelativePositions — CRDT-anchored, survive concurrent edits\n *\n * This module defines the shared vocabulary. Environment-specific logic lives in:\n * - src/server/positions.ts (Y.Doc operations)\n * - src/client/positions.ts (ProseMirror operations)\n */\n\n// ---------------------------------------------------------------------------\n// Branded types — compile-time guards against mixing coordinate systems\n// ---------------------------------------------------------------------------\n\ndeclare const FlatOffsetBrand: unique symbol;\ndeclare const PmPosBrand: unique symbol;\ndeclare const SerializedRelPosBrand: unique symbol;\n\n/** Flat text offset (includes heading prefixes & \\n separators). Server/MCP boundary. */\nexport type FlatOffset = number & { readonly [FlatOffsetBrand]: true };\n\n/** ProseMirror position (structural node boundaries). Client-side only. */\nexport type PmPos = number & { readonly [PmPosBrand]: true };\n\n/** JSON-serialized Y.js RelativePosition. Opaque — only created/consumed by position modules. */\nexport type SerializedRelPos = unknown & { readonly [SerializedRelPosBrand]: true };\n\n// ---------------------------------------------------------------------------\n// Factory functions — cast raw values into branded types\n// ---------------------------------------------------------------------------\n\nexport const toFlatOffset = (n: number): FlatOffset => n as FlatOffset;\nexport const toPmPos = (n: number): PmPos => n as PmPos;\nexport const toSerializedRelPos = (json: unknown): SerializedRelPos => json as SerializedRelPos;\n\n// ---------------------------------------------------------------------------\n// Range and result types\n// ---------------------------------------------------------------------------\n\n/** Flat-offset range used by MCP tools and annotations. */\nexport interface DocumentRange {\n from: FlatOffset;\n to: FlatOffset;\n}\n\n/** CRDT-anchored range that survives concurrent edits. Serialized via Y.relativePositionToJSON(). */\nexport interface RelativeRange {\n fromRel: SerializedRelPos;\n toRel: SerializedRelPos;\n}\n\n/** Result of validating a flat-offset range against a document. */\nexport type RangeValidation =\n | { ok: true; range: DocumentRange }\n | { ok: false; code: \"RANGE_GONE\" }\n | { ok: false; code: \"RANGE_MOVED\"; resolvedFrom: FlatOffset; resolvedTo: FlatOffset }\n | { ok: false; code: \"INVALID_RANGE\"; message: string }\n | { ok: false; code: \"HEADING_OVERLAP\" };\n\n/** Result of anchoredRange: validated flat + CRDT-anchored range ready to store on an Annotation. */\nexport type AnchoredRangeResult =\n | { ok: true; fullyAnchored: true; range: DocumentRange; relRange: RelativeRange }\n | { ok: true; fullyAnchored: false; range: DocumentRange; relRange?: undefined };\n\n/** A resolved element position inside a Y.Doc XmlFragment. */\nexport interface ElementPosition {\n elementIndex: number;\n /** Character offset within the element's text. Always 0 when clampedFromPrefix is true. */\n textOffset: number;\n /** True if the original offset fell inside a heading prefix and was clamped to 0 */\n clampedFromPrefix: boolean;\n}\n\n/** Resolution method used by annotationToPmRange, for diagnostic observability. */\nexport type ResolutionMethod = \"rel\" | \"flat\";\n\n/** Result of resolving an annotation to ProseMirror positions. */\nexport interface PmRangeResult {\n from: PmPos;\n to: PmPos;\n /** Which coordinate path was used to resolve the range. */\n method: ResolutionMethod;\n}\n\n/**\n * Tagged variant for the outcome of `refreshRange` (ADR-032).\n *\n * Each kind names a distinct resolution path the function previously\n * collapsed into a bare `Annotation` return:\n * - `ok` — annotation unchanged; range was already healthy\n * - `updated` — `relRange` resolved to new offsets; flat `range` was rewritten\n * - `attached` — annotation had no `relRange`; one was computed from the flat range\n * - `repaired` — dead `relRange` was re-anchored from the flat range\n * - `degraded` — dead `relRange` was stripped; annotation is now flat-only and will\n * be lazy-attached on a later read if conditions improve\n * - `failed` — `from > to` after refresh (\"inverted CRDT range\" — concurrent\n * edits moved the anchors past each other). Annotation is returned\n * unchanged for the caller's inspection.\n */\nexport type RefreshResult = {\n kind: \"ok\" | \"updated\" | \"attached\" | \"repaired\" | \"degraded\" | \"failed\";\n annotation: import(\"../types.js\").Annotation;\n};\n","import { z } from \"zod\";\nimport type { DocumentRange, RelativeRange } from \"./positions/types.js\";\n\n// Canonical definitions live in the positions module; re-exported for backward compatibility.\nexport type {\n DocumentRange,\n FlatOffset,\n PmPos,\n RelativeRange,\n SerializedRelPos,\n} from \"./positions/types.js\";\nexport { toFlatOffset, toPmPos, toSerializedRelPos } from \"./positions/types.js\";\n\n// --- Zod schemas (source of truth) ---\n\nexport const AnnotationTypeSchema = z.enum([\"highlight\", \"note\", \"comment\"]);\n\nexport const AnnotationStatusSchema = z.enum([\"pending\", \"accepted\", \"dismissed\"]);\nexport const HighlightColorSchema = z.enum([\"yellow\", \"green\", \"blue\", \"pink\"]);\nexport const SeveritySchema = z.enum([\"info\", \"warning\", \"error\", \"success\"]);\nexport const TandemModeSchema = z.enum([\"solo\", \"tandem\"]);\nexport const AuthorSchema = z.enum([\"user\", \"claude\", \"import\"]);\n/** Subset of {@link AuthorSchema} allowed on replies — `import` is annotations-only. */\nexport const ReplyAuthorSchema = z.enum([\"user\", \"claude\"]);\nexport const AnnotationActionSchema = z.enum([\"accept\", \"dismiss\"]);\nexport const ExportFormatSchema = z.enum([\"markdown\", \"json\"]);\nexport const DocumentFormatSchema = z.enum([\"md\", \"txt\", \"html\", \"docx\"]);\nexport const ToolErrorCodeSchema = z.enum([\n \"RANGE_GONE\",\n \"RANGE_MOVED\",\n \"FILE_LOCKED\",\n \"FILE_NOT_FOUND\",\n \"NO_DOCUMENT\",\n \"INVALID_RANGE\",\n \"INVALID_ARGUMENT\",\n \"NOT_FOUND\",\n \"ANNOTATION_RESOLVED\",\n \"FORMAT_ERROR\",\n \"PERMISSION_DENIED\",\n]);\n\n/**\n * Identifier strings the channel shim or monitor can POST to\n * `/api/channel-error` on terminal failure. The server logs them; defining\n * them as a closed set lets call sites import the constants instead of\n * free-form strings, and the route handler can validate before logging.\n */\nexport const ChannelErrorCodeSchema = z.enum([\"CHANNEL_CONNECT_FAILED\", \"MONITOR_CONNECT_FAILED\"]);\nexport type ChannelErrorCode = z.infer<typeof ChannelErrorCodeSchema>;\nexport const CHANNEL_CONNECT_FAILED: ChannelErrorCode = \"CHANNEL_CONNECT_FAILED\";\nexport const MONITOR_CONNECT_FAILED: ChannelErrorCode = \"MONITOR_CONNECT_FAILED\";\n\n// --- Derived TypeScript types ---\n\nexport type AnnotationType = z.infer<typeof AnnotationTypeSchema>;\nexport type AnnotationStatus = z.infer<typeof AnnotationStatusSchema>;\nexport type TandemMode = z.infer<typeof TandemModeSchema>;\nexport type HighlightColor = z.infer<typeof HighlightColorSchema>;\nexport type Severity = z.infer<typeof SeveritySchema>;\nexport type ReplyAuthor = z.infer<typeof ReplyAuthorSchema>;\n\n// --- Reply types ---\n\nexport interface AnnotationReply {\n id: string;\n annotationId: string;\n author: ReplyAuthor;\n text: string;\n timestamp: number;\n editedAt?: number;\n /**\n * Durable-annotation last-writer-wins counter. Server-internal field\n * mirrored from the on-disk envelope schema (see\n * `src/server/annotations/schema.ts` `AnnotationReplyRecordV1`). Optional\n * here so client code and legacy in-memory state that predates the durable\n * store don't trip TS. Every server-side write bumps this; legacy entries\n * lacking `rev` are treated as `rev: 0` on merge.\n */\n rev?: number;\n}\n\n// --- Annotation types ---\n\ninterface AnnotationBase {\n id: string;\n author: \"user\" | \"claude\" | \"import\";\n range: DocumentRange;\n /** CRDT-anchored range that survives edits. Falls back to `range` if absent. */\n relRange?: RelativeRange;\n content: string;\n status: AnnotationStatus;\n timestamp: number;\n /** Snapshot of the annotated document text at creation time. Truncated to 200 chars. */\n textSnapshot?: string;\n /** Timestamp of last edit to the annotation content. */\n editedAt?: number;\n /**\n * Durable-annotation last-writer-wins counter. Server-internal field\n * mirrored from the on-disk envelope schema (see\n * `src/server/annotations/schema.ts` `AnnotationRecordV1`). Optional here\n * so legacy session-restored state (pre-durable-store) and client code\n * that doesn't care about durability still type-check. Every server-side\n * user-intent write bumps this; entries lacking `rev` are treated as\n * `rev: 0` by the merge/sync code.\n */\n rev?: number;\n /** When true, marks this annotation as created during Solo mode. Consumers use this to hold back display until mode changes. */\n heldInSolo?: boolean;\n /** Audience: 'private' = personal (note/highlight), 'outbound' = visible to Claude. Derived by AR1 migration on read for legacy annotations. */\n audience?: \"private\" | \"outbound\";\n /** Set when this annotation was promoted from a note via \"Send to Claude\". */\n promotedFrom?: \"note\";\n /** For import-author annotations: original Word author and source file. */\n importSource?: { author: string; file: string };\n}\n\n/**\n * Discriminated union for annotations. Three canonical types:\n * - `highlight` — visual marker with color, not sent to Claude\n * - `note` — personal text annotation, findable but Claude doesn't act\n * - `comment` — text for Claude; optionally carries `suggestedText` (replacement)\n */\nexport type Annotation =\n | (AnnotationBase & {\n type: \"highlight\";\n color?: HighlightColor;\n suggestedText?: undefined;\n })\n | (AnnotationBase & {\n type: \"note\";\n color?: undefined;\n suggestedText?: undefined;\n })\n | (AnnotationBase & {\n type: \"comment\";\n color?: undefined;\n suggestedText?: string;\n });\n\n/**\n * Returns true for annotations that should be reviewed (accepted/dismissed).\n * User-authored notes are personal and never review targets.\n * Import-authored (.docx Word comments) ARE review targets — the primary docx use case.\n */\nexport function isReviewTarget(a: Annotation): boolean {\n return a.author !== \"user\";\n}\n\n/** Convenience: pending status AND a review target — used at bulk-action and keyboard-nav callsites. */\nexport function isPendingReviewTarget(a: Annotation): boolean {\n return a.status === \"pending\" && isReviewTarget(a);\n}\n\n/**\n * Authorship tracking range stored in Y.Map('authorship').\n * Uses the same flat-offset coordinate system as annotations.\n * RelativePositions anchor the range to survive concurrent edits.\n */\nexport interface AuthorshipRange {\n id: string;\n author: \"user\" | \"claude\";\n range: DocumentRange;\n /** CRDT-anchored range for edit survival. */\n relRange?: RelativeRange;\n /** Timestamp of when this range was created. */\n timestamp: number;\n}\n\nexport interface AnchoredRange {\n start: { nodeId: string; offset: number };\n end: { nodeId: string; offset: number };\n textSnapshot: string;\n stale: boolean;\n}\n\nexport interface OverlayEntry {\n id: string;\n overlayId: string;\n range: AnchoredRange;\n score: string;\n numericScore?: number;\n detail: {\n summary: string;\n explanation: string;\n suggestion?: string;\n severity?: Severity;\n references?: Array<{ label: string; url?: string; documentNodeId?: string }>;\n };\n dismissed: boolean;\n accepted?: boolean;\n data: Record<string, unknown>;\n}\n\nexport interface OverlayDefinition {\n id: string;\n label: string;\n type: string;\n visible: boolean;\n mode: \"snapshot\" | \"live\";\n entries: OverlayEntry[];\n createdAt: number;\n updatedAt: number;\n}\n\nexport interface DocumentGroup {\n id: string;\n name: string;\n documents: DocumentInfo[];\n createdAt: number;\n}\n\nexport interface DocumentInfo {\n id: string;\n filePath: string;\n fileName: string;\n format: z.infer<typeof DocumentFormatSchema>;\n tokenEstimate: number;\n pageEstimate: number;\n readOnly: boolean;\n}\n\nexport interface ToolSuccess<T = unknown> {\n error: false;\n data: T;\n version?: string;\n}\n\nexport interface ToolError {\n error: true;\n code: z.infer<typeof ToolErrorCodeSchema>;\n message: string;\n details?: Record<string, unknown>;\n}\n\nexport type ToolResponse<T = unknown> = ToolSuccess<T> | ToolError;\n\n/** Claude's awareness state as stored in Y.Map('awareness') key 'claude' */\nexport interface ClaudeAwareness {\n status: string;\n timestamp: number;\n active: boolean;\n focusParagraph: number | null;\n /** Flat character offset for character-level cursor positioning. */\n focusOffset: number | null;\n /**\n * Typing-presence indicator (#651). When set, Claude is actively executing\n * an MCP tool. `annotationId` (when present) lets per-card UI render an\n * inline typing indicator; an absent annotationId indicates a generic\n * \"Claude is working\" state surfaced in the status bar.\n *\n * ADR-027: never broadcast `annotationId` for `type === \"note\"` annotations\n * (the server middleware enforces this on write).\n */\n working?: {\n tool: string;\n annotationId?: string;\n /** Display-only wall-clock start time (ms). NOT an ownership key — see `token`. */\n startedAt: number;\n /**\n * Monotonic, collision-free ownership token (#823). Two same-doc tool calls\n * in the same millisecond would collide on `startedAt`; the clear path keys\n * identity on this counter instead so finishing one handler never wipes\n * another's still-active marker. Optional for back-compat with snapshots\n * written before #823.\n */\n token?: number;\n } | null;\n}\n\nexport interface SessionData {\n filePath: string;\n format: string;\n ydocState: string; // Base64-encoded Y.encodeStateAsUpdate()\n sourceFileMtime: number; // Source file mtime at save — detect external changes on resume\n lastAccessed: number;\n}\n\n/** Text selection snapshot captured when opening chat, attached to the next outgoing ChatMessage as its anchor. */\nexport interface CapturedAnchor extends DocumentRange {\n textSnapshot: string;\n}\n\n/** Chat message between user and Claude, stored in Y.Map('chat') on CTRL_ROOM */\nexport interface ChatMessage {\n id: string;\n author: \"user\" | \"claude\";\n text: string;\n timestamp: number;\n documentId?: string;\n anchor?: CapturedAnchor;\n replyTo?: string;\n read: boolean;\n}\n\n/** Server-to-client ephemeral notification (toast). Not persisted via CRDT. */\nexport interface TandemNotification {\n id: string;\n type:\n | \"annotation-error\"\n | \"save-error\"\n | \"session-restored\"\n | \"general-error\"\n | \"file-reloaded\"\n | \"review-pending\"\n | \"launcher\";\n severity: \"info\" | \"warning\" | \"error\";\n message: string;\n documentId?: string;\n dedupKey?: string;\n timestamp: number;\n toolName?: string;\n errorCode?: string;\n}\n","/**\n * Shared SSE consumer for the Tandem channel shim and plugin monitor.\n *\n * Extracted in #282 to deduplicate ~140 lines of retry / frame-parse /\n * awareness / mode-cache logic that used to be copy-pasted between\n * `src/channel/event-bridge.ts` and `src/monitor/index.ts`.\n *\n * Callers inject their per-event delivery mechanism via the `onEvent`\n * callback. The shared module never touches the MCP SDK or stdout — that\n * preserves the MCP-free constraint and keeps the channel shim and monitor\n * free to evolve their delivery surfaces independently.\n *\n * Per-request timeouts (#364) mirror the original monitor pattern: every\n * outbound fetch has a bounded deadline, the SSE body has an inactivity\n * watchdog, and the parse buffer is capped so a malformed upstream can't\n * OOM us. Without these, a half-open Tandem server wedges the consumer\n * silently.\n *\n * Mode-cache policy: stale-preserving. Once a real mode has been observed\n * from `/api/mode`, a transient fetch failure (network error or non-OK)\n * NEVER changes the cached mode — the consumer keeps reporting the last\n * successfully-fetched value. The mode only changes when the server reports\n * a different mode (i.e. the user actually toggled Solo/Tandem). This holds\n * for ALL failure paths, including the startup warm-up / first-fetch path:\n * a failure after a successful fetch can never downgrade a known mode.\n * The hardcoded `TANDEM_MODE_DEFAULT` is used ONLY on a genuine cold start —\n * a failure before any successful fetch has ever landed. The channel and\n * monitor previously diverged here (channel failed open to \"tandem\", monitor\n * failed closed to \"solo\"); both flipped the mode to a default on a hiccup.\n * Neither honored the user directive that mode must not change unless the\n * user changes it — stale-preserving does.\n *\n * Retry policy: exponential backoff with stable-uptime reset (monitor's\n * pattern). The channel previously reset retries on every successful event\n * — bringing exponential backoff + stable uptime gives the channel the\n * same robustness guarantees.\n *\n * Frame-skip policy: advance `lastEventId` past malformed-JSON and\n * failed-validation frames (channel's \"advance past garbage\" pattern). The\n * monitor previously did NOT advance on these — its catch / `!event`\n * branches just `continue`d, so a permanently-unparseable frame would be\n * replayed from `Last-Event-ID` on every reconnect forever (an infinite\n * parse-fail loop). Unifying on the channel's semantics fixes that latent\n * infinite re-delivery bug.\n */\n\nimport { API_CHANNEL_AWARENESS, API_CHANNEL_ERROR, API_EVENTS, API_MODE } from \"./api-paths.js\";\nimport { authFetch } from \"./cli-runtime.js\";\nimport {\n CHANNEL_AWARENESS_FETCH_TIMEOUT_MS,\n CHANNEL_CONNECT_FETCH_TIMEOUT_MS,\n CHANNEL_ERROR_REPORT_TIMEOUT_MS,\n CHANNEL_MAX_RETRIES,\n CHANNEL_MAX_SSE_BUFFER_BYTES,\n CHANNEL_MODE_FETCH_TIMEOUT_MS,\n CHANNEL_RETRY_DELAY_MS,\n CHANNEL_SSE_INACTIVITY_TIMEOUT_MS,\n TANDEM_MODE_DEFAULT,\n} from \"./constants.js\";\nimport type { TandemEvent } from \"./events/types.js\";\nimport { parseTandemEvent } from \"./events/types.js\";\nimport { describeFetchError, fetchWithTimeout } from \"./fetch-with-timeout.js\";\nimport { type ChannelErrorCode, type TandemMode, TandemModeSchema } from \"./types.js\";\n\nconst AWARENESS_DEBOUNCE_MS = 500;\nconst AWARENESS_CLEAR_MS = 3000;\nconst MODE_CACHE_TTL_MS = 2000;\nconst STABLE_CONNECTION_MS = 60_000; // Reset retries after this much continuous uptime\nconst RETRY_MAX_DELAY_MS = 30_000; // Exponential backoff cap\n\nexport interface EventConsumerOptions {\n /** Base URL of the Tandem server (no trailing slash). */\n tandemUrl: string;\n /** Log prefix used in stderr lines (e.g. `[Channel]` or `[Monitor]`). */\n logPrefix: string;\n /** Error code POSTed to /api/channel-error on retry exhaustion. */\n errorCode: ChannelErrorCode;\n /**\n * Per-event delivery callback. Called for every parsed, non-suppressed\n * SSE event. If this throws or rejects, `lastEventId` is NOT advanced and\n * the stream is torn down so the retry loop reconnects with the previous\n * `Last-Event-ID` — the server replays the dropped event.\n */\n onEvent: (event: TandemEvent, eventId: string | undefined) => Promise<void> | void;\n /**\n * Optional hook called after the retry-exhaustion error POST returns but\n * before `process.exit(1)`. The monitor uses it to write the visible\n * \"disconnected\" notice to stdout. Default is a noop.\n */\n onExhaustion?: () => void;\n}\n\n// --- Module-level state ---\n//\n// Kept at module scope (not function-local) so `flushFinalAwareness` (called\n// from the monitor's signal handler) can drain in-flight awareness POSTs\n// and send the shutdown clear. `_resetSseConsumerStateForTests` clears\n// every byte of state below in one call.\n\nconst shutdownTimers: {\n awarenessTimer: ReturnType<typeof setTimeout> | null;\n clearAwarenessTimer: ReturnType<typeof setTimeout> | null;\n lastDocumentId: string | null;\n} = { awarenessTimer: null, clearAwarenessTimer: null, lastDocumentId: null };\n\n/** Outstanding awareness POSTs — drained on shutdown so the server's last\n * seen awareness is the shutdown \"active:false\", not a racing update. */\nconst outstandingAwareness = new Set<Promise<unknown>>();\nfunction trackAwareness(p: Promise<unknown>): void {\n outstandingAwareness.add(p);\n p.finally(() => outstandingAwareness.delete(p));\n}\n\nlet cachedMode: TandemMode = TANDEM_MODE_DEFAULT;\nlet cachedModeAt = 0;\nlet cachedModeFailedAt = 0;\nlet _modeRefreshInFlight: Promise<void> | null = null;\n\n// --- Public entry point ---\n\n/**\n * Drive the SSE consumer: connect, parse frames, deliver events via\n * `onEvent`, debounce awareness POSTs, and reconnect with exponential\n * backoff on failure. Reports `opts.errorCode` to `/api/channel-error` and\n * calls `process.exit(1)` after `CHANNEL_MAX_RETRIES` consecutive\n * failures.\n */\nexport async function runEventConsumer(opts: EventConsumerOptions): Promise<void> {\n // Warm the mode cache before the first event so we don't default-suppress\n // or default-deliver under an unknown user setting. Errors are already\n // logged inside getCachedMode (stale-preserving; cold-start default only\n // when no fetch has ever succeeded) — keep going.\n await getCachedMode(opts.tandemUrl, opts.logPrefix).catch(() => {});\n\n let retries = 0;\n let lastEventId: string | undefined;\n\n while (retries < CHANNEL_MAX_RETRIES) {\n try {\n await connectAndStreamOnce(opts, lastEventId, {\n onEventId: (id) => {\n lastEventId = id;\n },\n onStable: () => {\n retries = 0;\n },\n });\n } catch (err) {\n retries++;\n console.error(\n `${opts.logPrefix} SSE connection failed (${retries}/${CHANNEL_MAX_RETRIES}):`,\n err instanceof Error ? err.message : err,\n );\n\n if (retries >= CHANNEL_MAX_RETRIES) {\n console.error(`${opts.logPrefix} SSE connection exhausted, reporting error and exiting`);\n try {\n await fetchWithTimeout(\n `${opts.tandemUrl}${API_CHANNEL_ERROR}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n error: opts.errorCode,\n message: `${opts.logPrefix} lost connection after ${CHANNEL_MAX_RETRIES} retries.`,\n }),\n },\n CHANNEL_ERROR_REPORT_TIMEOUT_MS,\n );\n } catch (reportErr) {\n console.error(\n `${opts.logPrefix} Could not report failure to server:`,\n describeFetchError(reportErr, API_CHANNEL_ERROR, CHANNEL_ERROR_REPORT_TIMEOUT_MS),\n );\n }\n opts.onExhaustion?.();\n process.exit(1);\n }\n\n // Exponential backoff: 2s, 4s, 8s, 16s, 30s (capped).\n const delay = Math.min(CHANNEL_RETRY_DELAY_MS * 2 ** (retries - 1), RETRY_MAX_DELAY_MS);\n console.error(\n `${opts.logPrefix} Retrying in ${delay}ms (attempt ${retries}/${CHANNEL_MAX_RETRIES})...`,\n );\n await new Promise((r) => setTimeout(r, delay));\n }\n }\n // Defensive: under normal exhaustion the catch above calls process.exit(1)\n // before we return here. Survives any future refactor that removes the\n // exit or makes it non-terminating (e.g. test shim).\n console.error(\n `${opts.logPrefix} Retry loop exited unexpectedly (retries=${retries}/${CHANNEL_MAX_RETRIES})`,\n );\n process.exit(1);\n}\n\nexport interface StreamCallbacks {\n onEventId: (id: string) => void;\n onStable?: () => void;\n}\n\n/**\n * Single-attempt SSE consumer. Performs one handshake, streams frames,\n * and returns / throws when the stream ends. Exported for tests that want\n * to exercise per-attempt behavior without driving the full retry loop.\n *\n * Production code should call `runEventConsumer` instead — it owns the\n * reconnect/backoff/exhaustion-report logic.\n */\nexport async function connectAndStreamOnce(\n opts: EventConsumerOptions,\n lastEventId: string | undefined,\n cb: StreamCallbacks,\n): Promise<void> {\n const onStable = cb.onStable ?? (() => {});\n const headers: Record<string, string> = { Accept: \"text/event-stream\" };\n if (lastEventId) headers[\"Last-Event-ID\"] = lastEventId;\n\n // Split handshake timeout from body lifetime. Using AbortSignal.timeout on\n // the fetch would kill the response body ReadableStream when the timeout\n // fires — every SSE stream would abort at CHANNEL_CONNECT_FETCH_TIMEOUT_MS,\n // making STABLE_CONNECTION_MS unreachable. A local AbortController cleared\n // in `finally` after the handshake settles means the body stream is no\n // longer governed by it.\n const connectCtrl = new AbortController();\n const connectTimer = setTimeout(\n () => connectCtrl.abort(new Error(\"handshake timeout\")),\n CHANNEL_CONNECT_FETCH_TIMEOUT_MS,\n );\n let res: Response;\n try {\n res = await authFetch(`${opts.tandemUrl}${API_EVENTS}`, {\n headers,\n signal: connectCtrl.signal,\n });\n } finally {\n clearTimeout(connectTimer);\n }\n if (!res.ok) throw new Error(`SSE endpoint returned ${res.status}`);\n if (!res.body) throw new Error(\"SSE endpoint returned no body\");\n\n // Stable-uptime reset: if the connection stays healthy for\n // STABLE_CONNECTION_MS, signal the caller to reset its retry budget.\n const stableTimer = setTimeout(onStable, STABLE_CONNECTION_MS);\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n // Inactivity watchdog. A healthy stream emits keepalive comments\n // periodically; if no bytes arrive for CHANNEL_SSE_INACTIVITY_TIMEOUT_MS,\n // cancel the reader. reader.cancel() resolves a pending read() with\n // {done: true} (does not reject), so we surface the cause via a flag.\n let lastActivityAt = Date.now();\n let inactivityTimedOut = false;\n const watchdog = setInterval(() => {\n if (Date.now() - lastActivityAt > CHANNEL_SSE_INACTIVITY_TIMEOUT_MS) {\n inactivityTimedOut = true;\n reader.cancel(new Error(\"SSE inactivity timeout\")).catch(() => {});\n }\n }, CHANNEL_SSE_INACTIVITY_TIMEOUT_MS / 4);\n\n let pendingAwareness: TandemEvent | null = null;\n\n function clearAwarenessNow(documentId?: string) {\n const p = fetchWithTimeout(\n `${opts.tandemUrl}${API_CHANNEL_AWARENESS}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n documentId: documentId ?? null,\n status: \"idle\",\n active: false,\n }),\n },\n CHANNEL_AWARENESS_FETCH_TIMEOUT_MS,\n ).catch((err) => {\n console.error(\n `${opts.logPrefix} Awareness clear failed:`,\n describeFetchError(\n err,\n `${API_CHANNEL_AWARENESS} clear`,\n CHANNEL_AWARENESS_FETCH_TIMEOUT_MS,\n ),\n );\n });\n trackAwareness(p);\n }\n\n function flushAwareness() {\n if (!pendingAwareness) return;\n const event = pendingAwareness;\n pendingAwareness = null;\n // Only update when the event has a real documentId. A doc-less event\n // (e.g. chat:message) must NOT wipe the last-known docId —\n // flushFinalAwareness needs a non-null id to send the shutdown clear.\n if (event.documentId) shutdownTimers.lastDocumentId = event.documentId;\n const p = fetchWithTimeout(\n `${opts.tandemUrl}${API_CHANNEL_AWARENESS}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n documentId: event.documentId,\n status: `processing: ${event.type}`,\n active: true,\n }),\n },\n CHANNEL_AWARENESS_FETCH_TIMEOUT_MS,\n ).catch((err) => {\n console.error(\n `${opts.logPrefix} Awareness update failed:`,\n describeFetchError(\n err,\n `${API_CHANNEL_AWARENESS} update`,\n CHANNEL_AWARENESS_FETCH_TIMEOUT_MS,\n ),\n );\n });\n trackAwareness(p);\n\n // Auto-clear after timeout so the indicator doesn't stick.\n if (shutdownTimers.clearAwarenessTimer) clearTimeout(shutdownTimers.clearAwarenessTimer);\n shutdownTimers.clearAwarenessTimer = setTimeout(\n () => clearAwarenessNow(event.documentId),\n AWARENESS_CLEAR_MS,\n );\n }\n\n function scheduleAwareness(event: TandemEvent) {\n pendingAwareness = event;\n if (shutdownTimers.awarenessTimer) clearTimeout(shutdownTimers.awarenessTimer);\n shutdownTimers.awarenessTimer = setTimeout(flushAwareness, AWARENESS_DEBOUNCE_MS);\n }\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n if (inactivityTimedOut) throw new Error(\"SSE inactivity timeout\");\n throw new Error(\"SSE stream ended\");\n }\n lastActivityAt = Date.now();\n\n buffer += decoder.decode(value, { stream: true });\n\n if (buffer.length > CHANNEL_MAX_SSE_BUFFER_BYTES) {\n throw new Error(\n `SSE buffer exceeded ${CHANNEL_MAX_SSE_BUFFER_BYTES} bytes without a frame boundary`,\n );\n }\n\n let boundary: number;\n while ((boundary = buffer.indexOf(\"\\n\\n\")) !== -1) {\n const frame = buffer.slice(0, boundary);\n buffer = buffer.slice(boundary + 2);\n\n if (frame.startsWith(\":\")) continue;\n\n let eventId: string | undefined;\n let data: string | undefined;\n\n for (const line of frame.split(\"\\n\")) {\n if (line.startsWith(\"id: \")) eventId = line.slice(4);\n else if (line.startsWith(\"data: \")) data = line.slice(6);\n }\n\n if (!data) continue;\n\n let raw: unknown;\n try {\n raw = JSON.parse(data);\n } catch (err) {\n console.error(\n `${opts.logPrefix} SSE JSON parse failed (eventId=${eventId ?? \"none\"}, len=${\n data.length\n }): ${err instanceof Error ? err.message : err}. Tail:`,\n data.slice(Math.max(0, data.length - 200)),\n );\n // Permanently unparseable — advance past it to prevent infinite\n // re-delivery on reconnect.\n if (eventId) cb.onEventId(eventId);\n continue;\n }\n\n const event = parseTandemEvent(raw);\n if (!event) {\n console.error(\n `${opts.logPrefix} SSE event failed validation (eventId=${\n eventId ?? \"none\"\n }): shape mismatch`,\n );\n if (eventId) cb.onEventId(eventId);\n continue;\n }\n\n // Solo mode suppression: drop non-chat events when mode is \"solo\".\n if (event.type !== \"chat:message\") {\n refreshMode(opts.tandemUrl, opts.logPrefix); // fire-and-forget\n if (getModeSync() === \"solo\") {\n console.error(`${opts.logPrefix} Solo mode: suppressed ${event.type} event`);\n if (eventId) cb.onEventId(eventId);\n continue;\n }\n }\n\n // Deliver the event. False-checkpoint guard: `cb.onEventId` MUST\n // stay below this so lastEventId never advances past an event that\n // didn't reach the consumer's delivery surface.\n try {\n await opts.onEvent(event, eventId);\n } catch (err) {\n console.error(`${opts.logPrefix} onEvent failed (transport broken?):`, err);\n throw err;\n }\n\n if (eventId) cb.onEventId(eventId);\n scheduleAwareness(event);\n }\n }\n } finally {\n // Single source of truth for timer cleanup — every exit path (success,\n // throw, reader.cancel) runs through here so awareness/inactivity\n // timers can't leak across reconnects.\n clearTimeout(stableTimer);\n clearInterval(watchdog);\n if (shutdownTimers.awarenessTimer) clearTimeout(shutdownTimers.awarenessTimer);\n if (shutdownTimers.clearAwarenessTimer) clearTimeout(shutdownTimers.clearAwarenessTimer);\n shutdownTimers.awarenessTimer = null;\n shutdownTimers.clearAwarenessTimer = null;\n pendingAwareness = null;\n }\n}\n\n// --- Mode cache ---\n\ntype FetchModeResult = { ok: true; mode: TandemMode } | { ok: false; reason: string };\n\n/** Fetch + validate /api/mode. Callers apply their own failure policy. */\nasync function fetchMode(tandemUrl: string): Promise<FetchModeResult> {\n try {\n const res = await fetchWithTimeout(\n `${tandemUrl}${API_MODE}`,\n {},\n CHANNEL_MODE_FETCH_TIMEOUT_MS,\n );\n if (!res.ok) return { ok: false, reason: `status ${res.status}` };\n const body = (await res.json()) as { mode?: unknown };\n const parsed = TandemModeSchema.safeParse(body.mode);\n if (!parsed.success) return { ok: false, reason: `invalid mode ${JSON.stringify(body.mode)}` };\n return { ok: true, mode: parsed.data };\n } catch (err) {\n return { ok: false, reason: describeFetchError(err, API_MODE, CHANNEL_MODE_FETCH_TIMEOUT_MS) };\n }\n}\n\n/**\n * Get the current collaboration mode, with a 2s TTL cache.\n *\n * **Stale-preserving** on any failure: once a real mode has been fetched\n * successfully, a transient `/api/mode` failure (network error or non-OK)\n * NEVER changes the cached mode — `cachedMode` is left untouched and the last\n * known value is returned. The mode only ever changes when the server reports\n * a new mode, i.e. when the user actually toggles Solo/Tandem.\n *\n * `cachedModeAt === 0` is the cold-start sentinel (no successful fetch ever).\n * In that one case — and only that case — a failure falls back to the\n * documented `TANDEM_MODE_DEFAULT`. After the first success, `cachedModeAt`\n * is non-zero forever, so failures can never revert to the cold-start default.\n *\n * On failure, `cachedModeAt` is NOT updated, so the next call retries\n * immediately rather than waiting out MODE_CACHE_TTL_MS.\n */\nexport async function getCachedMode(\n tandemUrl: string,\n logPrefix = \"[Tandem]\",\n): Promise<TandemMode> {\n const now = Date.now();\n if (now - cachedModeAt < MODE_CACHE_TTL_MS && cachedModeAt !== 0) return cachedMode;\n\n const result = await fetchMode(tandemUrl);\n if (!result.ok) {\n // Stale-preserving: keep the last known mode. A failure must never\n // overwrite a successfully-observed mode. Only on a genuine cold start\n // (no successful fetch ever, cachedModeAt === 0) do we fall back to the\n // documented default. cachedModeAt is left untouched so the next call\n // retries immediately instead of serving a stale cache window.\n if (cachedModeAt !== 0) {\n console.error(\n `${logPrefix} Mode check failed (${result.reason}), preserving last known mode '${cachedMode}'`,\n );\n return cachedMode;\n }\n console.error(\n `${logPrefix} Mode check failed (${result.reason}), no prior mode — using cold-start default '${TANDEM_MODE_DEFAULT}'`,\n );\n cachedMode = TANDEM_MODE_DEFAULT; // propagate cold-start default to hot path; do NOT update cachedModeAt\n return TANDEM_MODE_DEFAULT;\n }\n cachedMode = result.mode;\n cachedModeAt = now;\n return cachedMode;\n}\n\n/** Sync reader — always returns the last known mode. Use this on the hot path. */\nexport function getModeSync(): TandemMode {\n return cachedMode;\n}\n\n/**\n * Background refresh — fire-and-forget, deduplicated.\n *\n * Leaves `cachedMode` UNCHANGED on failure (stale-preferred). getCachedMode\n * fails closed at startup because no baseline exists; refreshMode prefers\n * stale because flipping mid-session would randomly suppress events and\n * surprise the user.\n */\nfunction refreshMode(tandemUrl: string, logPrefix: string): void {\n if (_modeRefreshInFlight) return;\n const now = Date.now();\n if (now - cachedModeAt < MODE_CACHE_TTL_MS) return;\n // Rate-limit retries after a failure so a server returning 500 quickly\n // (or hanging up to MODE_FETCH_TIMEOUT_MS) doesn't spawn a new fetch on\n // every hot-path event.\n if (now - cachedModeFailedAt < MODE_CACHE_TTL_MS) return;\n\n // Fire-and-forget. fetchMode() converts network/parse errors into\n // { ok: false }, and the inner try/finally clears `_modeRefreshInFlight` on\n // both success and thrown rejection — so today, the outer .catch is\n // unreachable. It exists as a belt-and-suspenders guard.\n _modeRefreshInFlight = (async () => {\n try {\n const result = await fetchMode(tandemUrl);\n if (result.ok) {\n cachedMode = result.mode;\n cachedModeAt = Date.now();\n cachedModeFailedAt = 0;\n } else {\n cachedModeFailedAt = Date.now();\n console.error(\n `${logPrefix} Background mode refresh failed (${result.reason}), keeping cached`,\n );\n }\n } finally {\n _modeRefreshInFlight = null;\n }\n })().catch((err) => {\n console.error(`${logPrefix} refreshMode unexpected error:`, err);\n cachedModeFailedAt = Date.now();\n });\n}\n\n// --- Shutdown drain (used by the monitor's signal handler) ---\n\n/**\n * Drain any in-flight awareness POSTs and send a final shutdown\n * \"active: false\" so the server's last-observed awareness state is clean.\n *\n * Returns true on success (or no-op when no docId was ever scheduled),\n * false when the shutdown POST itself fails.\n */\nexport async function flushFinalAwareness(\n tandemUrl: string,\n logPrefix = \"[Tandem]\",\n): Promise<boolean> {\n if (shutdownTimers.awarenessTimer) clearTimeout(shutdownTimers.awarenessTimer);\n if (shutdownTimers.clearAwarenessTimer) clearTimeout(shutdownTimers.clearAwarenessTimer);\n if (outstandingAwareness.size > 0) {\n await Promise.allSettled(outstandingAwareness);\n }\n // If no awareness was ever scheduled for a document, skip the POST —\n // sending {documentId: null} is ambiguous and the server may reject it.\n if (shutdownTimers.lastDocumentId === null) return true;\n try {\n const res = await fetchWithTimeout(\n `${tandemUrl}${API_CHANNEL_AWARENESS}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n documentId: shutdownTimers.lastDocumentId,\n status: \"idle\",\n active: false,\n }),\n },\n CHANNEL_AWARENESS_FETCH_TIMEOUT_MS,\n );\n if (!res.ok) {\n console.error(`${logPrefix} Shutdown awareness clear returned ${res.status}`);\n return false;\n }\n return true;\n } catch (err) {\n console.error(\n `${logPrefix} Shutdown awareness clear failed:`,\n describeFetchError(\n err,\n `${API_CHANNEL_AWARENESS} shutdown`,\n CHANNEL_AWARENESS_FETCH_TIMEOUT_MS,\n ),\n );\n return false;\n }\n}\n\n// --- Test-only helpers ---\n\n/** Testing-only. Resets module-level state so tests within a single file\n * don't contaminate each other. DO NOT call from production code. */\nexport function _resetSseConsumerStateForTests(): void {\n cachedMode = TANDEM_MODE_DEFAULT;\n cachedModeAt = 0;\n cachedModeFailedAt = 0;\n _modeRefreshInFlight = null;\n shutdownTimers.awarenessTimer = null;\n shutdownTimers.clearAwarenessTimer = null;\n shutdownTimers.lastDocumentId = null;\n outstandingAwareness.clear();\n}\n\n/** Testing-only — seeds the lastDocumentId that shutdown reads. */\nexport function _setLastDocumentIdForTests(id: string | null): void {\n shutdownTimers.lastDocumentId = id;\n}\n\n/** Testing-only — reads the last document id that shutdown would send. */\nexport function _getLastDocumentIdForTests(): string | null {\n return shutdownTimers.lastDocumentId;\n}\n\n/** Testing-only — seeds an outstanding awareness POST so the shutdown test\n * can assert the drain-before-exit behavior. */\nexport function _addOutstandingAwarenessForTests(p: Promise<unknown>): void {\n trackAwareness(p);\n}\n","/**\n * SSE event bridge: connects to Tandem server's /api/events endpoint and\n * pushes received events to Claude Code as channel notifications.\n *\n * All retry / SSE-frame / awareness / mode-cache logic lives in the shared\n * `src/shared/sse-consumer.ts` module (extracted in #282). This file is the\n * thin MCP-aware adapter that owns the delivery callback.\n */\n\nimport type { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { formatEventContent, formatEventMeta } from \"../shared/events/types.js\";\nimport { runEventConsumer } from \"../shared/sse-consumer.js\";\nimport { CHANNEL_CONNECT_FAILED } from \"../shared/types.js\";\n\n/**\n * Stdio-mode SSE bridge. New push-path work should target src/monitor/.\n * This path remains active for stdio-mode Claude Code connections.\n */\nexport async function startEventBridge(mcp: Server, tandemUrl: string): Promise<void> {\n return runEventConsumer({\n tandemUrl,\n logPrefix: \"[Channel]\",\n errorCode: CHANNEL_CONNECT_FAILED,\n onEvent: (event) =>\n mcp.notification({\n method: \"notifications/claude/channel\",\n params: {\n content: formatEventContent(event),\n meta: formatEventMeta(event),\n },\n }),\n });\n}\n","/**\n * Tandem Channel Shim — core runtime, shared by:\n * - src/channel/index.ts (standalone binary, used by the Desktop sidecar)\n * - src/cli/channel.ts (npm-delivered entry for the plugin `tandem-channel`)\n *\n * Bridges Tandem's SSE event stream → Claude Code channel notifications,\n * and exposes a `tandem_reply` tool for Claude to respond to chat messages.\n *\n * Uses the low-level MCP `Server` class (not `McpServer`) as required by\n * the Channels API spec.\n */\n\nimport { createConnection } from \"node:net\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { CallToolRequestSchema, ListToolsRequestSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\nimport { API_CHANNEL_PERMISSION, API_CHANNEL_REPLY } from \"../shared/api-paths.js\";\nimport { redirectConsoleToStderr, resolveTandemUrl } from \"../shared/cli-runtime.js\";\nimport {\n CHANNEL_PERMISSION_FETCH_TIMEOUT_MS,\n CHANNEL_REPLY_FETCH_TIMEOUT_MS,\n DEFAULT_MCP_PORT,\n} from \"../shared/constants.js\";\nimport {\n describeFetchError,\n fetchWithTimeout,\n isAbortOrTimeoutError,\n} from \"../shared/fetch-with-timeout.js\";\nimport { startEventBridge } from \"./event-bridge.js\";\n\nexport interface RunChannelOptions {\n /** Skip the non-fatal reachability probe. The CLI wrapper runs a strict\n * preflight upstream and we don't want to double-log \"server not reachable\"\n * noise. Defaults to false. */\n skipReachabilityLog?: boolean;\n}\n\nexport async function runChannel(opts: RunChannelOptions = {}): Promise<void> {\n redirectConsoleToStderr();\n\n const tandemUrl = resolveTandemUrl();\n\n const mcp = new Server(\n { name: \"tandem-channel\", version: \"0.1.0\" },\n {\n capabilities: {\n experimental: {\n \"claude/channel\": {},\n \"claude/channel/permission\": {},\n },\n tools: {},\n },\n instructions: [\n 'Events from Tandem arrive as <channel source=\"tandem-channel\" event_type=\"...\" document_id=\"...\">.',\n \"These are real-time push notifications of user actions in the collaborative document editor.\",\n \"Event types: annotation:created, annotation:accepted, annotation:dismissed, annotation:reply,\",\n \"chat:message, document:opened, document:closed, document:switched.\",\n \"Chat messages may include a 'selection' field with buffered selection context.\",\n \"Use your tandem MCP tools (tandem_getTextContent, tandem_comment, tandem_edit, etc.) to act on them.\",\n \"Reply to chat messages using tandem_reply. Pass document_id from the tag attributes.\",\n \"Do not reply to non-chat events — just act on them using tools.\",\n \"If you haven't received channel notifications recently, call tandem_checkInbox as a fallback.\",\n ].join(\" \"),\n },\n );\n\n mcp.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [\n {\n name: \"tandem_reply\",\n description: \"Reply to a chat message in Tandem\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n text: { type: \"string\", description: \"The reply message\" },\n documentId: {\n type: \"string\",\n description: \"Document ID from the channel event (optional)\",\n },\n replyTo: {\n type: \"string\",\n description: \"Message ID being replied to (optional)\",\n },\n },\n required: [\"text\"],\n },\n },\n ],\n }));\n\n mcp.setRequestHandler(CallToolRequestSchema, async (req) => {\n if (req.params.name === \"tandem_reply\") {\n const args = req.params.arguments as Record<string, unknown>;\n try {\n const res = await fetchWithTimeout(\n `${tandemUrl}${API_CHANNEL_REPLY}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(args),\n },\n CHANNEL_REPLY_FETCH_TIMEOUT_MS,\n );\n let data: unknown;\n try {\n data = await res.json();\n } catch (parseErr) {\n // Re-throw timeout/abort errors so they surface as structured\n // failures to Claude. AbortSignal.timeout fires DURING `res.json()`\n // (headers landed but body hung); without this re-throw, the bare\n // catch swallows AbortError and reports a fake-success \"Non-JSON\n // response\" payload — exactly the silent-failure pattern #364\n // exists to prevent.\n if (isAbortOrTimeoutError(parseErr)) throw parseErr;\n data = { message: \"Non-JSON response\" };\n }\n if (!res.ok) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Reply failed (${res.status}): ${JSON.stringify(data)}`,\n },\n ],\n isError: true,\n };\n }\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data) }] };\n } catch (err) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Failed to send reply: ${describeFetchError(\n err,\n API_CHANNEL_REPLY,\n CHANNEL_REPLY_FETCH_TIMEOUT_MS,\n )}`,\n },\n ],\n isError: true,\n };\n }\n }\n throw new Error(`Unknown tool: ${req.params.name}`);\n });\n\n const PermissionRequestSchema = z.object({\n method: z.literal(\"notifications/claude/channel/permission_request\"),\n params: z.object({\n request_id: z.string(),\n tool_name: z.string(),\n description: z.string(),\n input_preview: z.string(),\n }),\n });\n\n mcp.setNotificationHandler(PermissionRequestSchema, async ({ params }) => {\n try {\n const res = await fetchWithTimeout(\n `${tandemUrl}${API_CHANNEL_PERMISSION}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n requestId: params.request_id,\n toolName: params.tool_name,\n description: params.description,\n inputPreview: params.input_preview,\n }),\n },\n CHANNEL_PERMISSION_FETCH_TIMEOUT_MS,\n );\n if (!res.ok) {\n console.error(\n `[Channel] Permission relay got HTTP ${res.status} — browser may not see prompt`,\n );\n }\n } catch (err) {\n console.error(\n \"[Channel] Failed to forward permission request:\",\n describeFetchError(err, API_CHANNEL_PERMISSION, CHANNEL_PERMISSION_FETCH_TIMEOUT_MS),\n );\n }\n });\n\n console.error(`[Channel] Tandem channel shim starting (server: ${tandemUrl})`);\n\n if (!opts.skipReachabilityLog) {\n const reachable = await checkServerReachable(tandemUrl);\n if (!reachable) {\n console.error(`[Channel] Cannot reach Tandem server at ${tandemUrl}`);\n console.error(\"[Channel] Start it with: tandem start\");\n // Continue anyway — the event bridge will retry, and the server may start later\n }\n }\n\n const transport = new StdioServerTransport();\n await mcp.connect(transport);\n console.error(\"[Channel] Connected to Claude Code via stdio\");\n\n startEventBridge(mcp, tandemUrl).catch((err) => {\n console.error(\"[Channel] Event bridge failed unexpectedly:\", err);\n process.exit(1);\n });\n}\n\nasync function checkServerReachable(url: string, timeoutMs = 2000): Promise<boolean> {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n console.error(\n `[Channel] Invalid TANDEM_URL: \"${url}\" — expected format: http://127.0.0.1:3479`,\n );\n return false;\n }\n const port = parseInt(parsed.port || String(DEFAULT_MCP_PORT), 10);\n return new Promise((resolve) => {\n const socket = createConnection({ port, host: parsed.hostname }, () => {\n socket.destroy();\n resolve(true);\n });\n socket.setTimeout(timeoutMs);\n socket.on(\"timeout\", () => {\n socket.destroy();\n resolve(false);\n });\n socket.on(\"error\", (err) => {\n console.error(`[Channel] Server probe failed: ${err.message}`);\n socket.destroy();\n resolve(false);\n });\n });\n}\n","/**\n * Tandem channel subcommand — npm-delivered entry for the plugin\n * `tandem-channel` MCP server. Runs the unified preflight, then hands off to\n * the shared channel shim runtime in src/channel/run.ts.\n */\n\nimport { runChannel } from \"../channel/run.js\";\nimport { ensureTandemServer } from \"./preflight.js\";\n\nexport async function runChannelCli(): Promise<void> {\n await ensureTandemServer();\n await runChannel({ skipReachabilityLog: true });\n}\n","import envPaths from \"env-paths\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { TOKEN_FILE_NAME } from \"../constants.js\";\n\nexport function getTokenFilePath(): string {\n return path.join(envPaths(\"tandem\", { suffix: \"\" }).data, TOKEN_FILE_NAME);\n}\n\nexport async function readTokenFromFile(): Promise<string | null> {\n const filePath = getTokenFilePath();\n try {\n const content = await fs.promises.readFile(filePath, \"utf8\");\n // Remediate insecure permissions if a previous chmod failed (e.g., process crashed).\n if (process.platform !== \"win32\") {\n try {\n const stat = await fs.promises.stat(filePath);\n if ((stat.mode & 0o077) !== 0) {\n console.error(\"[tandem] auth token file has insecure permissions; attempting chmod 0600\");\n await fs.promises.chmod(filePath, 0o600);\n }\n } catch {\n // Non-fatal: stat/chmod failure doesn't invalidate the token we already read\n }\n }\n const trimmed = content.trim();\n return trimmed.length > 0 ? trimmed : null;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { promises as fsPromises } from \"node:fs\";\nimport path from \"node:path\";\nimport { API_ROTATE_TOKEN } from \"../shared/api-paths.js\";\nimport { getTokenFilePath, readTokenFromFile } from \"../shared/auth/token-file.js\";\nimport { resolveAuthTokenCandidate, resolveTandemUrl } from \"../shared/cli-runtime.js\";\nimport { applyConfigWithToken } from \"./setup.js\";\n\n/** SHA-256 fingerprint — first 8 hex chars. Never logs the full token value. */\nfunction fingerprint(token: string): string {\n return createHash(\"sha256\").update(token, \"utf8\").digest(\"hex\").slice(0, 8);\n}\n\nfunction generateToken(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\nexport async function rotateToken(): Promise<void> {\n console.error(\"\\n[tandem] Rotating auth token...\\n\");\n\n // Refuse to rotate when token comes from env — Tauri injects TANDEM_AUTH_TOKEN\n // before sidecar spawn, and Claude Code's plugin host injects\n // CLAUDE_PLUGIN_OPTION_AUTH_TOKEN from userConfig. In either case we have no\n // way to update the launcher; rotating the file would desync with what's\n // re-injected on the next launch.\n const { source: envAuthSource } = resolveAuthTokenCandidate();\n if (\n envAuthSource === \"TANDEM_AUTH_TOKEN\" ||\n envAuthSource === \"CLAUDE_PLUGIN_OPTION_AUTH_TOKEN\"\n ) {\n console.error(\n `[tandem] Error: ${envAuthSource} is set in the environment.\\n` +\n \" Token rotation is not supported in env-token mode (used by Tauri\\n\" +\n \" and Claude Code's plugin host). Unset the variable and let Tandem\\n\" +\n \" manage the token file, or rotate via the launcher's token management.\",\n );\n process.exit(1);\n }\n\n const oldToken = await readTokenFromFile();\n if (!oldToken) {\n console.error(\n \"[tandem] Error: no token file found. Run `tandem setup` first to initialize the token.\",\n );\n process.exit(1);\n }\n\n // writeTokenToFile uses O_EXCL; bypass it here — rotation is an intentional overwrite.\n // Use atomic write: write to a temp file first, then rename() into place.\n // rename() is atomic on the same filesystem — power-loss mid-write cannot leave an empty file.\n const newToken = generateToken();\n const tokenPath = getTokenFilePath();\n const dir = path.dirname(tokenPath);\n const tmpPath = path.join(dir, `.auth-token-tmp-${randomBytes(4).toString(\"hex\")}`);\n try {\n await fsPromises.writeFile(tmpPath, newToken, { encoding: \"utf8\", mode: 0o600 });\n await fsPromises.rename(tmpPath, tokenPath);\n } catch (err) {\n await fsPromises.unlink(tmpPath).catch(() => {});\n throw err;\n }\n\n const serverUrl = resolveTandemUrl();\n\n // Three distinct outcomes:\n // graceWindowActive = true → server accepted the rotation; grace window is live\n // serverRejected = true → server reachable but returned non-2xx\n // (neither) → fetch threw; server was not running\n let graceWindowActive = false;\n let serverRejected = false;\n let serverRejectedStatus = 0;\n try {\n const resp = await fetch(`${serverUrl}${API_ROTATE_TOKEN}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${oldToken}`,\n },\n body: JSON.stringify({}),\n signal: AbortSignal.timeout(5000),\n });\n if (resp.ok) {\n graceWindowActive = true;\n } else {\n serverRejected = true;\n serverRejectedStatus = resp.status;\n }\n } catch {\n console.error(\n \"[tandem] Warning: server is not reachable. The new token is written to disk.\\n\" +\n \" Restart the server to activate the grace window; reconnect Claude Code after.\",\n );\n }\n\n let updatedCount = 0;\n let configErrors: string[] = [];\n try {\n const result = await applyConfigWithToken(newToken);\n updatedCount = result.updated;\n configErrors = result.errors;\n } catch (err) {\n console.error(\n `[tandem] Warning: failed to update MCP configs: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // TODO(v0.8.1): After rotation, re-walk Cowork workspaces to rewrite\n // env.TANDEM_AUTH_TOKEN so post-rotation Cowork sessions don't 401\n // (security invariant §6 — silent-failure H1). The Tauri IPC dynamic import\n // approach is inert here: this CLI runs as a Node subprocess with no WebView,\n // so `@tauri-apps/api/core`'s `invoke()` has no bridge to Rust. The fix is\n // an HTTP bridge — add a POST /api/cowork-apply-token endpoint in the server\n // (guarded by the auth middleware) and call it from here after the server\n // accepts the rotation.\n\n if (serverRejected) {\n // Configs now reference the new token but the server still holds the old one.\n // Print a strong warning — do NOT print \"Rotated auth token\" as that implies success.\n console.error(\n `[tandem] WARNING: server rejected the rotation request (status: ${serverRejectedStatus}).`,\n );\n if (updatedCount > 0) {\n console.error(\n ` ${updatedCount} config file(s) updated to the new token, but the server still\\n` +\n \" holds the old token. Restart the server to complete rotation.\",\n );\n }\n console.error(` Old fingerprint: ${fingerprint(oldToken)}`);\n console.error(` New fingerprint: ${fingerprint(newToken)}`);\n for (const e of configErrors) {\n console.error(` Warning: could not update config — ${e}`);\n }\n console.error(\"\");\n return;\n }\n\n console.error(\"[tandem] Rotated auth token.\");\n console.error(` Old fingerprint: ${fingerprint(oldToken)}`);\n console.error(` New fingerprint: ${fingerprint(newToken)}`);\n console.error(` Updated ${updatedCount} config file(s).`);\n\n for (const e of configErrors) {\n console.error(` Warning: could not update config — ${e}`);\n }\n\n if (graceWindowActive) {\n console.error(\n \" Old token remains valid for 60 seconds; reconnect Claude Code within that window.\",\n );\n } else {\n console.error(\n \" Server was not running — start it with `tandem` and reconnect Claude Code with the new token.\",\n );\n }\n\n console.error(\"\");\n}\n","import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst SERVER_DIST = resolve(__dirname, \"../server/index.js\");\n\nexport function runStart(): void {\n if (!existsSync(SERVER_DIST)) {\n console.error(`[Tandem] Server not found at ${SERVER_DIST}`);\n console.error(\"[Tandem] The installation may be corrupted. Try: npm install -g tandem-editor\");\n process.exit(1);\n }\n\n console.error(\n \"[Tandem] Browser distribution is deprecated; the Tauri desktop app is the primary form factor.\",\n );\n console.error(\"[Tandem] See https://github.com/bloknayrb/tandem/issues/477 for context.\");\n console.error(\"[Tandem] Starting server...\");\n\n const proc = spawn(\"node\", [SERVER_DIST], {\n stdio: \"inherit\",\n env: process.env,\n });\n\n proc.on(\"error\", (err) => {\n console.error(`[Tandem] Failed to start server: ${err.message}`);\n process.exit(1);\n });\n\n proc.on(\"exit\", (code) => {\n process.exit(code ?? 0);\n });\n\n // Forward signals — proc.kill() with no argument uses SIGTERM on Unix\n // and TerminateProcess on Windows (correct cross-platform behavior).\n // On Windows SIGTERM is not emitted by the OS, but SIGINT (Ctrl+C) works.\n // Both are listed for Unix compatibility.\n for (const sig of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.once(sig, () => proc.kill());\n }\n}\n","/**\n * Tandem CLI — entry point for the `tandem` global command.\n * Shebang is added by tsup banner at build time.\n *\n * Usage:\n * tandem Start the Tandem server and open the editor\n * tandem setup Register Tandem MCP tools with your AI client (Claude Code / Claude Desktop by default)\n * tandem setup --force Register even if no AI install is auto-detected\n * tandem --help Show this help\n * tandem --version Show version\n */\n\nimport updateNotifier from \"update-notifier\";\n\nprocess.once(\"uncaughtException\", (err: unknown) => {\n const msg = err instanceof Error ? (err.stack ?? err.message) : String(err);\n try {\n process.stderr.write(`[tandem cli] uncaughtException: ${msg}\\n`);\n } catch {\n /* EPIPE */\n }\n process.exit(1);\n});\nprocess.once(\"unhandledRejection\", (reason: unknown) => {\n const detail = reason instanceof Error ? reason.message : String(reason);\n process.stderr.write(`[tandem cli] unhandledRejection: ${detail}\\n`);\n process.exit(1);\n});\n\n// Injected at build time by tsup define; declared here for TypeScript\ndeclare const __TANDEM_VERSION__: string;\nconst version = typeof __TANDEM_VERSION__ !== \"undefined\" ? __TANDEM_VERSION__ : \"0.0.0-dev\";\n\nconst args = process.argv.slice(2);\n\n// Skip the update notifier for stdio subcommands — the output is machine-consumed\n// by Claude Desktop's plugin loader, and any incidental write risks corrupting\n// the MCP wire or producing log noise no human will ever read.\nconst isStdioMode = args[0] === \"mcp-stdio\" || args[0] === \"channel\";\nif (!isStdioMode) {\n updateNotifier({ pkg: { name: \"tandem-editor\", version } }).notify();\n}\n\nif (args.includes(\"--help\") || args.includes(\"-h\")) {\n console.log(`tandem v${version}\n\nUsage:\n tandem Start Tandem server and open the editor\n tandem setup Register MCP tools with your AI client (Claude Code / Claude Desktop by default)\n tandem setup --force Register to default paths regardless of detection\n tandem setup --with-channel-shim Also register the stdio channel shim (legacy opt-in)\n tandem rotate-token Rotate the auth token with a 60-second grace window\n tandem mcp-stdio Run as a stdio MCP server proxying to local HTTP\n (used by the plugin's Cowork bridge; requires\n tandem server running on the host)\n tandem channel Run the Tandem channel shim (stdio MCP)\n (used by the plugin's tandem-channel entry)\n tandem --version\n tandem --help\n`);\n process.exit(0);\n}\n\nif (args.includes(\"--version\") || args.includes(\"-v\")) {\n console.log(version);\n process.exit(0);\n}\n\ntry {\n if (args[0] === \"--uninstall-scrub\") {\n // Hidden subcommand invoked by the Tauri NSIS uninstaller hook. Walks\n // Cowork workspaces and removes Tandem plugin entries + firewall rules.\n // Runs inside the already-signed tandem.exe (security invariant §10 —\n // prevents binary-planting during uninstall).\n const { runUninstallScrub } = await import(\"./uninstall-scrub.js\");\n const exitCode = await runUninstallScrub();\n process.exit(exitCode);\n } else if (args[0] === \"setup\") {\n const { runSetup } = await import(\"./setup.js\");\n await runSetup({\n force: args.includes(\"--force\"),\n withChannelShim: args.includes(\"--with-channel-shim\"),\n });\n } else if (args[0] === \"mcp-stdio\") {\n const { runMcpStdio } = await import(\"./mcp-stdio.js\");\n await runMcpStdio();\n } else if (args[0] === \"channel\") {\n const { runChannelCli } = await import(\"./channel.js\");\n await runChannelCli();\n } else if (args[0] === \"rotate-token\") {\n const { rotateToken } = await import(\"./rotate-token.js\");\n await rotateToken();\n } else if (!args[0] || args[0] === \"start\") {\n const { runStart } = await import(\"./start.js\");\n runStart();\n } else {\n console.error(`Unknown command: ${args[0]}`);\n console.error(\"Run 'tandem --help' for usage.\");\n process.exit(1);\n }\n} catch (err) {\n console.error(`\\n[Tandem] Fatal error: ${err instanceof Error ? err.message : String(err)}`);\n console.error(\"If this persists, try reinstalling: npm install -g tandem-editor\\n\");\n process.exit(1);\n}\n"],"mappings":"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","names":["path","write","path","execFile","promisify","args","execFileAsync","path","join","randomUUID","readFileSync","open","dirname","join","resolve","fileURLToPath","path","backupPath","__dirname","existsSync","join","VALID_TOKEN_RE","detail","buffered","init_types","init_types","init_types","init_types","StdioServerTransport","z","args","resolve","envPaths","fs","path","fsPromises","path","existsSync","dirname","resolve","fileURLToPath","__dirname","runUninstallScrub","runSetup","runMcpStdio","runChannelCli","rotateToken","runStart"]}
|