limits-openclaw 0.0.11 → 0.0.12

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
@@ -1,10 +1,10 @@
1
- # limits-openclaw
1
+ # limits-openclaw
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/limits-openclaw .svg?style=flat-square)](https://www.npmjs.com/package/limits-openclaw )
3
+ [![npm version](https://img.shields.io/npm/v/limits-openclaw.svg?style=flat-square)](https://www.npmjs.com/package/limits-openclaw)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT)
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
6
6
  [![Node.js](https://img.shields.io/badge/Node.js-18%2B-339933?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org/)
7
- [![GitHub](https://img.shields.io/badge/GitHub-limitsdev%2Flimits--openclaw-181717?style=flat-square&logo=github)](https://github.com/limitsdev/limits-openclaw )
7
+ [![GitHub](https://img.shields.io/badge/GitHub-limitsdev%2Flimits--openclaw-181717?style=flat-square&logo=github)](https://github.com/limitsdev/limits-openclaw)
8
8
 
9
9
  **Official** OpenClaw plugin for the [Limits](https://limits.dev) platform. Delegates policy enforcement via HTTP. Every decision is made by calling `POST {baseUrl}/openclaw/enforce` before and after each tool call. Optional policy-generator tools let the agent create and update policies from natural language.
10
10
 
@@ -31,23 +31,43 @@
31
31
  ## Installation
32
32
 
33
33
  ```bash
34
- npm install limits-openclaw
34
+ npm install limits-openclaw
35
35
  ```
36
36
 
37
37
  ```bash
38
- yarn add limits-openclaw
38
+ yarn add limits-openclaw
39
39
  ```
40
40
 
41
41
  ```bash
42
- pnpm add limits-openclaw
42
+ pnpm add limits-openclaw
43
43
  ```
44
44
 
45
45
  Then register the plugin with OpenClaw:
46
46
 
47
47
  ```bash
48
- openclaw plugins install ./node_modules/limits-openclaw
48
+ openclaw plugins install ./node_modules/limits-openclaw
49
49
  openclaw gateway restart
50
- openclaw plugins enable "limits-openclaw "
50
+ ```
51
+
52
+ **Add `limits-openclaw` to the Plugin Allowlist** (required before enabling):
53
+
54
+ **Option A — Dashboard:**
55
+
56
+ 1. Open the OpenClaw dashboard and go to **Config → Plugins → Plugin Allowlist**.
57
+ 2. Click **Add** and type `limits-openclaw`, then save and apply.
58
+
59
+ **Option B — CLI:**
60
+
61
+ ```bash
62
+ current=$(openclaw config get plugins.allow 2>/dev/null | grep -v '^\[.\+\] ')
63
+ updated=$(echo "${current:-[]}" | jq -c '(. // []) + ["limits-openclaw"] | unique')
64
+ openclaw config set plugins.allow "$updated" --json
65
+ ```
66
+
67
+ Then enable the plugin:
68
+
69
+ ```bash
70
+ openclaw plugins enable "limits-openclaw"
51
71
  ```
52
72
 
53
73
  ---
@@ -64,7 +84,7 @@ openclaw limits configure
64
84
  If you see **`error: unknown command 'limits'`**, the OpenClaw CLI may not load plugin commands in your environment. Run the configure script directly (from a directory where the package is installed):
65
85
 
66
86
  ```bash
67
- node node_modules/limits-openclaw /scripts/configure.js
87
+ node node_modules/limits-openclaw/scripts/configure.js
68
88
  ```
69
89
 
70
90
  Or set config manually:
@@ -73,7 +93,7 @@ Or set config manually:
73
93
  {
74
94
  "plugins": {
75
95
  "entries": {
76
- "limits-openclaw ": {
96
+ "limits-openclaw": {
77
97
  "enabled": true,
78
98
  "config": {
79
99
  "apiToken": "sk_your_organization_api_key"
@@ -92,7 +112,7 @@ Or set config manually:
92
112
  "list": [
93
113
  {
94
114
  "id": "main",
95
- "tools": { "allow": ["limits-openclaw "] }
115
+ "tools": { "allow": ["limits-openclaw"] }
96
116
  }
97
117
  ]
98
118
  }
@@ -127,7 +147,7 @@ flowchart LR
127
147
  1. **Before each tool call** (`before_tool_call`): the plugin POSTs `phase: "pre"` with tool name and args. Limits returns `ALLOW`, `BLOCK`, or `REWRITE` (with `rewriteArgs`). The plugin blocks the call, allows it, or replaces the tool arguments accordingly.
128
148
  2. **After each tool call** (`after_tool_call`): the plugin POSTs `phase: "post"` with tool name, args, and result. Limits returns `ALLOW`, `BLOCK`, `REDACT`, or `REWRITE` (with `redactedResult` or `rewrittenResult`). The plugin leaves the result unchanged, replaces it with a safe blocked response, or replaces it with the redacted/rewritten value.
129
149
 
130
- **Compatibility:** This plugin requires an OpenClaw build where tool hooks are wired into the execution path. If you see **`[limits-openclaw ] before_tool_call observed`** in logs once after a tool runs, hooks are working. See [OpenClaw #6535](https://github.com/openclaw/openclaw/issues/6535) if hooks never fire.
150
+ **Compatibility:** This plugin requires an OpenClaw build where tool hooks are wired into the execution path. If you see **`[limits-openclaw] before_tool_call observed`** in logs once after a tool runs, hooks are working. See [OpenClaw #6535](https://github.com/openclaw/openclaw/issues/6535) if hooks never fire.
131
151
 
132
152
  ---
133
153
 
@@ -150,16 +170,16 @@ Optionally, you can copy it manually. The example below is for Unix; on Windows
150
170
 
151
171
  ```bash
152
172
  mkdir -p ~/.openclaw/workspace/skills/limits-policy-generator
153
- cp -r ./node_modules/limits-openclaw /skills/limits-policy-generator/. ~/.openclaw/workspace/skills/limits-policy-generator/
173
+ cp -r ./node_modules/limits-openclaw/skills/limits-policy-generator/. ~/.openclaw/workspace/skills/limits-policy-generator/
154
174
  ```
155
175
 
156
176
  ### Expose tools to the agent
157
177
 
158
- The plugin registers these tools as **optional**. Add the plugin to the agent's tool allowlist (see [Quick Start](#quick-start)) so the agent can call them. Use `"limits-openclaw "` to allow all tools from this plugin, or allow by name: `["limits_generate_create_policy", "limits_generate_update_policy"]`.
178
+ The plugin registers these tools as **optional**. Add the plugin to the agent's tool allowlist (see [Quick Start](#quick-start)) so the agent can call them. Use `"limits-openclaw"` to allow all tools from this plugin, or allow by name: `["limits_generate_create_policy", "limits_generate_update_policy"]`.
159
179
 
160
180
  ### Sandboxed agents
161
181
 
162
- If the agent runs in a **sandbox**, add `"limits-openclaw "` (or the tool names) to `tools.sandbox.tools.allow` as well so the sandboxed agent can call the policy tools. Otherwise the agent can have the skill but cannot invoke the tools from inside the sandbox.
182
+ If the agent runs in a **sandbox**, add `"limits-openclaw"` (or the tool names) to `tools.sandbox.tools.allow` as well so the sandboxed agent can call the policy tools. Otherwise the agent can have the skill but cannot invoke the tools from inside the sandbox.
163
183
 
164
184
  ---
165
185
 
@@ -190,7 +210,7 @@ If the agent runs in a **sandbox**, add `"limits-openclaw "` (or the tool names)
190
210
  {
191
211
  "plugins": {
192
212
  "entries": {
193
- "limits-openclaw ": {
213
+ "limits-openclaw": {
194
214
  "enabled": true,
195
215
  "config": {
196
216
  "apiToken": "sk_your_organization_api_key",
@@ -253,12 +273,12 @@ Policies are scoped to OpenClaw tool calls using **tags** in the Limits dashboar
253
273
  |---------|-------------|
254
274
  | `openclaw plugins install <path>` | Install plugin from path. |
255
275
  | `openclaw plugins install -l <path>` | Link plugin (no copy, for development). |
256
- | `openclaw plugins enable "limits-openclaw "` | Enable the plugin. |
276
+ | `openclaw plugins enable "limits-openclaw"` | Enable the plugin. |
257
277
  | `openclaw plugins list` | List installed plugins. |
258
278
  | `openclaw plugins doctor` | Check plugin health. |
259
279
  | `openclaw limits configure` | Interactive wizard to set API token and sandbox allowlist. |
260
- | `openclaw config get 'plugins.entries["limits-openclaw "]'` | Show plugin config. |
261
- | `openclaw config set 'plugins.entries["limits-openclaw "].config.apiToken' "sk_..."` | Set apiToken manually. |
280
+ | `openclaw config get 'plugins.entries["limits-openclaw"]'` | Show plugin config. |
281
+ | `openclaw config set 'plugins.entries["limits-openclaw"].config.apiToken' "sk_..."` | Set apiToken manually. |
262
282
 
263
283
  ---
264
284
 
@@ -283,29 +303,29 @@ If you were using the plugin under the old name **limits-enforcer**, update your
283
303
 
284
304
  | Before | After |
285
305
  |--------|-------|
286
- | Plugin ID / allowlist: `"limits-enforcer"` | `"limits-openclaw "` |
287
- | Config path: `plugins.entries.limits-enforcer` | `plugins.entries["limits-openclaw "]` |
306
+ | Plugin ID / allowlist: `"limits-enforcer"` | `"limits-openclaw"` |
307
+ | Config path: `plugins.entries.limits-enforcer` | `plugins.entries["limits-openclaw"]` |
288
308
  | CLI command: `openclaw limits-enforcer configure` | `openclaw limits configure` |
289
- | Log prefix: `[limits-enforcer]` | `[limits-openclaw ]` |
309
+ | Log prefix: `[limits-enforcer]` | `[limits-openclaw]` |
290
310
 
291
- After renaming, reinstall the plugin and enable `"limits-openclaw "`.
311
+ After renaming, reinstall the plugin and enable `"limits-openclaw"`.
292
312
 
293
313
  ---
294
314
 
295
315
  ## Troubleshooting
296
316
 
297
- - **Config warning "plugin id mismatch (manifest uses 'limits-openclaw ', entry hints 'openclaw')"**
298
- Some OpenClaw gateways install scoped packages under `extensions/openclaw` instead of `extensions/limits-openclaw `. The gateway then infers the plugin id from the folder name (`openclaw`), which does not match the manifest id `limits-openclaw `, so the plugin may not load and `openclaw limits configure` shows **unknown command 'limits'**.
317
+ - **Config warning "plugin id mismatch (manifest uses 'limits-openclaw', entry hints 'openclaw')"**
318
+ Some OpenClaw gateways install scoped packages under `extensions/openclaw` instead of `extensions/limits-openclaw`. The gateway then infers the plugin id from the folder name (`openclaw`), which does not match the manifest id `limits-openclaw`, so the plugin may not load and `openclaw limits configure` shows **unknown command 'limits'**.
299
319
 
300
320
  **Workaround — install under the scoped path so the gateway matches the config entry:**
301
321
  ```bash
302
322
  # Create the scope directory and install the package there (adjust if you use npm -g or another path)
303
323
  mkdir -p ~/.openclaw/extensions/@limits
304
- cp -r ./node_modules/limits-openclaw ~/.openclaw/extensions/@limits/
324
+ cp -r ./node_modules/limits-openclaw ~/.openclaw/extensions/@limits/
305
325
 
306
326
  # Tell OpenClaw to load the plugin from that path
307
- openclaw plugins install ~/.openclaw/extensions/limits-openclaw
308
- openclaw plugins enable "limits-openclaw "
327
+ openclaw plugins install ~/.openclaw/extensions/limits-openclaw
328
+ openclaw plugins enable "limits-openclaw"
309
329
  openclaw gateway restart
310
330
  ```
311
331
  Then run `openclaw limits configure`.
@@ -313,7 +333,7 @@ After renaming, reinstall the plugin and enable `"limits-openclaw "`.
313
333
  - **`error: unknown command 'limits'`**
314
334
  The plugin did not load (often due to the id mismatch above). Use the workaround above, or run the configure wizard without the OpenClaw CLI:
315
335
  ```bash
316
- node node_modules/limits-openclaw /scripts/configure.js
336
+ node node_modules/limits-openclaw/scripts/configure.js
317
337
  ```
318
338
  Or from the plugin repo: `npm run configure`.
319
339
 
@@ -322,4 +342,4 @@ After renaming, reinstall the plugin and enable `"limits-openclaw "`.
322
342
 
323
343
  ## License
324
344
 
325
- MIT — [Limits](https://limits.dev) — [GitHub](https://github.com/limitsdev/limits-openclaw )
345
+ MIT — [Limits](https://limits.dev) — [GitHub](https://github.com/limitsdev/limits-openclaw)
package/dist/config.js CHANGED
@@ -10,7 +10,7 @@ const DEFAULTS = {
10
10
  redactLogs: true,
11
11
  };
12
12
  export function loadConfig(api) {
13
- const raw = api.config?.plugins?.entries?.["limits-openclaw "]?.config ?? {};
13
+ const raw = api.config?.plugins?.entries?.["limits-openclaw"]?.config ?? {};
14
14
  // baseUrl defaults to static; not in wizard or schema so users don't edit it (config override only for tests/advanced)
15
15
  const baseUrl = raw.baseUrl ?? DEFAULTS.baseUrl;
16
16
  const timeoutMs = typeof process.env.LIMITS_ENFORCER_TIMEOUT_MS === "string"
@@ -1,10 +1,9 @@
1
1
  import { createInterface } from "node:readline";
2
- import { spawn } from "node:child_process";
3
2
  import { dirname, join } from "node:path";
4
3
  import { fileURLToPath } from "node:url";
5
4
  import fs from "node:fs";
6
5
  import os from "node:os";
7
- const CONFIG_PREFIX = 'plugins.entries["limits-openclaw "].config';
6
+ const CONFIG_PREFIX = 'plugins.entries["limits-openclaw"].config';
8
7
  const SKILL_NAME = "limits-policy-generator";
9
8
  function getPluginRoot() {
10
9
  const currentDir = dirname(fileURLToPath(import.meta.url));
@@ -31,6 +30,71 @@ function copySkillToWorkspace() {
31
30
  console.error("\nFailed to copy skill:", err instanceof Error ? err.message : String(err));
32
31
  }
33
32
  }
33
+ function getConfigFilePath() {
34
+ return process.env.OPENCLAW_CONFIG || join(os.homedir(), ".openclaw", "openclaw.json");
35
+ }
36
+ function readConfigFile() {
37
+ try {
38
+ return JSON.parse(fs.readFileSync(getConfigFilePath(), "utf8"));
39
+ }
40
+ catch {
41
+ return {};
42
+ }
43
+ }
44
+ function writeConfigFile(cfg) {
45
+ fs.writeFileSync(getConfigFilePath(), JSON.stringify(cfg, null, 2) + "\n", "utf8");
46
+ }
47
+ function parseConfigPath(path) {
48
+ const segments = [];
49
+ const re = /\[["']([^"']+)["']\]|\.?([^.[\]]+)/g;
50
+ let m;
51
+ while ((m = re.exec(path)) !== null) {
52
+ const seg = m[1] ?? m[2];
53
+ if (seg)
54
+ segments.push(seg);
55
+ }
56
+ return segments;
57
+ }
58
+ function configGet(cfg, path) {
59
+ let cur = cfg;
60
+ for (const seg of parseConfigPath(path)) {
61
+ if (cur == null || typeof cur !== "object")
62
+ return undefined;
63
+ cur = cur[seg];
64
+ }
65
+ return cur;
66
+ }
67
+ function configSet(cfg, path, value) {
68
+ const segments = parseConfigPath(path);
69
+ let cur = cfg;
70
+ for (let i = 0; i < segments.length - 1; i++) {
71
+ const seg = segments[i];
72
+ if (cur[seg] == null || typeof cur[seg] !== "object")
73
+ cur[seg] = {};
74
+ cur = cur[seg];
75
+ }
76
+ cur[segments[segments.length - 1]] = value;
77
+ }
78
+ function runConfigSet(key, jsonValue) {
79
+ const fullKey = `${CONFIG_PREFIX}.${key}`;
80
+ const cfg = readConfigFile();
81
+ configSet(cfg, fullKey, JSON.parse(jsonValue));
82
+ writeConfigFile(cfg);
83
+ console.log(`Updated ${fullKey}.`);
84
+ return Promise.resolve();
85
+ }
86
+ function runConfigGet(fullKey) {
87
+ const cfg = readConfigFile();
88
+ const val = configGet(cfg, fullKey);
89
+ return Promise.resolve(val !== undefined ? JSON.stringify(val) : "");
90
+ }
91
+ function runConfigSetFull(fullKey, jsonValue) {
92
+ const cfg = readConfigFile();
93
+ configSet(cfg, fullKey, JSON.parse(jsonValue));
94
+ writeConfigFile(cfg);
95
+ console.log(`Updated ${fullKey}.`);
96
+ return Promise.resolve();
97
+ }
34
98
  export function ask(rl, question, defaultValue = "") {
35
99
  const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
36
100
  return new Promise((resolve) => {
@@ -39,41 +103,8 @@ export function ask(rl, question, defaultValue = "") {
39
103
  });
40
104
  });
41
105
  }
42
- function runConfigSet(key, value) {
43
- return new Promise((resolve, reject) => {
44
- const fullKey = `${CONFIG_PREFIX}.${key}`;
45
- const child = spawn("openclaw", ["config", "set", fullKey, value], {
46
- stdio: "inherit",
47
- shell: true,
48
- });
49
- child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`openclaw config set exited ${code}`)));
50
- child.on("error", reject);
51
- });
52
- }
53
- function runConfigGet(fullKey) {
54
- return new Promise((resolve) => {
55
- const child = spawn("openclaw", ["config", "get", fullKey], {
56
- stdio: ["inherit", "pipe", "inherit"],
57
- shell: true,
58
- });
59
- let out = "";
60
- child.stdout?.on("data", (d) => (out += d.toString()));
61
- child.on("close", () => resolve(out.trim()));
62
- child.on("error", () => resolve(""));
63
- });
64
- }
65
- function runConfigSetFull(fullKey, value) {
66
- return new Promise((resolve, reject) => {
67
- const child = spawn("openclaw", ["config", "set", fullKey, value], {
68
- stdio: "inherit",
69
- shell: true,
70
- });
71
- child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`openclaw config set exited ${code}`)));
72
- child.on("error", reject);
73
- });
74
- }
75
106
  /**
76
- * Run the configure wizard (interactive prompts, then openclaw config set).
107
+ * Run the configure wizard (interactive prompts, then direct config file write).
77
108
  * Call this when the user runs `openclaw limits configure` or after link.
78
109
  */
79
110
  export async function runConfigureWizard() {
@@ -100,18 +131,18 @@ export async function runConfigureWizard() {
100
131
  allow = [];
101
132
  }
102
133
  }
103
- if (allow.includes("limits-openclaw ")) {
104
- console.log("\nlimits-openclaw is already in tools.sandbox.tools.allow, skipping.");
134
+ if (allow.includes("limits-openclaw")) {
135
+ console.log("\nlimits-openclaw is already in tools.sandbox.tools.allow, skipping.");
105
136
  }
106
137
  else {
107
- allow.push("limits-openclaw ");
138
+ allow.push("limits-openclaw");
108
139
  await runConfigSetFull(SANDBOX_ALLOW_KEY, JSON.stringify(allow));
109
- console.log("\nAdded limits-openclaw to tools.sandbox.tools.allow.");
140
+ console.log("\nAdded limits-openclaw to tools.sandbox.tools.allow.");
110
141
  }
111
142
  }
112
143
  const addSkillYes = /^y(es)?$/i.test(addSkillAnswer.trim());
113
144
  if (addSkillYes)
114
145
  copySkillToWorkspace();
115
146
  console.log("\nDone. Restart the gateway if it is running.");
116
- console.log('Verify: openclaw config get plugins.entries["limits-openclaw "]\n');
147
+ console.log('Verify: openclaw config get plugins.entries["limits-openclaw"]\n');
117
148
  }
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * limits-openclaw : register(api) entrypoint — wires before_tool_call and after_tool_call.
2
+ * limits-openclaw: register(api) entrypoint — wires before_tool_call and after_tool_call.
3
3
  */
4
4
  import { loadConfig } from "./config.js";
5
5
  import { callEnforce } from "./enforcer.js";
@@ -182,7 +182,7 @@ function registerPolicyTools(api) {
182
182
  log("policy-generator tools registered (limits_generate_create_policy, limits_generate_update_policy)");
183
183
  }
184
184
  export function register(api) {
185
- log("limits-openclaw loaded");
185
+ log("limits-openclaw loaded");
186
186
  if (typeof api.on !== "function") {
187
187
  log("api.on not available, hooks not registered");
188
188
  return;
package/dist/logger.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Safe logger for limits-openclaw . Never logs apiToken, request body, or tool args.
2
+ * Safe logger for limits-openclaw. Never logs apiToken, request body, or tool args.
3
3
  */
4
- const PREFIX = "[limits-openclaw ]";
4
+ const PREFIX = "[limits-openclaw]";
5
5
  export function log(message, ...safeArgs) {
6
6
  const safe = safeArgs.map((a) => typeof a === "string" || typeof a === "number" || typeof a === "boolean"
7
7
  ? a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "limits-openclaw ",
3
- "version": "0.0.11",
2
+ "name": "limits-openclaw",
3
+ "version": "0.0.12",
4
4
  "description": "Delegates policy enforcement to the Limits platform before and after every tool call.",
5
5
  "keywords": [
6
6
  "openclaw",