@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.
Files changed (68) 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/pi-tools.js +32 -2
  5. package/dist/agents/skills/security.js +217 -0
  6. package/dist/auto-reply/reply/get-reply.js +6 -0
  7. package/dist/auto-reply/reply/message-preprocess-hooks.js +17 -0
  8. package/dist/build-info.json +3 -3
  9. package/dist/cli/banner.js +20 -1
  10. package/dist/cli/lazy-commands.example.js +113 -0
  11. package/dist/cli/lazy-commands.js +329 -0
  12. package/dist/cli/program/command-registry.js +13 -0
  13. package/dist/cli/program/register.skills.js +4 -0
  14. package/dist/cli/security-cli.js +211 -2
  15. package/dist/cli/tagline.js +7 -0
  16. package/dist/config/config.js +1 -0
  17. package/dist/config/secrets-integration.js +88 -0
  18. package/dist/config/types.cli.js +1 -0
  19. package/dist/config/types.security.js +33 -0
  20. package/dist/config/zod-schema.js +15 -0
  21. package/dist/config/zod-schema.providers-core.js +1 -0
  22. package/dist/config/zod-schema.security.js +113 -0
  23. package/dist/context-engine/index.js +33 -0
  24. package/dist/context-engine/legacy.js +181 -0
  25. package/dist/context-engine/registry.js +86 -0
  26. package/dist/context-engine/summarizing.js +293 -0
  27. package/dist/context-engine/types.js +7 -0
  28. package/dist/discord/monitor/message-handler.preflight.js +11 -2
  29. package/dist/gateway/http-common.js +6 -1
  30. package/dist/hooks/fire-and-forget.js +6 -0
  31. package/dist/hooks/internal-hooks.js +64 -19
  32. package/dist/hooks/message-hook-mappers.js +179 -0
  33. package/dist/infra/abort-pattern.js +106 -0
  34. package/dist/infra/retry.js +94 -0
  35. package/dist/secrets/index.js +28 -0
  36. package/dist/secrets/resolver.js +185 -0
  37. package/dist/secrets/runtime.js +142 -0
  38. package/dist/secrets/types.js +11 -0
  39. package/dist/security/capability-guards.js +89 -0
  40. package/dist/security/capability-manager.js +76 -0
  41. package/dist/security/capability.js +147 -0
  42. package/dist/security/dangerous-tools.js +80 -0
  43. package/dist/security/index.js +7 -0
  44. package/dist/security/middleware.js +105 -0
  45. package/dist/security/types.js +12 -0
  46. package/dist/skills/commands.js +351 -0
  47. package/dist/skills/index.js +167 -0
  48. package/dist/skills/loader.js +282 -0
  49. package/dist/skills/parser.js +461 -0
  50. package/dist/skills/registry.js +397 -0
  51. package/dist/skills/security.js +318 -0
  52. package/dist/skills/types.js +21 -0
  53. package/dist/slack/monitor/context.js +1 -0
  54. package/dist/slack/monitor/message-handler/dispatch.js +14 -1
  55. package/dist/slack/monitor/provider.js +2 -0
  56. package/dist/test-utils/index.js +219 -0
  57. package/dist/tui/index.js +595 -0
  58. package/docs/INTEGRATION_PLAN.md +475 -0
  59. package/docs/INTEGRATION_SUMMARY.md +215 -0
  60. package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
  61. package/docs/integrations/INTEGRATION_PLAN.md +424 -0
  62. package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
  63. package/docs/integrations/XYOPS_PLAN.md +978 -0
  64. package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
  65. package/docs/skills/SKILL.md +524 -0
  66. package/docs/skills.md +405 -0
  67. package/package.json +1 -1
  68. 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();
@@ -0,0 +1,4 @@
1
+ import { registerSkillsCommands } from "../../skills/commands.js";
2
+ export function registerSkillsCli(program) {
3
+ registerSkillsCommands(program);
4
+ }
@@ -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
  }