cross-agent-teams-mcp 0.5.5 → 0.5.7

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/README.md CHANGED
@@ -59,10 +59,39 @@ The daemon listens on `127.0.0.1:9100`. MCP endpoint is `http://127.0.0.1:9100/
59
59
  Common flags:
60
60
 
61
61
  - `--port <n>` (default `9100`)
62
+ - `--host <addr>` (default `127.0.0.1`)
63
+ - `--device <label>` (default: hostname-derived label)
62
64
  - `--token <t>` (Bearer auth)
63
65
  - `--db <path>` (default `~/.cross-agent-teams-mcp/data.db`)
64
66
  - `--pid-file <path>` (default `~/.cross-agent-teams-mcp/daemon.pid`)
65
67
 
68
+ ### Cross-host (LAN) collaboration
69
+
70
+ To let agents on another trusted machine use this daemon, bind the daemon to a LAN address and set a shared bearer token:
71
+
72
+ ```bash
73
+ npx -y cross-agent-teams-mcp@latest daemon \
74
+ --host 192.168.1.10 \
75
+ --port 9100 \
76
+ --token "$XATS_TOKEN" \
77
+ --device jt-laptop
78
+ ```
79
+
80
+ Then configure the peer host's Claude Code channel proxy to connect back to that daemon:
81
+
82
+ ```bash
83
+ npx -y -p cross-agent-teams-mcp@latest cross-agent-teams-channel \
84
+ --daemon-url http://192.168.1.10:9100/mcp \
85
+ --token "$XATS_TOKEN" \
86
+ --device gx-laptop
87
+ ```
88
+
89
+ Agents are namespaced by `(device, team, name)`. A bare `send_message({to_agent_name:"creator"})` resolves on the caller's own device; use `creator:gx-laptop` to address a same-team agent on another device. `list_agents` shows the `device` field so you can compose those addresses.
90
+
91
+ Security notes: non-loopback `--host` requires `--token`, and the token is shared by everyone who can use that daemon. Treat LAN exposure as trusted-team only; there is no per-agent authorization, device whitelist, or TLS in this mode.
92
+
93
+ Upgrade note: the first startup after this version auto-migrates the storage schema from `(team, name)` identity to `(device, team, name)` identity and backfills existing rows with the daemon's local `--device` label. Rolling back after registering multiple devices with the same `(team, name)` can violate the old uniqueness assumption.
94
+
66
95
  ## 2. Configure your agent's MCP client
67
96
 
68
97
  ### Recommended: `mcpsmgr` (shown in Quick start)
@@ -111,7 +140,7 @@ Then start Claude Code with the experimental channel loader so it subscribes to
111
140
  claude --dangerously-load-development-channels server:cross-agent-teams-channel
112
141
  ```
113
142
 
114
- The `server:<name>` suffix MUST equal the MCP server key in `.mcp.json` (`cross-agent-teams-channel` above). If your daemon uses `--token <t>`, add `"headers": { "Authorization": "Bearer <t>" }` to the HTTP entry.
143
+ The `server:<name>` suffix MUST equal the MCP server key in `.mcp.json` (`cross-agent-teams-channel` above). If your daemon uses `--token <t>`, add `"headers": { "Authorization": "Bearer <t>" }` to the HTTP entry, and add `--token <t>` to the channel proxy args.
115
144
 
116
145
  #### Codex CLI
117
146
 
@@ -222,6 +251,25 @@ Or with an explicit team:
222
251
 
223
252
  If you don't give a team, the agent uses your current working directory's basename — so you typically don't need to think about it.
224
253
 
254
+ ### Cross-device communication
255
+
256
+ When an agent connects to a daemon running on **another machine** (the Cross-host (LAN) section above), it must self-declare a `device` label at registration:
257
+
258
+ > Register me to xats as alice, device gx-laptop.
259
+
260
+ That label becomes part of the agent's identity — `(device, team, name)` — so two physical machines can both have a `creator` in `team=default` without collision. Once registered with a device, addressing across devices uses a `name:device` suffix:
261
+
262
+ > Send creator on jt-laptop a message: build is green.
263
+
264
+ This resolves to `creator:jt-laptop` and routes to that exact `(device=jt-laptop, team=…, name=creator)` row. Bare `creator` resolves on the caller's own device.
265
+
266
+ Notes:
267
+
268
+ - The daemon's local label is set with its own `--device` flag (defaults to the daemon host's hostname normalized to lowercase + `-`). Local-loopback agents auto-fill that label and don't need to specify `device` on register.
269
+ - A remote register call that omits `device` is rejected with `device_required_from_remote`; the channel proxy's startup hint surfaces the configured device value so the agent prompt includes it verbatim.
270
+ - `list_agents` returns a `device` field on every entry so you can see which devices contribute to your team and pick the right `name:device` target.
271
+ - The channel proxy's `--device` flag (`cross-agent-teams-channel --device gx-laptop ...`) sets the device label for the proxy row and is propagated into the registration hint surfaced to the agent. Match it to whatever label you want your machine's agents to appear under.
272
+
225
273
  ### Talk to other agents
226
274
 
227
275
  Address by name, by team, or by role:
package/README.zh-CN.md CHANGED
@@ -59,10 +59,39 @@ daemon 默认监听 `127.0.0.1:9100`. MCP endpoint: `http://127.0.0.1:9100/mcp`
59
59
  常用参数:
60
60
 
61
61
  - `--port <n>` (默认 `9100`)
62
+ - `--host <addr>` (默认 `127.0.0.1`)
63
+ - `--device <label>` (默认从 hostname 派生)
62
64
  - `--token <t>` (Bearer 鉴权)
63
65
  - `--db <path>` (默认 `~/.cross-agent-teams-mcp/data.db`)
64
66
  - `--pid-file <path>` (默认 `~/.cross-agent-teams-mcp/daemon.pid`)
65
67
 
68
+ ### 跨主机 (LAN) 协作
69
+
70
+ 要让可信局域网里另一台机器上的 agent 使用这个 daemon, 把 daemon 绑定到 LAN 地址并设置共享 bearer token:
71
+
72
+ ```bash
73
+ npx -y cross-agent-teams-mcp@latest daemon \
74
+ --host 192.168.1.10 \
75
+ --port 9100 \
76
+ --token "$XATS_TOKEN" \
77
+ --device jt-laptop
78
+ ```
79
+
80
+ 然后在对端机器上让 Claude Code channel proxy 连回这个 daemon:
81
+
82
+ ```bash
83
+ npx -y -p cross-agent-teams-mcp@latest cross-agent-teams-channel \
84
+ --daemon-url http://192.168.1.10:9100/mcp \
85
+ --token "$XATS_TOKEN" \
86
+ --device gx-laptop
87
+ ```
88
+
89
+ agent 身份现在按 `(device, team, name)` 命名空间区分. 裸的 `send_message({to_agent_name:"creator"})` 会解析到调用者自己的 device; 要发给另一个 device 上同 team 的 agent, 用 `creator:gx-laptop`. `list_agents` 会显示 `device` 字段, 方便拼出这个地址.
90
+
91
+ 安全说明: 非 loopback 的 `--host` 必须带 `--token`, 并且这个 token 会被所有能使用该 daemon 的人共享. LAN 暴露只适合可信团队环境; 当前模式没有 per-agent 鉴权, device 白名单或 TLS.
92
+
93
+ 升级说明: 升级到这个版本后首次启动会自动迁移存储 schema, 把身份从 `(team, name)` 改为 `(device, team, name)`, 并用 daemon 本机的 `--device` 标签回填旧数据. 如果已经注册了多个 device 上相同 `(team, name)` 的 agent 再回滚, 可能违反旧版本的唯一性假设.
94
+
66
95
  ## 2. 在 agent 端配置 MCP client
67
96
 
68
97
  ### 推荐: `mcpsmgr` (快速开始里已经演示)
@@ -111,7 +140,7 @@ npx mcpsmgr add jtianling/cross-agent-teams-mcp -a claude-code --port 9300
111
140
  claude --dangerously-load-development-channels server:cross-agent-teams-channel
112
141
  ```
113
142
 
114
- `server:<name>` 后缀 **必须** 等于 `.mcp.json` 里的 MCP server key (上例中是 `cross-agent-teams-channel`). 如果 daemon 启动带了 `--token <t>`, 在 HTTP 条目里加 `"headers": { "Authorization": "Bearer <t>" }`.
143
+ `server:<name>` 后缀 **必须** 等于 `.mcp.json` 里的 MCP server key (上例中是 `cross-agent-teams-channel`). 如果 daemon 启动带了 `--token <t>`, 在 HTTP 条目里加 `"headers": { "Authorization": "Bearer <t>" }`, 并在 channel proxy args 里加 `--token <t>`.
115
144
 
116
145
  #### Codex CLI
117
146
 
@@ -222,6 +251,25 @@ agent 第一次连上 xats 时不会自动注册, 要等你开口. 直接说:
222
251
 
223
252
  不传 team 的话, agent 会用当前工作目录的 basename 作为默认 team — 一般情况下你不用操心.
224
253
 
254
+ ### 跨设备 (device) 通信
255
+
256
+ 当 agent 连的是**另一台机器**上的 daemon (见上方"跨主机 (LAN) 协作"), 它注册时必须自报一个 `device` 标签:
257
+
258
+ > Register me to xats as alice, device gx-laptop.
259
+
260
+ 这个 device 会进入 agent 的身份键 — `(device, team, name)` — 所以两台机器都可以有 `team=default` 下的 `creator`, 不会撞名. 注册带上 device 之后, 跨设备寻址用 `name:device` 后缀:
261
+
262
+ > Send creator on jt-laptop a message: build is green.
263
+
264
+ 这条会被解析成 `creator:jt-laptop`, 准确路由到 `(device=jt-laptop, team=…, name=creator)` 这一行. 裸名字 `creator` 默认解析到 caller 自己 device 上的同名 agent.
265
+
266
+ 要点:
267
+
268
+ - daemon 自己的 device 标签用 `--device` 设置 (不传则从 daemon 主机的 hostname 派生, 小写 + 非 `[a-z0-9_-]` 替换为 `-`). loopback 进来的 agent 注册时不必传 device, daemon 会自动填上这个值.
269
+ - 远端 (非 loopback) 连接的 agent 注册时**必须**带 device, 否则 daemon 拒绝并返回 `device_required_from_remote`; channel proxy 的 startup hint 会把已配置的 device 值直接写进引导文案, agent 照抄即可.
270
+ - `list_agents` 每条返回都有 `device` 字段, 方便你看清 team 里都有哪些设备贡献了哪些 agent, 进而拼对的 `name:device`.
271
+ - channel proxy 自己的 `--device` 标志 (`cross-agent-teams-channel --device gx-laptop ...`) 决定 proxy 行的 device 字段, 同一台机器上的 agent 一般用同一个 device 名.
272
+
225
273
  ### 跟其它 agent 对话
226
274
 
227
275
  按名字, 按 team, 按 role 都行:
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  interface CliArgs {
3
3
  daemonUrl: string;
4
+ token?: string;
5
+ device?: string;
4
6
  }
5
7
  declare class CliArgError extends Error {
6
8
  constructor(message: string);
7
9
  }
8
- declare function buildStartupHint(csid: string): {
10
+ declare function buildStartupHint(csid: string, device?: string): {
9
11
  content: string;
10
12
  meta: {
11
13
  source: string;
@@ -3,6 +3,7 @@
3
3
  // plugins/cross-agent-teams-channel/src/cli.ts
4
4
  import { randomUUID } from "crypto";
5
5
  import { realpathSync } from "fs";
6
+ import { hostname } from "os";
6
7
  import { fileURLToPath } from "url";
7
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
9
 
@@ -83,7 +84,10 @@ async function parseToolResult(resp) {
83
84
  }
84
85
  async function runRegistrationSequence(config) {
85
86
  const order = [];
86
- const transport = new StreamableHTTPClientTransport(new URL(config.daemonUrl));
87
+ const requestInit = config.token ? { headers: { Authorization: `Bearer ${config.token}` } } : void 0;
88
+ const transport = new StreamableHTTPClientTransport(new URL(config.daemonUrl), {
89
+ requestInit
90
+ });
87
91
  const client = new Client({ name: "cross-agent-teams-proxy", version: "0.1.0" });
88
92
  if (config.notificationHandler) {
89
93
  client.fallbackNotificationHandler = async (n) => {
@@ -93,21 +97,25 @@ async function runRegistrationSequence(config) {
93
97
  };
94
98
  }
95
99
  await client.connect(transport);
100
+ const registerArgs = {
101
+ agent_type: "custom",
102
+ agent_type_name: "cross-agent-teams-channel",
103
+ model: "proxy",
104
+ role: "__channel_proxy__",
105
+ name: `channel-proxy-${process.pid}`,
106
+ team: "default",
107
+ claude_ui_pid: findClaudeUiPid(),
108
+ delivery: {
109
+ kind: "claude-channel",
110
+ channel_session_id: config.channel_session_id
111
+ }
112
+ };
113
+ if (config.device !== void 0) {
114
+ registerArgs.device = config.device;
115
+ }
96
116
  const registerResp = await client.callTool({
97
117
  name: "register_agent",
98
- arguments: {
99
- agent_type: "custom",
100
- agent_type_name: "cross-agent-teams-channel",
101
- model: "proxy",
102
- role: "__channel_proxy__",
103
- name: `channel-proxy-${process.pid}`,
104
- team: "default",
105
- claude_ui_pid: findClaudeUiPid(),
106
- delivery: {
107
- kind: "claude-channel",
108
- channel_session_id: config.channel_session_id
109
- }
110
- }
118
+ arguments: registerArgs
111
119
  });
112
120
  order.push("register_agent");
113
121
  const regResult = await parseToolResult(registerResp);
@@ -222,11 +230,15 @@ var CliArgError = class extends Error {
222
230
  this.name = "CliArgError";
223
231
  }
224
232
  };
225
- function buildStartupHint(csid) {
233
+ function buildStartupHint(csid, device) {
234
+ const isCrossHost = device !== void 0;
235
+ const deviceClause = isCrossHost ? `, device: "${device}"` : "";
236
+ const deviceRegisterFragment = isCrossHost ? ` (this proxy is running with --device "${device}", so include device: "${device}" verbatim \u2014 the daemon enforces per-origin device validation, and on a remote daemon omitting it returns device_required_from_remote)` : "";
237
+ const ask = isCrossHost ? `'To join cross-agent-teams (xats) and message other agents, reply with: Register to xats \u2014 name: your-agent-name, team: your-team-name (optional; defaults to the current working directory basename), device: ${device} (cross-host setup \u2014 keep this value verbatim).'` : `'To join cross-agent-teams (xats) and message other agents, reply with: Register to xats \u2014 name: your-agent-name, team: your-team-name (optional; defaults to the current working directory basename).'`;
226
238
  const content = [
227
239
  `cross-agent-teams-mcp: your channel_session_id is ${csid}.`,
228
- `Do NOT register automatically. First ask the user (in English) to register this session so it can talk to other agents \u2014 use exactly this wording: 'To join cross-agent-teams (xats) and message other agents, reply with: Register to xats \u2014 name: your-agent-name, team: your-team-name (optional; defaults to the current working directory basename).'`,
229
- `Once the user provides a name (and optionally a team), call register_agent({agent_type: "claude-code", name: "<name from user>", team: "<team from user, omit if not provided>", ui_pid: $PPID, project_dir: "<current working directory>"}) \u2014 do NOT pass channel_session_id here; the daemon auto-binds via ui_pid.`,
240
+ `Do NOT register automatically. First ask the user (in English) to register this session so it can talk to other agents \u2014 use exactly this wording: ${ask}`,
241
+ `Once the user provides a name (and optionally a team), call register_agent({agent_type: "claude-code", name: "<name from user>", team: "<team from user, omit if not provided>"${deviceClause}, ui_pid: $PPID, project_dir: "<current working directory>"})${deviceRegisterFragment}. Do NOT pass channel_session_id here; the daemon auto-binds via ui_pid.`,
230
242
  `bind_channel({channel_session_id: "${csid}"}) is the low-level rebind tool for an already-registered Claude host that needs to switch to a fresh csid; it is NOT the primary registration path.`,
231
243
  `Do not use curl or another external HTTP client for Claude registration here \u2014 that would create a different MCP session, and follow-up tools in Claude Code could still see unknown_agent.`
232
244
  ].join(" ");
@@ -237,6 +249,8 @@ function buildStartupHint(csid) {
237
249
  }
238
250
  function parseCliArgs(argv, env = process.env) {
239
251
  let daemonUrl;
252
+ let token;
253
+ let explicitDevice;
240
254
  for (let i = 0; i < argv.length; i++) {
241
255
  const flag = argv[i];
242
256
  const next = argv[i + 1];
@@ -245,6 +259,14 @@ function parseCliArgs(argv, env = process.env) {
245
259
  daemonUrl = next;
246
260
  i++;
247
261
  break;
262
+ case "--token":
263
+ token = next;
264
+ i++;
265
+ break;
266
+ case "--device":
267
+ explicitDevice = next;
268
+ i++;
269
+ break;
248
270
  default:
249
271
  break;
250
272
  }
@@ -252,12 +274,16 @@ function parseCliArgs(argv, env = process.env) {
252
274
  if (!daemonUrl || daemonUrl.length === 0) {
253
275
  daemonUrl = env.CROSS_AGENT_TEAMS_MCP_DAEMON_URL;
254
276
  }
277
+ if (!token || token.length === 0) {
278
+ token = env.CROSS_AGENT_TEAMS_MCP_TOKEN;
279
+ }
255
280
  if (!daemonUrl || daemonUrl.length === 0) {
256
281
  throw new CliArgError(
257
282
  "missing --daemon-url (or CROSS_AGENT_TEAMS_MCP_DAEMON_URL env var)"
258
283
  );
259
284
  }
260
- return { daemonUrl };
285
+ const device = explicitDevice !== void 0 ? resolveDeviceLabel(explicitDevice) : void 0;
286
+ return { daemonUrl, token, device };
261
287
  }
262
288
  async function main(argv = process.argv.slice(2), env = process.env) {
263
289
  let args;
@@ -275,13 +301,15 @@ async function main(argv = process.argv.slice(2), env = process.env) {
275
301
  let registrationEverSucceeded = false;
276
302
  const controller = runReconnectingProxy({
277
303
  daemonUrl: args.daemonUrl,
304
+ token: args.token,
305
+ device: args.device,
278
306
  channel_session_id: csid,
279
307
  notificationHandler: (params) => {
280
308
  relayChannelWake(hostServer, params);
281
309
  },
282
310
  onSequenceComplete: () => {
283
311
  registrationEverSucceeded = true;
284
- const hint = buildStartupHint(csid);
312
+ const hint = buildStartupHint(csid, args.device);
285
313
  relayChannelWake(hostServer, hint);
286
314
  }
287
315
  });
@@ -327,6 +355,18 @@ function isEntry() {
327
355
  if (isEntry()) {
328
356
  void main();
329
357
  }
358
+ function resolveDeviceLabel(explicit) {
359
+ const raw = explicit ?? hostname();
360
+ if (raw.includes(":")) {
361
+ throw new CliArgError("invalid_device_label");
362
+ }
363
+ const normalized = raw.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-");
364
+ const label = normalized.length > 0 ? normalized : "local";
365
+ if (label.length > 64) {
366
+ throw new CliArgError("invalid_device_label");
367
+ }
368
+ return label;
369
+ }
330
370
  export {
331
371
  CliArgError,
332
372
  buildStartupHint,
@@ -1 +1 @@
1
- {"version":3,"sources":["../plugins/cross-agent-teams-channel/src/cli.ts","../plugins/cross-agent-teams-channel/src/proxy.ts","../plugins/cross-agent-teams-channel/src/daemon-client.ts","../plugins/cross-agent-teams-channel/src/find-claude-pid.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { randomUUID } from 'node:crypto'\nimport { realpathSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { createProxyServer, relayChannelWake } from './proxy.js'\nimport { runReconnectingProxy } from './daemon-client.js'\n\ninterface CliArgs {\n daemonUrl: string\n}\n\nexport class CliArgError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'CliArgError'\n }\n}\n\nexport function buildStartupHint(csid: string): { content: string; meta: { source: string; kind: string } } {\n const content = [\n `cross-agent-teams-mcp: your channel_session_id is ${csid}.`,\n `Do NOT register automatically. First ask the user (in English) to register this session so it can talk to other agents — use exactly this wording: 'To join cross-agent-teams (xats) and message other agents, reply with: Register to xats — name: your-agent-name, team: your-team-name (optional; defaults to the current working directory basename).'`,\n `Once the user provides a name (and optionally a team), call register_agent({agent_type: \"claude-code\", name: \"<name from user>\", team: \"<team from user, omit if not provided>\", ui_pid: $PPID, project_dir: \"<current working directory>\"}) — do NOT pass channel_session_id here; the daemon auto-binds via ui_pid.`,\n `bind_channel({channel_session_id: \"${csid}\"}) is the low-level rebind tool for an already-registered Claude host that needs to switch to a fresh csid; it is NOT the primary registration path.`,\n `Do not use curl or another external HTTP client for Claude registration here — that would create a different MCP session, and follow-up tools in Claude Code could still see unknown_agent.`\n ].join(' ')\n return {\n content,\n meta: { source: 'cross_agent_teams_mcp', kind: 'startup_bind_hint' }\n }\n}\n\nexport function parseCliArgs(argv: readonly string[], env: NodeJS.ProcessEnv = process.env): CliArgs {\n let daemonUrl: string | undefined\n\n for (let i = 0; i < argv.length; i++) {\n const flag = argv[i]\n const next = argv[i + 1]\n switch (flag) {\n case '--daemon-url':\n daemonUrl = next; i++; break\n default:\n // Ignore unknown flags for forward-compat (including legacy\n // --agent-team / --agent-name, which are no longer honored).\n break\n }\n }\n\n if (!daemonUrl || daemonUrl.length === 0) {\n daemonUrl = env.CROSS_AGENT_TEAMS_MCP_DAEMON_URL\n }\n\n if (!daemonUrl || daemonUrl.length === 0) {\n throw new CliArgError(\n 'missing --daemon-url (or CROSS_AGENT_TEAMS_MCP_DAEMON_URL env var)'\n )\n }\n return { daemonUrl }\n}\n\nexport async function main(\n argv: readonly string[] = process.argv.slice(2),\n env: NodeJS.ProcessEnv = process.env\n): Promise<void> {\n let args: CliArgs\n try {\n args = parseCliArgs(argv, env)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n process.stderr.write(`cross-agent-teams-proxy: ${msg}\\n`)\n process.exit(2)\n }\n\n // Fresh csid per startup — no persistence. Multi-instance safe.\n const csid = randomUUID()\n\n const hostServer = createProxyServer()\n const stdioTransport = new StdioServerTransport()\n\n let registrationEverSucceeded = false\n const controller = runReconnectingProxy({\n daemonUrl: args.daemonUrl,\n channel_session_id: csid,\n notificationHandler: (params) => {\n relayChannelWake(hostServer, params as { content: string; meta: Record<string, string> })\n },\n onSequenceComplete: () => {\n registrationEverSucceeded = true\n // Announce csid to Claude via host-facing channel notification so Claude\n // can call bind_channel({channel_session_id}) to bind its own agent row.\n const hint = buildStartupHint(csid)\n relayChannelWake(hostServer, hint)\n }\n })\n\n let stopped = false\n const shutdown = async (): Promise<void> => {\n if (stopped) return\n stopped = true\n try { await controller.stop() } catch { /* best-effort */ }\n try { await hostServer.close() } catch { /* best-effort */ }\n if (!registrationEverSucceeded) {\n process.stderr.write(`cross-agent-teams-proxy: daemon unreachable at ${args.daemonUrl}\\n`)\n process.exit(1)\n }\n process.exit(0)\n }\n\n stdioTransport.onclose = () => { void shutdown() }\n\n await hostServer.connect(stdioTransport)\n\n process.on('SIGTERM', () => { void shutdown() })\n process.on('SIGINT', () => { void shutdown() })\n}\n\n// Entry-point check. The naive `import.meta.url === \\`file://${process.argv[1]}\\``\n// breaks when launched via an npm `.bin` symlink (npx, `npm install -g`):\n// process.argv[1] is the symlink path, while import.meta.url is already\n// resolved. Compare realpath-resolved file paths instead.\nfunction isEntry(): boolean {\n try {\n const metaPath = fileURLToPath(import.meta.url)\n const argvPath = realpathSync(process.argv[1])\n return metaPath === argvPath\n } catch {\n return false\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-misused-promises\nif (isEntry()) {\n void main()\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nexport function createProxyServer(): McpServer {\n return new McpServer(\n { name: 'cross-agent-teams-channel', version: '0.1.0' },\n { capabilities: { experimental: { 'claude/channel': {} } } }\n )\n}\n\nexport interface ChannelWakeParams {\n content: string\n meta: Record<string, string>\n}\n\nexport function relayChannelWake(server: McpServer, params: ChannelWakeParams): void {\n try {\n const notif = {\n method: 'notifications/claude/channel',\n params: params as unknown as Record<string, unknown>\n }\n const p = (server.server.notification as (n: typeof notif) => Promise<void>)(notif)\n if (p && typeof p.catch === 'function') {\n p.catch(() => { /* host closed — drop silently */ })\n }\n } catch {\n // host transport closed or not yet connected — drop silently\n }\n}\n","import { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport { findClaudeUiPid } from './find-claude-pid.js'\n\nexport interface RegistrationConfig {\n daemonUrl: string\n channel_session_id: string\n backoffInitialMs?: number\n backoffMaxMs?: number\n notificationHandler?: (payload: unknown) => void\n}\n\nexport interface ReconnectingProxyConfig extends RegistrationConfig {\n onSequenceComplete?: (order: string[]) => void\n onDisconnect?: () => void\n healthCheckIntervalMs?: number\n}\n\nexport interface ReconnectingProxyController {\n stop(): Promise<void>\n}\n\nexport interface RegistrationSequenceResult {\n order: string[]\n lastSubscribeResult: unknown\n client: Client\n transport: StreamableHTTPClientTransport\n close: () => Promise<void>\n}\n\ntype ToolResult = Record<string, unknown>\n\nasync function parseToolResult(resp: unknown): Promise<ToolResult> {\n const r = resp as { content?: Array<{ text?: string }> }\n const text = r.content?.[0]?.text\n if (typeof text !== 'string') return {}\n try { return JSON.parse(text) as ToolResult } catch { return {} }\n}\n\nexport async function runRegistrationSequence(\n config: RegistrationConfig\n): Promise<RegistrationSequenceResult> {\n const order: string[] = []\n const transport = new StreamableHTTPClientTransport(new URL(config.daemonUrl))\n const client = new Client({ name: 'cross-agent-teams-proxy', version: '0.1.0' })\n\n if (config.notificationHandler) {\n client.fallbackNotificationHandler = async (n) => {\n if (n.method === 'notifications/channel_wake') {\n config.notificationHandler!(n.params)\n }\n }\n }\n\n await client.connect(transport)\n\n // 1. register_agent as proxy — identity keyed on pid, stable across reconnects\n // so the (team, name) ON CONFLICT upsert reuses the same row instead of spamming new rows\n const registerResp = await client.callTool({\n name: 'register_agent',\n arguments: {\n agent_type: 'custom',\n agent_type_name: 'cross-agent-teams-channel',\n model: 'proxy',\n role: '__channel_proxy__',\n name: `channel-proxy-${process.pid}`,\n team: 'default',\n claude_ui_pid: findClaudeUiPid(),\n delivery: {\n kind: 'claude-channel',\n channel_session_id: config.channel_session_id,\n },\n }\n })\n order.push('register_agent')\n const regResult = await parseToolResult(registerResp)\n if (!('agent_id' in regResult)) {\n throw new Error(`register_agent failed: ${JSON.stringify(regResult)}`)\n }\n\n // 2. subscribe_channel_wake — proxy's csid is fresh per startup\n const subResp = await client.callTool({\n name: 'subscribe_channel_wake',\n arguments: { channel_session_id: config.channel_session_id }\n })\n order.push('subscribe_channel_wake')\n const subResult = await parseToolResult(subResp)\n if (!('ok' in subResult) || subResult.ok !== true) {\n throw new Error(`subscribe_channel_wake failed: ${JSON.stringify(subResult)}`)\n }\n\n return {\n order,\n lastSubscribeResult: subResult,\n client,\n transport,\n close: async () => {\n try { await client.close() } catch { /* best-effort */ }\n try { await transport.close() } catch { /* best-effort */ }\n }\n }\n}\n\nexport interface WaitForDisconnectInput {\n client: Pick<Client, 'callTool'>\n transport: { onclose?: (() => void) | null | undefined }\n}\n\nexport interface WaitForDisconnectOptions {\n healthCheckIntervalMs?: number\n shouldStop?: () => boolean\n}\n\nexport async function waitForDisconnect(\n seq: WaitForDisconnectInput,\n opts: WaitForDisconnectOptions = {}\n): Promise<void> {\n const interval = opts.healthCheckIntervalMs ?? 30_000\n const shouldStop = opts.shouldStop ?? (() => false)\n let disconnected = false\n let wakeup: (() => void) | null = null\n const closeHandler = (): void => {\n disconnected = true\n wakeup?.()\n }\n const prevOnClose = seq.transport.onclose\n seq.transport.onclose = (): void => { prevOnClose?.(); closeHandler() }\n while (!disconnected && !shouldStop()) {\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => { wakeup = null; resolve() }, interval)\n wakeup = (): void => { clearTimeout(timer); wakeup = null; resolve() }\n })\n if (disconnected || shouldStop()) break\n try {\n await seq.client.callTool({ name: 'echo', arguments: { msg: 'hb' } })\n } catch {\n disconnected = true\n break\n }\n }\n}\n\nexport function runReconnectingProxy(config: ReconnectingProxyConfig): ReconnectingProxyController {\n let stopped = false\n let currentSeq: RegistrationSequenceResult | null = null\n\n async function loop(): Promise<void> {\n while (!stopped) {\n try {\n const seq = await runRegistrationSequence(config)\n currentSeq = seq\n if (config.onSequenceComplete) config.onSequenceComplete([...seq.order])\n\n await waitForDisconnect(seq, {\n healthCheckIntervalMs: config.healthCheckIntervalMs,\n shouldStop: () => stopped,\n })\n if (config.onDisconnect) config.onDisconnect()\n try { await seq.close() } catch { /* best-effort */ }\n currentSeq = null\n } catch {\n // register/subscribe failed — wait and retry.\n }\n if (stopped) break\n const wait = config.backoffInitialMs ?? 500\n await new Promise(r => setTimeout(r, wait))\n }\n }\n\n void loop()\n\n return {\n stop: async () => {\n stopped = true\n if (currentSeq) {\n try { await currentSeq.close() } catch { /* best-effort */ }\n }\n }\n }\n}\n","import { execFileSync } from 'node:child_process'\n\nconst MAX_HOPS = 8\n\ninterface PsRow {\n ppid: number\n cmd: string\n}\n\nexport function readPsRow(pid: number): PsRow | null {\n try {\n const out = execFileSync('ps', ['-o', 'ppid=,args=', '-p', String(pid)], {\n encoding: 'utf8',\n timeout: 1000,\n stdio: ['ignore', 'pipe', 'ignore']\n })\n const trimmed = out.trim()\n if (!trimmed) return null\n const m = /^\\s*(\\d+)\\s+(.*)$/.exec(trimmed)\n if (!m) return null\n return { ppid: parseInt(m[1], 10), cmd: m[2] }\n } catch {\n return null\n }\n}\n\nexport function isClaudeCmd(cmd: string): boolean {\n const first = cmd.trim().split(/\\s+/)[0]\n if (!first) return false\n const base = first.replace(/^.*\\//, '')\n return base === 'claude'\n}\n\nexport function findClaudeUiPid(\n startPpid: number = process.ppid,\n reader: (pid: number) => PsRow | null = readPsRow\n): number {\n let pid = startPpid\n for (let i = 0; i < MAX_HOPS; i++) {\n const row = reader(pid)\n if (!row) break\n if (isClaudeCmd(row.cmd)) return pid\n if (row.ppid <= 1 || row.ppid === pid) break\n pid = row.ppid\n }\n return startPpid\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../plugins/cross-agent-teams-channel/src/cli.ts","../plugins/cross-agent-teams-channel/src/proxy.ts","../plugins/cross-agent-teams-channel/src/daemon-client.ts","../plugins/cross-agent-teams-channel/src/find-claude-pid.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { randomUUID } from 'node:crypto'\nimport { realpathSync } from 'node:fs'\nimport { hostname } from 'node:os'\nimport { fileURLToPath } from 'node:url'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { createProxyServer, relayChannelWake } from './proxy.js'\nimport { runReconnectingProxy } from './daemon-client.js'\n\ninterface CliArgs {\n daemonUrl: string\n token?: string\n // Omitted when the user did not pass --device. The daemon then auto-fills\n // its own local label on loopback registrations, which keeps zero-config\n // proxies working against a daemon whose operator chose a custom --device.\n device?: string\n}\n\nexport class CliArgError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'CliArgError'\n }\n}\n\nexport function buildStartupHint(\n csid: string,\n device?: string\n): { content: string; meta: { source: string; kind: string } } {\n // Local default path (no --device): emit the original same-host hint with NO\n // device-related noise. Pure-local users never need to think about device.\n // Cross-host path (--device passed): surface the device value in both the\n // user-facing ask and the register_agent call so the agent and the human\n // both know it is required (daemon returns device_required_from_remote\n // otherwise).\n const isCrossHost = device !== undefined\n const deviceClause = isCrossHost ? `, device: \"${device}\"` : ''\n const deviceRegisterFragment = isCrossHost\n ? ` (this proxy is running with --device \"${device}\", so include device: \"${device}\" verbatim — the daemon enforces per-origin device validation, and on a remote daemon omitting it returns device_required_from_remote)`\n : ''\n const ask = isCrossHost\n ? `'To join cross-agent-teams (xats) and message other agents, reply with: Register to xats — name: your-agent-name, team: your-team-name (optional; defaults to the current working directory basename), device: ${device} (cross-host setup — keep this value verbatim).'`\n : `'To join cross-agent-teams (xats) and message other agents, reply with: Register to xats — name: your-agent-name, team: your-team-name (optional; defaults to the current working directory basename).'`\n const content = [\n `cross-agent-teams-mcp: your channel_session_id is ${csid}.`,\n `Do NOT register automatically. First ask the user (in English) to register this session so it can talk to other agents — use exactly this wording: ${ask}`,\n `Once the user provides a name (and optionally a team), call register_agent({agent_type: \"claude-code\", name: \"<name from user>\", team: \"<team from user, omit if not provided>\"${deviceClause}, ui_pid: $PPID, project_dir: \"<current working directory>\"})${deviceRegisterFragment}. Do NOT pass channel_session_id here; the daemon auto-binds via ui_pid.`,\n `bind_channel({channel_session_id: \"${csid}\"}) is the low-level rebind tool for an already-registered Claude host that needs to switch to a fresh csid; it is NOT the primary registration path.`,\n `Do not use curl or another external HTTP client for Claude registration here — that would create a different MCP session, and follow-up tools in Claude Code could still see unknown_agent.`\n ].join(' ')\n return {\n content,\n meta: { source: 'cross_agent_teams_mcp', kind: 'startup_bind_hint' }\n }\n}\n\nexport function parseCliArgs(argv: readonly string[], env: NodeJS.ProcessEnv = process.env): CliArgs {\n let daemonUrl: string | undefined\n let token: string | undefined\n let explicitDevice: string | undefined\n\n for (let i = 0; i < argv.length; i++) {\n const flag = argv[i]\n const next = argv[i + 1]\n switch (flag) {\n case '--daemon-url':\n daemonUrl = next; i++; break\n case '--token':\n token = next; i++; break\n case '--device':\n explicitDevice = next; i++; break\n default:\n // Ignore unknown flags for forward-compat (including legacy\n // --agent-team / --agent-name, which are no longer honored).\n break\n }\n }\n\n if (!daemonUrl || daemonUrl.length === 0) {\n daemonUrl = env.CROSS_AGENT_TEAMS_MCP_DAEMON_URL\n }\n if (!token || token.length === 0) {\n token = env.CROSS_AGENT_TEAMS_MCP_TOKEN\n }\n\n if (!daemonUrl || daemonUrl.length === 0) {\n throw new CliArgError(\n 'missing --daemon-url (or CROSS_AGENT_TEAMS_MCP_DAEMON_URL env var)'\n )\n }\n // Only validate / pass the device when the user explicitly provided one.\n // Leaving it undefined lets daemon-client omit the field on register_agent\n // so the daemon's loopback auto-fill resolves it to the daemon's localDevice.\n const device =\n explicitDevice !== undefined ? resolveDeviceLabel(explicitDevice) : undefined\n return { daemonUrl, token, device }\n}\n\nexport async function main(\n argv: readonly string[] = process.argv.slice(2),\n env: NodeJS.ProcessEnv = process.env\n): Promise<void> {\n let args: CliArgs\n try {\n args = parseCliArgs(argv, env)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n process.stderr.write(`cross-agent-teams-proxy: ${msg}\\n`)\n process.exit(2)\n }\n\n // Fresh csid per startup — no persistence. Multi-instance safe.\n const csid = randomUUID()\n\n const hostServer = createProxyServer()\n const stdioTransport = new StdioServerTransport()\n\n let registrationEverSucceeded = false\n const controller = runReconnectingProxy({\n daemonUrl: args.daemonUrl,\n token: args.token,\n device: args.device,\n channel_session_id: csid,\n notificationHandler: (params) => {\n relayChannelWake(hostServer, params as { content: string; meta: Record<string, string> })\n },\n onSequenceComplete: () => {\n registrationEverSucceeded = true\n // Announce csid to Claude via host-facing channel notification so Claude\n // can call bind_channel({channel_session_id}) to bind its own agent row.\n const hint = buildStartupHint(csid, args.device)\n relayChannelWake(hostServer, hint)\n }\n })\n\n let stopped = false\n const shutdown = async (): Promise<void> => {\n if (stopped) return\n stopped = true\n try { await controller.stop() } catch { /* best-effort */ }\n try { await hostServer.close() } catch { /* best-effort */ }\n if (!registrationEverSucceeded) {\n process.stderr.write(`cross-agent-teams-proxy: daemon unreachable at ${args.daemonUrl}\\n`)\n process.exit(1)\n }\n process.exit(0)\n }\n\n stdioTransport.onclose = () => { void shutdown() }\n\n await hostServer.connect(stdioTransport)\n\n process.on('SIGTERM', () => { void shutdown() })\n process.on('SIGINT', () => { void shutdown() })\n}\n\n// Entry-point check. The naive `import.meta.url === \\`file://${process.argv[1]}\\``\n// breaks when launched via an npm `.bin` symlink (npx, `npm install -g`):\n// process.argv[1] is the symlink path, while import.meta.url is already\n// resolved. Compare realpath-resolved file paths instead.\nfunction isEntry(): boolean {\n try {\n const metaPath = fileURLToPath(import.meta.url)\n const argvPath = realpathSync(process.argv[1])\n return metaPath === argvPath\n } catch {\n return false\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-misused-promises\nif (isEntry()) {\n void main()\n}\n\nfunction resolveDeviceLabel(explicit?: string): string {\n const raw = explicit ?? hostname()\n if (raw.includes(':')) {\n throw new CliArgError('invalid_device_label')\n }\n const normalized = raw\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, '-')\n const label = normalized.length > 0 ? normalized : 'local'\n if (label.length > 64) {\n throw new CliArgError('invalid_device_label')\n }\n return label\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nexport function createProxyServer(): McpServer {\n return new McpServer(\n { name: 'cross-agent-teams-channel', version: '0.1.0' },\n { capabilities: { experimental: { 'claude/channel': {} } } }\n )\n}\n\nexport interface ChannelWakeParams {\n content: string\n meta: Record<string, string>\n}\n\nexport function relayChannelWake(server: McpServer, params: ChannelWakeParams): void {\n try {\n const notif = {\n method: 'notifications/claude/channel',\n params: params as unknown as Record<string, unknown>\n }\n const p = (server.server.notification as (n: typeof notif) => Promise<void>)(notif)\n if (p && typeof p.catch === 'function') {\n p.catch(() => { /* host closed — drop silently */ })\n }\n } catch {\n // host transport closed or not yet connected — drop silently\n }\n}\n","import { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport { findClaudeUiPid } from './find-claude-pid.js'\n\nexport interface RegistrationConfig {\n daemonUrl: string\n token?: string\n device?: string\n channel_session_id: string\n backoffInitialMs?: number\n backoffMaxMs?: number\n notificationHandler?: (payload: unknown) => void\n}\n\nexport interface ReconnectingProxyConfig extends RegistrationConfig {\n onSequenceComplete?: (order: string[]) => void\n onDisconnect?: () => void\n healthCheckIntervalMs?: number\n}\n\nexport interface ReconnectingProxyController {\n stop(): Promise<void>\n}\n\nexport interface RegistrationSequenceResult {\n order: string[]\n lastSubscribeResult: unknown\n client: Client\n transport: StreamableHTTPClientTransport\n close: () => Promise<void>\n}\n\ntype ToolResult = Record<string, unknown>\n\nasync function parseToolResult(resp: unknown): Promise<ToolResult> {\n const r = resp as { content?: Array<{ text?: string }> }\n const text = r.content?.[0]?.text\n if (typeof text !== 'string') return {}\n try { return JSON.parse(text) as ToolResult } catch { return {} }\n}\n\nexport async function runRegistrationSequence(\n config: RegistrationConfig\n): Promise<RegistrationSequenceResult> {\n const order: string[] = []\n const requestInit = config.token\n ? { headers: { Authorization: `Bearer ${config.token}` } }\n : undefined\n const transport = new StreamableHTTPClientTransport(new URL(config.daemonUrl), {\n requestInit,\n })\n const client = new Client({ name: 'cross-agent-teams-proxy', version: '0.1.0' })\n\n if (config.notificationHandler) {\n client.fallbackNotificationHandler = async (n) => {\n if (n.method === 'notifications/channel_wake') {\n config.notificationHandler!(n.params)\n }\n }\n }\n\n await client.connect(transport)\n\n // 1. register_agent as proxy — identity keyed on pid, stable across reconnects\n // so the (device, team, name) ON CONFLICT upsert reuses the same row instead of spamming new rows.\n // Only send `device` when the caller explicitly set one; otherwise let the daemon auto-fill\n // its local label (loopback) or reject (remote, which requires explicit device).\n const registerArgs: Record<string, unknown> = {\n agent_type: 'custom',\n agent_type_name: 'cross-agent-teams-channel',\n model: 'proxy',\n role: '__channel_proxy__',\n name: `channel-proxy-${process.pid}`,\n team: 'default',\n claude_ui_pid: findClaudeUiPid(),\n delivery: {\n kind: 'claude-channel',\n channel_session_id: config.channel_session_id,\n },\n }\n if (config.device !== undefined) {\n registerArgs.device = config.device\n }\n const registerResp = await client.callTool({\n name: 'register_agent',\n arguments: registerArgs,\n })\n order.push('register_agent')\n const regResult = await parseToolResult(registerResp)\n if (!('agent_id' in regResult)) {\n throw new Error(`register_agent failed: ${JSON.stringify(regResult)}`)\n }\n\n // 2. subscribe_channel_wake — proxy's csid is fresh per startup\n const subResp = await client.callTool({\n name: 'subscribe_channel_wake',\n arguments: { channel_session_id: config.channel_session_id }\n })\n order.push('subscribe_channel_wake')\n const subResult = await parseToolResult(subResp)\n if (!('ok' in subResult) || subResult.ok !== true) {\n throw new Error(`subscribe_channel_wake failed: ${JSON.stringify(subResult)}`)\n }\n\n return {\n order,\n lastSubscribeResult: subResult,\n client,\n transport,\n close: async () => {\n try { await client.close() } catch { /* best-effort */ }\n try { await transport.close() } catch { /* best-effort */ }\n }\n }\n}\n\nexport interface WaitForDisconnectInput {\n client: Pick<Client, 'callTool'>\n transport: { onclose?: (() => void) | null | undefined }\n}\n\nexport interface WaitForDisconnectOptions {\n healthCheckIntervalMs?: number\n shouldStop?: () => boolean\n}\n\nexport async function waitForDisconnect(\n seq: WaitForDisconnectInput,\n opts: WaitForDisconnectOptions = {}\n): Promise<void> {\n const interval = opts.healthCheckIntervalMs ?? 30_000\n const shouldStop = opts.shouldStop ?? (() => false)\n let disconnected = false\n let wakeup: (() => void) | null = null\n const closeHandler = (): void => {\n disconnected = true\n wakeup?.()\n }\n const prevOnClose = seq.transport.onclose\n seq.transport.onclose = (): void => { prevOnClose?.(); closeHandler() }\n while (!disconnected && !shouldStop()) {\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => { wakeup = null; resolve() }, interval)\n wakeup = (): void => { clearTimeout(timer); wakeup = null; resolve() }\n })\n if (disconnected || shouldStop()) break\n try {\n await seq.client.callTool({ name: 'echo', arguments: { msg: 'hb' } })\n } catch {\n disconnected = true\n break\n }\n }\n}\n\nexport function runReconnectingProxy(config: ReconnectingProxyConfig): ReconnectingProxyController {\n let stopped = false\n let currentSeq: RegistrationSequenceResult | null = null\n\n async function loop(): Promise<void> {\n while (!stopped) {\n try {\n const seq = await runRegistrationSequence(config)\n currentSeq = seq\n if (config.onSequenceComplete) config.onSequenceComplete([...seq.order])\n\n await waitForDisconnect(seq, {\n healthCheckIntervalMs: config.healthCheckIntervalMs,\n shouldStop: () => stopped,\n })\n if (config.onDisconnect) config.onDisconnect()\n try { await seq.close() } catch { /* best-effort */ }\n currentSeq = null\n } catch {\n // register/subscribe failed — wait and retry.\n }\n if (stopped) break\n const wait = config.backoffInitialMs ?? 500\n await new Promise(r => setTimeout(r, wait))\n }\n }\n\n void loop()\n\n return {\n stop: async () => {\n stopped = true\n if (currentSeq) {\n try { await currentSeq.close() } catch { /* best-effort */ }\n }\n }\n }\n}\n","import { execFileSync } from 'node:child_process'\n\nconst MAX_HOPS = 8\n\ninterface PsRow {\n ppid: number\n cmd: string\n}\n\nexport function readPsRow(pid: number): PsRow | null {\n try {\n const out = execFileSync('ps', ['-o', 'ppid=,args=', '-p', String(pid)], {\n encoding: 'utf8',\n timeout: 1000,\n stdio: ['ignore', 'pipe', 'ignore']\n })\n const trimmed = out.trim()\n if (!trimmed) return null\n const m = /^\\s*(\\d+)\\s+(.*)$/.exec(trimmed)\n if (!m) return null\n return { ppid: parseInt(m[1], 10), cmd: m[2] }\n } catch {\n return null\n }\n}\n\nexport function isClaudeCmd(cmd: string): boolean {\n const first = cmd.trim().split(/\\s+/)[0]\n if (!first) return false\n const base = first.replace(/^.*\\//, '')\n return base === 'claude'\n}\n\nexport function findClaudeUiPid(\n startPpid: number = process.ppid,\n reader: (pid: number) => PsRow | null = readPsRow\n): number {\n let pid = startPpid\n for (let i = 0; i < MAX_HOPS; i++) {\n const row = reader(pid)\n if (!row) break\n if (isClaudeCmd(row.cmd)) return pid\n if (row.ppid <= 1 || row.ppid === pid) break\n pid = row.ppid\n }\n return startPpid\n}\n"],"mappings":"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","names":[]}
package/dist/cli.d.ts CHANGED
@@ -1 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ interface DaemonCliArgs {
3
+ pidPath: string;
4
+ dbPath: string;
5
+ token?: string;
6
+ requestedPort: number;
7
+ host: string;
8
+ localDevice: string;
9
+ }
10
+ declare function parseDaemonCliArgs(argv?: readonly string[], env?: NodeJS.ProcessEnv): DaemonCliArgs;
11
+
12
+ export { type DaemonCliArgs, parseDaemonCliArgs };