@pushary/openclaw 0.0.0-stage → 0.1.1

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -2
  3. package/assets/icon.png +0 -0
  4. package/dist/api-key-shape.d.ts +3 -0
  5. package/dist/api-key-shape.js +5 -0
  6. package/dist/approval-hook.d.ts +23 -0
  7. package/dist/approval-hook.js +88 -0
  8. package/dist/approval-question.d.ts +19 -0
  9. package/dist/approval-question.js +66 -0
  10. package/dist/connection.d.ts +26 -0
  11. package/dist/connection.js +101 -0
  12. package/dist/decision-reply.d.ts +9 -0
  13. package/dist/decision-reply.js +6 -0
  14. package/dist/gate-result.d.ts +13 -0
  15. package/dist/gate-result.js +58 -0
  16. package/dist/gate.d.ts +49 -0
  17. package/dist/gate.js +173 -0
  18. package/dist/hook-budget.d.ts +10 -0
  19. package/dist/hook-budget.js +19 -0
  20. package/dist/host-api.d.ts +60 -0
  21. package/dist/host-api.js +1 -0
  22. package/dist/host-version.d.ts +12 -0
  23. package/dist/host-version.js +25 -0
  24. package/dist/index.d.ts +7 -0
  25. package/dist/index.js +32 -0
  26. package/dist/openclaw-paths.d.ts +14 -0
  27. package/dist/openclaw-paths.js +43 -0
  28. package/dist/openclaw-write-guard.d.ts +5 -0
  29. package/dist/openclaw-write-guard.js +93 -0
  30. package/dist/protected-writes.d.ts +33 -0
  31. package/dist/protected-writes.js +217 -0
  32. package/dist/pushary-client.d.ts +42 -0
  33. package/dist/pushary-client.js +103 -0
  34. package/dist/pushary-hosts.d.ts +2 -0
  35. package/dist/pushary-hosts.js +3 -0
  36. package/dist/redaction.d.ts +9 -0
  37. package/dist/redaction.js +53 -0
  38. package/dist/replies.d.ts +33 -0
  39. package/dist/replies.js +65 -0
  40. package/dist/settings.d.ts +34 -0
  41. package/dist/settings.js +103 -0
  42. package/openclaw.plugin.json +32 -0
  43. package/package.json +74 -4
@@ -0,0 +1,103 @@
1
+ import { FILE_WRITING_TOOLS, SESSIONS_SPAWN_TOOL } from './protected-writes.js';
2
+ export const PLUGIN_ID = 'pushary';
3
+ export const PLUGIN_NAME = 'Pushary';
4
+ export const PLUGIN_DESCRIPTION = 'Asks you on your phone before OpenClaw runs a gated tool call, and blocks the call unless you approve it.';
5
+ export const AGENT_LABEL = 'OpenClaw';
6
+ export const DEFAULT_OPENCLAW_AGENT_ID = 'main';
7
+ export const EXEC_TOOL = 'exec';
8
+ export const PROCESS_TOOL = 'process';
9
+ export const PLUGINS_TOOL = 'plugins';
10
+ export const GATEWAY_TOOL = 'gateway';
11
+ export const OPENCLAW_SYSTEM_TOOL = 'openclaw';
12
+ export const AUTOMATIONS_TOOL = 'automations';
13
+ export const TERMINAL_TOOL = 'terminal';
14
+ export const TOOL_ACTION_PARAMETER = 'action';
15
+ const TOOL_ID_PATTERN = /^[a-z0-9][a-z0-9_.:-]{0,99}$/;
16
+ const AGENT_NAME_SEPARATOR = ' - ';
17
+ const AGENT_ID_MAX_LENGTH = 40;
18
+ const HOST_TOOL_ALIASES = new Map([
19
+ ['bash', EXEC_TOOL],
20
+ ['exec_command', EXEC_TOOL],
21
+ ['shell', EXEC_TOOL],
22
+ ['shell_command', EXEC_TOOL],
23
+ ['apply-patch', 'apply_patch'],
24
+ ['tool_call', 'dispatch_action'],
25
+ ['cron', AUTOMATIONS_TOOL],
26
+ ['agent', 'spawn_agent'],
27
+ ]);
28
+ const HOST_SPELLINGS_OF_EXEC = [EXEC_TOOL, 'shell', 'shell_command'];
29
+ const PROCESS_ACTIONS_WITHOUT_APPROVAL = new Set(['list', 'poll', 'log', 'kill', 'clear', 'remove']);
30
+ const PLUGINS_ACTIONS_WITHOUT_APPROVAL = new Set(['list', 'inspect', 'search']);
31
+ const GATEWAY_ACTIONS_WITHOUT_APPROVAL = new Set(['config.get', 'config.schema.lookup']);
32
+ const OPENCLAW_SYSTEM_ACTIONS_WITHOUT_APPROVAL = new Set([
33
+ 'status', 'models', 'agents', 'channels', 'channel_info', 'audit', 'validate_config', 'doctor',
34
+ 'config_get', 'config_schema', 'gateway_status', 'plugin_list', 'plugin_search', 'open_agent', 'open_setup',
35
+ ]);
36
+ const AUTOMATIONS_ACTIONS_WITHOUT_APPROVAL = new Set(['status', 'list', 'get', 'runs', 'next_check']);
37
+ const TERMINAL_ACTIONS_WITHOUT_APPROVAL = new Set(['read', 'list']);
38
+ const ACTIONS_WITHOUT_APPROVAL_BY_TOOL = new Map([
39
+ [PROCESS_TOOL, PROCESS_ACTIONS_WITHOUT_APPROVAL],
40
+ [PLUGINS_TOOL, PLUGINS_ACTIONS_WITHOUT_APPROVAL],
41
+ [GATEWAY_TOOL, GATEWAY_ACTIONS_WITHOUT_APPROVAL],
42
+ [OPENCLAW_SYSTEM_TOOL, OPENCLAW_SYSTEM_ACTIONS_WITHOUT_APPROVAL],
43
+ [AUTOMATIONS_TOOL, AUTOMATIONS_ACTIONS_WITHOUT_APPROVAL],
44
+ [TERMINAL_TOOL, TERMINAL_ACTIONS_WITHOUT_APPROVAL],
45
+ ]);
46
+ export const DEFAULT_GATED_TOOLS = [EXEC_TOOL, PROCESS_TOOL, PLUGINS_TOOL, GATEWAY_TOOL, OPENCLAW_SYSTEM_TOOL, AUTOMATIONS_TOOL, TERMINAL_TOOL];
47
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
48
+ export const hostToolId = (name) => {
49
+ const lowered = name.trim().toLowerCase();
50
+ return HOST_TOOL_ALIASES.get(lowered) ?? lowered;
51
+ };
52
+ const configuredToolId = (value) => {
53
+ if (typeof value !== 'string')
54
+ return null;
55
+ const id = hostToolId(value);
56
+ return TOOL_ID_PATTERN.test(id) ? id : null;
57
+ };
58
+ const withDefaults = (extra) => [...new Set([...DEFAULT_GATED_TOOLS, ...extra])];
59
+ const DEFAULT_SETTINGS = { gatedTools: DEFAULT_GATED_TOOLS };
60
+ const rejectedEntry = (value) => (typeof value === 'string' ? value : JSON.stringify(value) ?? String(value));
61
+ export const readGateSettings = (pluginConfig) => {
62
+ if (pluginConfig === undefined)
63
+ return { kind: 'defaulted', settings: DEFAULT_SETTINGS };
64
+ if (!isRecord(pluginConfig))
65
+ return { kind: 'invalid', settings: DEFAULT_SETTINGS };
66
+ const configured = pluginConfig.gatedTools;
67
+ if (configured === undefined)
68
+ return { kind: 'defaulted', settings: DEFAULT_SETTINGS };
69
+ if (!Array.isArray(configured))
70
+ return { kind: 'invalid', settings: DEFAULT_SETTINGS };
71
+ const accepted = configured.flatMap(entry => {
72
+ const id = configuredToolId(entry);
73
+ return id === null ? [] : [id];
74
+ });
75
+ const rejected = configured.filter(entry => configuredToolId(entry) === null).map(rejectedEntry);
76
+ return { kind: 'configured', settings: { gatedTools: withDefaults(accepted) }, rejected };
77
+ };
78
+ const hostSpellingsOf = (toolId) => (toolId === EXEC_TOOL ? HOST_SPELLINGS_OF_EXEC : [toolId]);
79
+ export const hookMatcherFor = (settings) => {
80
+ const [first, ...rest] = [...new Set([...settings.gatedTools.flatMap(hostSpellingsOf), ...FILE_WRITING_TOOLS, SESSIONS_SPAWN_TOOL])];
81
+ return [first ?? EXEC_TOOL, ...rest];
82
+ };
83
+ const toolActionOf = (event) => {
84
+ const action = event.params[TOOL_ACTION_PARAMETER];
85
+ return typeof action === 'string' ? action.trim() : null;
86
+ };
87
+ const actionNeedsApproval = (actionsWithoutApproval, event) => {
88
+ const action = toolActionOf(event);
89
+ return action === null || !actionsWithoutApproval.has(action);
90
+ };
91
+ export const needsApproval = (settings, event) => {
92
+ const toolId = hostToolId(event.toolName);
93
+ if (!settings.gatedTools.includes(toolId))
94
+ return false;
95
+ const actionsWithoutApproval = ACTIONS_WITHOUT_APPROVAL_BY_TOOL.get(toolId);
96
+ return actionsWithoutApproval === undefined || actionNeedsApproval(actionsWithoutApproval, event);
97
+ };
98
+ export const askingAgentName = (openClawAgentId) => {
99
+ const agentId = openClawAgentId?.trim().slice(0, AGENT_ID_MAX_LENGTH);
100
+ return agentId && agentId !== DEFAULT_OPENCLAW_AGENT_ID
101
+ ? `${AGENT_LABEL}${AGENT_NAME_SEPARATOR}${agentId}`
102
+ : AGENT_LABEL;
103
+ };
@@ -0,0 +1,32 @@
1
+ {
2
+ "id": "pushary",
3
+ "name": "Pushary",
4
+ "description": "Asks you on your phone before OpenClaw runs a gated tool call, and blocks the call unless you approve it.",
5
+ "categories": [
6
+ "security"
7
+ ],
8
+ "activation": {
9
+ "onStartup": true
10
+ },
11
+ "uiHints": {
12
+ "gatedTools": {
13
+ "label": "More tools that need your approval",
14
+ "help": "OpenClaw tool ids to gate on top of the defaults (exec, process input, and the plugins, gateway and openclaw actions that change OpenClaw), for example message or browser."
15
+ }
16
+ },
17
+ "configSchema": {
18
+ "type": "object",
19
+ "additionalProperties": false,
20
+ "properties": {
21
+ "gatedTools": {
22
+ "type": "array",
23
+ "minItems": 1,
24
+ "uniqueItems": true,
25
+ "items": {
26
+ "type": "string",
27
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$"
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
package/package.json CHANGED
@@ -1,6 +1,76 @@
1
1
  {
2
2
  "name": "@pushary/openclaw",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Phone approvals for OpenClaw. Pushary asks you before your AI agent runs a gated tool call, and blocks the call unless you approve it.",
5
+ "author": "Pushary <business@pushary.com>",
6
+ "homepage": "https://pushary.com/human-in-the-loop-openclaw",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Pushary/pushary-openclaw.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://pushary.com/support"
13
+ },
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./openclaw.plugin.json": "./openclaw.plugin.json"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "assets",
29
+ "openclaw.plugin.json",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "sideEffects": false,
34
+ "engines": {
35
+ "node": ">=24.16.0"
36
+ },
37
+ "scripts": {
38
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
39
+ "typecheck": "tsc --noEmit",
40
+ "test": "node ../../scripts/ci/run-tests.mjs src",
41
+ "clean": "rm -rf dist",
42
+ "prepublishOnly": "npm run build && npm run typecheck"
43
+ },
44
+ "openclaw": {
45
+ "extensions": [
46
+ "./dist/index.js"
47
+ ],
48
+ "compat": {
49
+ "pluginApi": ">=2026.11.3"
50
+ },
51
+ "build": {
52
+ "openclawVersion": "2026.11.3"
53
+ },
54
+ "install": {
55
+ "npmSpec": "@pushary/openclaw",
56
+ "minHostVersion": ">=2026.11.3"
57
+ }
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^24.0.0",
61
+ "typescript": "^5"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ },
66
+ "keywords": [
67
+ "pushary",
68
+ "openclaw",
69
+ "openclaw-plugin",
70
+ "human-in-the-loop",
71
+ "approval",
72
+ "tool-approval",
73
+ "push-notifications",
74
+ "ai-agents"
75
+ ]
76
+ }