{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport type { ChildProcess } from 'node:child_process'\nimport type { Socket } from 'node:net'\n\nimport { spawn } from 'node:child_process'\nimport { connect } from 'node:net'\nimport { join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport lockfile from 'proper-lockfile'\n\nimport {\n  HUB_BUSY_EXIT_CODE,\n  PACKAGE_VERSION,\n  log,\n  LOCK_PATH,\n  RUNTIME_DIR,\n  SOCK_PATH,\n  ensureDir\n} from './shared'\n\nlet activeSocket: Socket | null = null\nlet shuttingDown = false\n\nfunction closeActiveSocket() {\n  if (!activeSocket) return\n  try {\n    activeSocket.end()\n  } catch {\n    // ignore\n  }\n  try {\n    activeSocket.destroy()\n  } catch {\n    // ignore\n  }\n  activeSocket = null\n}\n\nfunction shutdownCli(reason: string) {\n  if (shuttingDown) return\n  shuttingDown = true\n  log.info(`${reason} Shutting down CLI.`)\n  closeActiveSocket()\n  process.exit(0)\n}\n\nprocess.on('SIGINT', () => shutdownCli('SIGINT received.'))\nprocess.on('SIGTERM', () => shutdownCli('SIGTERM received.'))\n\nconst HUB_STARTUP_TIMEOUT = 5000\nconst CONNECT_RETRY_DELAY = 200\nconst FAILED_RESTART_DELAY = 5000\nconst HERE = fileURLToPath(new URL('.', import.meta.url))\nconst HUB_ENTRY = join(HERE, 'hub.mjs')\n\nensureDir(RUNTIME_DIR)\n\nfunction bridge(socket: Socket): Promise<void> {\n  return new Promise((resolve) => {\n    log.info('Bridge established with Hub. Forwarding I/O.')\n    activeSocket = socket\n\n    const onStdinEnd = () => {\n      shutdownCli('Consumer stream ended.')\n    }\n    process.stdin.once('end', onStdinEnd)\n\n    const onSocketClose = () => {\n      log.warn('Connection to Hub lost. Attempting to reconnect...')\n      activeSocket = null\n      process.stdin.removeListener('end', onStdinEnd)\n      process.stdin.unpipe(socket)\n      socket.unpipe(process.stdout)\n      socket.removeAllListeners()\n      resolve()\n    }\n    socket.once('close', onSocketClose)\n    socket.on('error', (err) => log.warn({ err }, 'Socket error occurred.'))\n\n    // The `{ end: false }` option prevents stdin from closing the socket.\n    process.stdin.pipe(socket, { end: false }).pipe(process.stdout)\n  })\n}\n\nfunction connectHub(): Promise<Socket> {\n  return new Promise((resolve, reject) => {\n    const socket = connect(SOCK_PATH)\n    socket.on('connect', () => {\n      socket.removeAllListeners('error')\n      resolve(socket)\n    })\n    socket.on('error', reject)\n  })\n}\n\nasync function connectWithRetry(timeout: number): Promise<Socket> {\n  const startTime = Date.now()\n  let delay = CONNECT_RETRY_DELAY\n  while (Date.now() - startTime < timeout) {\n    try {\n      return await connectHub()\n    } catch (err: unknown) {\n      if (\n        err &&\n        typeof err === 'object' &&\n        'code' in err &&\n        (err.code === 'ENOENT' || err.code === 'ECONNREFUSED')\n      ) {\n        const remainingTime = timeout - (Date.now() - startTime)\n        const waitTime = Math.min(delay, remainingTime)\n        if (waitTime <= 0) break\n        await new Promise((r) => setTimeout(r, waitTime))\n        delay = Math.min(delay * 1.5, 1000)\n      } else {\n        throw err\n      }\n    }\n  }\n  throw new Error(`Failed to connect to Hub within ${timeout}ms.`)\n}\n\nfunction startHub(): ChildProcess {\n  log.info('Spawning new Hub process...')\n  return spawn(process.execPath, [HUB_ENTRY], {\n    detached: true,\n    stdio: 'ignore'\n  })\n}\n\nasync function waitForHubStart(child: ChildProcess): Promise<Socket> {\n  let foundExistingHub = false\n  child.once('exit', (code) => {\n    foundExistingHub = code === HUB_BUSY_EXIT_CODE\n  })\n\n  const socket = await connectWithRetry(HUB_STARTUP_TIMEOUT)\n  if (foundExistingHub) {\n    log.info('Spawned Hub found an existing lifecycle lock. Using the active Hub.')\n  }\n  child.unref()\n  return socket\n}\n\nasync function tryBecomeLeaderAndStartHub(): Promise<Socket> {\n  let releaseLock: (() => Promise<void>) | undefined\n  try {\n    releaseLock = await lockfile.lock(LOCK_PATH, {\n      retries: { retries: 5, factor: 1.2, minTimeout: 50 },\n      stale: 15000\n    })\n  } catch {\n    log.info('Another process is starting the Hub. Waiting...')\n    return connectWithRetry(HUB_STARTUP_TIMEOUT)\n  }\n\n  log.info('Acquired lock. Starting Hub as the leader...')\n  let child: ChildProcess | null = null\n  try {\n    try {\n      return await connectHub()\n    } catch {\n      // If the Hub is not running, we proceed to start it.\n      log.info('Hub not running. Proceeding to start it...')\n    }\n    child = startHub()\n    child.on('error', (err) => log.error({ err }, 'Hub child process error.'))\n    return await waitForHubStart(child)\n  } catch (err: unknown) {\n    log.error({ err }, 'Failed to start or connect to the Hub.')\n    if (child && !child.killed && child.exitCode === null && child.signalCode === null) {\n      log.warn(`Killing stale Hub process (PID: ${child.pid})...`)\n      child.kill('SIGTERM')\n    }\n    throw err\n  } finally {\n    if (releaseLock) await releaseLock()\n  }\n}\n\nasync function main() {\n  log.info({ version: PACKAGE_VERSION }, 'TemPad MCP Client starting...')\n\n  while (true) {\n    try {\n      const socket = await connectHub().catch(() => {\n        log.info('Hub not running. Initiating startup sequence...')\n        return tryBecomeLeaderAndStartHub()\n      })\n      await bridge(socket)\n      log.info('Bridge disconnected. Restarting connection process...')\n    } catch (err: unknown) {\n      log.error(\n        { err },\n        `Connection attempt failed. Retrying in ${FAILED_RESTART_DELAY / 1000}s...`\n      )\n      await new Promise((r) => setTimeout(r, FAILED_RESTART_DELAY))\n    }\n  }\n}\n\nmain()\n"],"mappings":"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"}