@poolzin/pool-bot 2026.3.7 → 2026.3.9

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 (44) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/.buildstamp +1 -1
  3. package/dist/agents/error-classifier.js +302 -0
  4. package/dist/agents/skills/security.js +217 -0
  5. package/dist/build-info.json +3 -3
  6. package/dist/cli/lazy-commands.example.js +113 -0
  7. package/dist/cli/lazy-commands.js +329 -0
  8. package/dist/cli/program/command-registry.js +13 -0
  9. package/dist/cli/program/register.skills.js +4 -0
  10. package/dist/config/config.js +1 -0
  11. package/dist/config/secrets-integration.js +88 -0
  12. package/dist/context-engine/index.js +33 -0
  13. package/dist/context-engine/legacy.js +181 -0
  14. package/dist/context-engine/registry.js +86 -0
  15. package/dist/context-engine/summarizing.js +293 -0
  16. package/dist/context-engine/types.js +7 -0
  17. package/dist/infra/abort-pattern.js +106 -0
  18. package/dist/infra/retry.js +94 -0
  19. package/dist/secrets/index.js +28 -0
  20. package/dist/secrets/resolver.js +185 -0
  21. package/dist/secrets/runtime.js +142 -0
  22. package/dist/secrets/types.js +11 -0
  23. package/dist/security/dangerous-tools.js +80 -0
  24. package/dist/security/types.js +12 -0
  25. package/dist/skills/commands.js +351 -0
  26. package/dist/skills/index.js +167 -0
  27. package/dist/skills/loader.js +282 -0
  28. package/dist/skills/parser.js +461 -0
  29. package/dist/skills/registry.js +397 -0
  30. package/dist/skills/security.js +318 -0
  31. package/dist/skills/types.js +21 -0
  32. package/dist/test-utils/index.js +219 -0
  33. package/dist/tui/index.js +595 -0
  34. package/docs/INTEGRATION_PLAN.md +475 -0
  35. package/docs/INTEGRATION_SUMMARY.md +215 -0
  36. package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
  37. package/docs/integrations/INTEGRATION_PLAN.md +424 -0
  38. package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
  39. package/docs/integrations/XYOPS_PLAN.md +978 -0
  40. package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
  41. package/docs/skills/SKILL.md +524 -0
  42. package/docs/skills.md +405 -0
  43. package/package.json +1 -1
  44. package/skills/example-skill/SKILL.md +195 -0
@@ -0,0 +1,185 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve as resolvePath } from "node:path";
3
+ /**
4
+ * Parse a secret reference string
5
+ * Formats:
6
+ * - env:default:KEY - Environment variable
7
+ * - env:production:KEY - Environment variable from specific source
8
+ * - file:/path/to/secrets.json:KEY - JSON file
9
+ * - file:/path/to/secrets.json - Entire JSON file as secret
10
+ * - raw value - Direct value (backward compatibility)
11
+ */
12
+ export function parseSecretRef(value) {
13
+ // Check if it's a reference
14
+ const envMatch = value.match(/^env:([^:]+):(.+)$/);
15
+ if (envMatch) {
16
+ return {
17
+ isRef: true,
18
+ ref: {
19
+ provider: "env",
20
+ source: envMatch[1],
21
+ key: envMatch[2],
22
+ },
23
+ };
24
+ }
25
+ const fileMatch = value.match(/^file:([^:]+):(.+)$/);
26
+ if (fileMatch) {
27
+ return {
28
+ isRef: true,
29
+ ref: {
30
+ provider: "file",
31
+ source: fileMatch[1],
32
+ key: fileMatch[2],
33
+ },
34
+ };
35
+ }
36
+ const fileOnlyMatch = value.match(/^file:(.+)$/);
37
+ if (fileOnlyMatch) {
38
+ return {
39
+ isRef: true,
40
+ ref: {
41
+ provider: "file",
42
+ source: fileOnlyMatch[1],
43
+ key: "", // Entire file
44
+ },
45
+ };
46
+ }
47
+ // Not a reference - treat as direct value
48
+ return {
49
+ isRef: false,
50
+ rawValue: value,
51
+ };
52
+ }
53
+ /**
54
+ * Resolve a secret reference to its value
55
+ */
56
+ export function resolveSecret(ref, options = {}) {
57
+ const warnings = [];
58
+ switch (ref.provider) {
59
+ case "env":
60
+ return resolveEnvSecret(ref, options, warnings);
61
+ case "file":
62
+ return resolveFileSecret(ref, options, warnings);
63
+ case "exec":
64
+ return resolveExecSecret(ref, options, warnings);
65
+ default:
66
+ return {
67
+ value: undefined,
68
+ resolved: false,
69
+ warnings: [`Unknown provider: ${String(ref.provider)}`],
70
+ };
71
+ }
72
+ }
73
+ function resolveEnvSecret(ref, options, warnings) {
74
+ const value = process.env[ref.key];
75
+ if (value === undefined) {
76
+ if (!options.allowMissing) {
77
+ warnings.push(`Environment variable ${ref.key} is not set`);
78
+ }
79
+ return {
80
+ value: undefined,
81
+ resolved: false,
82
+ source: `env:${ref.source}:${ref.key}`,
83
+ warnings,
84
+ };
85
+ }
86
+ return {
87
+ value,
88
+ resolved: true,
89
+ source: `env:${ref.source}:${ref.key}`,
90
+ warnings,
91
+ };
92
+ }
93
+ function resolveFileSecret(ref, options, warnings) {
94
+ try {
95
+ const filePath = resolvePath(ref.source);
96
+ // Path traversal protection
97
+ if (options.basePath && !filePath.startsWith(resolvePath(options.basePath))) {
98
+ warnings.push(`Secret file path outside allowed directory: ${ref.source}`);
99
+ return {
100
+ value: undefined,
101
+ resolved: false,
102
+ source: `file:${ref.source}`,
103
+ warnings,
104
+ };
105
+ }
106
+ const content = readFileSync(filePath, "utf-8");
107
+ // If key is empty, return entire file content
108
+ if (!ref.key) {
109
+ return {
110
+ value: content.trim(),
111
+ resolved: true,
112
+ source: `file:${ref.source}`,
113
+ warnings,
114
+ };
115
+ }
116
+ // Try to parse as JSON
117
+ try {
118
+ const json = JSON.parse(content);
119
+ if (typeof json === "object" && json !== null) {
120
+ if (ref.key in json) {
121
+ const value = String(json[ref.key]);
122
+ return {
123
+ value,
124
+ resolved: true,
125
+ source: `file:${ref.source}:${ref.key}`,
126
+ warnings,
127
+ };
128
+ }
129
+ warnings.push(`Key "${ref.key}" not found in JSON file ${ref.source}`);
130
+ }
131
+ }
132
+ catch {
133
+ // Not valid JSON, treat as plain text
134
+ warnings.push(`File ${ref.source} is not valid JSON`);
135
+ }
136
+ if (!options.allowMissing) {
137
+ warnings.push(`Could not resolve secret from file: ${ref.source}:${ref.key}`);
138
+ }
139
+ return {
140
+ value: undefined,
141
+ resolved: false,
142
+ source: `file:${ref.source}:${ref.key}`,
143
+ warnings,
144
+ };
145
+ }
146
+ catch (error) {
147
+ const message = error instanceof Error ? error.message : String(error);
148
+ warnings.push(`Failed to read file ${ref.source}: ${message}`);
149
+ return {
150
+ value: undefined,
151
+ resolved: false,
152
+ source: `file:${ref.source}:${ref.key}`,
153
+ warnings,
154
+ };
155
+ }
156
+ }
157
+ function resolveExecSecret(ref, _options, warnings) {
158
+ // Exec provider not yet implemented
159
+ warnings.push("Exec provider is not yet implemented");
160
+ return {
161
+ value: undefined,
162
+ resolved: false,
163
+ source: `exec:${ref.source}:${ref.key}`,
164
+ warnings,
165
+ };
166
+ }
167
+ /**
168
+ * Resolve a value that may be a secret reference or direct value
169
+ */
170
+ export function resolveValue(value, options = {}) {
171
+ const parsed = parseSecretRef(value);
172
+ if (parsed.isRef && parsed.ref) {
173
+ return resolveSecret(parsed.ref, options);
174
+ }
175
+ // Direct value
176
+ const warnings = [];
177
+ if (options.warnOnDirectValues && parsed.rawValue !== undefined) {
178
+ warnings.push("Using direct secret value. Consider using env:default:KEY or file:/path:key format for better security");
179
+ }
180
+ return {
181
+ value: parsed.rawValue,
182
+ resolved: parsed.rawValue !== undefined,
183
+ warnings,
184
+ };
185
+ }
@@ -0,0 +1,142 @@
1
+ import { resolveValue } from "./resolver.js";
2
+ /**
3
+ * Runtime snapshot for resolved secrets
4
+ *
5
+ * This class manages a snapshot of resolved secrets that is created
6
+ * at startup and remains immutable during runtime for security.
7
+ */
8
+ export class SecretsRuntime {
9
+ snapshot;
10
+ config;
11
+ constructor(config = {}) {
12
+ this.config = {
13
+ allowDirectValues: true,
14
+ warnOnDirectValues: true,
15
+ failOnUnresolved: false,
16
+ ...config,
17
+ };
18
+ this.snapshot = {
19
+ secrets: new Map(),
20
+ unresolved: [],
21
+ warnings: [],
22
+ timestamp: new Date(),
23
+ };
24
+ }
25
+ /**
26
+ * Resolve a single secret and add to snapshot
27
+ */
28
+ resolve(key, value) {
29
+ const result = resolveValue(value, {
30
+ allowDirectValues: this.config.allowDirectValues,
31
+ warnOnDirectValues: this.config.warnOnDirectValues,
32
+ allowMissing: !this.config.failOnUnresolved,
33
+ });
34
+ if (result.resolved && result.value !== undefined) {
35
+ this.snapshot.secrets.set(key, {
36
+ value: result.value,
37
+ source: result.source || "direct",
38
+ warnings: result.warnings,
39
+ });
40
+ }
41
+ else {
42
+ this.snapshot.unresolved.push(key);
43
+ }
44
+ if (result.warnings.length > 0) {
45
+ this.snapshot.warnings.push(...result.warnings);
46
+ }
47
+ return result;
48
+ }
49
+ /**
50
+ * Resolve multiple secrets from an object
51
+ */
52
+ resolveObject(obj) {
53
+ const resolved = {};
54
+ for (const [key, value] of Object.entries(obj)) {
55
+ const result = this.resolve(key, value);
56
+ if (result.value !== undefined) {
57
+ resolved[key] = result.value;
58
+ }
59
+ }
60
+ return resolved;
61
+ }
62
+ /**
63
+ * Get a resolved secret value
64
+ */
65
+ get(key) {
66
+ return this.snapshot.secrets.get(key)?.value;
67
+ }
68
+ /**
69
+ * Check if a secret is resolved
70
+ */
71
+ has(key) {
72
+ return this.snapshot.secrets.has(key);
73
+ }
74
+ /**
75
+ * Get full snapshot info for a secret
76
+ */
77
+ getInfo(key) {
78
+ return this.snapshot.secrets.get(key);
79
+ }
80
+ /**
81
+ * Get all unresolved keys
82
+ */
83
+ getUnresolved() {
84
+ return [...this.snapshot.unresolved];
85
+ }
86
+ /**
87
+ * Get all warnings
88
+ */
89
+ getWarnings() {
90
+ return [...this.snapshot.warnings];
91
+ }
92
+ /**
93
+ * Get snapshot timestamp
94
+ */
95
+ getTimestamp() {
96
+ return this.snapshot.timestamp;
97
+ }
98
+ /**
99
+ * Create an immutable snapshot copy
100
+ */
101
+ createSnapshot() {
102
+ return {
103
+ secrets: new Map(this.snapshot.secrets),
104
+ unresolved: [...this.snapshot.unresolved],
105
+ warnings: [...this.snapshot.warnings],
106
+ timestamp: new Date(this.snapshot.timestamp),
107
+ };
108
+ }
109
+ /**
110
+ * Check if there are any unresolved required secrets
111
+ */
112
+ hasUnresolved() {
113
+ return this.snapshot.unresolved.length > 0;
114
+ }
115
+ /**
116
+ * Get summary of the runtime state
117
+ */
118
+ getSummary() {
119
+ return {
120
+ resolved: this.snapshot.secrets.size,
121
+ unresolved: this.snapshot.unresolved.length,
122
+ warnings: this.snapshot.warnings.length,
123
+ timestamp: this.snapshot.timestamp,
124
+ };
125
+ }
126
+ }
127
+ /**
128
+ * Global runtime instance (lazy initialized)
129
+ */
130
+ let globalRuntime = null;
131
+ export function getGlobalRuntime() {
132
+ if (!globalRuntime) {
133
+ globalRuntime = new SecretsRuntime();
134
+ }
135
+ return globalRuntime;
136
+ }
137
+ export function setGlobalRuntime(runtime) {
138
+ globalRuntime = runtime;
139
+ }
140
+ export function resetGlobalRuntime() {
141
+ globalRuntime = null;
142
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Secrets Management System for PoolBot
3
+ *
4
+ * Provides secure secret resolution with support for:
5
+ * - Environment variables (env:default:KEY)
6
+ * - JSON files (file:/path/to/secrets.json)
7
+ * - Direct values (for backward compatibility)
8
+ *
9
+ * Based on OpenClaw patterns but adapted for PoolBot architecture.
10
+ */
11
+ export {};
@@ -14,6 +14,20 @@ export const DEFAULT_GATEWAY_HTTP_TOOL_DENY = [
14
14
  "gateway",
15
15
  // Interactive setup — requires terminal QR scan, hangs on HTTP
16
16
  "whatsapp_login",
17
+ // Cron automation — scheduling can be used for persistence
18
+ "cron",
19
+ // Exec tools — remote code execution risk
20
+ "exec",
21
+ "shell",
22
+ "spawn",
23
+ // File system mutations
24
+ "fs_write",
25
+ "fs_delete",
26
+ "fs_move",
27
+ "apply_patch",
28
+ // Device pairing
29
+ "device_pair",
30
+ "pair_device",
17
31
  ];
18
32
  /**
19
33
  * ACP tools that should always require explicit user approval.
@@ -30,5 +44,71 @@ export const DANGEROUS_ACP_TOOL_NAMES = [
30
44
  "fs_delete",
31
45
  "fs_move",
32
46
  "apply_patch",
47
+ "cron",
48
+ "exec_approved",
49
+ "elevated_exec",
33
50
  ];
34
51
  export const DANGEROUS_ACP_TOOLS = new Set(DANGEROUS_ACP_TOOL_NAMES);
52
+ /**
53
+ * Tool categories by risk level
54
+ */
55
+ export const TOOL_CATEGORIES = {
56
+ /** Execution tools - can run arbitrary code */
57
+ EXECUTION: ["exec", "spawn", "shell", "exec_approved", "elevated_exec"],
58
+ /** File system mutators - can modify files */
59
+ FILE_MUTATORS: ["fs_write", "fs_delete", "fs_move", "apply_patch", "fs_mkdir", "fs_rename"],
60
+ /** Session orchestrators - can spawn/control sessions */
61
+ SESSION_ORCHESTRATORS: ["sessions_spawn", "sessions_send", "sessions_kill", "sessions_reset"],
62
+ /** Gateway control - can reconfigure gateway */
63
+ GATEWAY_CONTROL: ["gateway", "config_set", "config_reload"],
64
+ /** Automation - can schedule/automate */
65
+ AUTOMATION: ["cron", "hook_register", "automation_create"],
66
+ /** External integrations - can interact with external services */
67
+ EXTERNAL: ["whatsapp_login", "device_pair", "pair_device", "oauth_flow"],
68
+ };
69
+ /**
70
+ * Check if a tool is in a specific category
71
+ */
72
+ export function isToolInCategory(toolName, category) {
73
+ return TOOL_CATEGORIES[category].includes(toolName);
74
+ }
75
+ /**
76
+ * Check if a tool is considered dangerous
77
+ */
78
+ export function isDangerousTool(toolName) {
79
+ return DANGEROUS_ACP_TOOLS.has(toolName.toLowerCase().trim());
80
+ }
81
+ /**
82
+ * Check if a tool is denied over HTTP gateway
83
+ */
84
+ export function isGatewayHttpDenied(toolName) {
85
+ return DEFAULT_GATEWAY_HTTP_TOOL_DENY.includes(toolName.toLowerCase().trim());
86
+ }
87
+ /**
88
+ * Get risk level for a tool
89
+ */
90
+ export function getToolRiskLevel(toolName) {
91
+ if (["exec", "spawn", "shell", "sessions_spawn"].includes(toolName)) {
92
+ return "critical";
93
+ }
94
+ if (["fs_write", "fs_delete", "apply_patch", "gateway"].includes(toolName)) {
95
+ return "high";
96
+ }
97
+ if (["fs_move", "cron", "sessions_send"].includes(toolName)) {
98
+ return "medium";
99
+ }
100
+ return "low";
101
+ }
102
+ /**
103
+ * Get tools that require explicit approval
104
+ */
105
+ export function getToolsRequiringApproval(riskLevel = "high") {
106
+ const allDangerous = Array.from(DANGEROUS_ACP_TOOLS);
107
+ if (riskLevel === "critical") {
108
+ return allDangerous.filter((t) => getToolRiskLevel(t) === "critical");
109
+ }
110
+ if (riskLevel === "high") {
111
+ return allDangerous.filter((t) => ["critical", "high"].includes(getToolRiskLevel(t)));
112
+ }
113
+ return allDangerous;
114
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Security Module for PoolBot
3
+ *
4
+ * Provides enterprise-grade security controls:
5
+ * - Audit logging for security events
6
+ * - Dangerous tool detection
7
+ * - Safe regex validation
8
+ * - External content validation
9
+ *
10
+ * Based on OpenClaw patterns but adapted for PoolBot architecture.
11
+ */
12
+ export {};