weacpx 0.1.5 → 0.1.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 +43 -19
- package/config.example.json +3 -1
- package/dist/bridge/bridge-main.js +122 -18
- package/dist/cli.js +954 -600
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,14 +1,34 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Weacpx
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
连接微信与 acpx 协议,让 Claude Code / Codex 成为你口袋里的 24/7 伙伴。
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/weacpx)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Why Weacpx?
|
|
12
|
+
|
|
13
|
+
在 Agent-First 的开发模式下,编码任务必须依托顶级 Agents,🙅♀️不要通过 openclaw 去开发,现在有一个更好的方案。Weacpx 通过微信提供一个轻量化的远程入口,随时随地通过手机驱动你的顶级 Agents。
|
|
14
|
+
|
|
15
|
+
Weacpx 的核心价值主张很简单:
|
|
16
|
+
|
|
17
|
+
**随时随地访问** — 只要你有微信,就能控制你的 Agent。无需 VPN、Web 界面或复杂的云服务配置。
|
|
18
|
+
|
|
19
|
+
**统一的会话管理** — 通过 acpx 协议,weacpx 让你在微信里管理多个 Agent 会话(Codex、Claude Code 等),就像在本地终端一样。创建、切换、查询状态,全部通过简单的斜杠命令完成,这是其它简单基于 ACP 实现的远控 agent 所不具备的。
|
|
20
|
+
|
|
21
|
+
**轻量守护进程** — weacpx 作为后台守护进程运行,资源占用极低。不用启动一个臃肿 openclaw,不用担心在工作机器上使用会占用资源。启动、停止、查看状态都通过简单的 CLI 命令完成。
|
|
22
|
+
|
|
23
|
+
**权限可控** — 可以即时通过微信修改 agent 的权限,无论是 YOLO 还是只读。
|
|
4
24
|
|
|
5
25
|
## 安装前准备
|
|
6
26
|
|
|
7
27
|
开始前,至少需要:
|
|
8
28
|
|
|
9
29
|
- Node.js 22+ 或 Bun
|
|
10
|
-
- 一个可用的微信登录环境
|
|
11
30
|
- Claude Code 或 Codex
|
|
31
|
+
- 装了微信的手机
|
|
12
32
|
|
|
13
33
|
> `weacpx` 基于 `weixin-agent-sdk` 与 `acpx` 实现。
|
|
14
34
|
> 正常情况下,不需要再额外全局安装 `acpx`。
|
|
@@ -155,6 +175,8 @@ bun run dev
|
|
|
155
175
|
| `/ss attach <alias> -a <name> --ws <name> --name <transport-session>` | 恢复已存在的会话 |
|
|
156
176
|
| `/use <alias>` | 切换当前会话 |
|
|
157
177
|
| `/status` | 查看当前会话状态 |
|
|
178
|
+
| `/mode` | 查看当前会话已保存的 mode |
|
|
179
|
+
| `/mode <id>` | 设置当前会话 mode,例如 `/mode plan` |
|
|
158
180
|
| `/session reset` | 重置当前会话上下文,保留 alias/agent/workspace,但重新绑定到一个新的后端 session |
|
|
159
181
|
| `/clear` | `/session reset` 的快捷别名 |
|
|
160
182
|
| `/cancel` | 取消当前会话 |
|
|
@@ -165,6 +187,8 @@ bun run dev
|
|
|
165
187
|
- `/ss <agent> -d <path>` 是最常用入口,会自动按目录名推导并创建或复用 workspace,再创建或复用 session
|
|
166
188
|
- `/ss new <agent> -d <path>` 表示强制新建 session
|
|
167
189
|
- `/use <alias>` 用来切换当前会话
|
|
190
|
+
- `/mode` 会显示当前逻辑会话里保存的 mode;如果还没设置过,会显示“未设置”
|
|
191
|
+
- `/mode <id>` 会把 mode 透传给底层 `acpx set-mode`,成功后再写回当前逻辑会话
|
|
168
192
|
- `/session reset` 和 `/clear` 会保留当前逻辑会话名,但重新创建一个新的后端 session,从空上下文重新开始
|
|
169
193
|
- 非 `/` 开头的文本会发送到当前 session
|
|
170
194
|
|
|
@@ -248,12 +272,6 @@ bun run dev
|
|
|
248
272
|
- `~/.weacpx/runtime/stdout.log`
|
|
249
273
|
- `~/.weacpx/runtime/stderr.log`
|
|
250
274
|
|
|
251
|
-
常用环境变量:
|
|
252
|
-
|
|
253
|
-
- `WEACPX_CONFIG`
|
|
254
|
-
- `WEACPX_STATE`
|
|
255
|
-
- `WEACPX_WEIXIN_SDK`
|
|
256
|
-
|
|
257
275
|
### Transport 权限配置
|
|
258
276
|
|
|
259
277
|
`config.json` 中的 `transport` 支持以下权限字段:
|
|
@@ -300,19 +318,25 @@ bun run dev
|
|
|
300
318
|
|
|
301
319
|
## 注意事项
|
|
302
320
|
|
|
303
|
-
###
|
|
321
|
+
### Adapter mode 参考
|
|
304
322
|
|
|
305
|
-
`
|
|
323
|
+
`acpx set-mode` / 计划中的 `/mode <id>` 本质上都是给底层 ACP session 发送 `session/set_mode`。
|
|
324
|
+
这里的 `<id>` 不是 `weacpx` 或 `acpx` 统一规定的枚举,而是**各 adapter 自己定义**的值;填错时通常会收到 adapter 返回的 `Invalid params` 一类错误。
|
|
306
325
|
|
|
307
|
-
|
|
326
|
+
基于 `acpx` 内置 adapter 文档和各上游公开文档,当前能确认的信息如下:
|
|
308
327
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
328
|
+
| adapter | 已确认可用的 mode id | 说明 |
|
|
329
|
+
|------|------|------|
|
|
330
|
+
| `codex` | `plan` | `acpx` 自身示例明确使用过 `acpx codex set-mode plan`。`codex-acp` 还暴露了 `mode` 运行时配置项,但上游目前没有公开一份完整、稳定的 mode id 列表。 |
|
|
331
|
+
| `cursor` | `agent`、`plan`、`ask` | Cursor 官方文档/更新日志公开提到 `Plan mode`、`Ask mode`;Cursor 官方论坛在 ACP `session/configure` 示例中展示过 `availableModes` 为 `agent` / `plan` / `ask`。 |
|
|
332
|
+
| 其他内置 adapter | 暂无公开、稳定的 mode id 列表 | 包括 `claude`、`copilot`、`gemini`、`qoder`、`qwen`、`kimi`、`kiro`、`iflow`、`opencode`、`trae`、`droid`、`kilocode` 等。即使某些产品本身有“Ask / Agent / Plan”之类概念,其 ACP `set-mode` 可接受的精确字符串也往往没有在官方文档中写死。 |
|
|
333
|
+
|
|
334
|
+
建议:
|
|
335
|
+
|
|
336
|
+
- 对 `codex`,优先把 `plan` 当作已知可用值。
|
|
337
|
+
- 对 `cursor`,优先使用 `agent`、`plan`、`ask`。
|
|
338
|
+
- 对其他 adapter,不要在 `weacpx` 里写死候选值;最好把 `/mode <id>` 设计成透传,由 adapter 自己决定是否接受。
|
|
339
|
+
- 如果某个 adapter 后续补充了官方 mode 文档,再把它们补进这里。
|
|
316
340
|
|
|
317
341
|
### 如果 `/ss new` 失败
|
|
318
342
|
|
package/config.example.json
CHANGED
|
@@ -4,25 +4,43 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
7
12
|
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
8
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
22
|
for (let key of __getOwnPropNames(mod))
|
|
11
23
|
if (!__hasOwnProp.call(to, key))
|
|
12
24
|
__defProp(to, key, {
|
|
13
|
-
get: (
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
14
26
|
enumerable: true
|
|
15
27
|
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
16
30
|
return to;
|
|
17
31
|
};
|
|
18
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
19
37
|
var __export = (target, all) => {
|
|
20
38
|
for (var name in all)
|
|
21
39
|
__defProp(target, name, {
|
|
22
40
|
get: all[name],
|
|
23
41
|
enumerable: true,
|
|
24
42
|
configurable: true,
|
|
25
|
-
set: (
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
26
44
|
});
|
|
27
45
|
};
|
|
28
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -181,14 +199,28 @@ import { createInterface } from "node:readline";
|
|
|
181
199
|
// src/bridge/bridge-server.ts
|
|
182
200
|
init_prompt_output();
|
|
183
201
|
|
|
202
|
+
class BridgeInvalidRequestError extends Error {
|
|
203
|
+
}
|
|
204
|
+
var BRIDGE_METHODS = new Set([
|
|
205
|
+
"ping",
|
|
206
|
+
"shutdown",
|
|
207
|
+
"hasSession",
|
|
208
|
+
"ensureSession",
|
|
209
|
+
"prompt",
|
|
210
|
+
"setMode",
|
|
211
|
+
"cancel"
|
|
212
|
+
]);
|
|
213
|
+
|
|
184
214
|
class BridgeServer {
|
|
185
215
|
runtime;
|
|
186
216
|
constructor(runtime) {
|
|
187
217
|
this.runtime = runtime;
|
|
188
218
|
}
|
|
189
219
|
async handleLine(line) {
|
|
190
|
-
|
|
220
|
+
let requestId = extractRequestId(line);
|
|
191
221
|
try {
|
|
222
|
+
const request = parseBridgeRequest(line);
|
|
223
|
+
requestId = request.id;
|
|
192
224
|
const result = await this.dispatch(request.method, request.params);
|
|
193
225
|
return `${JSON.stringify({
|
|
194
226
|
id: request.id,
|
|
@@ -199,10 +231,10 @@ class BridgeServer {
|
|
|
199
231
|
} catch (error) {
|
|
200
232
|
const message = error instanceof Error ? error.message : String(error);
|
|
201
233
|
return `${JSON.stringify({
|
|
202
|
-
id:
|
|
234
|
+
id: requestId,
|
|
203
235
|
ok: false,
|
|
204
236
|
error: {
|
|
205
|
-
code: "BRIDGE_INTERNAL_ERROR",
|
|
237
|
+
code: error instanceof BridgeInvalidRequestError ? "BRIDGE_INVALID_REQUEST" : "BRIDGE_INTERNAL_ERROR",
|
|
206
238
|
message,
|
|
207
239
|
...error instanceof PromptCommandError ? {
|
|
208
240
|
details: {
|
|
@@ -224,38 +256,97 @@ class BridgeServer {
|
|
|
224
256
|
return await this.runtime.shutdown();
|
|
225
257
|
case "hasSession":
|
|
226
258
|
return await this.runtime.hasSession({
|
|
227
|
-
agent:
|
|
259
|
+
agent: requireString(params, "agent"),
|
|
228
260
|
agentCommand: asOptionalString(params.agentCommand),
|
|
229
|
-
cwd:
|
|
230
|
-
name:
|
|
261
|
+
cwd: requireString(params, "cwd"),
|
|
262
|
+
name: requireString(params, "name")
|
|
231
263
|
});
|
|
232
264
|
case "ensureSession":
|
|
233
265
|
return await this.runtime.ensureSession({
|
|
234
|
-
agent:
|
|
266
|
+
agent: requireString(params, "agent"),
|
|
235
267
|
agentCommand: asOptionalString(params.agentCommand),
|
|
236
|
-
cwd:
|
|
237
|
-
name:
|
|
268
|
+
cwd: requireString(params, "cwd"),
|
|
269
|
+
name: requireString(params, "name")
|
|
238
270
|
});
|
|
239
271
|
case "prompt":
|
|
240
272
|
return await this.runtime.prompt({
|
|
241
|
-
agent:
|
|
273
|
+
agent: requireString(params, "agent"),
|
|
274
|
+
agentCommand: asOptionalString(params.agentCommand),
|
|
275
|
+
cwd: requireString(params, "cwd"),
|
|
276
|
+
name: requireString(params, "name"),
|
|
277
|
+
text: requireString(params, "text")
|
|
278
|
+
});
|
|
279
|
+
case "setMode":
|
|
280
|
+
return await this.runtime.setMode({
|
|
281
|
+
agent: requireString(params, "agent"),
|
|
242
282
|
agentCommand: asOptionalString(params.agentCommand),
|
|
243
|
-
cwd:
|
|
244
|
-
name:
|
|
245
|
-
|
|
283
|
+
cwd: requireString(params, "cwd"),
|
|
284
|
+
name: requireString(params, "name"),
|
|
285
|
+
modeId: requireString(params, "modeId")
|
|
246
286
|
});
|
|
247
287
|
case "cancel":
|
|
248
288
|
return await this.runtime.cancel({
|
|
249
|
-
agent:
|
|
289
|
+
agent: requireString(params, "agent"),
|
|
250
290
|
agentCommand: asOptionalString(params.agentCommand),
|
|
251
|
-
cwd:
|
|
252
|
-
name:
|
|
291
|
+
cwd: requireString(params, "cwd"),
|
|
292
|
+
name: requireString(params, "name")
|
|
253
293
|
});
|
|
254
294
|
default:
|
|
255
295
|
throw new Error(`unsupported bridge method: ${method}`);
|
|
256
296
|
}
|
|
257
297
|
}
|
|
258
298
|
}
|
|
299
|
+
function extractRequestId(line) {
|
|
300
|
+
try {
|
|
301
|
+
const raw = JSON.parse(line);
|
|
302
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
303
|
+
return "unknown";
|
|
304
|
+
}
|
|
305
|
+
const id = raw.id;
|
|
306
|
+
return typeof id === "string" && id.length > 0 ? id : "unknown";
|
|
307
|
+
} catch {
|
|
308
|
+
return "unknown";
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function parseBridgeRequest(line) {
|
|
312
|
+
let raw;
|
|
313
|
+
try {
|
|
314
|
+
raw = JSON.parse(line);
|
|
315
|
+
} catch {
|
|
316
|
+
throw new BridgeInvalidRequestError("request must be valid JSON");
|
|
317
|
+
}
|
|
318
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
319
|
+
throw new BridgeInvalidRequestError("request must be a JSON object");
|
|
320
|
+
}
|
|
321
|
+
const request = raw;
|
|
322
|
+
const id = request.id;
|
|
323
|
+
const method = request.method;
|
|
324
|
+
const params = request.params;
|
|
325
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
326
|
+
throw new BridgeInvalidRequestError("id must be a non-empty string");
|
|
327
|
+
}
|
|
328
|
+
if (typeof method !== "string" || method.length === 0) {
|
|
329
|
+
throw new BridgeInvalidRequestError("method must be a non-empty string");
|
|
330
|
+
}
|
|
331
|
+
if (!BRIDGE_METHODS.has(method)) {
|
|
332
|
+
throw new BridgeInvalidRequestError(`unsupported bridge method: ${method}`);
|
|
333
|
+
}
|
|
334
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
335
|
+
throw new BridgeInvalidRequestError("params must be an object");
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
id,
|
|
339
|
+
method,
|
|
340
|
+
params
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function requireString(params, key) {
|
|
344
|
+
const value = params[key];
|
|
345
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
346
|
+
throw new BridgeInvalidRequestError(`${key} must be a non-empty string`);
|
|
347
|
+
}
|
|
348
|
+
return value;
|
|
349
|
+
}
|
|
259
350
|
function asOptionalString(value) {
|
|
260
351
|
if (typeof value !== "string" || value.length === 0) {
|
|
261
352
|
return;
|
|
@@ -322,6 +413,19 @@ class BridgeRuntime {
|
|
|
322
413
|
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
323
414
|
return { text: getPromptText(result) };
|
|
324
415
|
}
|
|
416
|
+
async setMode(input) {
|
|
417
|
+
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
418
|
+
"set-mode",
|
|
419
|
+
"-s",
|
|
420
|
+
input.name,
|
|
421
|
+
input.modeId
|
|
422
|
+
]));
|
|
423
|
+
const result = await this.run(spawnSpec.command, spawnSpec.args);
|
|
424
|
+
if (result.code !== 0) {
|
|
425
|
+
throw new Error(result.stderr || result.stdout || "set-mode failed");
|
|
426
|
+
}
|
|
427
|
+
return {};
|
|
428
|
+
}
|
|
325
429
|
async cancel(input) {
|
|
326
430
|
const spawnSpec = resolveSpawnCommand(this.command, this.buildSessionArgs(input, [
|
|
327
431
|
"cancel",
|