@compilr-dev/cli 0.5.4 → 0.5.6

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/dist/agent.js CHANGED
@@ -5,10 +5,12 @@
5
5
  * ClaudeProvider, OllamaProvider, OpenAIProvider, or GeminiProvider
6
6
  * and available tools.
7
7
  */
8
- import { Agent, ContextManager, DEFAULT_CONTEXT_CONFIG, createTaskTool, createSuggestTool, defaultAgentTypes, TOOL_SETS, BUILTIN_GUARDRAILS, createProviderFromType, } from '@compilr-dev/sdk';
8
+ import { Agent, ContextManager, DEFAULT_CONTEXT_CONFIG, createTaskTool, createSuggestTool, defaultAgentTypes, TOOL_SETS, BUILTIN_GUARDRAILS, createProviderFromType, CapabilityManager, CAPABILITY_PACKS, GIT_SAFETY_MODULE, PLATFORM_TOOL_HINTS_MODULE, FACTORY_TOOL_HINTS_MODULE, TOOL_USAGE_META_MODULE, createLoadCapabilityTool, generateCapabilityCatalog, } from '@compilr-dev/sdk';
9
9
  import { isAutoCompactEnabled, isDelegationEnabled, getSetting } from './settings/index.js';
10
10
  import { getApiKey } from './utils/credentials.js';
11
- import { createToolRegistry, createMinimalToolRegistry, getDirectTools, getMetaTools, initializeMetaTools, getToolIndexForSystemPrompt, getToolStats, setMetaToolFilter, createToolFallback, } from './tools.js';
11
+ import { createToolRegistry, createMinimalToolRegistry, getDirectTools, getMetaTools, initializeMetaTools, getToolIndexForSystemPrompt, getFilteredToolIndexForSystemPrompt, getToolStats, setMetaToolFilter, createToolFallback, getRegisteredMetaTools, } from './tools.js';
12
+ import { TOOL_GROUPS } from './multi-agent/tool-config.js';
13
+ import { setCapabilityManager } from './multi-agent/capability-loader.js';
12
14
  import { getAgentRegistry } from './agents/registry.js';
13
15
  import { SystemPromptBuilder } from './system-prompt/index.js';
14
16
  import { getDefaultModelForTier, getModelContextWindow } from './models/index.js';
@@ -126,6 +128,151 @@ function createProvider(options) {
126
128
  estimateTokens,
127
129
  });
128
130
  }
131
+ // =============================================================================
132
+ // Dynamic Capability Loading Helpers
133
+ // =============================================================================
134
+ /**
135
+ * Derive profile group IDs from a tool filter (array of tool names).
136
+ * A group is included if any of its tools are in the filter.
137
+ */
138
+ function getProfileGroupsFromToolFilter(toolFilter) {
139
+ const toolSet = new Set(toolFilter);
140
+ const groups = [];
141
+ for (const [groupId, group] of Object.entries(TOOL_GROUPS)) {
142
+ if (group.tools.some((t) => toolSet.has(t))) {
143
+ groups.push(groupId);
144
+ }
145
+ }
146
+ return groups;
147
+ }
148
+ /**
149
+ * Scan recent messages for "Tool not found: X" errors and auto-load
150
+ * the corresponding capability packs (max 2 per turn, loadable only).
151
+ * Also detects forbidden tool access for handoff suggestions.
152
+ */
153
+ function autoDetectCapabilities(manager, messages) {
154
+ const MAX_AUTO_LOADS = 2;
155
+ const result = { loaded: [], forbidden: [] };
156
+ const toolNotFoundPattern = /Tool not found: (\w+)/;
157
+ // Find the most recent user message (contains the last turn's tool results)
158
+ for (let i = messages.length - 1; i >= 0; i--) {
159
+ const msg = messages[i];
160
+ if (msg.role !== 'user' || typeof msg.content === 'string')
161
+ continue;
162
+ // Scan this message's tool_result blocks for "Tool not found" errors
163
+ for (const block of msg.content) {
164
+ if (block.type !== 'tool_result' || !block.isError || !block.content)
165
+ continue;
166
+ const match = toolNotFoundPattern.exec(block.content);
167
+ if (!match)
168
+ continue;
169
+ const toolName = match[1];
170
+ const packId = manager.findPackForTool(toolName);
171
+ if (!packId || manager.isLoaded(packId))
172
+ continue;
173
+ const tier = manager.getTier(packId);
174
+ if (tier === 'loadable' && result.loaded.length < MAX_AUTO_LOADS) {
175
+ manager.load(packId, 'auto-detect');
176
+ result.loaded.push(packId);
177
+ }
178
+ else if (tier === 'forbidden') {
179
+ result.forbidden.push({
180
+ toolName,
181
+ packId,
182
+ suggestedAgent: manager.getSuggestedAgent(packId),
183
+ });
184
+ }
185
+ }
186
+ // Only scan the most recent user message
187
+ break;
188
+ }
189
+ return result;
190
+ }
191
+ function createCapabilityHook(manager, basePrompt, orphanTools) {
192
+ return (ctx) => {
193
+ // Auto-detect capability needs from tool failures (safety net)
194
+ let forbiddenHints = [];
195
+ if (manager.hasLoadablePacks() || ctx.messages.length > 1) {
196
+ const detected = autoDetectCapabilities(manager, ctx.messages);
197
+ // Generate handoff suggestions for forbidden tool access
198
+ if (detected.forbidden.length > 0) {
199
+ forbiddenHints = detected.forbidden.map((f) => {
200
+ const agentHint = f.suggestedAgent
201
+ ? ` Consider using handoff("${f.suggestedAgent}", "Need ${f.packId} access for ${f.toolName}").`
202
+ : '';
203
+ return `[System] Tool "${f.toolName}" requires ${f.packId} capability which is outside your tool profile.${agentHint}`;
204
+ });
205
+ }
206
+ }
207
+ const activeToolNames = manager.getActiveToolNames();
208
+ const activeModuleIds = manager.getActivePromptModuleIds();
209
+ // Include orphan tools (not in any capability pack) — always visible
210
+ const allActiveTools = [...activeToolNames, ...orphanTools];
211
+ // Update meta-tool filter for this turn
212
+ setMetaToolFilter(allActiveTools);
213
+ // Build dynamic system prompt sections
214
+ const dynamicSections = [];
215
+ // Meta-tools protocol (how to use get_tool_info/use_tool)
216
+ dynamicSections.push(TOOL_USAGE_META_MODULE.content);
217
+ // Conditional modules based on loaded capability packs
218
+ if (activeModuleIds.has('git-safety')) {
219
+ dynamicSections.push(GIT_SAFETY_MODULE.content);
220
+ }
221
+ if (activeModuleIds.has('platform-tool-hints')) {
222
+ dynamicSections.push(PLATFORM_TOOL_HINTS_MODULE.content);
223
+ }
224
+ if (activeModuleIds.has('factory-tool-hints')) {
225
+ dynamicSections.push(FACTORY_TOOL_HINTS_MODULE.content);
226
+ }
227
+ // Prompt snippets from loaded packs
228
+ const snippets = manager.getActivePromptSnippets();
229
+ if (snippets.length > 0) {
230
+ dynamicSections.push('## Active Tool Capabilities\n' + snippets.join('\n'));
231
+ }
232
+ // Filtered tool index (only loaded tools)
233
+ const filteredIndex = getFilteredToolIndexForSystemPrompt(allActiveTools);
234
+ dynamicSections.push(filteredIndex);
235
+ // Capability catalog (if loadable packs remain)
236
+ if (manager.hasLoadablePacks()) {
237
+ const catalog = manager.getCatalog();
238
+ const catalogSection = generateCapabilityCatalog(catalog, manager.getLoadedPackIds());
239
+ if (catalogSection) {
240
+ dynamicSections.push(catalogSection);
241
+ }
242
+ }
243
+ // Preserve anchors from the current system prompt.
244
+ // The agents library injects anchors into systemContent BEFORE this hook runs.
245
+ // We must extract and re-append them, since we rebuild the prompt from basePrompt.
246
+ let anchorsBlock = '';
247
+ const anchorMarker = '## Active Anchors (Critical Information)';
248
+ const anchorIdx = ctx.systemPrompt.indexOf(anchorMarker);
249
+ if (anchorIdx > 0) {
250
+ // Find the preceding "---\n\n" separator
251
+ const separatorBefore = ctx.systemPrompt.lastIndexOf('---', anchorIdx);
252
+ const startIdx = separatorBefore > 0 ? separatorBefore : anchorIdx;
253
+ // Find the end: either the next "---" after the anchor block, or end of string
254
+ const afterAnchor = ctx.systemPrompt.indexOf('\n\n---\n\n', anchorIdx);
255
+ if (afterAnchor > 0) {
256
+ // Anchors are followed by more content (e.g., role ending) — extract just the anchor block
257
+ anchorsBlock = '\n\n' + ctx.systemPrompt.substring(startIdx, afterAnchor + 7); // include trailing "---\n\n"
258
+ }
259
+ else {
260
+ // Anchors are at the end of the prompt
261
+ anchorsBlock = '\n\n' + ctx.systemPrompt.substring(startIdx);
262
+ }
263
+ }
264
+ const hookResult = {
265
+ systemPrompt: basePrompt + '\n\n' + dynamicSections.join('\n\n') + anchorsBlock,
266
+ };
267
+ // Inject forbidden boundary messages (handoff suggestions)
268
+ if (forbiddenHints.length > 0) {
269
+ const hintContent = forbiddenHints.join('\n');
270
+ const hintMessage = { role: 'user', content: hintContent };
271
+ hookResult.messages = [...ctx.messages, hintMessage];
272
+ }
273
+ return hookResult;
274
+ };
275
+ }
129
276
  /**
130
277
  * Creates an Agent instance configured with all tools.
131
278
  */
@@ -165,18 +312,64 @@ export function createAgent(options = {}) {
165
312
  // which re-injects them on every LLM call (survives compaction)
166
313
  // Initialize meta-tools if enabled (needed for tool index)
167
314
  // includeWarmTools=true moves 11 warm tools into meta-registry (hot-tools mode)
168
- let metaToolsIndex;
169
- if (options.enableMetaTools && !options.minimal) {
315
+ const useMetaTools = options.enableMetaTools && !options.minimal;
316
+ if (useMetaTools) {
170
317
  initializeMetaTools(true);
171
- metaToolsIndex = getToolIndexForSystemPrompt();
172
- // Log tool stats (unless quiet mode)
318
+ }
319
+ // ==========================================================================
320
+ // Dynamic Capability Loading
321
+ // When meta-tools are enabled, create a CapabilityManager to control which
322
+ // tools and prompt modules are active per-turn.
323
+ // ==========================================================================
324
+ let capabilityManager;
325
+ let orphanToolNames = [];
326
+ if (useMetaTools) {
327
+ // Determine profile groups from toolFilter
328
+ const profileGroups = options.toolFilter
329
+ ? getProfileGroupsFromToolFilter(options.toolFilter)
330
+ : Object.keys(CAPABILITY_PACKS);
331
+ // Upfront groups: all direct-tier groups that are in the profile
332
+ const upfrontGroupIds = Object.entries(TOOL_GROUPS)
333
+ .filter(([, group]) => group.tier === 'direct')
334
+ .map(([id]) => id)
335
+ .filter((id) => profileGroups.includes(id));
336
+ capabilityManager = new CapabilityManager({
337
+ profileGroups,
338
+ packs: CAPABILITY_PACKS,
339
+ upfrontGroups: upfrontGroupIds,
340
+ });
341
+ // Expose the capability manager for slash command handlers (Phase 3)
342
+ setCapabilityManager(capabilityManager);
343
+ // Compute orphan tools (meta-registry tools not in any capability pack)
344
+ // These are always visible regardless of which packs are loaded.
345
+ const packedTools = new Set();
346
+ for (const pack of Object.values(CAPABILITY_PACKS)) {
347
+ for (const tool of pack.tools) {
348
+ packedTools.add(tool);
349
+ }
350
+ }
351
+ const registered = getRegisteredMetaTools();
352
+ orphanToolNames = registered
353
+ .map((t) => t.definition.name)
354
+ .filter((name) => !packedTools.has(name));
355
+ // Set initial meta-tool filter
356
+ setMetaToolFilter([...capabilityManager.getActiveToolNames(), ...orphanToolNames]);
357
+ // Log stats (unless quiet mode)
173
358
  if (!options.quiet) {
174
359
  const stats = getToolStats();
360
+ const loadedPacks = capabilityManager.getLoadedPackIds();
361
+ const loadableCount = capabilityManager.getCatalog().length;
175
362
  console.log(`[Meta-tools] Direct: ${String(stats.directTools)}, ` +
176
363
  `Meta: ${String(stats.metaRegistryTools)}, ` +
177
364
  `Est. savings: ~${String(stats.tokenSavings)} tokens`);
365
+ console.log(`[Capabilities] Upfront: ${String(loadedPacks.length)} packs, ` +
366
+ `Loadable: ${String(loadableCount)} packs`);
178
367
  }
179
368
  }
369
+ // Generate tool index for static system prompt (only when NOT using capability loading)
370
+ const metaToolsIndex = useMetaTools && !capabilityManager
371
+ ? getToolIndexForSystemPrompt()
372
+ : undefined;
180
373
  let systemPrompt;
181
374
  // Check if we should use minimal system prompt (for testing role identity)
182
375
  if (options.useMinimalSystemPrompt && options.systemPromptAddition) {
@@ -185,16 +378,18 @@ export function createAgent(options = {}) {
185
378
  }
186
379
  else {
187
380
  // Use modular system prompt builder
188
- // When a role-specific identity is provided (systemPromptAddition), skip the default identity module
381
+ // When capability loading is active, exclude conditional modules:
382
+ // git-safety, platform-tool-hints, factory-tool-hints, tool-usage-meta
383
+ // These are dynamically added by the BeforeLLM hook based on loaded capabilities.
189
384
  const promptBuilder = new SystemPromptBuilder({
190
- enableMetaTools: options.enableMetaTools && !options.minimal,
385
+ enableMetaTools: capabilityManager ? false : useMetaTools,
386
+ hasGit: capabilityManager ? false : undefined, // false = skip git-safety; undefined = auto-detect
191
387
  projectName: options.projectName,
192
388
  projectContext: options.projectContext,
193
389
  guidedModeContext: options.guidedModeContext,
194
390
  planModeContext: options.planModeContext,
195
391
  metaToolsIndex,
196
392
  hasRoleIdentity: !!options.systemPromptAddition, // Skip default identity when role is specified
197
- // hasGit is auto-detected by builder
198
393
  });
199
394
  const buildResult = promptBuilder.build();
200
395
  systemPrompt = buildResult.prompt;
@@ -219,6 +414,8 @@ ${options.systemPromptAddition}
219
414
  systemPrompt = options.systemPromptAddition + '\n\n' + systemPrompt + roleEnding;
220
415
  }
221
416
  }
417
+ // Store base prompt for capability-aware BeforeLLM hook
418
+ const baseSystemPrompt = capabilityManager ? systemPrompt : undefined;
222
419
  const agent = new Agent({
223
420
  provider,
224
421
  systemPrompt,
@@ -284,9 +481,10 @@ ${options.systemPromptAddition}
284
481
  },
285
482
  }
286
483
  : undefined,
287
- // Hooks: context pressure, file lock check/acquire
484
+ // Hooks: context pressure, capability loading, file lock check/acquire
288
485
  hooks: {
289
486
  beforeLLM: [
487
+ // Context pressure hook
290
488
  (ctx) => {
291
489
  const utilization = contextManager.getUtilization();
292
490
  if (utilization < 0.3)
@@ -306,6 +504,10 @@ ${options.systemPromptAddition}
306
504
  messages.splice(1, 0, { role: 'user', content: hint });
307
505
  return { messages };
308
506
  },
507
+ // Capability-aware system prompt assembly (with auto-detection safety net)
508
+ ...(capabilityManager && baseSystemPrompt
509
+ ? [createCapabilityHook(capabilityManager, baseSystemPrompt, orphanToolNames)]
510
+ : []),
309
511
  ],
310
512
  beforeTool: [createFileLockCheckHook(getActiveProject)],
311
513
  afterTool: [
@@ -338,6 +540,10 @@ ${options.systemPromptAddition}
338
540
  // Hot-tools mode: only hot tools + get_tool_info (for schema introspection)
339
541
  // Warm tools + meta-registry tools are accessed transparently via fallback handler
340
542
  tools = [...getDirectTools(true), ...getMetaTools()];
543
+ // Add load_capability tool when the agent has loadable packs
544
+ if (capabilityManager?.hasLoadablePacks()) {
545
+ tools.push(createLoadCapabilityTool(capabilityManager));
546
+ }
341
547
  }
342
548
  else {
343
549
  // Legacy mode: all tools declared directly
@@ -347,14 +553,17 @@ ${options.systemPromptAddition}
347
553
  // Apply tool filter if specified (filter direct tools)
348
554
  if (options.toolFilter && options.toolFilter.length > 0) {
349
555
  const allowedTools = new Set(options.toolFilter);
350
- // get_tool_info is always allowed for schema introspection
351
- const alwaysAllowed = new Set(['get_tool_info']);
556
+ // get_tool_info and load_capability are always allowed
557
+ const alwaysAllowed = new Set(['get_tool_info', 'load_capability']);
352
558
  tools = tools.filter((tool) => allowedTools.has(tool.definition.name) || alwaysAllowed.has(tool.definition.name));
353
- // Also set the meta-tool filter for use_tool/list_tools filtering
354
- setMetaToolFilter(options.toolFilter);
559
+ // When capability loading is active, the BeforeLLM hook manages the meta-tool filter.
560
+ // Otherwise, set it to the full tool filter.
561
+ if (!capabilityManager) {
562
+ setMetaToolFilter(options.toolFilter);
563
+ }
355
564
  }
356
- else {
357
- // No filter - clear any previous filter
565
+ else if (!capabilityManager) {
566
+ // No filter and no capability loading - clear any previous filter
358
567
  setMetaToolFilter(null);
359
568
  }
360
569
  // Type assertion needed for tool registry compatibility
@@ -10,6 +10,25 @@ import { setCurrentProject, getCurrentProject } from '../../tools/project-db.js'
10
10
  import { getSkillPrompt } from '../../repl-helpers.js';
11
11
  import { getModelTier, modelMeetsTier } from '../../utils/model-tiers.js';
12
12
  import { getCurrentProjectIdForTracking, handleProjectSwitch } from './session.js';
13
+ import { loadCapabilitiesForSkill } from '../../multi-agent/capability-loader.js';
14
+ /**
15
+ * Load capabilities for a skill and warn about forbidden packs.
16
+ * Called before queuing a skill-driven agent message.
17
+ */
18
+ function loadSkillCapabilities(skillName, ctx) {
19
+ const result = loadCapabilitiesForSkill(skillName);
20
+ if (!result)
21
+ return; // No capability loading active or no requirements
22
+ if (result.forbidden.length > 0) {
23
+ const suggestions = result.forbidden
24
+ .map((f) => `${f.packId}${f.suggestedAgent ? ` (try ${f.suggestedAgent})` : ''}`)
25
+ .join(', ');
26
+ ctx.ui.print({
27
+ type: 'warning',
28
+ message: `Some capabilities are restricted: ${suggestions}`,
29
+ });
30
+ }
31
+ }
13
32
  // =============================================================================
14
33
  // Helper Functions
15
34
  // =============================================================================
@@ -335,6 +354,8 @@ export const designCommand = {
335
354
  break;
336
355
  }
337
356
  }
357
+ // Load capabilities for the design skill
358
+ loadSkillCapabilities('design', ctx);
338
359
  // Get design skill prompt
339
360
  const designPrompt = getSkillPrompt('design');
340
361
  if (!designPrompt) {
@@ -384,6 +405,8 @@ export const sketchCommand = {
384
405
  ctx.ui.print({ type: 'error', message: 'No project selected. Run /new or /projects first.' });
385
406
  return Promise.resolve(true);
386
407
  }
408
+ // Load capabilities for the sketch skill
409
+ loadSkillCapabilities('sketch', ctx);
387
410
  // Get sketch skill prompt
388
411
  const sketchPrompt = getSkillPrompt('sketch');
389
412
  if (!sketchPrompt) {
@@ -474,8 +497,9 @@ export const refineCommand = {
474
497
  }
475
498
  }
476
499
  }
477
- // Get appropriate skill prompt
500
+ // Get appropriate skill prompt and load capabilities
478
501
  const skillName = isFocusedRefine ? 'refine-item' : 'refine';
502
+ loadSkillCapabilities(skillName, ctx);
479
503
  const refinePrompt = getSkillPrompt(skillName);
480
504
  if (!refinePrompt) {
481
505
  ctx.ui.print({ type: 'error', message: `${skillName} skill not found.` });
@@ -605,7 +629,8 @@ export const buildCommand = {
605
629
  }
606
630
  }
607
631
  }
608
- // Get build skill prompt and replace placeholders
632
+ // Load capabilities and get build skill prompt
633
+ loadSkillCapabilities('build', ctx);
609
634
  let buildPrompt = getSkillPrompt('build');
610
635
  if (!buildPrompt) {
611
636
  ctx.ui.print({ type: 'error', message: 'Build skill not found.' });
@@ -683,7 +708,8 @@ export const noteCommand = {
683
708
  ctx.ui.print({ type: 'error', message: 'No project selected. Run /new or /projects first.' });
684
709
  return Promise.resolve(true);
685
710
  }
686
- // Get session-notes skill prompt
711
+ // Load capabilities and get session-notes skill prompt
712
+ loadSkillCapabilities('session-notes', ctx);
687
713
  const notePrompt = getSkillPrompt('session-notes');
688
714
  if (!notePrompt) {
689
715
  ctx.ui.print({ type: 'error', message: 'Session-notes skill not found.' });
@@ -741,7 +767,8 @@ export const prdCommand = {
741
767
  ctx.ui.print({ type: 'error', message: 'No project selected. Run /new or /projects first.' });
742
768
  return Promise.resolve(true);
743
769
  }
744
- // Get PRD skill prompt
770
+ // Load capabilities and get PRD skill prompt
771
+ loadSkillCapabilities('prd', ctx);
745
772
  const prdPrompt = getSkillPrompt('prd');
746
773
  if (!prdPrompt) {
747
774
  ctx.ui.print({ type: 'error', message: 'PRD skill not found.' });
@@ -866,6 +893,8 @@ export const scaffoldCommand = {
866
893
  else {
867
894
  skillName = 'scaffold';
868
895
  }
896
+ // Load capability packs for this skill
897
+ loadSkillCapabilities(skillName, ctx);
869
898
  // Get skill prompt and replace path placeholder
870
899
  let scaffoldPrompt = getSkillPrompt(skillName);
871
900
  if (!scaffoldPrompt) {
@@ -946,7 +975,8 @@ export const archCommand = {
946
975
  // User cancelled
947
976
  return true;
948
977
  }
949
- // Get architecture skill prompt
978
+ // Load capabilities and get architecture skill prompt
979
+ loadSkillCapabilities('architecture', ctx);
950
980
  const archPrompt = getSkillPrompt('architecture');
951
981
  if (!archPrompt) {
952
982
  ctx.ui.print({ type: 'error', message: 'Architecture skill not found.' });
@@ -4,7 +4,7 @@
4
4
  * Commands for configuration: theme, config, verbose, model, tools, agents
5
5
  */
6
6
  import { ThemeOverlayV2, ModelOverlayV2, ToolsOverlayV2, KeysOverlayV2, MascotOverlayV2, AgentsOverlayV2, ConfigOverlayV2, PermissionsOverlayV2 } from '../../ui/overlay/index.js';
7
- import { getSetting, getSettings, recordUpdateCheck, permissionModeToAgentMode } from '../../settings/index.js';
7
+ import { getSetting, getSettings, setSetting, recordUpdateCheck, permissionModeToAgentMode, getVerbosity } from '../../settings/index.js';
8
8
  import { getToolPermissionInfo } from '../../agent.js';
9
9
  import { checkForUpdates, performUpdate } from '../../utils/update-checker.js';
10
10
  import { getRegisteredMetaTools, TOOL_NAMES, getToolStats } from '../../tools.js';
@@ -84,6 +84,9 @@ export const configCommand = {
84
84
  });
85
85
  const result = await ctx.ui.showOverlay(configOverlay);
86
86
  if (result?.settingsChanged) {
87
+ // Sync TerminalUI config with persisted settings
88
+ const updated = getSettings();
89
+ ctx.ui.setConfig({ verbosity: updated.verbosity });
87
90
  ctx.ui.print({ type: 'info', message: 'Settings updated' });
88
91
  }
89
92
  return true;
@@ -94,16 +97,19 @@ export const configCommand = {
94
97
  // =============================================================================
95
98
  export const verboseCommand = {
96
99
  name: 'verbose',
97
- description: 'Toggle verbose mode',
98
- details: 'Toggles verbose mode on or off. When enabled, shows additional debug information during agent execution.',
100
+ description: 'Cycle verbosity level',
101
+ details: 'Cycles verbosity through Normal Focused → Verbose. Normal shows compact output. Focused hides read-only tools (spinner only). Verbose shows thinking and full output.',
99
102
  examples: [{ code: '/verbose' }],
100
103
  execute(_args, ctx) {
101
- const config = ctx.ui.getConfig();
102
- const newVerbose = !config.verbose;
103
- ctx.ui.setConfig({ verbose: newVerbose });
104
+ const current = getVerbosity();
105
+ const cycle = { normal: 'focused', focused: 'verbose', verbose: 'normal' };
106
+ const next = cycle[current];
107
+ setSetting('verbosity', next);
108
+ ctx.ui.setConfig({ verbosity: next });
109
+ const labels = { normal: 'Normal', focused: 'Focused', verbose: 'Verbose' };
104
110
  ctx.ui.print({
105
111
  type: 'info',
106
- message: `Verbose mode: ${newVerbose ? 'ON' : 'OFF'}`,
112
+ message: `Verbosity: ${labels[next]}`,
107
113
  });
108
114
  return Promise.resolve(true);
109
115
  },
Binary file
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Capability Loader — Bridge between slash commands and the CapabilityManager.
3
+ *
4
+ * Stores a module-level reference to the current CapabilityManager
5
+ * (set by agent.ts during createAgent()). Provides a high-level function
6
+ * for slash command handlers to load capability packs required by a skill.
7
+ */
8
+ import type { CapabilityManager, CapabilityLoadResult } from '@compilr-dev/sdk';
9
+ /**
10
+ * Set the current CapabilityManager instance.
11
+ * Called by createAgent() after constructing the manager.
12
+ */
13
+ export declare function setCapabilityManager(manager: CapabilityManager | null): void;
14
+ /**
15
+ * Get the current CapabilityManager instance.
16
+ * Returns null if capability loading is not active.
17
+ */
18
+ export declare function getCapabilityManager(): CapabilityManager | null;
19
+ /**
20
+ * Load capability packs required by a skill invocation.
21
+ *
22
+ * Maps the skill's required + optional tool names to capability pack IDs,
23
+ * then loads each loadable pack. Returns a result indicating what was loaded,
24
+ * what was already loaded, and what is forbidden.
25
+ *
26
+ * @param skillName - Skill name (e.g., 'design', 'build', 'refactor')
27
+ * @returns Load result, or null if capability loading is not active or skill has no requirements
28
+ */
29
+ export declare function loadCapabilitiesForSkill(skillName: string): CapabilityLoadResult | null;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Capability Loader — Bridge between slash commands and the CapabilityManager.
3
+ *
4
+ * Stores a module-level reference to the current CapabilityManager
5
+ * (set by agent.ts during createAgent()). Provides a high-level function
6
+ * for slash command handlers to load capability packs required by a skill.
7
+ */
8
+ import { SKILL_REQUIREMENTS } from './skill-requirements.js';
9
+ // =============================================================================
10
+ // Module-level state
11
+ // =============================================================================
12
+ let currentManager = null;
13
+ /**
14
+ * Set the current CapabilityManager instance.
15
+ * Called by createAgent() after constructing the manager.
16
+ */
17
+ export function setCapabilityManager(manager) {
18
+ currentManager = manager;
19
+ }
20
+ /**
21
+ * Get the current CapabilityManager instance.
22
+ * Returns null if capability loading is not active.
23
+ */
24
+ export function getCapabilityManager() {
25
+ return currentManager;
26
+ }
27
+ // =============================================================================
28
+ // Skill-based capability loading
29
+ // =============================================================================
30
+ /**
31
+ * Load capability packs required by a skill invocation.
32
+ *
33
+ * Maps the skill's required + optional tool names to capability pack IDs,
34
+ * then loads each loadable pack. Returns a result indicating what was loaded,
35
+ * what was already loaded, and what is forbidden.
36
+ *
37
+ * @param skillName - Skill name (e.g., 'design', 'build', 'refactor')
38
+ * @returns Load result, or null if capability loading is not active or skill has no requirements
39
+ */
40
+ export function loadCapabilitiesForSkill(skillName) {
41
+ if (!currentManager) {
42
+ return null;
43
+ }
44
+ if (!(skillName in SKILL_REQUIREMENTS)) {
45
+ return null;
46
+ }
47
+ const requirements = SKILL_REQUIREMENTS[skillName];
48
+ // Combine required + optional tools
49
+ const allTools = [
50
+ ...requirements.required,
51
+ ...(requirements.optional ?? []),
52
+ ];
53
+ return currentManager.loadForSkill(allTools, 'slash-command');
54
+ }
package/dist/repl-v2.js CHANGED
@@ -24,7 +24,8 @@ import { getCurrentProject } from './tools/project-db.js';
24
24
  import { TerminalUI } from './ui/terminal-ui.js';
25
25
  import * as terminal from './ui/terminal.js';
26
26
  import { getStyles } from './themes/index.js';
27
- import { getSettings, getStartupMode, syncPermissionModeFromUI, getPermissionMode, permissionModeToAgentMode, getProjectSessionMode, isFirstRunComplete, getSessionRetentionDays, updateSettings } from './settings/index.js';
27
+ import { getSettings, getStartupMode, syncPermissionModeFromUI, getPermissionMode, permissionModeToAgentMode, getProjectSessionMode, isFirstRunComplete, getSessionRetentionDays, updateSettings, getVerbosity } from './settings/index.js';
28
+ import { isReadOnlyTool } from './utils/readonly-tools.js';
28
29
  import { renderMascotWithLogo } from './ui/mascot/renderer.js';
29
30
  import { getStartupHighlights } from './changelog/index.js';
30
31
  import { registerCommands, executeCommand, allCommands, getAutocompleteCommands, saveCurrentSession, saveCurrentTeam, loadProjectSession, archiveCurrentSession, convertMessagesToItems, } from './commands-v2/index.js';
@@ -801,6 +802,7 @@ export class ReplV2 {
801
802
  const initialUiMode = permissionModeToAgentMode(savedPermissionMode);
802
803
  this.ui = new TerminalUI({
803
804
  initialMode: initialUiMode,
805
+ config: { verbosity: getVerbosity() },
804
806
  });
805
807
  // Print welcome screen
806
808
  // Skip if: login overlay shows its own branded welcome,
@@ -2483,6 +2485,7 @@ export class ReplV2 {
2483
2485
  params: categoryParam,
2484
2486
  summary: `${String(count)} tools available`,
2485
2487
  success: result.success,
2488
+ readOnly: isReadOnlyTool('list_tools'),
2486
2489
  agentId: activeAgentId,
2487
2490
  });
2488
2491
  this.ui.setCurrentTool(null);
@@ -2517,6 +2520,7 @@ export class ReplV2 {
2517
2520
  content: content || undefined,
2518
2521
  diffLines: innerDiffLines,
2519
2522
  success,
2523
+ readOnly: isReadOnlyTool(innerToolName),
2520
2524
  agentId: activeAgentId,
2521
2525
  });
2522
2526
  this.ui.setCurrentTool(null);
@@ -2545,6 +2549,7 @@ export class ReplV2 {
2545
2549
  params: name,
2546
2550
  summary: `Schema: ${name}`,
2547
2551
  success: result.success,
2552
+ readOnly: isReadOnlyTool('get_tool_info'),
2548
2553
  agentId: activeAgentId,
2549
2554
  });
2550
2555
  this.ui.setCurrentTool(null);
@@ -2590,6 +2595,7 @@ export class ReplV2 {
2590
2595
  content: content || undefined,
2591
2596
  diffLines,
2592
2597
  success,
2598
+ readOnly: isReadOnlyTool(toolName),
2593
2599
  agentId: activeAgentId,
2594
2600
  });
2595
2601
  this.ui.setCurrentTool(null);
@@ -3154,6 +3160,7 @@ export class ReplV2 {
3154
3160
  params: '**/*.ts',
3155
3161
  summary: 'Found 42 files',
3156
3162
  content: 'src/index.ts\nsrc/config.ts\nsrc/utils/helpers.ts\n... and 39 more',
3163
+ readOnly: true,
3157
3164
  });
3158
3165
  this.ui.setTodos([
3159
3166
  { content: 'Analyze the request', status: 'completed' },
@@ -13,6 +13,7 @@ export type ProjectStartupMode = 'last' | 'off';
13
13
  export type MascotSetting = 'none' | 'random' | 'neutral' | 'thinking' | 'looking_left' | 'looking_right' | 'sleeping' | 'alert' | 'error' | 'success' | 'success_minimal' | 'searching' | 'skeptical';
14
14
  export type ProjectSessionMode = 'auto' | 'ask' | 'fresh';
15
15
  export type CompactMode = 'active' | 'all' | 'auto';
16
+ export type Verbosity = 'normal' | 'focused' | 'verbose';
16
17
  /**
17
18
  * Permission rule for a tool or pattern.
18
19
  * Supports wildcards: git_* matches git_commit, git_branch, etc.
@@ -34,6 +35,7 @@ export interface Settings {
34
35
  showTips: boolean;
35
36
  reviseCode: boolean;
36
37
  verbose: boolean;
38
+ verbosity: Verbosity;
37
39
  progressBar: boolean;
38
40
  checkUpdates: boolean;
39
41
  lastUpdateCheck: number | null;
@@ -82,6 +84,7 @@ export declare const getDefaultModel: () => string | null;
82
84
  export declare const isAutoCompactEnabled: () => boolean;
83
85
  export declare const isShowTipsEnabled: () => boolean;
84
86
  export declare const isReviseCodeEnabled: () => boolean;
87
+ export declare const getVerbosity: () => Verbosity;
85
88
  export declare const isVerboseEnabled: () => boolean;
86
89
  export declare const isProgressBarEnabled: () => boolean;
87
90
  export declare const isTelemetryEnabled: () => boolean;
@@ -166,6 +169,14 @@ export declare function sessionRetentionToDisplay(days: number): string;
166
169
  * Map display value to session retention days
167
170
  */
168
171
  export declare function displayToSessionRetention(display: string): number;
172
+ /**
173
+ * Map internal verbosity to display value
174
+ */
175
+ export declare function verbosityToDisplay(v: Verbosity): string;
176
+ /**
177
+ * Map display value to internal verbosity
178
+ */
179
+ export declare function displayToVerbosity(d: string): Verbosity;
169
180
  /**
170
181
  * Map internal compact mode to display value
171
182
  */