@coffeexdev/openclaw-sentinel 0.1.2 → 0.1.4

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
@@ -35,6 +35,10 @@ Formal JSON Schema for sentinel config/watchers is available at:
35
35
 
36
36
  You can validate a watcher config document (for example `.sentinel.json`) against this schema in CI or local tooling.
37
37
 
38
+ ## Documentation
39
+
40
+ - [Usage Guide](docs/USAGE.md)
41
+
38
42
  ## Install
39
43
 
40
44
  ```bash
@@ -43,6 +47,8 @@ npm i @coffeexdev/openclaw-sentinel
43
47
 
44
48
  ## Quick usage
45
49
 
50
+ No hosts are allowed by default — you must explicitly configure `allowedHosts` for watchers to connect to any endpoint.
51
+
46
52
  ```ts
47
53
  import { createSentinelPlugin } from "@coffeexdev/openclaw-sentinel";
48
54
 
@@ -65,8 +71,8 @@ sentinel.register({
65
71
  {
66
72
  "action": "create",
67
73
  "watcher": {
68
- "id": "sentinel-poker-alert",
69
- "skillId": "skills.sentinel-poker",
74
+ "id": "sentinel-alert",
75
+ "skillId": "skills.general-monitor"
70
76
  "enabled": true,
71
77
  "strategy": "http-poll",
72
78
  "endpoint": "https://api.github.com/events",
@@ -75,7 +81,7 @@ sentinel.register({
75
81
  "conditions": [{ "path": "type", "op": "eq", "value": "PushEvent" }],
76
82
  "fire": {
77
83
  "webhookPath": "/internal/sentinel/fire",
78
- "eventName": "sentinel-poker_push",
84
+ "eventName": "sentinel_push",
79
85
  "payloadTemplate": {
80
86
  "watcher": "${watcher.id}",
81
87
  "event": "${event.name}",
@@ -104,7 +110,7 @@ Set `"fireOnce": true` to automatically disable a watcher after its first matche
104
110
 
105
111
  ## Example scenarios
106
112
 
107
- ### Sentinel Poker feed monitoring
113
+ ### Feed monitoring example
108
114
 
109
115
  Watch API changes and fire internal webhook events for orchestration.
110
116
 
@@ -0,0 +1,2 @@
1
+ import type { OpenClawPluginConfigSchema } from "openclaw/plugin-sdk";
2
+ export declare const sentinelConfigSchema: OpenClawPluginConfigSchema;
@@ -0,0 +1,115 @@
1
+ import { z } from "zod";
2
+ const limitsSchema = z.object({
3
+ maxWatchersTotal: z.number().int().positive().default(200),
4
+ maxWatchersPerSkill: z.number().int().positive().default(20),
5
+ maxConditionsPerWatcher: z.number().int().positive().default(25),
6
+ maxIntervalMsFloor: z.number().int().positive().default(1000),
7
+ });
8
+ const configZodSchema = z.object({
9
+ allowedHosts: z.array(z.string()).default([]),
10
+ localDispatchBase: z.string().url().default("http://127.0.0.1:18789"),
11
+ dispatchAuthToken: z.string().optional(),
12
+ stateFilePath: z.string().optional(),
13
+ limits: limitsSchema.default({}),
14
+ });
15
+ export const sentinelConfigSchema = {
16
+ safeParse: (value) => {
17
+ if (value === undefined)
18
+ return { success: true, data: undefined };
19
+ return configZodSchema.safeParse(value);
20
+ },
21
+ jsonSchema: {
22
+ type: "object",
23
+ additionalProperties: false,
24
+ properties: {
25
+ allowedHosts: {
26
+ type: "array",
27
+ items: { type: "string" },
28
+ description: "Hostnames the watchers are permitted to connect to. Must be explicitly configured — no hosts are allowed by default.",
29
+ default: [],
30
+ },
31
+ localDispatchBase: {
32
+ type: "string",
33
+ format: "uri",
34
+ description: "Base URL for internal webhook dispatch",
35
+ default: "http://127.0.0.1:18789",
36
+ },
37
+ dispatchAuthToken: {
38
+ type: "string",
39
+ description: "Bearer token for authenticating webhook dispatch requests",
40
+ },
41
+ stateFilePath: {
42
+ type: "string",
43
+ description: "Custom path for the sentinel state persistence file",
44
+ },
45
+ limits: {
46
+ type: "object",
47
+ additionalProperties: false,
48
+ description: "Resource limits for watcher creation",
49
+ properties: {
50
+ maxWatchersTotal: {
51
+ type: "number",
52
+ description: "Maximum total watchers across all skills",
53
+ default: 200,
54
+ },
55
+ maxWatchersPerSkill: {
56
+ type: "number",
57
+ description: "Maximum watchers per skill",
58
+ default: 20,
59
+ },
60
+ maxConditionsPerWatcher: {
61
+ type: "number",
62
+ description: "Maximum conditions per watcher definition",
63
+ default: 25,
64
+ },
65
+ maxIntervalMsFloor: {
66
+ type: "number",
67
+ description: "Minimum allowed polling interval in milliseconds",
68
+ default: 1000,
69
+ },
70
+ },
71
+ },
72
+ },
73
+ },
74
+ uiHints: {
75
+ allowedHosts: {
76
+ label: "Allowed Hosts",
77
+ help: "Hostnames the watchers are permitted to connect to",
78
+ },
79
+ localDispatchBase: {
80
+ label: "Dispatch Base URL",
81
+ help: "Base URL for internal webhook dispatch (default: http://127.0.0.1:18789)",
82
+ },
83
+ dispatchAuthToken: {
84
+ label: "Dispatch Auth Token",
85
+ help: "Bearer token for webhook dispatch authentication (or use SENTINEL_DISPATCH_TOKEN env var)",
86
+ sensitive: true,
87
+ placeholder: "sk-...",
88
+ },
89
+ stateFilePath: {
90
+ label: "State File Path",
91
+ help: "Custom path for sentinel state persistence file",
92
+ advanced: true,
93
+ },
94
+ "limits.maxWatchersTotal": {
95
+ label: "Max Watchers",
96
+ help: "Maximum total watchers across all skills",
97
+ advanced: true,
98
+ },
99
+ "limits.maxWatchersPerSkill": {
100
+ label: "Max Per Skill",
101
+ help: "Maximum watchers a single skill can create",
102
+ advanced: true,
103
+ },
104
+ "limits.maxConditionsPerWatcher": {
105
+ label: "Max Conditions",
106
+ help: "Maximum conditions per watcher definition",
107
+ advanced: true,
108
+ },
109
+ "limits.maxIntervalMsFloor": {
110
+ label: "Min Poll Interval (ms)",
111
+ help: "Minimum allowed polling interval in milliseconds",
112
+ advanced: true,
113
+ },
114
+ },
115
+ };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,19 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
1
2
  import { WatcherManager } from "./watcherManager.js";
2
3
  import { SentinelConfig } from "./types.js";
3
4
  export declare function createSentinelPlugin(overrides?: Partial<SentinelConfig>): {
4
5
  manager: WatcherManager;
5
6
  init(): Promise<void>;
6
- register(api: {
7
- registerTool: (name: string, handler: (input: unknown) => Promise<unknown>) => void;
8
- }): void;
7
+ register(api: OpenClawPluginApi): void;
9
8
  };
9
+ declare const sentinelPlugin: {
10
+ id: string;
11
+ name: string;
12
+ description: string;
13
+ configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
14
+ register(api: OpenClawPluginApi): void;
15
+ };
16
+ export declare const register: (api: OpenClawPluginApi) => void;
17
+ export declare const activate: (api: OpenClawPluginApi) => void;
18
+ export default sentinelPlugin;
10
19
  export * from "./types.js";
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { registerSentinelControl } from "./tool.js";
2
2
  import { WatcherManager } from "./watcherManager.js";
3
+ import { sentinelConfigSchema } from "./configSchema.js";
3
4
  export function createSentinelPlugin(overrides) {
4
5
  const config = {
5
- allowedHosts: ["api.github.com", "api.coingecko.com", "example.com"],
6
+ allowedHosts: [],
6
7
  localDispatchBase: "http://127.0.0.1:18789",
7
8
  dispatchAuthToken: process.env.SENTINEL_DISPATCH_TOKEN,
8
9
  limits: {
@@ -31,8 +32,23 @@ export function createSentinelPlugin(overrides) {
31
32
  await manager.init();
32
33
  },
33
34
  register(api) {
34
- registerSentinelControl(api.registerTool, manager);
35
+ registerSentinelControl(api.registerTool.bind(api), manager);
35
36
  },
36
37
  };
37
38
  }
39
+ // OpenClaw plugin entrypoint (default plugin object with register)
40
+ const sentinelPlugin = {
41
+ id: "openclaw-sentinel",
42
+ name: "OpenClaw Sentinel",
43
+ description: "Secure declarative gateway-native watcher plugin for OpenClaw",
44
+ configSchema: sentinelConfigSchema,
45
+ register(api) {
46
+ const plugin = createSentinelPlugin(api.pluginConfig);
47
+ void plugin.init();
48
+ plugin.register(api);
49
+ },
50
+ };
51
+ export const register = sentinelPlugin.register.bind(sentinelPlugin);
52
+ export const activate = sentinelPlugin.register.bind(sentinelPlugin);
53
+ export default sentinelPlugin;
38
54
  export * from "./types.js";
package/dist/tool.d.ts CHANGED
@@ -1,2 +1,5 @@
1
+ import type { AnyAgentTool } from "openclaw/plugin-sdk";
1
2
  import { WatcherManager } from "./watcherManager.js";
2
- export declare function registerSentinelControl(registerTool: (name: string, handler: (input: unknown) => Promise<unknown>) => void, manager: WatcherManager): void;
3
+ type RegisterToolFn = (tool: AnyAgentTool) => void;
4
+ export declare function registerSentinelControl(registerTool: RegisterToolFn, manager: WatcherManager): void;
5
+ export {};
package/dist/tool.js CHANGED
@@ -1,5 +1,6 @@
1
+ import { jsonResult } from "openclaw/plugin-sdk";
1
2
  import { z } from "zod";
2
- const inputSchema = z
3
+ const ParamsSchema = z
3
4
  .object({
4
5
  action: z.enum(["create", "enable", "disable", "remove", "status", "list"]),
5
6
  id: z.string().optional(),
@@ -7,32 +8,34 @@ const inputSchema = z
7
8
  })
8
9
  .strict();
9
10
  export function registerSentinelControl(registerTool, manager) {
10
- registerTool("sentinel_control", async (input) => {
11
- const parsed = inputSchema.parse(input);
12
- switch (parsed.action) {
13
- case "create":
14
- return manager.create(parsed.watcher);
15
- case "enable":
16
- if (!parsed.id)
17
- throw new Error("id required");
18
- await manager.enable(parsed.id);
19
- return { ok: true };
20
- case "disable":
21
- if (!parsed.id)
22
- throw new Error("id required");
23
- await manager.disable(parsed.id);
24
- return { ok: true };
25
- case "remove":
26
- if (!parsed.id)
27
- throw new Error("id required");
28
- await manager.remove(parsed.id);
29
- return { ok: true };
30
- case "status":
31
- if (!parsed.id)
32
- throw new Error("id required");
33
- return manager.status(parsed.id);
34
- case "list":
35
- return manager.list();
36
- }
11
+ registerTool({
12
+ name: "sentinel_control",
13
+ label: "sentinel_control",
14
+ description: "Create/manage sentinel watchers",
15
+ parameters: {
16
+ action: {
17
+ type: "string",
18
+ enum: ["create", "enable", "disable", "remove", "status", "list"],
19
+ },
20
+ id: { type: "string" },
21
+ watcher: { type: "object" },
22
+ },
23
+ async execute(_toolCallId, params) {
24
+ const payload = ParamsSchema.parse((params ?? {}));
25
+ switch (payload.action) {
26
+ case "create":
27
+ return jsonResult(await manager.create(payload.watcher));
28
+ case "enable":
29
+ return jsonResult(await manager.enable(payload.id ?? ""));
30
+ case "disable":
31
+ return jsonResult(await manager.disable(payload.id ?? ""));
32
+ case "remove":
33
+ return jsonResult(await manager.remove(payload.id ?? ""));
34
+ case "status":
35
+ return jsonResult(manager.status(payload.id ?? ""));
36
+ case "list":
37
+ return jsonResult(manager.list());
38
+ }
39
+ },
37
40
  });
38
41
  }
@@ -2,8 +2,97 @@
2
2
  "id": "openclaw-sentinel",
3
3
  "configSchema": {
4
4
  "type": "object",
5
- "additionalProperties": true,
6
- "properties": {}
5
+ "additionalProperties": false,
6
+ "properties": {
7
+ "allowedHosts": {
8
+ "type": "array",
9
+ "items": { "type": "string" },
10
+ "description": "Hostnames the watchers are permitted to connect to. Must be explicitly configured — no hosts are allowed by default.",
11
+ "default": []
12
+ },
13
+ "localDispatchBase": {
14
+ "type": "string",
15
+ "format": "uri",
16
+ "description": "Base URL for internal webhook dispatch",
17
+ "default": "http://127.0.0.1:18789"
18
+ },
19
+ "dispatchAuthToken": {
20
+ "type": "string",
21
+ "description": "Bearer token for authenticating webhook dispatch requests"
22
+ },
23
+ "stateFilePath": {
24
+ "type": "string",
25
+ "description": "Custom path for the sentinel state persistence file"
26
+ },
27
+ "limits": {
28
+ "type": "object",
29
+ "additionalProperties": false,
30
+ "description": "Resource limits for watcher creation",
31
+ "properties": {
32
+ "maxWatchersTotal": {
33
+ "type": "number",
34
+ "description": "Maximum total watchers across all skills",
35
+ "default": 200
36
+ },
37
+ "maxWatchersPerSkill": {
38
+ "type": "number",
39
+ "description": "Maximum watchers per skill",
40
+ "default": 20
41
+ },
42
+ "maxConditionsPerWatcher": {
43
+ "type": "number",
44
+ "description": "Maximum conditions per watcher definition",
45
+ "default": 25
46
+ },
47
+ "maxIntervalMsFloor": {
48
+ "type": "number",
49
+ "description": "Minimum allowed polling interval in milliseconds",
50
+ "default": 1000
51
+ }
52
+ }
53
+ }
54
+ }
55
+ },
56
+ "uiHints": {
57
+ "allowedHosts": {
58
+ "label": "Allowed Hosts",
59
+ "help": "Hostnames the watchers are permitted to connect to"
60
+ },
61
+ "localDispatchBase": {
62
+ "label": "Dispatch Base URL",
63
+ "help": "Base URL for internal webhook dispatch"
64
+ },
65
+ "dispatchAuthToken": {
66
+ "label": "Dispatch Auth Token",
67
+ "help": "Bearer token for webhook dispatch authentication",
68
+ "sensitive": true,
69
+ "placeholder": "sk-..."
70
+ },
71
+ "stateFilePath": {
72
+ "label": "State File Path",
73
+ "help": "Custom path for sentinel state persistence file",
74
+ "advanced": true
75
+ },
76
+ "limits.maxWatchersTotal": {
77
+ "label": "Max Watchers",
78
+ "help": "Maximum total watchers across all skills",
79
+ "advanced": true
80
+ },
81
+ "limits.maxWatchersPerSkill": {
82
+ "label": "Max Per Skill",
83
+ "help": "Maximum watchers a single skill can create",
84
+ "advanced": true
85
+ },
86
+ "limits.maxConditionsPerWatcher": {
87
+ "label": "Max Conditions",
88
+ "help": "Maximum conditions per watcher definition",
89
+ "advanced": true
90
+ },
91
+ "limits.maxIntervalMsFloor": {
92
+ "label": "Min Poll Interval (ms)",
93
+ "help": "Minimum allowed polling interval in milliseconds",
94
+ "advanced": true
95
+ }
7
96
  },
8
97
  "install": {
9
98
  "npmSpec": "@coffeexdev/openclaw-sentinel"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffeexdev/openclaw-sentinel",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Secure declarative gateway-native watcher plugin for OpenClaw",
5
5
  "keywords": [
6
6
  "openclaw",
@@ -38,7 +38,8 @@
38
38
  "changeset": "changeset",
39
39
  "version-packages": "changeset version",
40
40
  "release": "changeset publish",
41
- "prepack": "npm run build"
41
+ "prepack": "npm run build",
42
+ "prepare": "husky"
42
43
  },
43
44
  "dependencies": {
44
45
  "re2-wasm": "^1.0.2",
@@ -49,15 +50,24 @@
49
50
  "@changesets/cli": "^2.29.7",
50
51
  "@types/node": "^24.0.0",
51
52
  "@types/ws": "^8.5.13",
53
+ "husky": "^9.1.7",
54
+ "lint-staged": "^16.3.2",
55
+ "openclaw": "latest",
52
56
  "oxfmt": "^0.36.0",
53
57
  "typescript": "^5.8.2",
54
58
  "vitest": "^3.0.8"
55
59
  },
60
+ "peerDependencies": {
61
+ "openclaw": ">=2026.3.2"
62
+ },
56
63
  "optionalDependencies": {
57
64
  "re2": "^1.23.3"
58
65
  },
66
+ "lint-staged": {
67
+ "*.{ts,js,jsx,tsx,mjs,cjs,json,yml,yaml,md,css,html}": "oxfmt --write"
68
+ },
59
69
  "engines": {
60
- "node": ">=20"
70
+ "node": ">=22"
61
71
  },
62
72
  "openclaw": {
63
73
  "extensions": [