forge-openclaw-plugin 0.2.11 → 0.2.13

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
@@ -72,21 +72,22 @@ Current OpenClaw builds should use package discovery:
72
72
 
73
73
  ```bash
74
74
  openclaw plugins install forge-openclaw-plugin
75
- openclaw gateway restart
76
- ```
77
-
78
- If your OpenClaw install does not enable it automatically, run:
79
-
80
- ```bash
81
75
  openclaw plugins enable forge-openclaw-plugin
76
+ node -e 'const fs=require("fs"); const p=process.env.HOME+"/.openclaw/openclaw.json"; const j=JSON.parse(fs.readFileSync(p,"utf8")); j.plugins ??= {}; j.plugins.allow = Array.from(new Set([...(j.plugins.allow || []), "forge-openclaw-plugin"])); fs.writeFileSync(p, JSON.stringify(j, null, 2)+"\n");'
82
77
  openclaw gateway restart
78
+ openclaw forge health
83
79
  ```
84
80
 
81
+ `openclaw plugins enable forge-openclaw-plugin` marks the plugin enabled, but it does not guarantee that `plugins.allow` was repaired. The `node -e ...` command above preserves the current allow list and appends `"forge-openclaw-plugin"` if it is missing.
82
+
85
83
  For release-parity local development from this repo:
86
84
 
87
85
  ```bash
88
86
  openclaw plugins install ./projects/forge/openclaw-plugin
87
+ openclaw plugins enable forge-openclaw-plugin
88
+ node -e 'const fs=require("fs"); const p=process.env.HOME+"/.openclaw/openclaw.json"; const j=JSON.parse(fs.readFileSync(p,"utf8")); j.plugins ??= {}; j.plugins.allow = Array.from(new Set([...(j.plugins.allow || []), "forge-openclaw-plugin"])); fs.writeFileSync(p, JSON.stringify(j, null, 2)+"\n");'
89
89
  openclaw gateway restart
90
+ openclaw forge health
90
91
  ```
91
92
 
92
93
  Equivalent config:
@@ -202,13 +203,26 @@ The skill is entity-format-driven. It teaches the agent how to:
202
203
 
203
204
  For local use, set the plugin origin to `http://127.0.0.1` or `http://localhost` and the plugin will bring Forge up on the configured port automatically.
204
205
 
205
- If you want to stop that plugin-managed local runtime cleanly, use:
206
+ If you want to manage that plugin-managed local runtime cleanly, use:
206
207
 
207
208
  ```bash
208
- forge stop
209
+ openclaw forge start
210
+ openclaw forge stop
211
+ openclaw forge restart
212
+ openclaw forge status
209
213
  ```
210
214
 
211
- This only stops the runtime when it was auto-started by the OpenClaw plugin. If Forge was started manually some other way, `forge stop` tells you that instead of killing unrelated processes.
215
+ These commands only manage the runtime when it was auto-started by the OpenClaw plugin. If Forge was started manually some other way, they tell you that instead of killing unrelated processes.
216
+
217
+ If the local runtime fails to come up, check the plugin-managed runtime log at:
218
+
219
+ ```bash
220
+ ~/.openclaw/logs/forge-openclaw-plugin/127.0.0.1-4317.log
221
+ ```
222
+
223
+ On clean installs, the plugin now also repairs missing bundled runtime dependencies on first local start before it launches Forge.
224
+
225
+ The startup error now points at that log file when the child process exits before Forge becomes healthy.
212
226
 
213
227
  ## Publishing and listing
214
228
 
@@ -6,6 +6,34 @@ export type ForgeRuntimeStopResult = {
6
6
  message: string;
7
7
  pid: number | null;
8
8
  };
9
+ export type ForgeRuntimeStartResult = {
10
+ ok: boolean;
11
+ started: boolean;
12
+ managed: boolean;
13
+ message: string;
14
+ pid: number | null;
15
+ baseUrl: string;
16
+ };
17
+ export type ForgeRuntimeStatusResult = {
18
+ ok: boolean;
19
+ running: boolean;
20
+ healthy: boolean;
21
+ managed: boolean;
22
+ message: string;
23
+ pid: number | null;
24
+ baseUrl: string;
25
+ };
26
+ export type ForgeRuntimeRestartResult = {
27
+ ok: boolean;
28
+ restarted: boolean;
29
+ managed: boolean;
30
+ message: string;
31
+ pid: number | null;
32
+ baseUrl: string;
33
+ };
9
34
  export declare function ensureForgeRuntimeReady(config: ForgePluginConfig): Promise<void>;
35
+ export declare function startForgeRuntime(config: ForgePluginConfig): Promise<ForgeRuntimeStartResult>;
10
36
  export declare function primeForgeRuntime(config: ForgePluginConfig): void;
11
37
  export declare function stopForgeRuntime(config: ForgePluginConfig): Promise<ForgeRuntimeStopResult>;
38
+ export declare function getForgeRuntimeStatus(config: ForgePluginConfig): Promise<ForgeRuntimeStatusResult>;
39
+ export declare function restartForgeRuntime(config: ForgePluginConfig): Promise<ForgeRuntimeRestartResult>;
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
- import { existsSync } from "node:fs";
2
+ import { closeSync, existsSync, mkdirSync, openSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import path from "node:path";
5
5
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
@@ -10,7 +10,10 @@ const HEALTHCHECK_TIMEOUT_MS = 1_500;
10
10
  const HEALTHCHECK_INTERVAL_MS = 250;
11
11
  let managedRuntimeChild = null;
12
12
  let managedRuntimeKey = null;
13
+ let managedRuntimeLogPath = null;
14
+ let lastRuntimeExitDetails = null;
13
15
  let startupPromise = null;
16
+ const dependencyInstallPromises = new Map();
14
17
  function runtimeKey(config) {
15
18
  return `${config.origin}:${config.port}`;
16
19
  }
@@ -26,7 +29,8 @@ async function writeRuntimeState(config, pid) {
26
29
  origin: config.origin,
27
30
  port: config.port,
28
31
  baseUrl: config.baseUrl,
29
- startedAt: new Date().toISOString()
32
+ startedAt: new Date().toISOString(),
33
+ logPath: managedRuntimeLogPath
30
34
  };
31
35
  await writeFile(statePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
32
36
  }
@@ -45,7 +49,8 @@ async function readRuntimeState(config) {
45
49
  origin: typeof parsed.origin === "string" ? parsed.origin : config.origin,
46
50
  port: typeof parsed.port === "number" ? parsed.port : config.port,
47
51
  baseUrl: typeof parsed.baseUrl === "string" ? parsed.baseUrl : config.baseUrl,
48
- startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : new Date(0).toISOString()
52
+ startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : new Date(0).toISOString(),
53
+ logPath: typeof parsed.logPath === "string" ? parsed.logPath : null
49
54
  };
50
55
  }
51
56
  catch {
@@ -82,6 +87,86 @@ function isLocalOrigin(origin) {
82
87
  function getCurrentModuleRoot() {
83
88
  return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
84
89
  }
90
+ function getRuntimeLogPath(config) {
91
+ const origin = new URL(config.origin).hostname.toLowerCase().replace(/[^a-z0-9._-]+/g, "-");
92
+ return path.join(homedir(), ".openclaw", "logs", "forge-openclaw-plugin", `${origin}-${config.port}.log`);
93
+ }
94
+ function openRuntimeLogFile(logPath) {
95
+ mkdirSync(path.dirname(logPath), { recursive: true });
96
+ return openSync(logPath, "a");
97
+ }
98
+ function isPackagedServerPlan(plan) {
99
+ return plan.entryFile.endsWith(path.join("dist", "server", "index.js"));
100
+ }
101
+ function getNpmInvocation() {
102
+ const binDir = path.dirname(process.execPath);
103
+ const npmCli = process.platform === "win32" ? path.join(binDir, "npm.cmd") : path.join(binDir, "npm");
104
+ if (existsSync(npmCli)) {
105
+ return {
106
+ command: process.execPath,
107
+ args: [npmCli]
108
+ };
109
+ }
110
+ return {
111
+ command: "npm",
112
+ args: []
113
+ };
114
+ }
115
+ async function getMissingRuntimeDependencies(packageRoot) {
116
+ const packageJsonPath = path.join(packageRoot, "package.json");
117
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
118
+ const dependencyNames = Object.keys(packageJson.dependencies ?? {});
119
+ return dependencyNames.filter((dependencyName) => !existsSync(path.join(packageRoot, "node_modules", dependencyName, "package.json")));
120
+ }
121
+ async function installMissingRuntimeDependencies(packageRoot, logPath) {
122
+ const { command, args } = getNpmInvocation();
123
+ const logFd = openRuntimeLogFile(logPath);
124
+ try {
125
+ await new Promise((resolve, reject) => {
126
+ const child = spawn(command, [...args, "install", "--omit=dev", "--silent", "--ignore-scripts"], {
127
+ cwd: packageRoot,
128
+ env: process.env,
129
+ stdio: ["ignore", logFd, logFd]
130
+ });
131
+ child.once("error", reject);
132
+ child.once("exit", (code, signal) => {
133
+ if (code === 0) {
134
+ resolve();
135
+ return;
136
+ }
137
+ reject(new Error(`npm dependency install exited with ${signal ? `signal ${signal}` : `code ${code ?? "unknown"}`}`));
138
+ });
139
+ });
140
+ }
141
+ finally {
142
+ closeSync(logFd);
143
+ }
144
+ }
145
+ async function ensurePackagedRuntimeDependencies(plan, config) {
146
+ if (!isPackagedServerPlan(plan)) {
147
+ return;
148
+ }
149
+ const missingDependencies = await getMissingRuntimeDependencies(plan.packageRoot);
150
+ if (missingDependencies.length === 0) {
151
+ return;
152
+ }
153
+ const logPath = getRuntimeLogPath(config);
154
+ managedRuntimeLogPath = logPath;
155
+ const installKey = plan.packageRoot;
156
+ const existingInstall = dependencyInstallPromises.get(installKey);
157
+ if (existingInstall) {
158
+ return existingInstall;
159
+ }
160
+ const installPromise = installMissingRuntimeDependencies(plan.packageRoot, logPath)
161
+ .catch((error) => {
162
+ throw new Error(`Forge runtime dependencies are missing (${missingDependencies.join(", ")}) and automatic install failed. Check logs at ${logPath}. Cause: ${error instanceof Error ? error.message : String(error)}`);
163
+ })
164
+ .finally(() => {
165
+ dependencyInstallPromises.delete(installKey);
166
+ });
167
+ dependencyInstallPromises.set(installKey, installPromise);
168
+ return installPromise;
169
+ }
85
170
  function resolveLaunchPlan() {
86
171
  const moduleRoot = getCurrentModuleRoot();
87
172
  // Published or linked plugin package runtime.
@@ -126,8 +211,10 @@ async function isForgeHealthy(config, timeoutMs) {
126
211
  }
127
212
  }
128
213
  function spawnManagedRuntime(config, plan) {
129
- const isPackagedServer = plan.entryFile.endsWith(path.join("dist", "server", "index.js"));
214
+ const isPackagedServer = isPackagedServerPlan(plan);
130
215
  const args = isPackagedServer ? [plan.entryFile] : [plan.entryFile, path.join(plan.packageRoot, "server", "src", "index.ts")];
216
+ const logPath = getRuntimeLogPath(config);
217
+ const logFd = openRuntimeLogFile(logPath);
131
218
  const child = spawn(process.execPath, args, {
132
219
  cwd: plan.packageRoot,
133
220
  env: {
@@ -137,11 +224,20 @@ function spawnManagedRuntime(config, plan) {
137
224
  FORGE_BASE_PATH: "/forge/",
138
225
  ...(config.dataRoot ? { FORGE_DATA_ROOT: config.dataRoot } : {})
139
226
  },
140
- stdio: "ignore",
227
+ stdio: ["ignore", logFd, logFd],
141
228
  detached: true
142
229
  });
230
+ closeSync(logFd);
143
231
  child.unref();
144
- child.once("exit", () => {
232
+ managedRuntimeLogPath = logPath;
233
+ lastRuntimeExitDetails = null;
234
+ child.once("exit", (code, signal) => {
235
+ lastRuntimeExitDetails = {
236
+ pid: child.pid ?? -1,
237
+ code,
238
+ signal,
239
+ logPath
240
+ };
145
241
  if (managedRuntimeChild === child) {
146
242
  managedRuntimeChild = null;
147
243
  managedRuntimeKey = null;
@@ -154,15 +250,31 @@ function spawnManagedRuntime(config, plan) {
154
250
  // State tracking is best effort. Runtime health checks remain authoritative.
155
251
  });
156
252
  }
157
- async function waitForRuntime(config, timeoutMs) {
253
+ function formatRuntimeFailure(details, config) {
254
+ if (!details) {
255
+ return `Forge local runtime did not become healthy at ${config.baseUrl} within ${STARTUP_TIMEOUT_MS}ms`;
256
+ }
257
+ const suffix = details.logPath ? ` Check logs at ${details.logPath}.` : "";
258
+ if (details.signal) {
259
+ return `Forge local runtime exited before becoming healthy at ${config.baseUrl} (signal ${details.signal}).${suffix}`;
260
+ }
261
+ if (typeof details.code === "number") {
262
+ return `Forge local runtime exited before becoming healthy at ${config.baseUrl} (code ${details.code}).${suffix}`;
263
+ }
264
+ return `Forge local runtime exited before becoming healthy at ${config.baseUrl}.${suffix}`;
265
+ }
266
+ async function waitForRuntime(config, timeoutMs, expectedPid) {
158
267
  const deadline = Date.now() + timeoutMs;
159
268
  while (Date.now() < deadline) {
160
269
  if (await isForgeHealthy(config, HEALTHCHECK_TIMEOUT_MS)) {
161
270
  return;
162
271
  }
272
+ if (expectedPid !== null && lastRuntimeExitDetails?.pid === expectedPid) {
273
+ throw new Error(formatRuntimeFailure(lastRuntimeExitDetails, config));
274
+ }
163
275
  await new Promise((resolve) => setTimeout(resolve, HEALTHCHECK_INTERVAL_MS));
164
276
  }
165
- throw new Error(`Forge local runtime did not become healthy at ${config.baseUrl} within ${timeoutMs}ms`);
277
+ throw new Error(formatRuntimeFailure(lastRuntimeExitDetails, config));
166
278
  }
167
279
  export async function ensureForgeRuntimeReady(config) {
168
280
  if (!isLocalOrigin(config.origin)) {
@@ -183,15 +295,69 @@ export async function ensureForgeRuntimeReady(config) {
183
295
  if (await isForgeHealthy(config, HEALTHCHECK_TIMEOUT_MS)) {
184
296
  return;
185
297
  }
298
+ await ensurePackagedRuntimeDependencies(plan, config);
186
299
  if (!managedRuntimeChild || managedRuntimeKey !== key || managedRuntimeChild.killed) {
187
300
  spawnManagedRuntime(config, plan);
188
301
  }
189
- await waitForRuntime(config, STARTUP_TIMEOUT_MS);
302
+ await waitForRuntime(config, STARTUP_TIMEOUT_MS, managedRuntimeChild?.pid ?? null);
190
303
  })().finally(() => {
191
304
  startupPromise = null;
192
305
  });
193
306
  return startupPromise;
194
307
  }
308
+ export async function startForgeRuntime(config) {
309
+ if (!isLocalOrigin(config.origin)) {
310
+ return {
311
+ ok: false,
312
+ started: false,
313
+ managed: false,
314
+ message: "Forge start only supports local plugin-managed runtimes. Remote Forge targets must be started where they are hosted.",
315
+ pid: null,
316
+ baseUrl: config.baseUrl
317
+ };
318
+ }
319
+ const existingState = await readRuntimeState(config);
320
+ if (!existingState && (await isForgeHealthy(config, HEALTHCHECK_TIMEOUT_MS))) {
321
+ return {
322
+ ok: true,
323
+ started: false,
324
+ managed: false,
325
+ message: `Forge is already running on ${config.baseUrl}, but it does not look like a plugin-managed runtime.`,
326
+ pid: null,
327
+ baseUrl: config.baseUrl
328
+ };
329
+ }
330
+ if (existingState && processExists(existingState.pid) && (await isForgeHealthy(config, HEALTHCHECK_TIMEOUT_MS))) {
331
+ return {
332
+ ok: true,
333
+ started: false,
334
+ managed: true,
335
+ message: `Forge is already running on ${config.baseUrl}.`,
336
+ pid: existingState.pid,
337
+ baseUrl: config.baseUrl
338
+ };
339
+ }
340
+ await ensureForgeRuntimeReady(config);
341
+ const state = await readRuntimeState(config);
342
+ if (!state && (await isForgeHealthy(config, HEALTHCHECK_TIMEOUT_MS))) {
343
+ return {
344
+ ok: true,
345
+ started: false,
346
+ managed: false,
347
+ message: `Forge is healthy on ${config.baseUrl}, but it does not look like a plugin-managed runtime.`,
348
+ pid: null,
349
+ baseUrl: config.baseUrl
350
+ };
351
+ }
352
+ return {
353
+ ok: true,
354
+ started: true,
355
+ managed: true,
356
+ message: `Started the plugin-managed Forge runtime on ${config.baseUrl}.`,
357
+ pid: state?.pid ?? managedRuntimeChild?.pid ?? null,
358
+ baseUrl: config.baseUrl
359
+ };
360
+ }
195
361
  export function primeForgeRuntime(config) {
196
362
  void ensureForgeRuntimeReady(config).catch(() => {
197
363
  // Keep plugin registration non-blocking. Failures surface on first real call.
@@ -255,3 +421,111 @@ export async function stopForgeRuntime(config) {
255
421
  pid: state.pid
256
422
  };
257
423
  }
424
+ export async function getForgeRuntimeStatus(config) {
425
+ const healthy = await isForgeHealthy(config, HEALTHCHECK_TIMEOUT_MS);
426
+ const state = await readRuntimeState(config);
427
+ const pid = state?.pid ?? null;
428
+ const managed = Boolean(state);
429
+ const running = healthy || (pid !== null && processExists(pid));
430
+ if (!isLocalOrigin(config.origin)) {
431
+ return {
432
+ ok: healthy,
433
+ running: healthy,
434
+ healthy,
435
+ managed: false,
436
+ message: healthy
437
+ ? `Forge is reachable at ${config.baseUrl}. Runtime lifecycle is managed remotely.`
438
+ : `Forge is not reachable at ${config.baseUrl}. Runtime lifecycle is managed remotely.`,
439
+ pid: null,
440
+ baseUrl: config.baseUrl
441
+ };
442
+ }
443
+ if (managed && pid !== null && !processExists(pid)) {
444
+ await clearRuntimeState(config);
445
+ return {
446
+ ok: true,
447
+ running: false,
448
+ healthy: false,
449
+ managed: true,
450
+ message: "The saved Forge runtime PID was stale. The plugin-managed runtime is stopped.",
451
+ pid,
452
+ baseUrl: config.baseUrl
453
+ };
454
+ }
455
+ if (healthy && managed) {
456
+ return {
457
+ ok: true,
458
+ running: true,
459
+ healthy: true,
460
+ managed: true,
461
+ message: `Forge is running and healthy on ${config.baseUrl}.`,
462
+ pid,
463
+ baseUrl: config.baseUrl
464
+ };
465
+ }
466
+ if (healthy) {
467
+ return {
468
+ ok: true,
469
+ running: true,
470
+ healthy: true,
471
+ managed: false,
472
+ message: `Forge is running on ${config.baseUrl}, but it does not look like a plugin-managed runtime.`,
473
+ pid: null,
474
+ baseUrl: config.baseUrl
475
+ };
476
+ }
477
+ if (managed) {
478
+ return {
479
+ ok: true,
480
+ running: false,
481
+ healthy: false,
482
+ managed: true,
483
+ message: "The plugin-managed Forge runtime is stopped.",
484
+ pid,
485
+ baseUrl: config.baseUrl
486
+ };
487
+ }
488
+ return {
489
+ ok: true,
490
+ running: false,
491
+ healthy: false,
492
+ managed: false,
493
+ message: "Forge is not running through the plugin-managed local runtime.",
494
+ pid: null,
495
+ baseUrl: config.baseUrl
496
+ };
497
+ }
498
+ export async function restartForgeRuntime(config) {
499
+ if (!isLocalOrigin(config.origin)) {
500
+ return {
501
+ ok: false,
502
+ restarted: false,
503
+ managed: false,
504
+ message: "Forge restart only supports local plugin-managed runtimes. Remote Forge targets must be restarted where they are hosted.",
505
+ pid: null,
506
+ baseUrl: config.baseUrl
507
+ };
508
+ }
509
+ const stopResult = await stopForgeRuntime(config);
510
+ if (!stopResult.ok) {
511
+ return {
512
+ ok: false,
513
+ restarted: false,
514
+ managed: stopResult.managed,
515
+ message: stopResult.message,
516
+ pid: stopResult.pid,
517
+ baseUrl: config.baseUrl
518
+ };
519
+ }
520
+ const startResult = await startForgeRuntime(config);
521
+ return {
522
+ ok: startResult.ok,
523
+ restarted: startResult.ok,
524
+ managed: true,
525
+ message: startResult.ok
526
+ ? `Restarted the plugin-managed Forge runtime on ${config.baseUrl}.`
527
+ : startResult.message,
528
+ pid: startResult.pid,
529
+ baseUrl: config.baseUrl
530
+ };
531
+ }
@@ -1,5 +1,5 @@
1
1
  import { canBootstrapOperatorSession, callConfiguredForgeApi, expectForgeSuccess, readJsonRequestBody, readSingleHeaderValue, requireApiToken, writeForgeProxyResponse, writePluginError, writeRedirectResponse } from "./api-client.js";
2
- import { stopForgeRuntime } from "./local-runtime.js";
2
+ import { getForgeRuntimeStatus, restartForgeRuntime, startForgeRuntime, stopForgeRuntime } from "./local-runtime.js";
3
3
  import { collectSupportedPluginApiRouteKeys, makeApiRouteKey } from "./parity.js";
4
4
  function passthroughSearch(path, url) {
5
5
  return `${path}${url.search}`;
@@ -375,9 +375,18 @@ export function registerForgePluginCli(api, config) {
375
375
  command.command("ui").description("Print the Forge UI entrypoint").action(async () => {
376
376
  console.log(JSON.stringify({ webAppUrl: await resolveForgeUiUrl(config), pluginUiRoute: "/forge/v1/ui" }, null, 2));
377
377
  });
378
+ command.command("start").description("Start the local Forge runtime when it is managed by the OpenClaw plugin").action(async () => {
379
+ console.log(JSON.stringify(await startForgeRuntime(config), null, 2));
380
+ });
378
381
  command.command("stop").description("Stop the local Forge runtime when it was auto-started by the OpenClaw plugin").action(async () => {
379
382
  console.log(JSON.stringify(await stopForgeRuntime(config), null, 2));
380
383
  });
384
+ command.command("restart").description("Restart the local Forge runtime when it is managed by the OpenClaw plugin").action(async () => {
385
+ console.log(JSON.stringify(await restartForgeRuntime(config), null, 2));
386
+ });
387
+ command.command("status").description("Report whether the local Forge runtime is running and whether it is plugin-managed").action(async () => {
388
+ console.log(JSON.stringify(await getForgeRuntimeStatus(config), null, 2));
389
+ });
381
390
  command.command("doctor").description("Run plugin connectivity and curated route diagnostics").action(async () => {
382
391
  console.log(JSON.stringify(await runDoctor(config), null, 2));
383
392
  });
@@ -2,7 +2,7 @@
2
2
  "id": "forge-openclaw-plugin",
3
3
  "name": "Forge",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
- "version": "0.2.11",
5
+ "version": "0.2.13",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-openclaw-plugin",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "Curated OpenClaw adapter for the Forge collaboration API, UI entrypoint, and localhost auto-start runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -16,8 +16,8 @@ Forge data location rule:
16
16
  - on a linked repo-local install, this usually means `<repo>/openclaw-plugin/data/forge.sqlite`
17
17
  - if the user wants the data somewhere else for persistence, backup, or manual control, tell them to set `plugins.entries["forge-openclaw-plugin"].config.dataRoot` and restart the OpenClaw gateway
18
18
  - if the user asks where the data is stored or how to move it, explain the current default plainly and show the exact config field
19
- - if the user wants to stop a plugin-managed local Forge runtime cleanly, tell them to run `forge stop`
20
- - `forge stop` only shuts down a runtime that the OpenClaw plugin auto-started itself; if Forge was started manually elsewhere, it will say so instead of killing random local processes
19
+ - if the user wants to manage a plugin-managed local Forge runtime cleanly, tell them to run `openclaw forge start`, `openclaw forge stop`, `openclaw forge restart`, or `openclaw forge status`
20
+ - these commands only manage a runtime that the OpenClaw plugin auto-started itself; if Forge was started manually elsewhere, they will say so instead of killing random local processes
21
21
 
22
22
  Use these exact entity meanings when deciding what the user is describing.
23
23