limits-openclaw 0.0.6

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.
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ const callEnforceMock = vi.fn();
3
+ vi.mock("../../src/enforcer.js", () => ({ callEnforce: callEnforceMock }));
4
+ describe("preHook", () => {
5
+ let beforeHandler = null;
6
+ beforeEach(async () => {
7
+ callEnforceMock.mockReset();
8
+ beforeHandler = null;
9
+ const api = {
10
+ config: {
11
+ plugins: {
12
+ entries: {
13
+ "limits-openclaw": {
14
+ enabled: true,
15
+ config: {
16
+ baseUrl: "https://api.test.com",
17
+ failMode: "allow",
18
+ tokenSource: "event.metadata.apiToken",
19
+ },
20
+ },
21
+ },
22
+ },
23
+ },
24
+ on(event, handler) {
25
+ if (event === "before_tool_call")
26
+ beforeHandler = handler;
27
+ },
28
+ };
29
+ const { register } = await import("../../src/index.js");
30
+ register(api);
31
+ });
32
+ async function runPre(event, ctx) {
33
+ if (!beforeHandler)
34
+ throw new Error("before_tool_call not registered");
35
+ return beforeHandler(event, ctx);
36
+ }
37
+ it("ALLOW returns null", async () => {
38
+ callEnforceMock.mockResolvedValue({ action: "ALLOW" });
39
+ const event = {
40
+ toolName: "read_file",
41
+ toolParams: {},
42
+ metadata: { apiToken: "sk_ok" },
43
+ };
44
+ const result = await runPre(event, undefined);
45
+ expect(result).toBe(null);
46
+ });
47
+ it("BLOCK returns { block: true, reason }", async () => {
48
+ callEnforceMock.mockResolvedValue({
49
+ action: "BLOCK",
50
+ reason: "Policy forbids this tool",
51
+ });
52
+ const event = {
53
+ toolName: "stripe_charge",
54
+ metadata: { apiToken: "sk_ok" },
55
+ };
56
+ const result = await runPre(event, undefined);
57
+ expect(result).toEqual({ block: true, reason: "Policy forbids this tool" });
58
+ });
59
+ it("REWRITE returns { args }", async () => {
60
+ callEnforceMock.mockResolvedValue({
61
+ action: "REWRITE",
62
+ rewriteArgs: { amount: 100, currency: "usd" },
63
+ });
64
+ const event = {
65
+ toolName: "payment",
66
+ toolParams: { amount: 500 },
67
+ metadata: { apiToken: "sk_ok" },
68
+ };
69
+ const result = await runPre(event, undefined);
70
+ expect(result).toEqual({ args: { amount: 100, currency: "usd" } });
71
+ });
72
+ });
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { extractToken } from "../../src/token.js";
3
+ describe("extractToken", () => {
4
+ const origEnv = process.env;
5
+ beforeEach(() => {
6
+ process.env = { ...origEnv };
7
+ });
8
+ afterEach(() => {
9
+ process.env = origEnv;
10
+ });
11
+ it("reads event.metadata.apiToken", () => {
12
+ const event = { metadata: { apiToken: "sk_test_123" } };
13
+ expect(extractToken("event.metadata.apiToken", event, undefined)).toBe("sk_test_123");
14
+ });
15
+ it("reads ctx.auth.token", () => {
16
+ const ctx = { auth: { token: "bearer_abc" } };
17
+ expect(extractToken("ctx.auth.token", ctx, ctx)).toBe("bearer_abc");
18
+ });
19
+ it("reads env.LIMITS_API_TOKEN", () => {
20
+ process.env.LIMITS_API_TOKEN = "env_secret";
21
+ expect(extractToken("env.LIMITS_API_TOKEN", undefined, undefined)).toBe("env_secret");
22
+ });
23
+ it("returns undefined for missing event path", () => {
24
+ const event = {};
25
+ expect(extractToken("event.metadata.apiToken", event, undefined)).toBe(undefined);
26
+ });
27
+ it("returns undefined for missing ctx path", () => {
28
+ const ctx = {};
29
+ expect(extractToken("ctx.auth.token", undefined, ctx)).toBe(undefined);
30
+ });
31
+ it("returns undefined for missing env key", () => {
32
+ expect(extractToken("env.NONEXISTENT_KEY_XYZ", undefined, undefined)).toBe(undefined);
33
+ });
34
+ it("returns undefined for non-string value at path", () => {
35
+ const event = { metadata: { apiToken: 42 } };
36
+ expect(extractToken("event.metadata.apiToken", event, undefined)).toBe(undefined);
37
+ });
38
+ it("returns undefined for malformed source (single segment)", () => {
39
+ expect(extractToken("event", {}, undefined)).toBe(undefined);
40
+ });
41
+ it("returns undefined for empty string", () => {
42
+ expect(extractToken("", {}, undefined)).toBe(undefined);
43
+ });
44
+ it("trims tokenSource", () => {
45
+ const event = { metadata: { apiToken: "trimmed" } };
46
+ expect(extractToken(" event.metadata.apiToken ", event, undefined)).toBe("trimmed");
47
+ });
48
+ });
@@ -0,0 +1,45 @@
1
+ {
2
+ "id": "limits-openclaw",
3
+ "name": "limits-openclaw",
4
+ "description": "Delegates policy enforcement to the Limits platform before and after every tool call. Optional policy-generator tools for creating/updating policies from natural language.",
5
+ "version": "0.0.1",
6
+ "main": "./dist/index.js",
7
+ "configSchema": {
8
+ "type": "object",
9
+ "additionalProperties": true,
10
+ "properties": {
11
+ "baseUrl": {
12
+ "type": "string",
13
+ "description": "Base URL for POST /openclaw/enforce and for policy-generator API calls. Same URL for enforcement and policy tools."
14
+ },
15
+ "apiToken": {
16
+ "type": "string",
17
+ "description": "Organization API key for /openclaw/enforce and policy-generator tools. Store securely in gateway config or use LIMITS_ENFORCER_API_TOKEN env."
18
+ },
19
+ "timeoutMs": {
20
+ "type": "number",
21
+ "default": 2500,
22
+ "description": "Request timeout in milliseconds."
23
+ },
24
+ "failMode": {
25
+ "type": "string",
26
+ "enum": [
27
+ "allow",
28
+ "block"
29
+ ],
30
+ "default": "allow",
31
+ "description": "When Limits backend is unreachable: allow = proceed; block = block the call (pre) or replace result (post)."
32
+ },
33
+ "tokenSource": {
34
+ "type": "string",
35
+ "default": "event.metadata.apiToken",
36
+ "description": "Dot-separated path to API token: event.metadata.apiToken, ctx.auth.token, or env.LIMITS_API_TOKEN."
37
+ },
38
+ "redactLogs": {
39
+ "type": "boolean",
40
+ "default": true,
41
+ "description": "Whether to avoid logging sensitive data."
42
+ }
43
+ }
44
+ }
45
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "limits-openclaw",
3
+ "version": "0.0.6",
4
+ "description": "Delegates policy enforcement to the Limits platform before and after every tool call.",
5
+ "keywords": [
6
+ "openclaw",
7
+ "limits",
8
+ "policy",
9
+ "enforcement",
10
+ "agent"
11
+ ],
12
+ "author": "Limits",
13
+ "license": "MIT",
14
+ "type": "module",
15
+ "main": "./dist/index.js",
16
+ "exports": {
17
+ ".": "./dist/index.js"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "openclaw.plugin.json",
22
+ "README.md",
23
+ "LICENSE",
24
+ "package.json"
25
+ ],
26
+ "openclaw": {
27
+ "id": "limits-openclaw",
28
+ "entry": "./dist/index.js",
29
+ "hooks": [
30
+ "./"
31
+ ],
32
+ "extensions": [
33
+ "./"
34
+ ],
35
+ "events": [
36
+ "before_tool_call",
37
+ "after_tool_call"
38
+ ]
39
+ },
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.json",
42
+ "prepublishOnly": "npm run build",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest",
45
+ "configure": "node scripts/configure.js"
46
+ },
47
+ "engines": {
48
+ "node": ">=18.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "typescript": "^5.0.0",
52
+ "@types/node": "^20.0.0",
53
+ "vitest": "^2.0.0"
54
+ }
55
+ }