soclaw-middleware-anything
v1.0.4
Published
Gateway node middleware with manifest-driven CLI commands
Maintainers
Readme
soclaw-middleware-anything
面向 Gateway(WebSocket) 的节点进程:内嵌 Express 健康检查与服务端点,并把远程下发的指令转到本地 命令注册表 执行。命令来源包括包内置清单、环境变量挂载的清单,以及运行时 applyManifestLayer 注入;支持 Node 模块、Python / ES Module 子进程、以及带 mode:"cli" 的 Node 脚本。
运行时要求:Node.js ≥ 18。
仓库结构(设计与分层)
| 路径 | 作用 |
|------|------|
| src/index.js | HTTP 服务入口:端口探测、GatewayClient 建连、优雅退出。 |
| src/gateway-client.js | WebSocket 客户端、与网关协议交互;内部 require('./command') 执行业务命令。 |
| src/cli.js | 本机调试/脚本化调用的 CLI:命令名 + 可选 JSON 参数。 |
| src/command/index.js | 启动时 bootstrapCommandRegistry,并注册若干内置占位命令(如 delete_track)。 |
| src/command/bootstrap-commands.js | 清单解析、include 展开、路径校验、COMMAND_MANIFEST_* 环境变量处理。 |
| src/command/commands.manifest.json | 包内置命令清单(如 cli_ping;可自行扩展 modules)。 |
| src/services/cli-command-registry.js | 内存中的 Map 注册表;execute 分发给函数或 { execute } 对象。 |
| src/services/cli-script-runner.js | .py / .mjs / mode:cli 等子进程处理器封装。 |
| bin/soclaw-middleware-anything.js | npm bin 桩:转调 dist/cli.js(发布后)或构建产物。 |
| scripts/build.mjs | 使用 esbuild 生成 dist/,并把需按路径动态 require 的处理器脚本拷入 dist/command/。 |
整体数据流可概括为(兼容各类 Markdown 渲染器;若需图示可在仓库内用 Mermaid 插件查看):
Gateway (WebSocket) ──► GatewayClient ──► command.execute ◄── bin / dist/cli.js
│
清单 + env + applyManifestLayer ──► bootstrap-commands
│
cli-command-registry ◄── 注册表
├──► Node handler 模块
└──► 子进程(Python / node CLI)安装
npm install soclaw-middleware-anything从 git 克隆 参与开发时,在首次跑测试或链接本地包前请执行 npm run build 生成 dist/(package.json 的 main / exports 指向 dist/;仅从 npm 安装时已含构建产物)。
Windows:npm run start、npm run dev、npm run start:dist 以及 cli 系列脚本已用 cross-env 调用,减少 CMD 与 Unix shell 差异导致的启动问题;业务侧若在脚本里写 FOO=bar node ...,也请改用 cross-env FOO=bar node ...。
作为依赖启动网关节点
在业务项目根目录放置 config.yaml(与运行 node 时的当前工作目录一致;可从包内 config.example.yaml 复制)。也支持环境变量 CONFIG_PATH 指向其它路径。已在环境中的变量优先生效,不被文件覆盖。然后:
node node_modules/soclaw-middleware-anything/dist/index.js或在业务 package.json 的 scripts 中:
{
"scripts": {
"gateway-node": "node node_modules/soclaw-middleware-anything/dist/index.js"
}
}本地开发可直接跑源码(无需先发包):
npm run dev
# 等价于 cross-env nodemon src/index.js(Windows 友好)验证构建产物:
npm run build && npm run start:distCLI 与可执行入口(npm bin)
安装后可通过 bin 字段 暴露的可执行名调用(与 package.json 中 bin 一致,默认可执行名为 soclaw-middleware-anything):
npx soclaw-middleware-anything cli_ping '{"message":"hi"}'
# 全局:npm i -g soclaw-middleware-anything
soclaw-middleware-anything --help实现要点:bin/soclaw-middleware-anything.js 仅负责定位包根目录并 require('.../dist/cli.js')。不要把 command 与 CLI/网关打进同一个 bundle,否则打包后 __dirname 会落在 dist/ 而非 dist/command/,内置清单会错误解析到 dist/cli-ping 等路径。当前仓库的 scripts/build.mjs 已对网关与 CLI 入口将 src/command/index.js 视为 external,运行时改为 require('./command/index.js')。
外部命令与脚本的「动态注入」
无需改包内源码;通过 环境变量、清单文件 或 代码 API 挂载宿主仓库中的 JSON 与脚本即可。
1. 清单文件(推荐)
在宿主项目中新增 JSON,例如 config/commands.json:
{
"version": 2,
"moduleRoot": "./handlers",
"modules": [
{ "command": "my_export", "path": "./export.js" },
{ "command": "my_tool", "path": "./tool.py" },
{ "command": "legacy_ping", "path": "./ping.js", "replace": true }
]
}path相对moduleRoot;moduleRoot相对 本清单文件所在目录。- 旧字段
openMacApp已废弃:若仍存在且非空,加载时会告警并忽略,请改用modules注册命令。 .py/.mjs:走子进程,由cli-script-runner调用解释器,脚本应在 stdout 输出合法 JSON 对象(约定与 CLI 第二参数一致)。.js/.cjs:默认按require模块 加载(须导出函数或{ execute })。.js且"mode":"cli"(或"subprocess"):按node script.js '<json>'子进程执行。- Windows 上支持
.bat/.cmd子进程;非 Windows 会跳过注册并告警。 "replace": true:覆盖此前已注册的同名命令。
启动前设置(COMMAND_MANIFEST_PATH 中路径相对当前 工作目录):
export COMMAND_MANIFEST_PATH=./config/commands.json
# 多个文件:逗号或换行分隔
# export COMMAND_MANIFEST_PATH=./a.json,./b.json可选:对 COMMAND_MANIFEST_JSON 使用 COMMAND_MODULES_ROOT 作为内联 JSON 里相对路径的解析根目录(见下)。
2. 环境变量内联整段 JSON
适合容器 / CI:
export COMMAND_MANIFEST_JSON='{"version":2,"modules":[{"command":"x","path":"./x.js"}]}'
export COMMAND_MODULES_ROOT=./lib说明:内联 JSON 不支持 include(若写了会被忽略并记日志)。
3. 清单组合与 include
include:引入其它 JSON(路径相对当前清单文件),递归展开后按顺序叠加;成环会抛错。moduleRoot:本文件内所有modules[].path的基准目录。
加载顺序:包内置 commands.manifest.json → COMMAND_MANIFEST_PATH 所列文件(依次)→ COMMAND_MANIFEST_JSON。
4. 代码里追加一层(高级)
const command = require('soclaw-middleware-anything/command');
command.applyManifestLayer(
{
version: 2,
modules: [{ command: 'dyn', path: './dyn.js' }]
},
'/absolute/path/to/your/handlers'
);第二个参数为绝对路径目录;每个 path 解析后必须落在该目录内(包内做路径安全校验,防止 .. 穿越)。
程序式子路径导出(发布后路径)
require('soclaw-middleware-anything')→dist/index.js(网关侧效果;注意其启动时会监听 HTTP)。require('soclaw-middleware-anything/command')→ 命令注册与execute、listCommands等。require('soclaw-middleware-anything/command/bootstrap')→bootstrapCommandRegistry、applyManifestLayer、collectLayersFromManifestFile等底层 API。
构建与发布(npm)
为何使用 esbuild 而非 Vite
本包入口长期为 CommonJS(require)。在仅用 Vite 5 的 SSR/Lib 模式打包时,Rollup 往往不展开入口文件里的相对 require 依赖图,产物里会残留 require("./gateway-client") 等引用却未生成对应文件;若强行把整个工程打进单文件,则 command 与 CLI/网关共用入口时 __dirname 会变成 dist/,破坏内置清单对 dist/command/*.js 的解析。
因此发布流采用 esbuild 多入口打包,并对网关 / CLI 将 src/command/index.js 标记为 external,运行时改为 require("./command/index.js"),保证 __dirname === .../dist/command。若未来将源码统一迁到 ESM(import),可以再评估 Vite 或其它打包器。
常用命令
npm run build # 写入 dist/
npm test
npm pack --dry-run # 自检将要发布到 npm 的文件列表(见 package.json files)
npm publishprepack 会在发布前执行 npm run build 并做一次 require('./dist/command') 烟测。
测试说明
| 测试文件 | 覆盖内容 |
|----------|----------|
| src/command/bootstrap-core.test.js | 清单 include 顺序与成环、applyManifestLayer 路径穿越、replace、以及 COMMAND_MANIFEST_PATH 与 bootstrapCommandRegistry 集成。 |
| src/scripts/hello-runtimes.test.js | .cjs 直载、.mjs / mode:cli / .py / .bat(Windows)子进程行为的契约测试。 |
运行:
npm test许可
MIT
