openclaw-plugin-grinders-farm 0.3.0 → 0.3.2
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 +58 -30
- package/dist/index.js +63 -22
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,35 +1,54 @@
|
|
|
1
1
|
# openclaw-plugin-grinders-farm
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`Grinder's Farm` 的 OpenClaw 插件。
|
|
4
|
+
安装后你可以直接在聊天里使用 `/farm ...` 命令游玩,并开启自动推进与多频道推送。
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
- `grinders_farm` tool (for skill `command-dispatch: tool`)
|
|
7
|
-
|
|
8
|
-
## Install (End User)
|
|
6
|
+
## 安装
|
|
9
7
|
|
|
10
8
|
```bash
|
|
11
9
|
openclaw plugins install openclaw-plugin-grinders-farm --dangerously-force-unsafe-install
|
|
12
10
|
```
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
安装完成后,确认已加载:
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
```bash
|
|
15
|
+
openclaw plugins inspect grinders-farm
|
|
16
|
+
```
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## 使用流程(推荐)
|
|
19
19
|
|
|
20
|
-
1.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
1. 在目标频道发送一次:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
/farm farm
|
|
24
|
+
```
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
2. 开始自动推进:
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
```text
|
|
29
|
+
/farm start
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
3. 停止自动推进:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
/farm stop
|
|
36
|
+
```
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
说明:
|
|
31
39
|
|
|
32
|
-
|
|
40
|
+
- `start` 固定每 20 分钟推进一天
|
|
41
|
+
- 会自动推送到所有已绑定频道(先发送过 `/farm ...` 的频道)
|
|
42
|
+
|
|
43
|
+
## 常用配置
|
|
44
|
+
|
|
45
|
+
你可以在 OpenClaw 的 plugin 配置里设置:
|
|
46
|
+
|
|
47
|
+
- `autoStartWorkerOnGatewayBoot`:Gateway 启动时是否自动启动推进
|
|
48
|
+
- `imageServerPort`:本地农场图片服务端口
|
|
49
|
+
- `gameRoot`:当自动定位失败时,手动指定项目根目录
|
|
50
|
+
|
|
51
|
+
最小配置示例:
|
|
33
52
|
|
|
34
53
|
```json
|
|
35
54
|
{
|
|
@@ -47,18 +66,27 @@ If only CLI mode is available, plugin still works and can auto-run `grinders-far
|
|
|
47
66
|
}
|
|
48
67
|
```
|
|
49
68
|
|
|
50
|
-
|
|
69
|
+
## 常见问题
|
|
51
70
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"gameRoot": "/path/to/grinders-farm"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
71
|
+
### 插件已安装但命令不可用
|
|
72
|
+
|
|
73
|
+
先检查:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
openclaw plugins inspect grinders-farm
|
|
64
77
|
```
|
|
78
|
+
|
|
79
|
+
如果不是 `loaded`,重启 Gateway 后再试。
|
|
80
|
+
|
|
81
|
+
### 自动推送没有发到某个频道
|
|
82
|
+
|
|
83
|
+
在该频道先执行一次 `/farm farm` 完成绑定,再执行 `/farm start`。
|
|
84
|
+
|
|
85
|
+
### 找不到 `grinders-farm` 可执行文件
|
|
86
|
+
|
|
87
|
+
可设置环境变量:
|
|
88
|
+
|
|
89
|
+
- `GRINDERS_FARM_ROOT`
|
|
90
|
+
- `GRINDERS_FARM_CLI_BIN`
|
|
91
|
+
|
|
92
|
+
然后重启 Gateway。
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,8 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
10
10
|
const FARM_IMAGE_PATH = path.join(os.homedir(), ".grinders-farm", "farm.png");
|
|
11
11
|
const OPENCLAW_MEDIA_DIR = path.join(os.homedir(), ".openclaw", "media", "grinders-farm");
|
|
12
12
|
const ONESHOT_RELATIVE_PATH = path.join("src", "adapters", "oneshot.ts");
|
|
13
|
-
const DEFAULT_START_INTERVAL_SEC = 20;
|
|
13
|
+
const DEFAULT_START_INTERVAL_SEC = 20 * 60;
|
|
14
|
+
const ONESHOT_TIMEOUT_MS = 15_000;
|
|
14
15
|
function resolveGameRoot(api) {
|
|
15
16
|
const raw = api.pluginConfig;
|
|
16
17
|
const fromEnv = process.env.GRINDERS_FARM_ROOT?.trim();
|
|
@@ -25,16 +26,42 @@ function isValidGameRoot(candidate) {
|
|
|
25
26
|
}
|
|
26
27
|
function resolveClawFarmCliBin() {
|
|
27
28
|
const fromEnv = process.env.GRINDERS_FARM_CLI_BIN?.trim();
|
|
28
|
-
if (fromEnv)
|
|
29
|
-
|
|
29
|
+
if (fromEnv) {
|
|
30
|
+
if (fromEnv.endsWith("/dist/src/adapters/terminal.js")) {
|
|
31
|
+
const oneshotJs = fromEnv.replace(/terminal\.js$/, "oneshot.js");
|
|
32
|
+
if (fs.existsSync(oneshotJs)) {
|
|
33
|
+
return { bin: process.execPath, binArgsPrefix: [oneshotJs] };
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return fromEnv.endsWith(".js") ? { bin: process.execPath, binArgsPrefix: [fromEnv] } : { bin: fromEnv, binArgsPrefix: [] };
|
|
38
|
+
}
|
|
30
39
|
try {
|
|
40
|
+
const oneshotWhich = spawnSync("sh", ["-lc", "command -v grinders-farm-oneshot"], {
|
|
41
|
+
encoding: "utf8",
|
|
42
|
+
maxBuffer: 1024 * 1024,
|
|
43
|
+
});
|
|
44
|
+
const oneshotOut = (oneshotWhich.stdout ?? "").trim();
|
|
45
|
+
if (oneshotOut)
|
|
46
|
+
return { bin: oneshotOut, binArgsPrefix: [] };
|
|
31
47
|
const which = spawnSync("sh", ["-lc", "command -v grinders-farm"], {
|
|
32
48
|
encoding: "utf8",
|
|
33
49
|
maxBuffer: 1024 * 1024,
|
|
34
50
|
});
|
|
35
51
|
const out = (which.stdout ?? "").trim();
|
|
36
|
-
if (out)
|
|
37
|
-
|
|
52
|
+
if (out) {
|
|
53
|
+
if (out.endsWith("/dist/src/adapters/terminal.js")) {
|
|
54
|
+
const oneshotJs = out.replace(/terminal\.js$/, "oneshot.js");
|
|
55
|
+
if (fs.existsSync(oneshotJs)) {
|
|
56
|
+
return { bin: process.execPath, binArgsPrefix: [oneshotJs] };
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (out.endsWith(".js")) {
|
|
61
|
+
return { bin: process.execPath, binArgsPrefix: [out] };
|
|
62
|
+
}
|
|
63
|
+
return { bin: out, binArgsPrefix: [] };
|
|
64
|
+
}
|
|
38
65
|
}
|
|
39
66
|
catch {
|
|
40
67
|
// ignore
|
|
@@ -48,7 +75,7 @@ function resolveRuntimeMode(api) {
|
|
|
48
75
|
}
|
|
49
76
|
const bin = resolveClawFarmCliBin();
|
|
50
77
|
if (bin) {
|
|
51
|
-
return { kind: "cli-bin", bin };
|
|
78
|
+
return { kind: "cli-bin", bin: bin.bin, binArgsPrefix: bin.binArgsPrefix };
|
|
52
79
|
}
|
|
53
80
|
return null;
|
|
54
81
|
}
|
|
@@ -61,29 +88,43 @@ function getPluginTiming(api) {
|
|
|
61
88
|
function runOneshot(mode, argsLine) {
|
|
62
89
|
const argv = argsLine.trim().length > 0 ? argsLine.trim().split(/\s+/) : ["farm"];
|
|
63
90
|
const resolvedOpenclawBin = resolveOpenclawBin();
|
|
91
|
+
const env = {
|
|
92
|
+
...process.env,
|
|
93
|
+
OPENCLAW_BIN: process.env.OPENCLAW_BIN?.trim() || resolvedOpenclawBin,
|
|
94
|
+
};
|
|
64
95
|
const result = mode.kind === "repo-tsx"
|
|
65
96
|
? spawnSync("npx", ["tsx", path.join(mode.gameRoot, ONESHOT_RELATIVE_PATH), ...argv], {
|
|
66
97
|
cwd: mode.gameRoot,
|
|
67
98
|
encoding: "utf8",
|
|
68
99
|
maxBuffer: 8 * 1024 * 1024,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
OPENCLAW_BIN: process.env.OPENCLAW_BIN?.trim() || resolvedOpenclawBin,
|
|
72
|
-
},
|
|
100
|
+
timeout: ONESHOT_TIMEOUT_MS,
|
|
101
|
+
env,
|
|
73
102
|
})
|
|
74
|
-
: spawnSync(mode.bin, argv, {
|
|
103
|
+
: spawnSync(mode.bin, [...mode.binArgsPrefix, ...argv], {
|
|
75
104
|
encoding: "utf8",
|
|
76
105
|
maxBuffer: 8 * 1024 * 1024,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
OPENCLAW_BIN: process.env.OPENCLAW_BIN?.trim() || resolvedOpenclawBin,
|
|
80
|
-
},
|
|
106
|
+
timeout: ONESHOT_TIMEOUT_MS,
|
|
107
|
+
env,
|
|
81
108
|
});
|
|
82
|
-
const
|
|
83
|
-
|
|
109
|
+
const finalResult = result.error && result.error.code === "ENOEXEC"
|
|
110
|
+
? spawnSync(process.execPath, [mode.kind === "cli-bin" ? mode.bin : "", ...(mode.kind === "cli-bin" ? mode.binArgsPrefix : []), ...argv].filter(Boolean), {
|
|
111
|
+
encoding: "utf8",
|
|
112
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
113
|
+
timeout: ONESHOT_TIMEOUT_MS,
|
|
114
|
+
env,
|
|
115
|
+
})
|
|
116
|
+
: result;
|
|
117
|
+
const stderr = finalResult.stderr?.trim() ?? "";
|
|
118
|
+
const stdout = finalResult.stdout?.trim() ?? "";
|
|
84
119
|
const combined = [stdout, stderr].filter(Boolean).join("\n");
|
|
85
|
-
const code =
|
|
86
|
-
|
|
120
|
+
const code = finalResult.status ?? (finalResult.error ? 1 : 0);
|
|
121
|
+
if (finalResult.error && finalResult.error.code === "ETIMEDOUT") {
|
|
122
|
+
return {
|
|
123
|
+
text: "grinders-farm 命令执行超时。请确认你安装的是支持 oneshot 的新版 grinders-farm(含 grinders-farm-oneshot),或在插件 config 里设置 gameRoot 指向源码仓库。",
|
|
124
|
+
exitCode: 1,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return { text: combined || (finalResult.error ? String(finalResult.error.message) : "(no output)"), exitCode: code };
|
|
87
128
|
}
|
|
88
129
|
function isTelegramChannel(ctx) {
|
|
89
130
|
const surface = (ctx.channel ?? ctx.channelId ?? "").toString().trim().toLowerCase();
|
|
@@ -247,7 +288,7 @@ function createFarmTool(mode) {
|
|
|
247
288
|
const plugin = {
|
|
248
289
|
id: "grinders-farm",
|
|
249
290
|
name: "Grinder's Farm",
|
|
250
|
-
description: "Local grinders-farm subprocess
|
|
291
|
+
description: "Local grinders-farm subprocess for farm commands and push.",
|
|
251
292
|
register(api) {
|
|
252
293
|
const runtime = resolveRuntimeMode(api);
|
|
253
294
|
if (!runtime) {
|
|
@@ -257,7 +298,7 @@ const plugin = {
|
|
|
257
298
|
api.registerTool(createFarmTool(runtime));
|
|
258
299
|
api.registerCommand({
|
|
259
300
|
name: "farm",
|
|
260
|
-
description: "Grinder's Farm — run a game command
|
|
301
|
+
description: "Grinder's Farm — run a game command and bind this chat for farm push.",
|
|
261
302
|
acceptsArgs: true,
|
|
262
303
|
handler: async (ctx) => {
|
|
263
304
|
const bound = await bindDeliveryFromPluginCommand(ctx);
|
|
@@ -296,7 +337,7 @@ const plugin = {
|
|
|
296
337
|
return;
|
|
297
338
|
const autoStart = runOneshot(runtime, "start");
|
|
298
339
|
if (autoStart.exitCode === 0) {
|
|
299
|
-
api.logger?.info?.("grinders-farm: 已通过 CLI 模式启动自动推进(固定
|
|
340
|
+
api.logger?.info?.("grinders-farm: 已通过 CLI 模式启动自动推进(固定 20 分钟)");
|
|
300
341
|
}
|
|
301
342
|
else {
|
|
302
343
|
api.logger?.warn?.(`grinders-farm: CLI 模式自动推进启动失败:${autoStart.text}`);
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "grinders-farm",
|
|
3
3
|
"name": "Grinder's Farm",
|
|
4
|
-
"description": "Local grinders-farm plugin: /farm and grinders_farm
|
|
4
|
+
"description": "Local grinders-farm plugin: /farm command and grinders_farm tool with local game state and push.",
|
|
5
5
|
"configSchema": {
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-plugin-grinders-farm",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "OpenClaw plugin for Grinder's Farm: deterministic /farm command and grinders_farm tool.",
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
"openclaw": ">=2026.1.0"
|
|
25
25
|
},
|
|
26
26
|
"openclaw": {
|
|
27
|
-
"extensions": [
|
|
27
|
+
"extensions": [
|
|
28
|
+
"./dist/index.js"
|
|
29
|
+
]
|
|
28
30
|
}
|
|
29
31
|
}
|