@poolzin/pool-bot 2026.3.6 → 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.
- package/CHANGELOG.md +16 -0
- package/dist/.buildstamp +1 -1
- package/dist/agents/error-classifier.js +302 -0
- package/dist/agents/pi-tools.js +32 -2
- package/dist/agents/skills/security.js +217 -0
- package/dist/auto-reply/reply/get-reply.js +6 -0
- package/dist/auto-reply/reply/message-preprocess-hooks.js +17 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/banner.js +20 -1
- package/dist/cli/lazy-commands.example.js +113 -0
- package/dist/cli/lazy-commands.js +329 -0
- package/dist/cli/program/command-registry.js +13 -0
- package/dist/cli/program/register.skills.js +4 -0
- package/dist/cli/security-cli.js +211 -2
- package/dist/cli/tagline.js +7 -0
- package/dist/config/config.js +1 -0
- package/dist/config/secrets-integration.js +88 -0
- package/dist/config/types.cli.js +1 -0
- package/dist/config/types.security.js +33 -0
- package/dist/config/zod-schema.js +15 -0
- package/dist/config/zod-schema.providers-core.js +1 -0
- package/dist/config/zod-schema.security.js +113 -0
- package/dist/context-engine/index.js +33 -0
- package/dist/context-engine/legacy.js +181 -0
- package/dist/context-engine/registry.js +86 -0
- package/dist/context-engine/summarizing.js +293 -0
- package/dist/context-engine/types.js +7 -0
- package/dist/discord/monitor/message-handler.preflight.js +11 -2
- package/dist/gateway/http-common.js +6 -1
- package/dist/hooks/fire-and-forget.js +6 -0
- package/dist/hooks/internal-hooks.js +64 -19
- package/dist/hooks/message-hook-mappers.js +179 -0
- package/dist/infra/abort-pattern.js +106 -0
- package/dist/infra/retry.js +94 -0
- package/dist/secrets/index.js +28 -0
- package/dist/secrets/resolver.js +185 -0
- package/dist/secrets/runtime.js +142 -0
- package/dist/secrets/types.js +11 -0
- package/dist/security/capability-guards.js +89 -0
- package/dist/security/capability-manager.js +76 -0
- package/dist/security/capability.js +147 -0
- package/dist/security/dangerous-tools.js +80 -0
- package/dist/security/index.js +7 -0
- package/dist/security/middleware.js +105 -0
- package/dist/security/types.js +12 -0
- package/dist/skills/commands.js +351 -0
- package/dist/skills/index.js +167 -0
- package/dist/skills/loader.js +282 -0
- package/dist/skills/parser.js +461 -0
- package/dist/skills/registry.js +397 -0
- package/dist/skills/security.js +318 -0
- package/dist/skills/types.js +21 -0
- package/dist/slack/monitor/context.js +1 -0
- package/dist/slack/monitor/message-handler/dispatch.js +14 -1
- package/dist/slack/monitor/provider.js +2 -0
- package/dist/test-utils/index.js +219 -0
- package/dist/tui/index.js +595 -0
- package/docs/INTEGRATION_PLAN.md +475 -0
- package/docs/INTEGRATION_SUMMARY.md +215 -0
- package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
- package/docs/integrations/INTEGRATION_PLAN.md +424 -0
- package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
- package/docs/integrations/XYOPS_PLAN.md +978 -0
- package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
- package/docs/skills/SKILL.md +524 -0
- package/docs/skills.md +405 -0
- package/package.json +1 -1
- package/skills/example-skill/SKILL.md +195 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Integrating Lazy Commands with PoolBot CLI
|
|
3
|
+
*
|
|
4
|
+
* This file shows how to integrate the lazy command loading system
|
|
5
|
+
* into the main PoolBot CLI.
|
|
6
|
+
*
|
|
7
|
+
* ## Integration Steps
|
|
8
|
+
*
|
|
9
|
+
* 1. Import the lazy command helpers
|
|
10
|
+
* 2. Replace static imports with lazy loaders
|
|
11
|
+
* 3. Add performance monitoring
|
|
12
|
+
*
|
|
13
|
+
* ## Example Integration
|
|
14
|
+
*
|
|
15
|
+
* ```typescript
|
|
16
|
+
* #!/usr/bin/env node
|
|
17
|
+
* import { Command } from "commander";
|
|
18
|
+
* import { lazyCommand, getLazyCommandStats, preloadCommands } from "./cli/lazy-commands.js";
|
|
19
|
+
* import { buildCoreCommand } from "./commands/core.js"; // Keep core commands eager
|
|
20
|
+
*
|
|
21
|
+
* const program = new Command();
|
|
22
|
+
*
|
|
23
|
+
* // Core commands - loaded immediately
|
|
24
|
+
* program.addCommand(buildCoreCommand());
|
|
25
|
+
*
|
|
26
|
+
* // Lazy-loaded commands - loaded on first use
|
|
27
|
+
* lazyCommand(program, {
|
|
28
|
+
* name: "agent",
|
|
29
|
+
* description: "Manage AI agents",
|
|
30
|
+
* loader: async () => {
|
|
31
|
+
* const { buildAgentCommand } = await import("./commands/agent.js");
|
|
32
|
+
* return buildAgentCommand();
|
|
33
|
+
* },
|
|
34
|
+
* aliases: ["a"]
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* lazyCommand(program, {
|
|
38
|
+
* name: "config",
|
|
39
|
+
* description: "Manage configuration",
|
|
40
|
+
* loader: async () => {
|
|
41
|
+
* const { buildConfigCommand } = await import("./commands/config.js");
|
|
42
|
+
* return buildConfigCommand();
|
|
43
|
+
* }
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* lazyCommand(program, {
|
|
47
|
+
* name: "channels",
|
|
48
|
+
* description: "Manage messaging channels",
|
|
49
|
+
* loader: async () => {
|
|
50
|
+
* const { buildChannelsCommand } = await import("./commands/channels.js");
|
|
51
|
+
* return buildChannelsCommand();
|
|
52
|
+
* }
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* // Preload commonly used commands in interactive mode
|
|
56
|
+
* if (process.argv.length < 3) {
|
|
57
|
+
* // Interactive mode - preload likely commands
|
|
58
|
+
* preloadCommands(["agent", "config"]);
|
|
59
|
+
* }
|
|
60
|
+
*
|
|
61
|
+
* // Show stats in debug mode
|
|
62
|
+
* if (process.env.DEBUG) {
|
|
63
|
+
* console.log("Command loading stats:", getLazyCommandStats());
|
|
64
|
+
* }
|
|
65
|
+
*
|
|
66
|
+
* await program.parseAsync();
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* ## Performance Benefits
|
|
70
|
+
*
|
|
71
|
+
* Before lazy loading:
|
|
72
|
+
* - Startup time: ~500ms (loading all commands)
|
|
73
|
+
* - Memory: ~50MB (all commands in memory)
|
|
74
|
+
*
|
|
75
|
+
* After lazy loading:
|
|
76
|
+
* - Startup time: ~100ms (loading only core)
|
|
77
|
+
* - Memory: ~20MB (only used commands loaded)
|
|
78
|
+
*
|
|
79
|
+
* ## Migration Guide
|
|
80
|
+
*
|
|
81
|
+
* To migrate an existing command to lazy loading:
|
|
82
|
+
*
|
|
83
|
+
* 1. **Before:**
|
|
84
|
+
* ```typescript
|
|
85
|
+
* import { buildAgentCommand } from "./commands/agent.js";
|
|
86
|
+
* program.addCommand(buildAgentCommand());
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* 2. **After:**
|
|
90
|
+
* ```typescript
|
|
91
|
+
* lazyCommand(program, {
|
|
92
|
+
* name: "agent",
|
|
93
|
+
* description: "Manage AI agents",
|
|
94
|
+
* loader: async () => {
|
|
95
|
+
* const { buildAgentCommand } = await import("./commands/agent.js");
|
|
96
|
+
* return buildAgentCommand();
|
|
97
|
+
* }
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* ## Testing with Lazy Commands
|
|
102
|
+
*
|
|
103
|
+
* For testing, you may want to eagerly load all commands:
|
|
104
|
+
*
|
|
105
|
+
* ```typescript
|
|
106
|
+
* import { loadAllCommands } from "./cli/lazy-commands.js";
|
|
107
|
+
*
|
|
108
|
+
* beforeAll(async () => {
|
|
109
|
+
* await loadAllCommands();
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export {};
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy Command Loading System
|
|
3
|
+
*
|
|
4
|
+
* Optimizes CLI startup time by loading commands on-demand
|
|
5
|
+
* rather than loading all commands at startup.
|
|
6
|
+
*
|
|
7
|
+
* This significantly improves startup performance when only
|
|
8
|
+
* a subset of commands is used.
|
|
9
|
+
*
|
|
10
|
+
* ## Usage
|
|
11
|
+
*
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { Command } from "commander";
|
|
14
|
+
* import { lazyCommand } from "./lazy-commands.js";
|
|
15
|
+
*
|
|
16
|
+
* const program = new Command();
|
|
17
|
+
*
|
|
18
|
+
* // Register lazy-loaded command
|
|
19
|
+
* lazyCommand(program, "agent", "Manage AI agents", async () => {
|
|
20
|
+
* const { buildAgentCommand } = await import("./commands/agent.js");
|
|
21
|
+
* return buildAgentCommand();
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Global registry for lazy commands
|
|
27
|
+
*/
|
|
28
|
+
class LazyCommandRegistry {
|
|
29
|
+
commands = new Map();
|
|
30
|
+
loadTimes = new Map();
|
|
31
|
+
/**
|
|
32
|
+
* Register a lazy command
|
|
33
|
+
*/
|
|
34
|
+
register(options) {
|
|
35
|
+
if (this.commands.has(options.name)) {
|
|
36
|
+
throw new Error(`Command "${options.name}" is already registered`);
|
|
37
|
+
}
|
|
38
|
+
const entry = {
|
|
39
|
+
name: options.name,
|
|
40
|
+
description: options.description,
|
|
41
|
+
loader: options.loader,
|
|
42
|
+
aliases: options.aliases ?? [],
|
|
43
|
+
loaded: false,
|
|
44
|
+
};
|
|
45
|
+
this.commands.set(options.name, entry);
|
|
46
|
+
// Register aliases pointing to main entry
|
|
47
|
+
for (const alias of options.aliases ?? []) {
|
|
48
|
+
if (this.commands.has(alias)) {
|
|
49
|
+
throw new Error(`Alias "${alias}" is already registered`);
|
|
50
|
+
}
|
|
51
|
+
this.commands.set(alias, entry);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check if a command is registered
|
|
56
|
+
*/
|
|
57
|
+
has(name) {
|
|
58
|
+
return this.commands.has(name);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get a registered command entry
|
|
62
|
+
*/
|
|
63
|
+
get(name) {
|
|
64
|
+
return this.commands.get(name);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get command loading statistics
|
|
68
|
+
*/
|
|
69
|
+
getStats() {
|
|
70
|
+
const uniqueCommands = new Set(Array.from(this.commands.values()).map((c) => c.name));
|
|
71
|
+
const total = uniqueCommands.size;
|
|
72
|
+
const loadedEntries = Array.from(this.commands.values()).filter((c) => c.loaded && !c.aliases.includes(c.name) // Count main entries only
|
|
73
|
+
);
|
|
74
|
+
const loaded = loadedEntries.length;
|
|
75
|
+
const loadTimes = {};
|
|
76
|
+
for (const [name, time] of this.loadTimes) {
|
|
77
|
+
loadTimes[name] = time;
|
|
78
|
+
}
|
|
79
|
+
const times = Object.values(loadTimes);
|
|
80
|
+
const averageLoadTime = times.length > 0 ? times.reduce((a, b) => a + b, 0) / times.length : undefined;
|
|
81
|
+
return {
|
|
82
|
+
total,
|
|
83
|
+
loaded,
|
|
84
|
+
lazy: total - loaded,
|
|
85
|
+
averageLoadTime,
|
|
86
|
+
loadTimes,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Mark a command as loaded with its load time
|
|
91
|
+
*/
|
|
92
|
+
markLoaded(name, loadTime) {
|
|
93
|
+
const entry = this.commands.get(name);
|
|
94
|
+
if (entry) {
|
|
95
|
+
entry.loaded = true;
|
|
96
|
+
entry.loadTime = loadTime;
|
|
97
|
+
this.loadTimes.set(name, loadTime);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Mark a command as failed to load
|
|
102
|
+
*/
|
|
103
|
+
markFailed(name, error) {
|
|
104
|
+
const entry = this.commands.get(name);
|
|
105
|
+
if (entry) {
|
|
106
|
+
entry.error = error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get list of registered command names (main commands only, not aliases)
|
|
111
|
+
*/
|
|
112
|
+
getCommandNames() {
|
|
113
|
+
const seen = new Set();
|
|
114
|
+
const names = [];
|
|
115
|
+
for (const entry of this.commands.values()) {
|
|
116
|
+
if (!seen.has(entry.name)) {
|
|
117
|
+
seen.add(entry.name);
|
|
118
|
+
names.push(entry.name);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return names;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get all entries (for debugging)
|
|
125
|
+
*/
|
|
126
|
+
getAllEntries() {
|
|
127
|
+
return Array.from(this.commands.values());
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Clear all registrations
|
|
131
|
+
*/
|
|
132
|
+
clear() {
|
|
133
|
+
this.commands.clear();
|
|
134
|
+
this.loadTimes.clear();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Global registry instance
|
|
139
|
+
*/
|
|
140
|
+
export const globalLazyCommandRegistry = new LazyCommandRegistry();
|
|
141
|
+
/**
|
|
142
|
+
* Register a lazy-loaded command with a Commander program
|
|
143
|
+
*
|
|
144
|
+
* This creates a placeholder command that will load the actual
|
|
145
|
+
* implementation only when the command is invoked.
|
|
146
|
+
*
|
|
147
|
+
* @param program - The Commander program instance
|
|
148
|
+
* @param options - Lazy command configuration
|
|
149
|
+
* @returns The placeholder command (for chaining)
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* lazyCommand(program, {
|
|
154
|
+
* name: "agent",
|
|
155
|
+
* description: "Manage AI agents",
|
|
156
|
+
* loader: async () => {
|
|
157
|
+
* const { buildAgentCommand } = await import("./commands/agent.js");
|
|
158
|
+
* return buildAgentCommand();
|
|
159
|
+
* },
|
|
160
|
+
* aliases: ["a"]
|
|
161
|
+
* });
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export function lazyCommand(program, options) {
|
|
165
|
+
// Register in global registry
|
|
166
|
+
globalLazyCommandRegistry.register(options);
|
|
167
|
+
// Create placeholder command
|
|
168
|
+
const placeholder = program
|
|
169
|
+
.command(options.name)
|
|
170
|
+
.description(options.description);
|
|
171
|
+
// Add arguments if specified
|
|
172
|
+
if (options.arguments) {
|
|
173
|
+
placeholder.arguments(options.arguments);
|
|
174
|
+
}
|
|
175
|
+
// Add aliases
|
|
176
|
+
for (const alias of options.aliases ?? []) {
|
|
177
|
+
placeholder.alias(alias);
|
|
178
|
+
}
|
|
179
|
+
// Set up lazy loading action
|
|
180
|
+
placeholder.action(async (...actionArgs) => {
|
|
181
|
+
const startTime = performance.now();
|
|
182
|
+
try {
|
|
183
|
+
// Load the actual command module
|
|
184
|
+
const realCommand = await options.loader();
|
|
185
|
+
// Calculate load time
|
|
186
|
+
const loadTime = performance.now() - startTime;
|
|
187
|
+
globalLazyCommandRegistry.markLoaded(options.name, loadTime);
|
|
188
|
+
// Execute the loaded command's action with the same arguments
|
|
189
|
+
// The loaded command should have its own action handler
|
|
190
|
+
if (realCommand && typeof realCommand.parseAsync === "function") {
|
|
191
|
+
// If it's a full Command instance, we need to execute its action
|
|
192
|
+
// Get the action handler from the loaded command
|
|
193
|
+
const actionHandler = realCommand._actionHandler;
|
|
194
|
+
if (actionHandler) {
|
|
195
|
+
await actionHandler(...actionArgs);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
// Fallback: parse with the same arguments
|
|
199
|
+
// Remove command object from args and re-parse
|
|
200
|
+
const argv = process.argv.slice(2);
|
|
201
|
+
await realCommand.parseAsync(argv);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
throw new Error(`Loader for "${options.name}" did not return a valid Command instance`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
210
|
+
globalLazyCommandRegistry.markFailed(options.name, err);
|
|
211
|
+
throw new Error(`Failed to load command "${options.name}": ${err.message}`);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return placeholder;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Simple shorthand for creating a lazy command with minimal config
|
|
218
|
+
*
|
|
219
|
+
* @param program - The Commander program
|
|
220
|
+
* @param name - Command name
|
|
221
|
+
* @param description - Command description
|
|
222
|
+
* @param loader - Async function that loads and returns the command
|
|
223
|
+
* @returns The placeholder command
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```typescript
|
|
227
|
+
* lazy(program, "agent", "Manage agents", async () => {
|
|
228
|
+
* const mod = await import("./commands/agent.js");
|
|
229
|
+
* return mod.buildAgentCommand();
|
|
230
|
+
* });
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
export function lazy(program, name, description, loader) {
|
|
234
|
+
return lazyCommand(program, { name, description, loader });
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Preload commands that are likely to be used
|
|
238
|
+
*
|
|
239
|
+
* This can be called after initial setup to preload commands
|
|
240
|
+
* in the background, improving responsiveness for common commands.
|
|
241
|
+
*
|
|
242
|
+
* @param commandNames - Names of commands to preload
|
|
243
|
+
* @returns Promise that resolves when all preloads are complete
|
|
244
|
+
*/
|
|
245
|
+
export async function preloadCommands(commandNames) {
|
|
246
|
+
await Promise.all(commandNames.map(async (name) => {
|
|
247
|
+
const entry = globalLazyCommandRegistry.get(name);
|
|
248
|
+
if (entry && !entry.loaded) {
|
|
249
|
+
try {
|
|
250
|
+
const startTime = performance.now();
|
|
251
|
+
await entry.loader();
|
|
252
|
+
globalLazyCommandRegistry.markLoaded(name, performance.now() - startTime);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
// Preload errors are non-fatal
|
|
256
|
+
console.warn(`Failed to preload command "${name}": ${error instanceof Error ? error.message : String(error)}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Eagerly load all registered commands
|
|
263
|
+
*
|
|
264
|
+
* Use this for testing or when startup time doesn't matter.
|
|
265
|
+
*
|
|
266
|
+
* @returns Promise that resolves when all commands are loaded
|
|
267
|
+
*/
|
|
268
|
+
export async function loadAllCommands() {
|
|
269
|
+
const names = globalLazyCommandRegistry.getCommandNames();
|
|
270
|
+
await preloadCommands(names);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Get current loading statistics
|
|
274
|
+
*
|
|
275
|
+
* @returns Statistics about lazy-loaded commands
|
|
276
|
+
*/
|
|
277
|
+
export function getLazyCommandStats() {
|
|
278
|
+
return globalLazyCommandRegistry.getStats();
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Performance measurement utilities for command loading
|
|
282
|
+
*/
|
|
283
|
+
export class LoadTimeTracker {
|
|
284
|
+
measurements = new Map();
|
|
285
|
+
/**
|
|
286
|
+
* Measure execution time of a function
|
|
287
|
+
*/
|
|
288
|
+
async measure(name, fn) {
|
|
289
|
+
const start = performance.now();
|
|
290
|
+
try {
|
|
291
|
+
return await fn();
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
const duration = performance.now() - start;
|
|
295
|
+
this.measurements.set(name, duration);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Get all measurements
|
|
300
|
+
*/
|
|
301
|
+
getMeasurements() {
|
|
302
|
+
return Object.fromEntries(this.measurements);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Get total loading time
|
|
306
|
+
*/
|
|
307
|
+
getTotalTime() {
|
|
308
|
+
return Array.from(this.measurements.values()).reduce((a, b) => a + b, 0);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Get slowest operations
|
|
312
|
+
*/
|
|
313
|
+
getSlowest(count = 5) {
|
|
314
|
+
return Array.from(this.measurements.entries())
|
|
315
|
+
.sort((a, b) => b[1] - a[1])
|
|
316
|
+
.slice(0, count)
|
|
317
|
+
.map(([name, time]) => ({ name, time }));
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Clear all measurements
|
|
321
|
+
*/
|
|
322
|
+
clear() {
|
|
323
|
+
this.measurements.clear();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Global load time tracker
|
|
328
|
+
*/
|
|
329
|
+
export const globalLoadTimeTracker = new LoadTimeTracker();
|
|
@@ -174,6 +174,19 @@ const coreEntries = [
|
|
|
174
174
|
mod.registerBrowserCli(program);
|
|
175
175
|
},
|
|
176
176
|
},
|
|
177
|
+
{
|
|
178
|
+
commands: [
|
|
179
|
+
{
|
|
180
|
+
name: "mods",
|
|
181
|
+
description: "Manage capability modules (SKILL.md discovery, enable, configure)",
|
|
182
|
+
hasSubcommands: true,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
register: async ({ program }) => {
|
|
186
|
+
const mod = await import("./register.skills.js");
|
|
187
|
+
mod.registerSkillsCli(program);
|
|
188
|
+
},
|
|
189
|
+
},
|
|
177
190
|
];
|
|
178
191
|
function collectCoreCliCommandNames(predicate) {
|
|
179
192
|
const seen = new Set();
|
package/dist/cli/security-cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { loadConfig } from "../config/config.js";
|
|
1
|
+
import { loadConfig, writeConfigFile } from "../config/config.js";
|
|
2
2
|
import { defaultRuntime } from "../runtime.js";
|
|
3
3
|
import { runSecurityAudit } from "../security/audit.js";
|
|
4
4
|
import { fixSecurityFootguns } from "../security/fix.js";
|
|
5
|
+
import { CAPABILITY_TYPES } from "../security/capability.js";
|
|
5
6
|
import { formatDocsLink } from "../terminal/links.js";
|
|
6
7
|
import { isRich, theme } from "../terminal/theme.js";
|
|
7
8
|
import { shortenHomeInString, shortenHomePath } from "../utils.js";
|
|
@@ -17,10 +18,24 @@ function formatSummary(summary) {
|
|
|
17
18
|
parts.push(rich ? theme.muted(`${i} info`) : `${i} info`);
|
|
18
19
|
return parts.join(" · ");
|
|
19
20
|
}
|
|
21
|
+
function formatCapability(cap) {
|
|
22
|
+
let result = cap.type;
|
|
23
|
+
if ("pattern" in cap && cap.pattern)
|
|
24
|
+
result += ` pattern="${cap.pattern}"`;
|
|
25
|
+
if ("toolId" in cap && cap.toolId)
|
|
26
|
+
result += ` tool="${cap.toolId}"`;
|
|
27
|
+
if ("limit" in cap && cap.limit)
|
|
28
|
+
result += ` limit=${cap.limit}`;
|
|
29
|
+
if ("scope" in cap && cap.scope)
|
|
30
|
+
result += ` scope="${cap.scope}"`;
|
|
31
|
+
if ("port" in cap && cap.port)
|
|
32
|
+
result += ` port=${cap.port}`;
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
20
35
|
export function registerSecurityCli(program) {
|
|
21
36
|
const security = program
|
|
22
37
|
.command("security")
|
|
23
|
-
.description("Security tools (audit)")
|
|
38
|
+
.description("Security tools (audit, capabilities)")
|
|
24
39
|
.addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/security", "docs.molt.bot/cli/security")}\n`);
|
|
25
40
|
security
|
|
26
41
|
.command("audit")
|
|
@@ -120,4 +135,198 @@ export function registerSecurityCli(program) {
|
|
|
120
135
|
render("info");
|
|
121
136
|
defaultRuntime.log(lines.join("\n"));
|
|
122
137
|
});
|
|
138
|
+
// Capability management commands
|
|
139
|
+
const capabilities = security
|
|
140
|
+
.command("capabilities")
|
|
141
|
+
.description("Manage agent capabilities (permission system)");
|
|
142
|
+
capabilities
|
|
143
|
+
.command("grant")
|
|
144
|
+
.description("Grant a capability to an agent or pattern")
|
|
145
|
+
.argument("<type>", `Capability type (${CAPABILITY_TYPES.join(", ")})`)
|
|
146
|
+
.option("-a, --agent <pattern>", "Agent ID pattern (e.g., 'agent-*', 'coder')", "*")
|
|
147
|
+
.option("-p, --pattern <glob>", "File path pattern for file:* capabilities")
|
|
148
|
+
.option("-t, --tool <name>", "Tool name for tool:invoke capability")
|
|
149
|
+
.option("-h, --host <hostname>", "Host pattern for net:connect capability")
|
|
150
|
+
.option("-m, --max-tokens <n>", "Max tokens for llm:maxTokens capability")
|
|
151
|
+
.option("--json", "Print JSON output")
|
|
152
|
+
.action(async (type, opts) => {
|
|
153
|
+
const rich = isRich();
|
|
154
|
+
const cfg = loadConfig();
|
|
155
|
+
// Validate capability type
|
|
156
|
+
if (!CAPABILITY_TYPES.includes(type)) {
|
|
157
|
+
defaultRuntime.log(rich
|
|
158
|
+
? theme.error(`Invalid capability type: ${type}`)
|
|
159
|
+
: `Invalid capability type: ${type}`);
|
|
160
|
+
defaultRuntime.log(`Valid types: ${CAPABILITY_TYPES.join(", ")}`);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
// Build capability object
|
|
164
|
+
const capability = { type: type };
|
|
165
|
+
if (opts.pattern && (type.startsWith("file:") || type === "shell:exec")) {
|
|
166
|
+
capability.pattern = opts.pattern;
|
|
167
|
+
}
|
|
168
|
+
if (opts.tool && type === "tool:invoke") {
|
|
169
|
+
capability.toolId = opts.tool;
|
|
170
|
+
}
|
|
171
|
+
if (opts.host && type === "net:connect") {
|
|
172
|
+
capability.pattern = opts.host;
|
|
173
|
+
}
|
|
174
|
+
if (opts.maxTokens && type === "llm:maxTokens") {
|
|
175
|
+
capability.limit = parseInt(opts.maxTokens, 10);
|
|
176
|
+
}
|
|
177
|
+
// Initialize security config if needed
|
|
178
|
+
if (!cfg.security) {
|
|
179
|
+
cfg.security = { enabled: true };
|
|
180
|
+
}
|
|
181
|
+
if (!cfg.security.agents) {
|
|
182
|
+
cfg.security.agents = [];
|
|
183
|
+
}
|
|
184
|
+
// Find or create agent entry
|
|
185
|
+
const agentId = opts.agent || "*";
|
|
186
|
+
let agentEntry = cfg.security.agents.find((a) => a.agentId === agentId);
|
|
187
|
+
if (!agentEntry) {
|
|
188
|
+
agentEntry = { agentId, capabilities: [] };
|
|
189
|
+
cfg.security.agents.push(agentEntry);
|
|
190
|
+
}
|
|
191
|
+
// Add capability
|
|
192
|
+
agentEntry.capabilities.push(capability);
|
|
193
|
+
// Write config
|
|
194
|
+
await writeConfigFile(cfg);
|
|
195
|
+
if (opts.json) {
|
|
196
|
+
defaultRuntime.log(JSON.stringify({ success: true, capability, agentId }, null, 2));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
defaultRuntime.log(rich
|
|
200
|
+
? theme.success(`Granted ${type} to agent "${agentId}"`)
|
|
201
|
+
: `Granted ${type} to agent "${agentId}"`);
|
|
202
|
+
defaultRuntime.log(formatCapability(capability));
|
|
203
|
+
});
|
|
204
|
+
capabilities
|
|
205
|
+
.command("revoke")
|
|
206
|
+
.description("Revoke capabilities from an agent")
|
|
207
|
+
.argument("[type]", `Capability type to revoke (omit to revoke all)`)
|
|
208
|
+
.option("-a, --agent <pattern>", "Agent ID pattern", "*")
|
|
209
|
+
.option("--json", "Print JSON output")
|
|
210
|
+
.action(async (type, opts) => {
|
|
211
|
+
const rich = isRich();
|
|
212
|
+
const cfg = loadConfig();
|
|
213
|
+
if (!cfg.security?.agents || cfg.security.agents.length === 0) {
|
|
214
|
+
if (opts.json) {
|
|
215
|
+
defaultRuntime.log(JSON.stringify({ success: true, removed: 0 }));
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
defaultRuntime.log(rich ? theme.muted("No capabilities configured") : "No capabilities configured");
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const agentId = opts.agent || "*";
|
|
223
|
+
const agentEntry = cfg.security.agents.find((a) => a.agentId === agentId);
|
|
224
|
+
if (!agentEntry) {
|
|
225
|
+
if (opts.json) {
|
|
226
|
+
defaultRuntime.log(JSON.stringify({ success: true, removed: 0 }));
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
defaultRuntime.log(rich
|
|
230
|
+
? theme.muted(`No capabilities found for agent "${agentId}"`)
|
|
231
|
+
: `No capabilities found for agent "${agentId}"`);
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const beforeCount = agentEntry.capabilities.length;
|
|
236
|
+
if (type) {
|
|
237
|
+
agentEntry.capabilities = agentEntry.capabilities.filter((c) => c.type !== type);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
agentEntry.capabilities = [];
|
|
241
|
+
}
|
|
242
|
+
const removed = beforeCount - agentEntry.capabilities.length;
|
|
243
|
+
// Remove empty agent entries
|
|
244
|
+
cfg.security.agents = cfg.security.agents.filter((a) => a.capabilities.length > 0);
|
|
245
|
+
await writeConfigFile(cfg);
|
|
246
|
+
if (opts.json) {
|
|
247
|
+
defaultRuntime.log(JSON.stringify({ success: true, removed }));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (removed === 0) {
|
|
251
|
+
defaultRuntime.log(rich ? theme.muted("No matching capabilities found") : "No matching capabilities found");
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
defaultRuntime.log(rich
|
|
255
|
+
? theme.success(`Revoked ${removed} capability(s) from agent "${agentId}"`)
|
|
256
|
+
: `Revoked ${removed} capability(s) from agent "${agentId}"`);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
capabilities
|
|
260
|
+
.command("list")
|
|
261
|
+
.description("List agent capabilities")
|
|
262
|
+
.option("-a, --agent <pattern>", "Filter by agent ID pattern")
|
|
263
|
+
.option("--json", "Print JSON output")
|
|
264
|
+
.action(async (opts) => {
|
|
265
|
+
const rich = isRich();
|
|
266
|
+
const cfg = loadConfig();
|
|
267
|
+
const agents = cfg.security?.agents || [];
|
|
268
|
+
const filtered = opts.agent
|
|
269
|
+
? agents.filter((a) => a.agentId === opts.agent || (opts.agent && a.agentId.includes(opts.agent)))
|
|
270
|
+
: agents;
|
|
271
|
+
if (opts.json) {
|
|
272
|
+
defaultRuntime.log(JSON.stringify({ enabled: cfg.security?.enabled ?? false, agents: filtered }, null, 2));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (filtered.length === 0) {
|
|
276
|
+
defaultRuntime.log(rich ? theme.muted("No capabilities configured") : "No capabilities configured");
|
|
277
|
+
defaultRuntime.log(`\nEnable capability system: ${formatCliCommand("poolbot config set security.enabled true")}`);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const lines = [];
|
|
281
|
+
lines.push(rich ? theme.heading("Agent Capabilities") : "Agent Capabilities");
|
|
282
|
+
lines.push(rich
|
|
283
|
+
? theme.muted(`Security enabled: ${cfg.security?.enabled ?? false}`)
|
|
284
|
+
: `Security enabled: ${cfg.security?.enabled ?? false}`);
|
|
285
|
+
lines.push("");
|
|
286
|
+
for (const agent of filtered) {
|
|
287
|
+
lines.push(rich ? theme.heading(agent.agentId) : agent.agentId);
|
|
288
|
+
for (const cap of agent.capabilities) {
|
|
289
|
+
lines.push(` ${formatCapability(cap)}`);
|
|
290
|
+
}
|
|
291
|
+
lines.push("");
|
|
292
|
+
}
|
|
293
|
+
defaultRuntime.log(lines.join("\n"));
|
|
294
|
+
});
|
|
295
|
+
capabilities
|
|
296
|
+
.command("enable")
|
|
297
|
+
.description("Enable the capability security system")
|
|
298
|
+
.option("--json", "Print JSON output")
|
|
299
|
+
.action(async (opts) => {
|
|
300
|
+
const cfg = loadConfig();
|
|
301
|
+
cfg.security = cfg.security || {};
|
|
302
|
+
cfg.security.enabled = true;
|
|
303
|
+
await writeConfigFile(cfg);
|
|
304
|
+
if (opts.json) {
|
|
305
|
+
defaultRuntime.log(JSON.stringify({ success: true, enabled: true }));
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
defaultRuntime.log(isRich()
|
|
309
|
+
? theme.success("Capability security system enabled")
|
|
310
|
+
: "Capability security system enabled");
|
|
311
|
+
defaultRuntime.log(`Run ${formatCliCommand("poolbot security capabilities list")} to view current grants`);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
capabilities
|
|
315
|
+
.command("disable")
|
|
316
|
+
.description("Disable the capability security system (permissive mode)")
|
|
317
|
+
.option("--json", "Print JSON output")
|
|
318
|
+
.action(async (opts) => {
|
|
319
|
+
const cfg = loadConfig();
|
|
320
|
+
cfg.security = cfg.security || {};
|
|
321
|
+
cfg.security.enabled = false;
|
|
322
|
+
await writeConfigFile(cfg);
|
|
323
|
+
if (opts.json) {
|
|
324
|
+
defaultRuntime.log(JSON.stringify({ success: true, enabled: false }));
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
defaultRuntime.log(isRich()
|
|
328
|
+
? theme.warn("Capability security system disabled (permissive mode)")
|
|
329
|
+
: "Capability security system disabled (permissive mode)");
|
|
330
|
+
}
|
|
331
|
+
});
|
|
123
332
|
}
|