@m2canvas/connect 0.5.0 → 0.6.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/README.md +5 -4
- package/dist/bridge/adapters.js +118 -3
- package/dist/bridge/agents.js +5 -0
- package/dist/bridge/auth-call.js +68 -0
- package/dist/bridge/authorize.js +341 -0
- package/dist/bridge/env.js +12 -0
- package/dist/bridge/errors.js +9 -0
- package/dist/bridge/exec.js +53 -12
- package/dist/bridge/instance.js +239 -0
- package/dist/bridge/main.js +63 -25
- package/dist/bridge/precheck.js +22 -6
- package/dist/bridge/protocol.js +11 -1
- package/dist/bridge/server.js +51 -20
- package/dist/bridge/service.js +103 -20
- package/package.json +1 -1
package/dist/bridge/server.js
CHANGED
|
@@ -8,17 +8,22 @@
|
|
|
8
8
|
* - 只监听 127.0.0.1;upgrade 校验(对端、Host、Origin、路径)在完成握手之前,失败直接写 HTTP 状态并销毁 socket。
|
|
9
9
|
* - 握手后第一条消息必须是 hello;未配对的连接只能调用 pair,其余已知方法与 acp 帧一律 not_paired。
|
|
10
10
|
* 连接只有两种方式成为已配对:hello 带有效 token,或 hello 的 pairSecret 与启动命令的承诺一致(pairing.ts 文件头)。
|
|
11
|
-
* - bridge 自己的方法里,页面可控的参数只有 agent id
|
|
11
|
+
* - bridge 自己的方法里,页面可控的参数只有 agent id(枚举校验)、授权动作(枚举校验)、配对码、processId —— 进不了 spawn 的命令、参数、cwd、环境。
|
|
12
|
+
* agent/authorize 与 agent/authorize-cancel 的 params 只能有规定的键,多一个就 bad_message(分派在 auth-call.ts,命令清单写死在 authorize.ts)。
|
|
12
13
|
* - 页面发给 agent 的 ACP 行禁止含换行,并经 acp-guard.ts 检查:只能用白名单里的方法、会话目录固定为 bridge 的 cwd、不能让 agent 额外拉起 MCP 服务器。
|
|
13
14
|
*
|
|
14
|
-
* 边界:bridge 不解析 agent 发来的 ACP,不判断用户是否开通了本地 Agent(change 4 / 3
|
|
15
|
+
* 边界:bridge 不解析 agent 发来的 ACP,不判断用户是否开通了本地 Agent(change 4 / 3 负责)。
|
|
16
|
+
* 登录 / m2 授权只代跑写死清单里的命令,授权页由用户在浏览器里亲自完成(authorize.ts);授权链接只发给发起的连接,不进终端日志。
|
|
15
17
|
*/
|
|
16
18
|
import { createServer } from 'node:http';
|
|
17
19
|
import { randomUUID } from 'node:crypto';
|
|
18
20
|
import { WebSocketServer } from 'ws';
|
|
19
21
|
import { TOKEN_IDLE_DAYS } from './pairing.js';
|
|
20
22
|
import { ProcessError, ProcessManager } from './processes.js';
|
|
21
|
-
import {
|
|
23
|
+
import { isBridgeAgentId } from './agents.js';
|
|
24
|
+
import { handleAuthCall } from './auth-call.js';
|
|
25
|
+
import { AuthorizationManager } from './authorize.js';
|
|
26
|
+
import { BRIDGE_CLOSE_CODES, BRIDGE_METHODS, BRIDGE_SUPPORTED_PROTOCOLS, } from './protocol.js';
|
|
22
27
|
import { checkPageAcpLine } from './acp-guard.js';
|
|
23
28
|
import { checkUpgrade } from './security.js';
|
|
24
29
|
import { BridgeFailure } from './service.js';
|
|
@@ -32,9 +37,6 @@ const MAX_CALL_ID = 64;
|
|
|
32
37
|
function isObject(value) {
|
|
33
38
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
34
39
|
}
|
|
35
|
-
function isAgentId(value) {
|
|
36
|
-
return typeof value === 'string' && BRIDGE_AGENT_IDS.includes(value);
|
|
37
|
-
}
|
|
38
40
|
function isMethod(value) {
|
|
39
41
|
return typeof value === 'string' && BRIDGE_METHODS.includes(value);
|
|
40
42
|
}
|
|
@@ -77,6 +79,23 @@ export function startBridgeServer(options) {
|
|
|
77
79
|
},
|
|
78
80
|
onStderr: options.verbose ? (_id, agent, text) => process.stderr.write(`[${agent}] ${text}`) : undefined,
|
|
79
81
|
});
|
|
82
|
+
const authorizations = new AuthorizationManager({
|
|
83
|
+
timeoutMs: options.timing?.authTimeoutMs,
|
|
84
|
+
graceMs: options.timing?.authGraceMs,
|
|
85
|
+
pollMs: options.timing?.authPollMs,
|
|
86
|
+
});
|
|
87
|
+
const authContext = {
|
|
88
|
+
authorizations,
|
|
89
|
+
service: options.service,
|
|
90
|
+
log,
|
|
91
|
+
isConnected: (ownerId) => connections.has(ownerId),
|
|
92
|
+
// 授权链接只发给发起的连接(D4)
|
|
93
|
+
sendLink: (ownerId, data) => {
|
|
94
|
+
const owner = connections.get(ownerId);
|
|
95
|
+
if (owner)
|
|
96
|
+
send(owner, { type: 'event', event: 'auth/link', data });
|
|
97
|
+
},
|
|
98
|
+
};
|
|
80
99
|
const unpairedCount = () => [...connections.values()].filter((conn) => !conn.paired).length;
|
|
81
100
|
const markPaired = (conn) => {
|
|
82
101
|
conn.paired = true;
|
|
@@ -111,8 +130,10 @@ export function startBridgeServer(options) {
|
|
|
111
130
|
}
|
|
112
131
|
if (method === 'agents/list')
|
|
113
132
|
return { agents: await options.service.list() };
|
|
133
|
+
if (method === 'agent/authorize' || method === 'agent/authorize-cancel')
|
|
134
|
+
return handleAuthCall(authContext, conn.id, method, params);
|
|
114
135
|
const agent = params.agent;
|
|
115
|
-
if (!
|
|
136
|
+
if (!isBridgeAgentId(agent))
|
|
116
137
|
throw new BridgeFailure('unknown_agent', `unknown agent ${String(agent)}`);
|
|
117
138
|
switch (method) {
|
|
118
139
|
case 'agent/prepare':
|
|
@@ -296,12 +317,12 @@ export function startBridgeServer(options) {
|
|
|
296
317
|
clearTimeout(conn.timer);
|
|
297
318
|
connections.delete(conn.id);
|
|
298
319
|
processes.stopOwner(conn.id);
|
|
320
|
+
authorizations.cancelOwner(conn.id);
|
|
299
321
|
});
|
|
300
322
|
// ws 的错误(非法帧、超过 maxPayload 等)之后必然紧跟 'close',清理在 close 里做;这里只防止未处理的 error 事件让进程崩溃
|
|
301
323
|
ws.on('error', () => undefined);
|
|
302
324
|
});
|
|
303
325
|
});
|
|
304
|
-
// 心跳:浏览器自动回 pong;连续一个周期没回(休眠、崩溃、断网)就断开并清理它的进程
|
|
305
326
|
// 被拒连接的终端日志限频:任何网站都能反复连 127.0.0.1,逐条打印会把配对码和「X wants to pair」顶出视野(review r3 S5)。
|
|
306
327
|
// 同一原因在一个窗口内只打第一条,窗口结束时汇总被合并的次数。
|
|
307
328
|
const rejectWindowMs = options.timing?.rejectLogWindowMs ?? REJECT_LOG_WINDOW_MS;
|
|
@@ -332,25 +353,33 @@ export function startBridgeServer(options) {
|
|
|
332
353
|
},
|
|
333
354
|
};
|
|
334
355
|
})();
|
|
335
|
-
const heartbeat = setInterval(() => {
|
|
336
|
-
for (const conn of connections.values()) {
|
|
337
|
-
if (!conn.alive) {
|
|
338
|
-
conn.ws.terminate();
|
|
339
|
-
continue;
|
|
340
|
-
}
|
|
341
|
-
conn.alive = false;
|
|
342
|
-
conn.ws.ping();
|
|
343
|
-
}
|
|
344
|
-
}, timing.pingIntervalMs);
|
|
345
356
|
return new Promise((resolve, reject) => {
|
|
346
|
-
|
|
357
|
+
// 监听失败(端口被占)时停掉已经开始的计时器:--replace 会在同一进程里再监听一次(review r4 N4)。心跳在监听成功后才开始
|
|
358
|
+
const fail = (error) => {
|
|
359
|
+
rejectLog.stop();
|
|
360
|
+
wss.close();
|
|
361
|
+
reject(error);
|
|
362
|
+
};
|
|
363
|
+
http.once('error', fail);
|
|
347
364
|
http.listen(options.port, '127.0.0.1', () => {
|
|
348
|
-
http.off('error',
|
|
365
|
+
http.off('error', fail);
|
|
366
|
+
// 心跳:浏览器自动回 pong;连续一个周期没回(休眠、崩溃、断网)就断开并清理它的进程
|
|
367
|
+
const heartbeat = setInterval(() => {
|
|
368
|
+
for (const conn of connections.values()) {
|
|
369
|
+
if (!conn.alive) {
|
|
370
|
+
conn.ws.terminate();
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
conn.alive = false;
|
|
374
|
+
conn.ws.ping();
|
|
375
|
+
}
|
|
376
|
+
}, timing.pingIntervalMs);
|
|
349
377
|
const address = http.address();
|
|
350
378
|
resolve({
|
|
351
379
|
port: address.port,
|
|
352
380
|
address: address.address,
|
|
353
381
|
processes,
|
|
382
|
+
authorizations,
|
|
354
383
|
broadcast(event, data) {
|
|
355
384
|
for (const conn of connections.values())
|
|
356
385
|
if (conn.paired)
|
|
@@ -359,6 +388,8 @@ export function startBridgeServer(options) {
|
|
|
359
388
|
close() {
|
|
360
389
|
clearInterval(heartbeat);
|
|
361
390
|
rejectLog.stop();
|
|
391
|
+
// 先于关闭连接:标记取消,被杀掉的授权命令不会被当成「失败」再去重跑预检
|
|
392
|
+
authorizations.killAllNow();
|
|
362
393
|
for (const conn of connections.values())
|
|
363
394
|
conn.ws.close(BRIDGE_CLOSE_CODES.goingAway, 'bridge is shutting down');
|
|
364
395
|
processes.stopAll();
|
package/dist/bridge/service.js
CHANGED
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 页面方法背后的 agent 操作:列表、准备适配器、预检、写 m2
|
|
2
|
+
* 页面方法背后的 agent 操作:列表、准备适配器、预检、写 m2 配置、生成启动规格与授权命令计划。server.ts 只做协议,这里只做业务。
|
|
3
3
|
*
|
|
4
4
|
* 前置条件统一在这里检查,错误以 BridgeFailure(带协议错误码)抛出:
|
|
5
|
-
* -
|
|
5
|
+
* - 拉不起来 → agent_not_installed(安装命令与文档链接由前端提供,bridge 不代装)。claude / codex 用适配器自带的程序,
|
|
6
|
+
* 本机没装命令行也能拉起(D11:`available` 恒为 true;`installed` 仍只表示本机检测到了命令行);
|
|
6
7
|
* - 适配器类 agent 未就绪 → adapter_not_ready(页面要先调 agent/prepare)。
|
|
7
8
|
*
|
|
8
9
|
* m2/configure 以子进程运行本包自己的 cli.js `--client <id> --yes`:stdin 不是 TTY,cli.js 走现有的非交互分支
|
|
9
10
|
* (只写配置、不跑登录、退出码 0 = 全部写入或已存在)。这样一字不改 clients.ts,也不让它的 spawnSync 卡住转发。
|
|
10
|
-
* 改 cli.js 非交互分支的行为时要同步检查这里。
|
|
11
|
+
* 改 cli.js 非交互分支的行为时要同步检查这里。clients.ts 只认 PATH 上跑得起来的 claude / codex,所以对这两家把自带程序所在目录
|
|
12
|
+
* 和 bridge 自己的 node 排到 PATH 最前(D10),本机没装命令行也能写配置。
|
|
13
|
+
*
|
|
14
|
+
* 授权(change acp-terminal-free-auth):authorizePlan 按写死清单生成命令(authorize.ts),m2 的配置项名只取 bridge 自己的 m2 预检:
|
|
15
|
+
* 优先用最近一次预检 / 写配置时得到的名字(D19,页面先预检才会显示按钮,省掉一次重跑 —— Claude 的 `mcp list` 实测约 5 秒),没有记录时重跑;
|
|
16
|
+
* 预检 / 写配置的结果按同一判据带上 `authorize` 能力,页面只在能力存在时才调用 agent/authorize。
|
|
11
17
|
*/
|
|
18
|
+
import { dirname, join } from 'node:path';
|
|
12
19
|
import { ADAPTERS, isAdapterAgent } from './adapters.js';
|
|
20
|
+
import { BridgeFailure } from './errors.js';
|
|
13
21
|
import { LAUNCH_KIND, NATIVE_ACP_ARGS } from './agents.js';
|
|
22
|
+
import { authCapability, buildAuthPlan, isSafeAuthEntry } from './authorize.js';
|
|
23
|
+
import { prependPath } from './env.js';
|
|
14
24
|
import { combinedOutput } from './exec.js';
|
|
15
25
|
import { precheckAgent, precheckM2 } from './precheck.js';
|
|
16
|
-
import { BRIDGE_AGENT_IDS } from './protocol.js';
|
|
26
|
+
import { BRIDGE_AGENT_IDS, } from './protocol.js';
|
|
17
27
|
export const CONFIGURE_TIMEOUT_MS = 120_000;
|
|
18
28
|
/**
|
|
19
29
|
* Claude 适配器(claude-agent-acp@0.84.0)声明 `node >=22`,实测在 Node 20 上可用(ACP PoC 与本 change 的端到端都是 Node 20);
|
|
@@ -21,15 +31,7 @@ export const CONFIGURE_TIMEOUT_MS = 120_000;
|
|
|
21
31
|
*/
|
|
22
32
|
export const MIN_NODE_MAJOR_FOR_CLAUDE = 20;
|
|
23
33
|
const CONFIGURE_OUTPUT_LIMIT = 4096;
|
|
24
|
-
export
|
|
25
|
-
code;
|
|
26
|
-
data;
|
|
27
|
-
constructor(code, message, data) {
|
|
28
|
-
super(message);
|
|
29
|
-
this.code = code;
|
|
30
|
-
this.data = data;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
34
|
+
export { BridgeFailure };
|
|
33
35
|
/** bridge 的 agent id → connect `--client` 的取值 */
|
|
34
36
|
const CLIENT_ID = {
|
|
35
37
|
claude: 'claude-code',
|
|
@@ -40,25 +42,74 @@ const CLIENT_ID = {
|
|
|
40
42
|
};
|
|
41
43
|
export function createAgentService(deps) {
|
|
42
44
|
const precheckDeps = { ...deps, env: deps.probeEnv };
|
|
43
|
-
|
|
45
|
+
/**
|
|
46
|
+
* 最近一次 m2 预检得到的配置项名(D19)。只由 bridge 自己的预检写入,页面仍不能指定(契约 3);
|
|
47
|
+
* 最近一次没有名字(未配置、读不出)就清掉,授权时重跑。名字过期(预检到点击之间用户改了配置)时,授权命令失败并带输出(再点一次即可:命令结束后的预检已经刷新了记录),
|
|
48
|
+
* 或者针对同名的新配置项(用户在授权页上看得到授权对象)。Claude / Codex / Cursor 实测名字不存在时退出码 1;OpenCode 不是,所以它不用记录(见下)。
|
|
49
|
+
*/
|
|
50
|
+
const m2Entries = new Map();
|
|
51
|
+
/**
|
|
52
|
+
* 授权前总是重新检查、不用记录的 agent(D21)。OpenCode 的 m2 授权先 `mcp logout <entry>`:名字过期时可能删掉同名的别的服务器的凭证;
|
|
53
|
+
* 而且名字不存在时 `mcp logout` / `mcp auth` 都以退出码 0 结束(实测 1.18.5),bridge 会判成成功。多 2–4 秒,换来名字一定是当下的。
|
|
54
|
+
*/
|
|
55
|
+
const alwaysRecheck = new Set(['opencode']);
|
|
56
|
+
function remember(agent, m2) {
|
|
57
|
+
if (m2.state !== 'not_configured' && m2.entry)
|
|
58
|
+
m2Entries.set(agent, m2.entry);
|
|
59
|
+
else
|
|
60
|
+
m2Entries.delete(agent);
|
|
61
|
+
return m2;
|
|
62
|
+
}
|
|
63
|
+
/** 能拉起(D11):claude / codex 用适配器自带的程序,不要求本机命令行;其余三家要本机装了 */
|
|
64
|
+
async function available(agent) {
|
|
44
65
|
const detected = await deps.detect(agent);
|
|
45
|
-
if (!detected.installed)
|
|
66
|
+
if (!isAdapterAgent(agent) && !detected.installed)
|
|
46
67
|
throw new BridgeFailure('agent_not_installed', `${agent} is not installed on this machine`);
|
|
47
68
|
return detected;
|
|
48
69
|
}
|
|
49
70
|
async function ready(agent) {
|
|
50
|
-
const detected = await
|
|
71
|
+
const detected = await available(agent);
|
|
51
72
|
if (isAdapterAgent(agent) && !deps.adapters.isReady(agent)) {
|
|
52
73
|
throw new BridgeFailure('adapter_not_ready', `call agent/prepare for ${agent} first`, { adapter: deps.adapters.info(agent) });
|
|
53
74
|
}
|
|
54
75
|
return detected;
|
|
55
76
|
}
|
|
77
|
+
const capability = (agent, action) => authCapability(agent, action, {
|
|
78
|
+
platform: deps.platform,
|
|
79
|
+
expectAvailable: deps.expectAvailable(),
|
|
80
|
+
claudeBinary: agent === 'claude' ? deps.adapters.claudeBinary() : null,
|
|
81
|
+
});
|
|
82
|
+
/** 预检 / 写配置结果带上「能不能代跑」:与 authorizePlan 用同一判据(含 m2 配置项名的校验),页面看到按钮就一定能调用 */
|
|
83
|
+
function withCapability(check, agent, action) {
|
|
84
|
+
const authorize = capability(agent, action);
|
|
85
|
+
const entry = 'entry' in check ? check.entry : null;
|
|
86
|
+
if (!authorize || (entry !== null && !isSafeAuthEntry(entry)))
|
|
87
|
+
return check;
|
|
88
|
+
return { ...check, authorize };
|
|
89
|
+
}
|
|
90
|
+
const withCapabilities = (result) => ({
|
|
91
|
+
...result,
|
|
92
|
+
login: withCapability(result.login, result.agent, 'login'),
|
|
93
|
+
m2: withCapability(result.m2, result.agent, 'm2'),
|
|
94
|
+
});
|
|
95
|
+
/** D10:clients.ts 只认 PATH 上能跑起来的 claude / codex */
|
|
96
|
+
function configureEnv(agent) {
|
|
97
|
+
const node = dirname(deps.nodePath);
|
|
98
|
+
if (agent === 'claude') {
|
|
99
|
+
const claude = deps.adapters.claudeBinary();
|
|
100
|
+
return prependPath(deps.probeEnv, claude ? [dirname(claude), node] : [node], deps.platform);
|
|
101
|
+
}
|
|
102
|
+
if (agent === 'codex')
|
|
103
|
+
return prependPath(deps.probeEnv, [join(deps.adapters.installDir('codex'), 'node_modules', '.bin'), node], deps.platform);
|
|
104
|
+
return deps.probeEnv;
|
|
105
|
+
}
|
|
56
106
|
return {
|
|
57
107
|
async list() {
|
|
58
108
|
const detected = await Promise.all(BRIDGE_AGENT_IDS.map((id) => deps.detect(id)));
|
|
59
109
|
return detected.map((agent) => ({
|
|
60
110
|
id: agent.id,
|
|
61
111
|
installed: agent.installed,
|
|
112
|
+
available: isAdapterAgent(agent.id) || agent.installed,
|
|
62
113
|
path: agent.path,
|
|
63
114
|
version: agent.version,
|
|
64
115
|
launch: LAUNCH_KIND[agent.id],
|
|
@@ -66,7 +117,7 @@ export function createAgentService(deps) {
|
|
|
66
117
|
}));
|
|
67
118
|
},
|
|
68
119
|
async prepare(agent) {
|
|
69
|
-
await
|
|
120
|
+
await available(agent);
|
|
70
121
|
if (!isAdapterAgent(agent))
|
|
71
122
|
return null;
|
|
72
123
|
const major = Number(deps.nodeVersion.split('.')[0]);
|
|
@@ -80,12 +131,14 @@ export function createAgentService(deps) {
|
|
|
80
131
|
return info;
|
|
81
132
|
},
|
|
82
133
|
async precheck(agent) {
|
|
83
|
-
|
|
134
|
+
const result = await precheckAgent(await ready(agent), precheckDeps);
|
|
135
|
+
remember(agent, result.m2);
|
|
136
|
+
return withCapabilities(result);
|
|
84
137
|
},
|
|
85
138
|
async configure(agent) {
|
|
86
139
|
const detected = await ready(agent);
|
|
87
140
|
const result = await deps.exec(deps.nodePath, [deps.cliPath, '--client', CLIENT_ID[agent], '--yes'], {
|
|
88
|
-
env:
|
|
141
|
+
env: configureEnv(agent),
|
|
89
142
|
cwd: deps.cwd,
|
|
90
143
|
timeoutMs: CONFIGURE_TIMEOUT_MS,
|
|
91
144
|
});
|
|
@@ -93,7 +146,7 @@ export function createAgentService(deps) {
|
|
|
93
146
|
return {
|
|
94
147
|
ok: result.code === 0,
|
|
95
148
|
output: output.length > CONFIGURE_OUTPUT_LIMIT ? output.slice(-CONFIGURE_OUTPUT_LIMIT) : output,
|
|
96
|
-
m2: await precheckM2(detected, precheckDeps),
|
|
149
|
+
m2: withCapability(remember(agent, await precheckM2(detected, precheckDeps)), agent, 'm2'),
|
|
97
150
|
};
|
|
98
151
|
},
|
|
99
152
|
async launch(agent) {
|
|
@@ -105,5 +158,35 @@ export function createAgentService(deps) {
|
|
|
105
158
|
throw new BridgeFailure('internal', `no executable path for ${agent}`);
|
|
106
159
|
return { command: detected.path, args: NATIVE_ACP_ARGS[agent] };
|
|
107
160
|
},
|
|
161
|
+
async authorizePlan(agent, action) {
|
|
162
|
+
const verb = action === 'login' ? 'sign-in' : 'M2 authorization';
|
|
163
|
+
if (deps.platform !== 'darwin')
|
|
164
|
+
throw new BridgeFailure('auth_unavailable', `the bridge only runs ${verb} on macOS; use the terminal command instead`);
|
|
165
|
+
const detected = await ready(agent);
|
|
166
|
+
if (!capability(agent, action))
|
|
167
|
+
throw new BridgeFailure('auth_unavailable', `the bridge cannot run ${verb} for ${agent}; use the terminal command instead`);
|
|
168
|
+
let entry = null;
|
|
169
|
+
if (action === 'm2') {
|
|
170
|
+
// 配置项名不一定叫 m2,也不能由页面指定:用 bridge 最近一次预检的结果,没有就重跑(D19);OpenCode 总是重跑(D21)
|
|
171
|
+
const rechecked = alwaysRecheck.has(agent) || !m2Entries.has(agent) ? remember(agent, await precheckM2(detected, precheckDeps)) : null;
|
|
172
|
+
entry = m2Entries.get(agent) ?? null;
|
|
173
|
+
if (!entry) {
|
|
174
|
+
// 读不出(unknown)不等于没配置:不能叫已经配置了 M2 的用户「先添加」(review r5 R5-N5)
|
|
175
|
+
const why = rechecked?.state === 'unknown' ? `couldn't read ${agent}'s MCP config; check again, or use the terminal command` : `add M2 to ${agent}'s MCP config first (m2/configure)`;
|
|
176
|
+
throw new BridgeFailure('auth_unavailable', why);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return buildAuthPlan({
|
|
180
|
+
agent,
|
|
181
|
+
action,
|
|
182
|
+
detected,
|
|
183
|
+
entry,
|
|
184
|
+
cwd: deps.cwd,
|
|
185
|
+
env: deps.probeEnv,
|
|
186
|
+
nodePath: deps.nodePath,
|
|
187
|
+
claudeBinary: agent === 'claude' ? deps.adapters.claudeBinary() : null,
|
|
188
|
+
codexDir: deps.adapters.installDir('codex'),
|
|
189
|
+
});
|
|
190
|
+
},
|
|
108
191
|
};
|
|
109
192
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m2canvas/connect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Connect your local agent clients (Claude Code, Codex, Cursor, OpenCode, Qoder) to M2 Canvas over MCP in one command, and run the local bridge that lets M2 drive them over ACP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|