@calliopelabs/cli 0.8.20 → 2.0.2
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/agents/agent-config-loader.d.ts +60 -0
- package/dist/agents/agent-config-loader.d.ts.map +1 -0
- package/dist/agents/agent-config-loader.js +402 -0
- package/dist/agents/agent-config-loader.js.map +1 -0
- package/dist/agents/agent-config-presets.d.ts +10 -0
- package/dist/agents/agent-config-presets.d.ts.map +1 -0
- package/dist/agents/agent-config-presets.js +940 -0
- package/dist/agents/agent-config-presets.js.map +1 -0
- package/dist/agents/agent-config-types.d.ts +145 -0
- package/dist/agents/agent-config-types.d.ts.map +1 -0
- package/dist/agents/agent-config-types.js +12 -0
- package/dist/agents/agent-config-types.js.map +1 -0
- package/dist/{agterm → agents}/agent-detection.d.ts +1 -1
- package/dist/{agterm → agents}/agent-detection.d.ts.map +1 -1
- package/dist/{agterm → agents}/agent-detection.js +21 -5
- package/dist/agents/agent-detection.js.map +1 -0
- package/dist/agents/aggregator.d.ts +19 -0
- package/dist/agents/aggregator.d.ts.map +1 -0
- package/dist/agents/aggregator.js +141 -0
- package/dist/agents/aggregator.js.map +1 -0
- package/dist/{agterm → agents}/cli-backend.d.ts +1 -1
- package/dist/{agterm → agents}/cli-backend.d.ts.map +1 -1
- package/dist/{agterm → agents}/cli-backend.js +90 -12
- package/dist/agents/cli-backend.js.map +1 -0
- package/dist/agents/council-types.d.ts +113 -0
- package/dist/agents/council-types.d.ts.map +1 -0
- package/dist/agents/council-types.js +81 -0
- package/dist/agents/council-types.js.map +1 -0
- package/dist/agents/council.d.ts +107 -0
- package/dist/agents/council.d.ts.map +1 -0
- package/dist/agents/council.js +586 -0
- package/dist/agents/council.js.map +1 -0
- package/dist/agents/decomposer.d.ts +33 -0
- package/dist/agents/decomposer.d.ts.map +1 -0
- package/dist/agents/decomposer.js +138 -0
- package/dist/agents/decomposer.js.map +1 -0
- package/dist/agents/dynamic-tools.d.ts +52 -0
- package/dist/agents/dynamic-tools.d.ts.map +1 -0
- package/dist/agents/dynamic-tools.js +395 -0
- package/dist/agents/dynamic-tools.js.map +1 -0
- package/dist/agents/index.d.ts +29 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +29 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/installer.d.ts +39 -0
- package/dist/agents/installer.d.ts.map +1 -0
- package/dist/agents/installer.js +205 -0
- package/dist/agents/installer.js.map +1 -0
- package/dist/{agterm → agents}/orchestrator.d.ts +7 -2
- package/dist/agents/orchestrator.d.ts.map +1 -0
- package/dist/{agterm → agents}/orchestrator.js +22 -2
- package/dist/agents/orchestrator.js.map +1 -0
- package/dist/agents/sdk-backend.d.ts +63 -0
- package/dist/agents/sdk-backend.d.ts.map +1 -0
- package/dist/agents/sdk-backend.js +489 -0
- package/dist/agents/sdk-backend.js.map +1 -0
- package/dist/agents/swarm-types.d.ts +83 -0
- package/dist/agents/swarm-types.d.ts.map +1 -0
- package/dist/agents/swarm-types.js +20 -0
- package/dist/agents/swarm-types.js.map +1 -0
- package/dist/agents/swarm.d.ts +74 -0
- package/dist/agents/swarm.d.ts.map +1 -0
- package/dist/agents/swarm.js +307 -0
- package/dist/agents/swarm.js.map +1 -0
- package/dist/{agterm → agents}/tools.d.ts +7 -5
- package/dist/agents/tools.d.ts.map +1 -0
- package/dist/agents/tools.js +776 -0
- package/dist/agents/tools.js.map +1 -0
- package/dist/{agterm → agents}/types.d.ts +14 -2
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/{agterm → agents}/types.js +2 -2
- package/dist/agents/types.js.map +1 -0
- package/dist/api-server.d.ts +26 -0
- package/dist/api-server.d.ts.map +1 -0
- package/dist/api-server.js +230 -0
- package/dist/api-server.js.map +1 -0
- package/dist/auto-checkpoint.d.ts +35 -0
- package/dist/auto-checkpoint.d.ts.map +1 -0
- package/dist/auto-checkpoint.js +143 -0
- package/dist/auto-checkpoint.js.map +1 -0
- package/dist/auto-compressor.d.ts +44 -0
- package/dist/auto-compressor.d.ts.map +1 -0
- package/dist/auto-compressor.js +145 -0
- package/dist/auto-compressor.js.map +1 -0
- package/dist/background-jobs.d.ts +45 -0
- package/dist/background-jobs.d.ts.map +1 -0
- package/dist/background-jobs.js +122 -0
- package/dist/background-jobs.js.map +1 -0
- package/dist/bin.d.ts +6 -2
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +127 -24
- package/dist/bin.js.map +1 -1
- package/dist/checkpoint.d.ts +49 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/checkpoint.js +219 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/circuit-breaker/breaker.d.ts +80 -0
- package/dist/circuit-breaker/breaker.d.ts.map +1 -0
- package/dist/circuit-breaker/breaker.js +408 -0
- package/dist/circuit-breaker/breaker.js.map +1 -0
- package/dist/circuit-breaker/defaults.d.ts +8 -0
- package/dist/circuit-breaker/defaults.d.ts.map +1 -0
- package/dist/circuit-breaker/defaults.js +35 -0
- package/dist/circuit-breaker/defaults.js.map +1 -0
- package/dist/circuit-breaker/index.d.ts +9 -0
- package/dist/circuit-breaker/index.d.ts.map +1 -0
- package/dist/circuit-breaker/index.js +8 -0
- package/dist/circuit-breaker/index.js.map +1 -0
- package/dist/circuit-breaker/types.d.ts +77 -0
- package/dist/circuit-breaker/types.d.ts.map +1 -0
- package/dist/circuit-breaker/types.js +8 -0
- package/dist/circuit-breaker/types.js.map +1 -0
- package/dist/circuit-breaker.d.ts +8 -0
- package/dist/circuit-breaker.d.ts.map +1 -0
- package/dist/circuit-breaker.js +7 -0
- package/dist/circuit-breaker.js.map +1 -0
- package/dist/cli/agent.d.ts +9 -0
- package/dist/cli/agent.d.ts.map +1 -0
- package/dist/cli/agent.js +262 -0
- package/dist/cli/agent.js.map +1 -0
- package/dist/cli/commands.d.ts +12 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/{cli.js → cli/commands.js} +285 -422
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +222 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/types.d.ts +30 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +20 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/companions.d.ts +54 -0
- package/dist/companions.d.ts.map +1 -0
- package/dist/companions.js +440 -0
- package/dist/companions.js.map +1 -0
- package/dist/config.d.ts +23 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +95 -22
- package/dist/config.js.map +1 -1
- package/dist/diff.d.ts +27 -0
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +415 -10
- package/dist/diff.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +20 -11
- package/dist/errors.js.map +1 -1
- package/dist/git-status.d.ts +23 -0
- package/dist/git-status.d.ts.map +1 -0
- package/dist/git-status.js +92 -0
- package/dist/git-status.js.map +1 -0
- package/dist/headless.d.ts +25 -0
- package/dist/headless.d.ts.map +1 -0
- package/dist/headless.js +182 -0
- package/dist/headless.js.map +1 -0
- package/dist/hud/api.d.ts +35 -0
- package/dist/hud/api.d.ts.map +1 -0
- package/dist/hud/api.js +448 -0
- package/dist/hud/api.js.map +1 -0
- package/dist/hud/palettes.d.ts +9 -0
- package/dist/hud/palettes.d.ts.map +1 -0
- package/dist/hud/palettes.js +280 -0
- package/dist/hud/palettes.js.map +1 -0
- package/dist/hud/skins.d.ts +12 -0
- package/dist/hud/skins.d.ts.map +1 -0
- package/dist/hud/skins.js +365 -0
- package/dist/hud/skins.js.map +1 -0
- package/dist/hud/theme-packs/api.d.ts +51 -0
- package/dist/hud/theme-packs/api.d.ts.map +1 -0
- package/dist/hud/theme-packs/api.js +145 -0
- package/dist/hud/theme-packs/api.js.map +1 -0
- package/dist/hud/theme-packs/index.d.ts +18 -0
- package/dist/hud/theme-packs/index.d.ts.map +1 -0
- package/dist/hud/theme-packs/index.js +38 -0
- package/dist/hud/theme-packs/index.js.map +1 -0
- package/dist/hud/theme-packs/types.d.ts +29 -0
- package/dist/hud/theme-packs/types.d.ts.map +1 -0
- package/dist/hud/theme-packs/types.js +9 -0
- package/dist/hud/theme-packs/types.js.map +1 -0
- package/dist/hud/types.d.ts +182 -0
- package/dist/hud/types.d.ts.map +1 -0
- package/dist/hud/types.js +7 -0
- package/dist/hud/types.js.map +1 -0
- package/dist/idle-eviction.d.ts +34 -0
- package/dist/idle-eviction.d.ts.map +1 -0
- package/dist/idle-eviction.js +78 -0
- package/dist/idle-eviction.js.map +1 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/iteration-ledger.d.ts +105 -0
- package/dist/iteration-ledger.d.ts.map +1 -0
- package/dist/iteration-ledger.js +237 -0
- package/dist/iteration-ledger.js.map +1 -0
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +1 -27
- package/dist/markdown.js.map +1 -1
- package/dist/mcp.d.ts +35 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +291 -7
- package/dist/mcp.js.map +1 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +12 -2
- package/dist/memory.js.map +1 -1
- package/dist/model-detection.d.ts +5 -0
- package/dist/model-detection.d.ts.map +1 -1
- package/dist/model-detection.js +278 -10
- package/dist/model-detection.js.map +1 -1
- package/dist/model-router.d.ts.map +1 -1
- package/dist/model-router.js +33 -11
- package/dist/model-router.js.map +1 -1
- package/dist/plugins.d.ts +8 -0
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +97 -6
- package/dist/plugins.js.map +1 -1
- package/dist/providers/anthropic.d.ts +10 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +221 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/bedrock.d.ts +17 -0
- package/dist/providers/bedrock.d.ts.map +1 -0
- package/dist/providers/bedrock.js +574 -0
- package/dist/providers/bedrock.js.map +1 -0
- package/dist/providers/compat.d.ts +13 -0
- package/dist/providers/compat.d.ts.map +1 -0
- package/dist/providers/compat.js +202 -0
- package/dist/providers/compat.js.map +1 -0
- package/dist/providers/google.d.ts +10 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +203 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/index.d.ts +23 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +145 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/ollama.d.ts +17 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +289 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai.d.ts +121 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +485 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/types.d.ts +63 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +164 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/sandbox-native.d.ts +59 -0
- package/dist/sandbox-native.d.ts.map +1 -0
- package/dist/sandbox-native.js +292 -0
- package/dist/sandbox-native.js.map +1 -0
- package/dist/sandbox.d.ts +2 -2
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/sandbox.js +59 -13
- package/dist/sandbox.js.map +1 -1
- package/dist/scope.d.ts +3 -1
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +13 -1
- package/dist/scope.js.map +1 -1
- package/dist/session-timeout.d.ts +31 -0
- package/dist/session-timeout.d.ts.map +1 -0
- package/dist/session-timeout.js +100 -0
- package/dist/session-timeout.js.map +1 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +29 -17
- package/dist/setup.js.map +1 -1
- package/dist/smart-router.d.ts +73 -0
- package/dist/smart-router.d.ts.map +1 -0
- package/dist/smart-router.js +332 -0
- package/dist/smart-router.js.map +1 -0
- package/dist/storage.d.ts +19 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +164 -1
- package/dist/storage.js.map +1 -1
- package/dist/streaming.d.ts +4 -0
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +12 -0
- package/dist/streaming.js.map +1 -1
- package/dist/styles.d.ts +32 -0
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +91 -0
- package/dist/styles.js.map +1 -1
- package/dist/summarization.d.ts +1 -1
- package/dist/summarization.js +4 -4
- package/dist/summarization.js.map +1 -1
- package/dist/terminal-image.d.ts +115 -0
- package/dist/terminal-image.d.ts.map +1 -0
- package/dist/terminal-image.js +766 -0
- package/dist/terminal-image.js.map +1 -0
- package/dist/terminal-recording.d.ts +55 -0
- package/dist/terminal-recording.d.ts.map +1 -0
- package/dist/terminal-recording.js +182 -0
- package/dist/terminal-recording.js.map +1 -0
- package/dist/themes.d.ts +19 -35
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +101 -210
- package/dist/themes.js.map +1 -1
- package/dist/tmux.d.ts +35 -0
- package/dist/tmux.d.ts.map +1 -0
- package/dist/tmux.js +106 -0
- package/dist/tmux.js.map +1 -0
- package/dist/tools.d.ts +3 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +587 -45
- package/dist/tools.js.map +1 -1
- package/dist/trust.d.ts +53 -0
- package/dist/trust.d.ts.map +1 -0
- package/dist/trust.js +154 -0
- package/dist/trust.js.map +1 -0
- package/dist/types.d.ts +7 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +70 -32
- package/dist/types.js.map +1 -1
- package/dist/ui/agent.d.ts +61 -0
- package/dist/ui/agent.d.ts.map +1 -0
- package/dist/ui/agent.js +768 -0
- package/dist/ui/agent.js.map +1 -0
- package/dist/ui/chat-input.d.ts +32 -0
- package/dist/ui/chat-input.d.ts.map +1 -0
- package/dist/ui/chat-input.js +355 -0
- package/dist/ui/chat-input.js.map +1 -0
- package/dist/ui/commands.d.ts +92 -0
- package/dist/ui/commands.d.ts.map +1 -0
- package/dist/ui/commands.js +3006 -0
- package/dist/ui/commands.js.map +1 -0
- package/dist/ui/completions.d.ts +22 -0
- package/dist/ui/completions.d.ts.map +1 -0
- package/dist/ui/completions.js +215 -0
- package/dist/ui/completions.js.map +1 -0
- package/dist/ui/components.d.ts +38 -0
- package/dist/ui/components.d.ts.map +1 -0
- package/dist/ui/components.js +422 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/context.d.ts +12 -0
- package/dist/ui/context.d.ts.map +1 -0
- package/dist/ui/context.js +102 -0
- package/dist/ui/context.js.map +1 -0
- package/dist/ui/error-boundary.d.ts +33 -0
- package/dist/ui/error-boundary.d.ts.map +1 -0
- package/dist/ui/error-boundary.js +94 -0
- package/dist/ui/error-boundary.js.map +1 -0
- package/dist/ui/frame.d.ts +13 -0
- package/dist/ui/frame.d.ts.map +1 -0
- package/dist/ui/frame.js +89 -0
- package/dist/ui/frame.js.map +1 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +928 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/messages.d.ts +19 -0
- package/dist/ui/messages.d.ts.map +1 -0
- package/dist/ui/messages.js +181 -0
- package/dist/ui/messages.js.map +1 -0
- package/dist/ui/modals.d.ts +52 -0
- package/dist/ui/modals.d.ts.map +1 -0
- package/dist/ui/modals.js +204 -0
- package/dist/ui/modals.js.map +1 -0
- package/dist/ui/pack-picker.d.ts +12 -0
- package/dist/ui/pack-picker.d.ts.map +1 -0
- package/dist/ui/pack-picker.js +101 -0
- package/dist/ui/pack-picker.js.map +1 -0
- package/dist/ui/status-bar.d.ts +20 -0
- package/dist/ui/status-bar.d.ts.map +1 -0
- package/dist/ui/status-bar.js +41 -0
- package/dist/ui/status-bar.js.map +1 -0
- package/dist/ui/theme-picker.d.ts +24 -0
- package/dist/ui/theme-picker.d.ts.map +1 -0
- package/dist/ui/theme-picker.js +190 -0
- package/dist/ui/theme-picker.js.map +1 -0
- package/dist/ui/types.d.ts +62 -0
- package/dist/ui/types.d.ts.map +1 -0
- package/dist/ui/types.js +7 -0
- package/dist/ui/types.js.map +1 -0
- package/dist/version-check.d.ts.map +1 -1
- package/dist/version-check.js +1 -9
- package/dist/version-check.js.map +1 -1
- package/package.json +8 -3
- package/dist/agterm/agent-detection.js.map +0 -1
- package/dist/agterm/cli-backend.js.map +0 -1
- package/dist/agterm/index.d.ts +0 -12
- package/dist/agterm/index.d.ts.map +0 -1
- package/dist/agterm/index.js +0 -15
- package/dist/agterm/index.js.map +0 -1
- package/dist/agterm/orchestrator.d.ts.map +0 -1
- package/dist/agterm/orchestrator.js.map +0 -1
- package/dist/agterm/tools.d.ts.map +0 -1
- package/dist/agterm/tools.js +0 -278
- package/dist/agterm/tools.js.map +0 -1
- package/dist/agterm/types.d.ts.map +0 -1
- package/dist/agterm/types.js.map +0 -1
- package/dist/cli.d.ts +0 -14
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/providers.d.ts +0 -51
- package/dist/providers.d.ts.map +0 -1
- package/dist/providers.js +0 -1146
- package/dist/providers.js.map +0 -1
- package/dist/ui-cli.d.ts +0 -17
- package/dist/ui-cli.d.ts.map +0 -1
- package/dist/ui-cli.js +0 -3730
- package/dist/ui-cli.js.map +0 -1
|
@@ -0,0 +1,3006 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Module - Command Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles all slash commands (/help, /mode, /provider, etc.)
|
|
5
|
+
* Extracted from TerminalChat using a CommandContext state bag.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import * as config from '../config.js';
|
|
10
|
+
import { selectProvider, getAvailableProviders } from '../providers/index.js';
|
|
11
|
+
import { getSystemPrompt, DEFAULT_MODELS, MODE_CONFIG } from '../types.js';
|
|
12
|
+
import { getVersion, getLatestVersion } from '../version-check.js';
|
|
13
|
+
import { getAvailableModels } from '../model-detection.js';
|
|
14
|
+
import * as storage from '../storage.js';
|
|
15
|
+
import * as mcp from '../mcp.js';
|
|
16
|
+
import * as skills from '../skills.js';
|
|
17
|
+
import * as modelRouter from '../model-router.js';
|
|
18
|
+
import * as summarization from '../summarization.js';
|
|
19
|
+
import { addToScope, removeFromScope, getScopeSummary, getScopeDetails, resetScope } from '../scope.js';
|
|
20
|
+
import { getAgentStatusReport, swarmManager, councilManager, COUNCIL_TEMPLATES, getInstallReport, installItem, installAllMissing, listAgentDefs, listTeamDefs, getAgent, getTeam, scaffoldAgentsDir, saveAgentDef, getAvailableExecutors } from '../agents/index.js';
|
|
21
|
+
import { smartRoute, getDefaultSmartRoutingConfig, detectTaskType } from '../smart-router.js';
|
|
22
|
+
import { getCurrentSkin, getCurrentPalette, applySkin, applyPalette, listSkins, listPalettes } from '../hud/api.js';
|
|
23
|
+
import { getCurrentCompanion, applyCompanion, listCompanions, getMoodText } from '../companions.js';
|
|
24
|
+
import { createJob, runJob, cancelJob, getJob, formatJob, formatJobsList, clearFinishedJobs } from '../background-jobs.js';
|
|
25
|
+
import { listRecordings, loadRecording, formatRecording, deleteRecording } from '../terminal-recording.js';
|
|
26
|
+
import { startApiServer, stopApiServer, isApiServerRunning } from '../api-server.js';
|
|
27
|
+
import { getTerminalImageInfo, getImageModeLabel, renderSkinBanner, renderTransition } from '../terminal-image.js';
|
|
28
|
+
import { applyThemePack, listThemePacks, getCurrentPack, getCompanionMode, setCompanionMode, getThemePack } from '../hud/theme-packs/api.js';
|
|
29
|
+
import { getModelContextLimit } from '../model-detection.js';
|
|
30
|
+
import { resetContextWarnings } from './context.js';
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// handleCommand
|
|
33
|
+
// ============================================================================
|
|
34
|
+
export async function handleCommand(cmd, ctx) {
|
|
35
|
+
const parts = cmd.split(/\s+/);
|
|
36
|
+
const command = parts[0].toLowerCase();
|
|
37
|
+
switch (command) {
|
|
38
|
+
case '/help':
|
|
39
|
+
case '/h':
|
|
40
|
+
ctx.addMessage('system', `--- Model & Routing ---
|
|
41
|
+
/provider [name] - Switch AI provider (/p)
|
|
42
|
+
/model [name] - Switch model (/m)
|
|
43
|
+
/models - List available models
|
|
44
|
+
/mode [plan|hybrid|work] - Switch modes (Shift+Tab to cycle)
|
|
45
|
+
/persona [name] - Switch personality
|
|
46
|
+
/route [on|off] - Auto model routing (/autoroute)
|
|
47
|
+
/smart [on|off|cost|test] - Cross-provider smart routing
|
|
48
|
+
/breaker [status|resume] - Circuit breaker control (/cb)
|
|
49
|
+
|
|
50
|
+
--- Conversation ---
|
|
51
|
+
/edit - Edit and resend last message
|
|
52
|
+
/undo / /redo - Undo/redo (up to 10 steps)
|
|
53
|
+
/copy - Copy last response to clipboard
|
|
54
|
+
/export [file.md] - Export conversation to markdown
|
|
55
|
+
/branch [new|switch] - Conversation branches
|
|
56
|
+
/clear - Clear conversation (/c)
|
|
57
|
+
|
|
58
|
+
--- Session & State ---
|
|
59
|
+
/session [list|info|fork|save] - Session management (/sessions)
|
|
60
|
+
/resume [sessionId] - Resume session (restores full context)
|
|
61
|
+
/checkpoint [list|clear] - File checkpoints (/cp)
|
|
62
|
+
/restore <path> [index] - Restore file from checkpoint
|
|
63
|
+
/bookmark [name|list|goto] - Bookmarks (/bm)
|
|
64
|
+
/queue [show|clear|flush] - Message queue (/q)
|
|
65
|
+
|
|
66
|
+
--- Context & Scope ---
|
|
67
|
+
/scope [details|reset] - File access scope (/dirs)
|
|
68
|
+
/add-dir / /remove-dir - Manage scope directories
|
|
69
|
+
/context [load|summary] - Context management
|
|
70
|
+
/summarize - Compact context to save tokens
|
|
71
|
+
/trust [add|remove|list] - Project trust registry
|
|
72
|
+
/find <pattern> - Fuzzy file search
|
|
73
|
+
/search <query> - Search conversation
|
|
74
|
+
|
|
75
|
+
--- Tasks & Planning ---
|
|
76
|
+
/todo [add|done|list] - Manage TODOs
|
|
77
|
+
/plans [list|view] - View plan history
|
|
78
|
+
/template [save|use|del] - Prompt templates (/t)
|
|
79
|
+
/plan / /work - Quick mode switch
|
|
80
|
+
/approve [notes] - Approve pending plan & execute
|
|
81
|
+
|
|
82
|
+
--- Appearance ---
|
|
83
|
+
/skin [name] - Switch HUD skin (${(await import('../hud/api.js')).listSkins().length}+ available)
|
|
84
|
+
/palette [name] - Switch color palette
|
|
85
|
+
/companion [name] - Switch companion personality
|
|
86
|
+
/pack [name] - Apply theme pack (skin+palette+companion)
|
|
87
|
+
/theme [name] - Color themes
|
|
88
|
+
/layout [name] - UI layout (classic/split/etc)
|
|
89
|
+
/density [normal|compact] - Display density
|
|
90
|
+
/collapse [tools|all|off] - Tool output visibility
|
|
91
|
+
/intensity [1-5] - Immersion level
|
|
92
|
+
/emoji [on|off] - Toggle emoji
|
|
93
|
+
/banner - Show skin banner art
|
|
94
|
+
|
|
95
|
+
--- Tools & Integration ---
|
|
96
|
+
/mcp [add|remove|tools] - MCP servers
|
|
97
|
+
/skills [add|remove] - Agent skills
|
|
98
|
+
/memory [init|add|show] - Project memory (CALLIOPE.md)
|
|
99
|
+
/project [init|show|run] - Project config (.calliope)
|
|
100
|
+
/hooks [list|add] - Pre/post tool hooks
|
|
101
|
+
/profile [name|save|del] - Switch/save/delete profiles
|
|
102
|
+
|
|
103
|
+
--- Multi-Agent ---
|
|
104
|
+
/agents - Sub-agent status (--agents mode)
|
|
105
|
+
/swarm [start|coord|status] - Agent swarms & coordination
|
|
106
|
+
/loop [prompt] [n] - Iterative agent loop
|
|
107
|
+
/cancel-loop - Stop running loop (/stop)
|
|
108
|
+
|
|
109
|
+
--- System ---
|
|
110
|
+
/status - Show status (/s)
|
|
111
|
+
/config - Show config
|
|
112
|
+
/set <key> <value> - Set config variable
|
|
113
|
+
/cost - Cost tracking summary
|
|
114
|
+
/confirm [on|off] - Toggle risky op confirmation
|
|
115
|
+
/debug [on|off] - Debug state/logging
|
|
116
|
+
/unstick - Emergency reset
|
|
117
|
+
/upgrade - Check for updates
|
|
118
|
+
/keys or /? - Keyboard shortcuts
|
|
119
|
+
/exit - Exit (/quit)
|
|
120
|
+
|
|
121
|
+
File references: @filename, ./path, /absolute/path
|
|
122
|
+
Modes: Plan | Hybrid | Work | Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}${ctx.agtermEnabled ? '\nAgents: ON' : ''}`);
|
|
123
|
+
break;
|
|
124
|
+
case '/provider':
|
|
125
|
+
case '/providers':
|
|
126
|
+
case '/p':
|
|
127
|
+
if (parts[1]) {
|
|
128
|
+
const p = parts[1].toLowerCase();
|
|
129
|
+
ctx.setProvider(p);
|
|
130
|
+
ctx.addMessage('system', `Provider: ${selectProvider(p)}`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
ctx.addMessage('system', `Provider: ${ctx.actualProvider} | Available: ${getAvailableProviders().join(', ')}`);
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
case '/model':
|
|
137
|
+
case '/m':
|
|
138
|
+
if (parts[1]) {
|
|
139
|
+
const newModel = parts[1];
|
|
140
|
+
const oldModel = ctx.model || ctx.actualModel;
|
|
141
|
+
// Check context compatibility before switching (#26)
|
|
142
|
+
const oldLimit = getModelContextLimit(ctx.actualProvider, oldModel);
|
|
143
|
+
const newLimit = getModelContextLimit(ctx.actualProvider, newModel);
|
|
144
|
+
const currentTokens = ctx.estimateContextTokens();
|
|
145
|
+
const newPct = Math.round((currentTokens / newLimit) * 100);
|
|
146
|
+
ctx.setModel(newModel);
|
|
147
|
+
ctx.setContextTokens(currentTokens);
|
|
148
|
+
let switchWarning = '';
|
|
149
|
+
if (newPct > 80) {
|
|
150
|
+
switchWarning = `\n⚠️ Context at ${newPct}% of new model limit (${Math.round(currentTokens / 1000)}K/${Math.round(newLimit / 1000)}K). Consider /summarize compact.`;
|
|
151
|
+
}
|
|
152
|
+
else if (newLimit < oldLimit) {
|
|
153
|
+
switchWarning = `\n📉 Context window: ${Math.round(oldLimit / 1000)}K → ${Math.round(newLimit / 1000)}K (${newPct}% used)`;
|
|
154
|
+
}
|
|
155
|
+
ctx.addMessage('system', `Model: ${oldModel} → ${newModel}${switchWarning}`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
ctx.addMessage('system', `Discovering models for ${ctx.actualProvider}...`);
|
|
159
|
+
try {
|
|
160
|
+
const models = await getAvailableModels(ctx.actualProvider);
|
|
161
|
+
if (models.length > 0) {
|
|
162
|
+
ctx.setAvailableModels(models);
|
|
163
|
+
ctx.setModalMode('model');
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
ctx.addMessage('error', 'No models found');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
ctx.addMessage('error', `Failed to fetch models: ${e instanceof Error ? e.message : String(e)}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
case '/models':
|
|
175
|
+
ctx.addMessage('system', `Discovering models for ${ctx.actualProvider}...`);
|
|
176
|
+
try {
|
|
177
|
+
const models = await getAvailableModels(ctx.actualProvider);
|
|
178
|
+
if (models.length > 0) {
|
|
179
|
+
ctx.setAvailableModels(models);
|
|
180
|
+
ctx.setModalMode('model');
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
ctx.addMessage('error', 'No models found');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
ctx.addMessage('error', `Failed to fetch models: ${e instanceof Error ? e.message : String(e)}`);
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
case '/mode':
|
|
191
|
+
if (parts[1] && ['plan', 'hybrid', 'work'].includes(parts[1])) {
|
|
192
|
+
const m = parts[1];
|
|
193
|
+
ctx.setMode(m);
|
|
194
|
+
ctx.addMessage('system', `Mode: ${MODE_CONFIG[m].icon} ${MODE_CONFIG[m].label} - ${MODE_CONFIG[m].description}`);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const currentConfig = MODE_CONFIG[ctx.mode];
|
|
198
|
+
ctx.addMessage('system', `Mode: ${currentConfig.icon} ${currentConfig.label}\nOptions: plan (\u{1F4CB}), hybrid (\u{1F504}), work (\u{1F527})\nUse Shift+Tab to cycle`);
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
case '/persona':
|
|
202
|
+
if (parts[1] && ['calliope', 'muse', 'minimal'].includes(parts[1])) {
|
|
203
|
+
const p = parts[1];
|
|
204
|
+
ctx.setPersona(p);
|
|
205
|
+
ctx.llmMessages.current = [{ role: 'system', content: getSystemPrompt(p) }];
|
|
206
|
+
ctx.addMessage('system', `Persona: ${p}`);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
ctx.addMessage('system', `Persona: ${ctx.persona} | Options: calliope, muse, minimal`);
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
case '/clear':
|
|
213
|
+
case '/c':
|
|
214
|
+
ctx.setMessages([]);
|
|
215
|
+
ctx.llmMessages.current = [{ role: 'system', content: getSystemPrompt(ctx.persona) }];
|
|
216
|
+
ctx.setStats({ inputTokens: 0, outputTokens: 0, cost: 0, messageCount: 0 });
|
|
217
|
+
resetContextWarnings(); // Reset context warning state
|
|
218
|
+
break;
|
|
219
|
+
case '/copy': {
|
|
220
|
+
// Copy last assistant response to clipboard
|
|
221
|
+
const lastAssistant = [...ctx.messages].reverse().find(m => m.type === 'assistant');
|
|
222
|
+
if (lastAssistant) {
|
|
223
|
+
try {
|
|
224
|
+
const { execSync } = await import('child_process');
|
|
225
|
+
// Try different clipboard commands based on platform
|
|
226
|
+
const content = lastAssistant.content;
|
|
227
|
+
if (process.platform === 'darwin') {
|
|
228
|
+
execSync('pbcopy', { input: content });
|
|
229
|
+
}
|
|
230
|
+
else if (process.platform === 'win32') {
|
|
231
|
+
execSync('clip', { input: content });
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// Linux - try xclip, xsel, or wl-copy
|
|
235
|
+
try {
|
|
236
|
+
execSync('xclip -selection clipboard', { input: content });
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
try {
|
|
240
|
+
execSync('xsel --clipboard --input', { input: content });
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
execSync('wl-copy', { input: content });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
ctx.addMessage('system', '\u2713 Copied to clipboard');
|
|
248
|
+
}
|
|
249
|
+
catch (e) {
|
|
250
|
+
ctx.addMessage('error', `Clipboard not available: ${e instanceof Error ? e.message : String(e)}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
ctx.addMessage('system', 'No assistant message to copy');
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case '/export': {
|
|
259
|
+
// Export conversation to markdown
|
|
260
|
+
const filename = parts[1] || `calliope-export-${Date.now()}.md`;
|
|
261
|
+
const fsModule = await import('fs');
|
|
262
|
+
const path = await import('path');
|
|
263
|
+
let markdown = `# Calliope Conversation Export\n\n`;
|
|
264
|
+
markdown += `**Date:** ${new Date().toLocaleString()}\n`;
|
|
265
|
+
markdown += `**Provider:** ${ctx.actualProvider}\n`;
|
|
266
|
+
markdown += `**Model:** ${ctx.actualModel}\n\n---\n\n`;
|
|
267
|
+
for (const msg of ctx.messages) {
|
|
268
|
+
if (msg.type === 'user') {
|
|
269
|
+
markdown += `## \u{1F464} User\n\n${msg.content}\n\n`;
|
|
270
|
+
}
|
|
271
|
+
else if (msg.type === 'assistant') {
|
|
272
|
+
markdown += `## \u{1F916} Assistant\n\n${msg.content}\n\n`;
|
|
273
|
+
}
|
|
274
|
+
else if (msg.type === 'tool') {
|
|
275
|
+
markdown += `> \u{1F527} Tool: ${msg.content}\n\n`;
|
|
276
|
+
}
|
|
277
|
+
else if (msg.type === 'system') {
|
|
278
|
+
markdown += `> \u{2139}\u{FE0F} ${msg.content}\n\n`;
|
|
279
|
+
}
|
|
280
|
+
else if (msg.type === 'error') {
|
|
281
|
+
markdown += `> \u{26A0}\u{FE0F} Error: ${msg.content}\n\n`;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const filepath = path.resolve(process.cwd(), filename);
|
|
285
|
+
fsModule.writeFileSync(filepath, markdown);
|
|
286
|
+
ctx.addMessage('system', `\u2713 Exported to ${filename}`);
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case '/edit': {
|
|
290
|
+
// Edit last user message
|
|
291
|
+
const lastUserIdx = [...ctx.messages].reverse().findIndex(m => m.type === 'user');
|
|
292
|
+
if (lastUserIdx >= 0) {
|
|
293
|
+
const lastUser = ctx.messages[ctx.messages.length - 1 - lastUserIdx];
|
|
294
|
+
ctx.setInput(lastUser.content);
|
|
295
|
+
ctx.addMessage('system', 'Edit the message above and press Enter to resend');
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
ctx.addMessage('system', 'No user message to edit');
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case '/undo': {
|
|
303
|
+
if (ctx.undoStack.current.length === 0) {
|
|
304
|
+
ctx.addMessage('system', 'Nothing to undo.');
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
// Save current state to redo stack
|
|
308
|
+
ctx.redoStack.current.push({
|
|
309
|
+
messages: [...ctx.messages],
|
|
310
|
+
llmMessages: [...ctx.llmMessages.current],
|
|
311
|
+
timestamp: new Date(),
|
|
312
|
+
});
|
|
313
|
+
// Restore previous state
|
|
314
|
+
const prevState = ctx.undoStack.current.pop();
|
|
315
|
+
ctx.setMessages(prevState.messages);
|
|
316
|
+
ctx.llmMessages.current = prevState.llmMessages;
|
|
317
|
+
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
318
|
+
ctx.addMessage('system', `\u2713 Undone (${ctx.undoStack.current.length} more available)`);
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
case '/redo': {
|
|
322
|
+
if (ctx.redoStack.current.length === 0) {
|
|
323
|
+
ctx.addMessage('system', 'Nothing to redo.');
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
// Save current state to undo stack
|
|
327
|
+
ctx.undoStack.current.push({
|
|
328
|
+
messages: [...ctx.messages],
|
|
329
|
+
llmMessages: [...ctx.llmMessages.current],
|
|
330
|
+
timestamp: new Date(),
|
|
331
|
+
});
|
|
332
|
+
// Restore redo state
|
|
333
|
+
const redoState = ctx.redoStack.current.pop();
|
|
334
|
+
ctx.setMessages(redoState.messages);
|
|
335
|
+
ctx.llmMessages.current = redoState.llmMessages;
|
|
336
|
+
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
337
|
+
ctx.addMessage('system', `\u2713 Redone (${ctx.redoStack.current.length} more available)`);
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
case '/status':
|
|
341
|
+
case '/s': {
|
|
342
|
+
const imgInfo = getTerminalImageInfo();
|
|
343
|
+
ctx.addMessage('system', `${ctx.actualProvider}:${ctx.actualModel} | ${ctx.stats.messageCount} msgs | ${ctx.stats.inputTokens + ctx.stats.outputTokens} tokens | terminal: ${getImageModeLabel(imgInfo.mode)}${imgInfo.truecolor ? ' (truecolor)' : ''} ${imgInfo.width}cols`);
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
case '/config':
|
|
347
|
+
ctx.addMessage('system', `Config: ${config.getConfigPath()}\nProviders: ${config.getConfiguredProviders().join(', ') || 'none'}\nmaxIterations: ${config.get('maxIterations')}`);
|
|
348
|
+
break;
|
|
349
|
+
case '/agents': {
|
|
350
|
+
if (!ctx.agtermEnabled) {
|
|
351
|
+
ctx.addMessage('system', 'Agents mode not enabled. Start with --agents flag to unlock multi-agent features.');
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
const sub = parts[1];
|
|
355
|
+
if (!sub) {
|
|
356
|
+
ctx.addMessage('system', getAgentStatusReport());
|
|
357
|
+
}
|
|
358
|
+
else if (sub === 'defs' || sub === 'list') {
|
|
359
|
+
const defs = listAgentDefs(process.cwd());
|
|
360
|
+
if (defs.length === 0) {
|
|
361
|
+
ctx.addMessage('system', 'No agent definitions loaded. Run /agents init to create examples.');
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
const lines = defs.map(d => {
|
|
365
|
+
const src = d._source || 'unknown';
|
|
366
|
+
return ` ${d.name} — ${d.engine}/${d.provider || 'auto'}/${d.model || 'default'} [${src}]${d.description ? `\n ${d.description}` : ''}`;
|
|
367
|
+
});
|
|
368
|
+
ctx.addMessage('system', `Agent Definitions (${defs.length}):\n${lines.join('\n')}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
else if (sub === 'teams') {
|
|
372
|
+
const teams = listTeamDefs(process.cwd());
|
|
373
|
+
if (teams.length === 0) {
|
|
374
|
+
ctx.addMessage('system', 'No team definitions loaded. Run /agents init to create examples.');
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
const lines = teams.map(t => {
|
|
378
|
+
const src = t._source || 'unknown';
|
|
379
|
+
return ` ${t.name} — ${t.mode}, ${t.members.length} members [${src}]${t.description ? `\n ${t.description}` : ''}`;
|
|
380
|
+
});
|
|
381
|
+
ctx.addMessage('system', `Team Definitions (${teams.length}):\n${lines.join('\n')}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
else if (sub === 'init') {
|
|
385
|
+
const result = scaffoldAgentsDir(process.cwd());
|
|
386
|
+
if (result.created.length > 0) {
|
|
387
|
+
ctx.addMessage('system', `Created .calliope/agents/ with examples:\n${result.created.map(f => ` ${f}`).join('\n')}`);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
ctx.addMessage('system', '.calliope/agents/ already exists.');
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else if (sub === 'show') {
|
|
394
|
+
const name = parts[2];
|
|
395
|
+
if (!name) {
|
|
396
|
+
ctx.addMessage('system', 'Usage: /agents show <name>');
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
const agentDef = getAgent(name, process.cwd());
|
|
400
|
+
const teamDef = getTeam(name, process.cwd());
|
|
401
|
+
if (agentDef) {
|
|
402
|
+
const lines = [
|
|
403
|
+
`Agent: ${agentDef.name}`,
|
|
404
|
+
agentDef.description ? `Description: ${agentDef.description}` : '',
|
|
405
|
+
`Engine: ${agentDef.engine}`,
|
|
406
|
+
`Provider: ${agentDef.provider || 'auto'}`,
|
|
407
|
+
`Model: ${agentDef.model || 'default'}`,
|
|
408
|
+
agentDef.role ? `Role: ${agentDef.role}` : '',
|
|
409
|
+
agentDef.weight !== undefined ? `Weight: ${agentDef.weight}` : '',
|
|
410
|
+
agentDef.instructions ? `Instructions:\n ${agentDef.instructions.split('\n').join('\n ')}` : '',
|
|
411
|
+
`Source: ${agentDef._source || 'unknown'}`,
|
|
412
|
+
agentDef._filePath ? `File: ${agentDef._filePath}` : '',
|
|
413
|
+
].filter(Boolean);
|
|
414
|
+
ctx.addMessage('system', lines.join('\n'));
|
|
415
|
+
}
|
|
416
|
+
else if (teamDef) {
|
|
417
|
+
const lines = [
|
|
418
|
+
`Team: ${teamDef.name}`,
|
|
419
|
+
teamDef.description ? `Description: ${teamDef.description}` : '',
|
|
420
|
+
`Mode: ${teamDef.mode}`,
|
|
421
|
+
`Members (${teamDef.members.length}):`,
|
|
422
|
+
...teamDef.members.map(m => ` ${m.name} — ${m.engine}/${m.provider || 'auto'}/${m.model || 'default'} [${m.role || 'no role'}] w=${m.weight}`),
|
|
423
|
+
].filter(Boolean);
|
|
424
|
+
ctx.addMessage('system', lines.join('\n'));
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
ctx.addMessage('system', `No agent or team found with name '${name}'.`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
ctx.addMessage('system', `Unknown subcommand: ${sub}\nUsage: /agents [defs|teams|init|show <name>]`);
|
|
433
|
+
}
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
case '/install-agents': {
|
|
437
|
+
// /install-agents [name] — install missing agent CLIs and SDK backends
|
|
438
|
+
const target = parts[1];
|
|
439
|
+
if (target) {
|
|
440
|
+
ctx.addMessage('system', `Installing ${target}...`);
|
|
441
|
+
const result = installItem(target);
|
|
442
|
+
ctx.addMessage('system', result.success
|
|
443
|
+
? `✓ ${target} installed successfully.`
|
|
444
|
+
: `✗ ${result.output}`);
|
|
445
|
+
}
|
|
446
|
+
else if (parts.includes('--all')) {
|
|
447
|
+
ctx.addMessage('system', 'Installing all missing agents and SDK backends...');
|
|
448
|
+
const result = await installAllMissing();
|
|
449
|
+
const lines = [];
|
|
450
|
+
if (result.installed.length > 0) {
|
|
451
|
+
lines.push(`✓ Installed: ${result.installed.join(', ')}`);
|
|
452
|
+
}
|
|
453
|
+
if (result.failed.length > 0) {
|
|
454
|
+
lines.push(`✗ Failed:\n ${result.failed.join('\n ')}`);
|
|
455
|
+
}
|
|
456
|
+
if (result.installed.length === 0 && result.failed.length === 0) {
|
|
457
|
+
lines.push('Everything is already installed!');
|
|
458
|
+
}
|
|
459
|
+
ctx.addMessage('system', lines.join('\n'));
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
const report = await getInstallReport();
|
|
463
|
+
ctx.addMessage('system', `${report}\n\nUsage:\n /install-agents <name> Install a specific agent or SDK\n /install-agents --all Install everything missing`);
|
|
464
|
+
}
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
case '/build-agent': {
|
|
468
|
+
// Interactive agent definition builder
|
|
469
|
+
// Parse inline args: /build-agent [name] [--engine X] [--provider X] [--model X]
|
|
470
|
+
const agentName = parts[1] && !parts[1].startsWith('--') ? parts[1] : undefined;
|
|
471
|
+
const engineArg = parts.includes('--engine') ? parts[parts.indexOf('--engine') + 1] : undefined;
|
|
472
|
+
const providerArg = parts.includes('--provider') ? parts[parts.indexOf('--provider') + 1] : undefined;
|
|
473
|
+
const modelArg = parts.includes('--model') ? parts[parts.indexOf('--model') + 1] : undefined;
|
|
474
|
+
const roleArg = parts.includes('--role') ? parts[parts.indexOf('--role') + 1] : undefined;
|
|
475
|
+
if (!agentName) {
|
|
476
|
+
// Interactive wizard — show guided step-by-step builder
|
|
477
|
+
const executors = await getAvailableExecutors();
|
|
478
|
+
const providers = config.getConfiguredProviders();
|
|
479
|
+
// Engine descriptions
|
|
480
|
+
const engineDescriptions = {
|
|
481
|
+
'cli': 'Built-in CLI agent loop (works with all providers, most flexible)',
|
|
482
|
+
'claude-sdk': 'Anthropic Claude SDK (native tool use, streaming, best for Anthropic models)',
|
|
483
|
+
'openai-sdk': 'OpenAI SDK (function calling, JSON mode, best for OpenAI models)',
|
|
484
|
+
'google-adk': 'Google ADK (grounding, search, best for Gemini models)',
|
|
485
|
+
};
|
|
486
|
+
// Provider-to-engine recommendations
|
|
487
|
+
const providerEngineMap = {
|
|
488
|
+
anthropic: 'claude-sdk',
|
|
489
|
+
openai: 'openai-sdk',
|
|
490
|
+
google: 'google-adk',
|
|
491
|
+
together: 'cli',
|
|
492
|
+
openrouter: 'cli',
|
|
493
|
+
groq: 'cli',
|
|
494
|
+
fireworks: 'cli',
|
|
495
|
+
mistral: 'cli',
|
|
496
|
+
ollama: 'cli',
|
|
497
|
+
bedrock: 'cli',
|
|
498
|
+
};
|
|
499
|
+
// Common roles
|
|
500
|
+
const roles = [
|
|
501
|
+
{ name: 'coder', desc: 'Writes and refactors code' },
|
|
502
|
+
{ name: 'reviewer', desc: 'Reviews code for bugs, style, and security' },
|
|
503
|
+
{ name: 'architect', desc: 'Designs system architecture and APIs' },
|
|
504
|
+
{ name: 'researcher', desc: 'Gathers information and summarizes findings' },
|
|
505
|
+
{ name: 'qa-engineer', desc: 'Writes tests and validates correctness' },
|
|
506
|
+
{ name: 'devops', desc: 'Infrastructure, CI/CD, and deployment' },
|
|
507
|
+
{ name: 'writer', desc: 'Documentation, READMEs, and technical writing' },
|
|
508
|
+
{ name: 'analyst', desc: 'Data analysis and insights' },
|
|
509
|
+
];
|
|
510
|
+
const lines = [];
|
|
511
|
+
lines.push('=== Build Agent Wizard ===');
|
|
512
|
+
lines.push('');
|
|
513
|
+
// Step 1: Engine
|
|
514
|
+
lines.push('STEP 1: Choose an engine');
|
|
515
|
+
lines.push('The engine determines how the agent executes (SDK backend or CLI loop).');
|
|
516
|
+
lines.push('');
|
|
517
|
+
for (const eng of ['cli', 'claude-sdk', 'openai-sdk', 'google-adk']) {
|
|
518
|
+
const available = executors.includes(eng);
|
|
519
|
+
const marker = available ? '[installed]' : '[not installed]';
|
|
520
|
+
lines.push(` ${eng} ${marker}`);
|
|
521
|
+
lines.push(` ${engineDescriptions[eng] || ''}`);
|
|
522
|
+
}
|
|
523
|
+
lines.push('');
|
|
524
|
+
if (executors.length === 1) {
|
|
525
|
+
lines.push(' Recommendation: Only "cli" is available. Install SDK packages for more options.');
|
|
526
|
+
lines.push(' Run /install-agents to see what can be installed.');
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
lines.push(` Available on this system: ${executors.join(', ')}`);
|
|
530
|
+
}
|
|
531
|
+
lines.push('');
|
|
532
|
+
// Step 2: Provider
|
|
533
|
+
lines.push('STEP 2: Choose a provider');
|
|
534
|
+
lines.push('The provider determines which API your agent calls.');
|
|
535
|
+
lines.push('');
|
|
536
|
+
if (providers.length === 0) {
|
|
537
|
+
lines.push(' No providers configured! Run calliope --setup to add API keys.');
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
for (const p of providers) {
|
|
541
|
+
const recEngine = providerEngineMap[p] || 'cli';
|
|
542
|
+
const model = DEFAULT_MODELS[p] || 'auto';
|
|
543
|
+
lines.push(` ${p} (best with --engine ${recEngine}, default model: ${model})`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
lines.push('');
|
|
547
|
+
// Step 3: Model
|
|
548
|
+
lines.push('STEP 3: Choose a model');
|
|
549
|
+
lines.push('Each provider has a default model. Override with --model if needed.');
|
|
550
|
+
lines.push('');
|
|
551
|
+
if (providers.length > 0) {
|
|
552
|
+
for (const p of providers) {
|
|
553
|
+
lines.push(` ${p}: ${DEFAULT_MODELS[p] || 'auto'}`);
|
|
554
|
+
}
|
|
555
|
+
lines.push('');
|
|
556
|
+
lines.push(' Tip: Run /models to see all available models for your providers.');
|
|
557
|
+
}
|
|
558
|
+
lines.push('');
|
|
559
|
+
// Step 4: Role
|
|
560
|
+
lines.push('STEP 4: Set a role');
|
|
561
|
+
lines.push('The role labels what this agent specializes in. Common roles:');
|
|
562
|
+
lines.push('');
|
|
563
|
+
for (const r of roles) {
|
|
564
|
+
lines.push(` ${r.name.padEnd(14)} ${r.desc}`);
|
|
565
|
+
}
|
|
566
|
+
lines.push('');
|
|
567
|
+
lines.push(' You can also use any custom role name.');
|
|
568
|
+
lines.push('');
|
|
569
|
+
// Step 5: Instructions
|
|
570
|
+
lines.push('STEP 5: Write instructions (after creation)');
|
|
571
|
+
lines.push('After creating the agent, edit its YAML file to add detailed instructions.');
|
|
572
|
+
lines.push('');
|
|
573
|
+
lines.push(' Template:');
|
|
574
|
+
lines.push(' instructions: |');
|
|
575
|
+
lines.push(' You are a specialized {role} agent.');
|
|
576
|
+
lines.push(' Your focus areas: ...');
|
|
577
|
+
lines.push(' When reviewing code: ...');
|
|
578
|
+
lines.push(' Output format: ...');
|
|
579
|
+
lines.push('');
|
|
580
|
+
// Final command
|
|
581
|
+
lines.push('--- Ready to build? ---');
|
|
582
|
+
lines.push('');
|
|
583
|
+
if (providers.includes('anthropic') && executors.includes('claude-sdk')) {
|
|
584
|
+
lines.push(' /build-agent my-agent --engine claude-sdk --provider anthropic --role coder');
|
|
585
|
+
}
|
|
586
|
+
else if (providers.includes('openai') && executors.includes('openai-sdk')) {
|
|
587
|
+
lines.push(' /build-agent my-agent --engine openai-sdk --provider openai --role coder');
|
|
588
|
+
}
|
|
589
|
+
else if (providers.length > 0) {
|
|
590
|
+
const firstProvider = providers[0];
|
|
591
|
+
lines.push(` /build-agent my-agent --engine cli --provider ${firstProvider} --role coder`);
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
lines.push(' /build-agent my-agent --engine cli --provider anthropic --role coder');
|
|
595
|
+
}
|
|
596
|
+
lines.push('');
|
|
597
|
+
lines.push('Add --model <model> to override the default model for the chosen provider.');
|
|
598
|
+
ctx.addMessage('system', lines.join('\n'));
|
|
599
|
+
}
|
|
600
|
+
else if (!engineArg && !providerArg && !modelArg && !roleArg) {
|
|
601
|
+
// Name provided but no flags — show a concise personalized wizard
|
|
602
|
+
const executors = await getAvailableExecutors();
|
|
603
|
+
const providers = config.getConfiguredProviders();
|
|
604
|
+
const providerEngineMap = {
|
|
605
|
+
anthropic: 'claude-sdk', openai: 'openai-sdk', google: 'google-adk',
|
|
606
|
+
together: 'cli', openrouter: 'cli', groq: 'cli', fireworks: 'cli',
|
|
607
|
+
mistral: 'cli', ollama: 'cli', bedrock: 'cli',
|
|
608
|
+
};
|
|
609
|
+
const lines = [];
|
|
610
|
+
lines.push(`=== Building agent: ${agentName} ===`);
|
|
611
|
+
lines.push('');
|
|
612
|
+
lines.push('Choose your configuration:');
|
|
613
|
+
lines.push('');
|
|
614
|
+
// Show ready-to-run commands for each configured provider
|
|
615
|
+
let optionNum = 0;
|
|
616
|
+
for (const p of providers) {
|
|
617
|
+
const recEngine = providerEngineMap[p] || 'cli';
|
|
618
|
+
const engineAvailable = executors.includes(recEngine);
|
|
619
|
+
const engine = engineAvailable ? recEngine : 'cli';
|
|
620
|
+
const model = DEFAULT_MODELS[p] || 'auto';
|
|
621
|
+
optionNum++;
|
|
622
|
+
lines.push(` Option ${optionNum}: ${p}`);
|
|
623
|
+
lines.push(` /build-agent ${agentName} --engine ${engine} --provider ${p} --model ${model} --role coder`);
|
|
624
|
+
lines.push('');
|
|
625
|
+
}
|
|
626
|
+
if (optionNum === 0) {
|
|
627
|
+
lines.push(' No providers configured. Run calliope --setup to add API keys.');
|
|
628
|
+
lines.push(` Or build with defaults: /build-agent ${agentName} --engine cli --provider anthropic --role coder`);
|
|
629
|
+
}
|
|
630
|
+
lines.push('Common roles: coder, reviewer, architect, researcher, qa-engineer, devops, writer');
|
|
631
|
+
lines.push('');
|
|
632
|
+
lines.push('Pick an option above and change --role to match your needs.');
|
|
633
|
+
lines.push(`After creation, customize instructions in .calliope/agents/${agentName}.yaml`);
|
|
634
|
+
ctx.addMessage('system', lines.join('\n'));
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
// Build and save agent definition (flags provided)
|
|
638
|
+
const newDef = {
|
|
639
|
+
name: agentName,
|
|
640
|
+
engine: engineArg || 'cli',
|
|
641
|
+
provider: providerArg,
|
|
642
|
+
model: modelArg,
|
|
643
|
+
role: roleArg,
|
|
644
|
+
weight: 1.0,
|
|
645
|
+
instructions: `You are a specialized agent (${roleArg || agentName}). Complete assigned tasks thoroughly.`,
|
|
646
|
+
limits: { timeout: 600000 },
|
|
647
|
+
};
|
|
648
|
+
const filePath = saveAgentDef(process.cwd(), newDef);
|
|
649
|
+
ctx.addMessage('system', `Agent '${agentName}' created: ${filePath}\n\nEdit the file to customize instructions and settings.\nUse with: spawn_agent agentDef="${agentName}"`);
|
|
650
|
+
}
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
case '/build-team': {
|
|
654
|
+
// Interactive team builder
|
|
655
|
+
const teamNameArg = parts[1] && !parts[1].startsWith('--') ? parts[1] : undefined;
|
|
656
|
+
const modeArg = parts.includes('--mode') ? parts[parts.indexOf('--mode') + 1] : undefined;
|
|
657
|
+
if (!teamNameArg) {
|
|
658
|
+
// Interactive wizard — guided team builder
|
|
659
|
+
const teams = listTeamDefs(process.cwd());
|
|
660
|
+
const agents = listAgentDefs(process.cwd());
|
|
661
|
+
const lines = [];
|
|
662
|
+
lines.push('=== Build Team Wizard ===');
|
|
663
|
+
lines.push('');
|
|
664
|
+
// Step 1: Available agents
|
|
665
|
+
lines.push('STEP 1: Review your available agents');
|
|
666
|
+
lines.push('Teams are composed of agent definitions. Here is what you have:');
|
|
667
|
+
lines.push('');
|
|
668
|
+
if (agents.length === 0) {
|
|
669
|
+
lines.push(' No agents defined yet!');
|
|
670
|
+
lines.push(' Create agents first: /build-agent (run without args for the wizard)');
|
|
671
|
+
lines.push('');
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
for (const a of agents) {
|
|
675
|
+
const src = a._source === 'builtin' ? ' (built-in)' : '';
|
|
676
|
+
lines.push(` ${a.name.padEnd(20)} engine: ${a.engine.padEnd(12)} provider: ${(a.provider || 'auto').padEnd(12)} role: ${a.role || 'any'}${src}`);
|
|
677
|
+
}
|
|
678
|
+
lines.push('');
|
|
679
|
+
lines.push(` Total: ${agents.length} agents available`);
|
|
680
|
+
if (agents.length < 2) {
|
|
681
|
+
lines.push(' You need at least 2 agents for a team. Create more with /build-agent.');
|
|
682
|
+
}
|
|
683
|
+
lines.push('');
|
|
684
|
+
}
|
|
685
|
+
// Step 2: Choose mode
|
|
686
|
+
lines.push('STEP 2: Choose a coordination mode');
|
|
687
|
+
lines.push('The mode determines how agents work together:');
|
|
688
|
+
lines.push('');
|
|
689
|
+
lines.push(' competitive');
|
|
690
|
+
lines.push(' All agents tackle the same task independently.');
|
|
691
|
+
lines.push(' The best response wins (scored by quality/relevance).');
|
|
692
|
+
lines.push(' Best for: code review, creative alternatives, getting multiple perspectives.');
|
|
693
|
+
lines.push('');
|
|
694
|
+
lines.push(' collaborative');
|
|
695
|
+
lines.push(' Agents work in a pipeline, each building on the previous output.');
|
|
696
|
+
lines.push(' First agent plans, next implements, last reviews.');
|
|
697
|
+
lines.push(' Best for: refactoring, multi-step tasks, plan-implement-review workflows.');
|
|
698
|
+
lines.push('');
|
|
699
|
+
lines.push(' consensus');
|
|
700
|
+
lines.push(' All agents must agree before a result is accepted.');
|
|
701
|
+
lines.push(' Disagreements trigger additional rounds of discussion.');
|
|
702
|
+
lines.push(' Best for: security audits, critical decisions, high-stakes analysis.');
|
|
703
|
+
lines.push('');
|
|
704
|
+
lines.push(' overseer');
|
|
705
|
+
lines.push(' A lead agent decomposes the task and delegates to workers.');
|
|
706
|
+
lines.push(' The overseer reviews and merges all results.');
|
|
707
|
+
lines.push(' Best for: complex projects, research, large feature implementation.');
|
|
708
|
+
lines.push('');
|
|
709
|
+
// Step 3: Strategy recommendations
|
|
710
|
+
lines.push('STEP 3: Pick a strategy based on your task');
|
|
711
|
+
lines.push('');
|
|
712
|
+
lines.push(' Task Type Mode Recommended Agents');
|
|
713
|
+
lines.push(' ─────────────────────────────────────────────────────────');
|
|
714
|
+
lines.push(' Code review competitive code-reviewer, qa-engineer');
|
|
715
|
+
lines.push(' Refactoring collaborative architect, coder, reviewer');
|
|
716
|
+
lines.push(' Security audit consensus security-analyst, code-reviewer, qa-engineer');
|
|
717
|
+
lines.push(' Research overseer researcher, analyst, writer');
|
|
718
|
+
lines.push(' Feature development overseer architect, coder, qa-engineer, reviewer');
|
|
719
|
+
lines.push(' Documentation collaborative researcher, writer');
|
|
720
|
+
lines.push('');
|
|
721
|
+
// Existing teams
|
|
722
|
+
if (teams.length > 0) {
|
|
723
|
+
lines.push('Existing teams:');
|
|
724
|
+
for (const t of teams) {
|
|
725
|
+
lines.push(` ${t.name.padEnd(20)} mode: ${t.mode.padEnd(14)} members: ${t.members.length}`);
|
|
726
|
+
}
|
|
727
|
+
lines.push('');
|
|
728
|
+
}
|
|
729
|
+
// Final command
|
|
730
|
+
lines.push('--- Ready to build? ---');
|
|
731
|
+
lines.push('');
|
|
732
|
+
if (agents.length >= 2) {
|
|
733
|
+
const sampleAgents = agents.slice(0, 3).map(a => a.name).join(', ');
|
|
734
|
+
lines.push(` /build-team my-team --mode collaborative`);
|
|
735
|
+
lines.push('');
|
|
736
|
+
lines.push(` This will auto-compose a team from your available agents (${sampleAgents}${agents.length > 3 ? ', ...' : ''}).`);
|
|
737
|
+
lines.push(' After creation, edit the YAML to fine-tune members and settings.');
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
lines.push(' First, create at least 2 agents:');
|
|
741
|
+
lines.push(' /build-agent planner --engine cli --provider anthropic --role architect');
|
|
742
|
+
lines.push(' /build-agent coder --engine cli --provider anthropic --role coder');
|
|
743
|
+
lines.push('');
|
|
744
|
+
lines.push(' Then build a team:');
|
|
745
|
+
lines.push(' /build-team my-team --mode collaborative');
|
|
746
|
+
}
|
|
747
|
+
lines.push('');
|
|
748
|
+
lines.push('After creation, use the team with:');
|
|
749
|
+
lines.push(' /swarm --team <name> <prompt>');
|
|
750
|
+
lines.push(' spawn_agent team="<name>" prompt="..."');
|
|
751
|
+
ctx.addMessage('system', lines.join('\n'));
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
// Create a team with available agents
|
|
755
|
+
const availableAgents = listAgentDefs(process.cwd());
|
|
756
|
+
const mode = (modeArg || 'collaborative');
|
|
757
|
+
// Auto-compose a reasonable team from available agent definitions
|
|
758
|
+
const memberAgents = availableAgents
|
|
759
|
+
.filter(a => a._source !== 'builtin' || ['code-reviewer', 'architect', 'qa-engineer', 'researcher'].includes(a.name))
|
|
760
|
+
.slice(0, 4);
|
|
761
|
+
if (memberAgents.length < 2) {
|
|
762
|
+
ctx.addMessage('system', `Need at least 2 agent definitions to build a team.\nCreate agents first with /build-agent <name>, then compose them with /build-team.`);
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
const { saveTeamDef } = await import('../agents/index.js');
|
|
766
|
+
const teamDef = {
|
|
767
|
+
name: teamNameArg,
|
|
768
|
+
description: `${teamNameArg} team`,
|
|
769
|
+
mode,
|
|
770
|
+
members: memberAgents.map(a => ({ agent: a.name, role: a.role, weight: a.weight })),
|
|
771
|
+
swarm: { strategy: 'parallel', aggregation: 'structured', maxWorkers: 3 },
|
|
772
|
+
council: { maxRounds: 2, consensusThreshold: 0.67 },
|
|
773
|
+
};
|
|
774
|
+
const filePath = saveTeamDef(process.cwd(), teamDef);
|
|
775
|
+
ctx.addMessage('system', `Team '${teamNameArg}' created: ${filePath}
|
|
776
|
+
|
|
777
|
+
Members:
|
|
778
|
+
${memberAgents.map(a => ` ${a.name} [${a.role || 'any'}]`).join('\n')}
|
|
779
|
+
|
|
780
|
+
Mode: ${mode}
|
|
781
|
+
|
|
782
|
+
Use with:
|
|
783
|
+
/swarm --team ${teamNameArg} <prompt>
|
|
784
|
+
/swarm coord --team ${teamNameArg} <prompt>
|
|
785
|
+
spawn_agent team="${teamNameArg}" prompt="..."
|
|
786
|
+
|
|
787
|
+
Edit the YAML to customize members, strategy, and coordination settings.`);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
case '/set': {
|
|
793
|
+
// /set <key> <value>
|
|
794
|
+
const key = parts[1];
|
|
795
|
+
const value = parts.slice(2).join(' ');
|
|
796
|
+
if (!key || !value) {
|
|
797
|
+
ctx.addMessage('system', `Usage: /set <key> <value>
|
|
798
|
+
Available keys:
|
|
799
|
+
maxIterations <number> - Max agent iterations (current: ${config.get('maxIterations')})
|
|
800
|
+
persona <name> - calliope, muse, minimal
|
|
801
|
+
fancyOutput <bool> - true/false`);
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
try {
|
|
805
|
+
if (key === 'maxIterations') {
|
|
806
|
+
const num = parseInt(value, 10);
|
|
807
|
+
if (isNaN(num) || num < 1 || num > 10000) {
|
|
808
|
+
ctx.addMessage('error', 'maxIterations must be 1-10000');
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
811
|
+
config.set('maxIterations', num);
|
|
812
|
+
ctx.addMessage('system', `\u2713 maxIterations set to ${num}`);
|
|
813
|
+
}
|
|
814
|
+
else if (key === 'persona') {
|
|
815
|
+
if (!['calliope', 'muse', 'minimal'].includes(value)) {
|
|
816
|
+
ctx.addMessage('error', 'persona must be: calliope, muse, or minimal');
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
config.set('persona', value);
|
|
820
|
+
ctx.setPersona(value);
|
|
821
|
+
ctx.addMessage('system', `\u2713 persona set to ${value}`);
|
|
822
|
+
}
|
|
823
|
+
else if (key === 'fancyOutput') {
|
|
824
|
+
const bool = value === 'true';
|
|
825
|
+
config.set('fancyOutput', bool);
|
|
826
|
+
ctx.addMessage('system', `\u2713 fancyOutput set to ${bool}`);
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
ctx.addMessage('error', `Unknown config key: ${key}`);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
catch (err) {
|
|
833
|
+
ctx.addMessage('error', `Failed to set ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
834
|
+
}
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
837
|
+
case '/setup':
|
|
838
|
+
ctx.addMessage('system', 'Run `calliope --setup` to reconfigure.');
|
|
839
|
+
break;
|
|
840
|
+
case '/layout': {
|
|
841
|
+
// /layout [classic|response-top|response-bottom|split|zen|focus|dashboard|minimal]
|
|
842
|
+
const layoutArg = parts[1];
|
|
843
|
+
if (!layoutArg) {
|
|
844
|
+
ctx.addMessage('system', `Current layout: ${ctx.layout}
|
|
845
|
+
|
|
846
|
+
Available layouts:
|
|
847
|
+
classic - Everything in chronological order
|
|
848
|
+
response-top - Calliope response at top, tools below
|
|
849
|
+
response-bottom - Tools at top, response at bottom (default)
|
|
850
|
+
split - Side by side: tools left, response right
|
|
851
|
+
zen - Response only, tools hidden — distraction-free
|
|
852
|
+
focus - Latest response pinned top, compact tool log
|
|
853
|
+
dashboard - Three-panel: stats, response, tools
|
|
854
|
+
minimal - No decorations, raw text output
|
|
855
|
+
|
|
856
|
+
Usage: /layout <name>`);
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
const validLayouts = ['classic', 'response-top', 'response-bottom', 'split', 'zen', 'focus', 'dashboard', 'minimal'];
|
|
860
|
+
if (!validLayouts.includes(layoutArg)) {
|
|
861
|
+
ctx.addMessage('error', `Invalid layout. Choose: ${validLayouts.join(', ')}`);
|
|
862
|
+
break;
|
|
863
|
+
}
|
|
864
|
+
config.set('layout', layoutArg);
|
|
865
|
+
ctx.setLayout(layoutArg);
|
|
866
|
+
ctx.addMessage('system', `\u2713 Layout set to: ${layoutArg}`);
|
|
867
|
+
break;
|
|
868
|
+
}
|
|
869
|
+
case '/density': {
|
|
870
|
+
// /density [normal|compact]
|
|
871
|
+
const densityArg = parts[1];
|
|
872
|
+
if (!densityArg) {
|
|
873
|
+
ctx.addMessage('system', `Current density: ${ctx.density}
|
|
874
|
+
|
|
875
|
+
Available densities:
|
|
876
|
+
normal - Standard spacing
|
|
877
|
+
compact - Reduced whitespace for more info
|
|
878
|
+
|
|
879
|
+
Usage: /density <normal|compact>`);
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
const validDensities = ['normal', 'compact'];
|
|
883
|
+
if (!validDensities.includes(densityArg)) {
|
|
884
|
+
ctx.addMessage('error', `Invalid density. Choose: normal, compact`);
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
config.set('density', densityArg);
|
|
888
|
+
ctx.setDensity(densityArg);
|
|
889
|
+
ctx.addMessage('system', `\u2713 Density set to: ${densityArg}`);
|
|
890
|
+
break;
|
|
891
|
+
}
|
|
892
|
+
case '/collapse': {
|
|
893
|
+
// /collapse [tools|thinking|all|off] [limit N]
|
|
894
|
+
const subCmd = parts[1];
|
|
895
|
+
if (!subCmd) {
|
|
896
|
+
ctx.addMessage('system', `Collapse settings:
|
|
897
|
+
collapseTools: ${ctx.collapseSettings.collapseTools}
|
|
898
|
+
collapseThinking: ${ctx.collapseSettings.collapseThinking}
|
|
899
|
+
toolDisplayLimit: ${ctx.collapseSettings.toolDisplayLimit} (0 = all expanded)
|
|
900
|
+
|
|
901
|
+
Usage:
|
|
902
|
+
/collapse tools - Toggle tool output collapsing
|
|
903
|
+
/collapse thinking - Toggle thinking block collapsing
|
|
904
|
+
/collapse all - Collapse both tools and thinking
|
|
905
|
+
/collapse off - Expand everything
|
|
906
|
+
/collapse limit <N> - Show last N tools expanded (0 = all)`);
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
if (subCmd === 'tools') {
|
|
910
|
+
const newVal = !ctx.collapseSettings.collapseTools;
|
|
911
|
+
config.set('collapseTools', newVal);
|
|
912
|
+
ctx.setCollapseSettings(prev => ({ ...prev, collapseTools: newVal }));
|
|
913
|
+
ctx.addMessage('system', `\u2713 collapseTools set to ${newVal}`);
|
|
914
|
+
}
|
|
915
|
+
else if (subCmd === 'thinking') {
|
|
916
|
+
const newVal = !ctx.collapseSettings.collapseThinking;
|
|
917
|
+
config.set('collapseThinking', newVal);
|
|
918
|
+
ctx.setCollapseSettings(prev => ({ ...prev, collapseThinking: newVal }));
|
|
919
|
+
ctx.addMessage('system', `\u2713 collapseThinking set to ${newVal}`);
|
|
920
|
+
}
|
|
921
|
+
else if (subCmd === 'all') {
|
|
922
|
+
config.set('collapseTools', true);
|
|
923
|
+
config.set('collapseThinking', true);
|
|
924
|
+
ctx.setCollapseSettings(prev => ({ ...prev, collapseTools: true, collapseThinking: true }));
|
|
925
|
+
ctx.addMessage('system', '\u2713 Collapsing tools and thinking');
|
|
926
|
+
}
|
|
927
|
+
else if (subCmd === 'off') {
|
|
928
|
+
config.set('collapseTools', false);
|
|
929
|
+
config.set('collapseThinking', false);
|
|
930
|
+
ctx.setCollapseSettings(prev => ({ ...prev, collapseTools: false, collapseThinking: false }));
|
|
931
|
+
ctx.addMessage('system', '\u2713 Expanding all output');
|
|
932
|
+
}
|
|
933
|
+
else if (subCmd === 'limit') {
|
|
934
|
+
const limit = parseInt(parts[2], 10);
|
|
935
|
+
if (isNaN(limit) || limit < 0 || limit > 100) {
|
|
936
|
+
ctx.addMessage('error', 'Limit must be 0-100');
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
config.set('toolDisplayLimit', limit);
|
|
940
|
+
ctx.setCollapseSettings(prev => ({ ...prev, toolDisplayLimit: limit }));
|
|
941
|
+
ctx.addMessage('system', `\u2713 toolDisplayLimit set to ${limit}`);
|
|
942
|
+
}
|
|
943
|
+
else {
|
|
944
|
+
ctx.addMessage('error', 'Unknown collapse option. Use: tools, thinking, all, off, or limit <N>');
|
|
945
|
+
}
|
|
946
|
+
break;
|
|
947
|
+
}
|
|
948
|
+
case '/loop': {
|
|
949
|
+
// Parse /loop "<prompt>" [--max-iterations N] [--completion-promise "text"]
|
|
950
|
+
const loopArgs = parts.slice(1).join(' ');
|
|
951
|
+
const maxIterMatch = loopArgs.match(/--max-iterations\s+(\d+)/);
|
|
952
|
+
const completionMatch = loopArgs.match(/--completion-promise\s+"([^"]+)"/);
|
|
953
|
+
let prompt = loopArgs
|
|
954
|
+
.replace(/--max-iterations\s+\d+/, '')
|
|
955
|
+
.replace(/--completion-promise\s+"[^"]+"/, '')
|
|
956
|
+
.trim();
|
|
957
|
+
// Handle quoted prompt
|
|
958
|
+
const quotedMatch = prompt.match(/^"([^"]+)"$/);
|
|
959
|
+
if (quotedMatch)
|
|
960
|
+
prompt = quotedMatch[1];
|
|
961
|
+
if (!prompt) {
|
|
962
|
+
ctx.addMessage('system', `Usage: /loop "<prompt>" [--max-iterations N] [--completion-promise "text"]
|
|
963
|
+
Example: /loop "Build a REST API" --max-iterations 50 --completion-promise "DONE"`);
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
// Start the loop
|
|
967
|
+
ctx.setLoopActive(true);
|
|
968
|
+
ctx.setLoopPrompt(prompt);
|
|
969
|
+
ctx.setLoopMaxIterations(maxIterMatch ? parseInt(maxIterMatch[1], 10) : 100);
|
|
970
|
+
ctx.setLoopCompletionPromise(completionMatch ? completionMatch[1] : undefined);
|
|
971
|
+
ctx.setLoopIteration(0);
|
|
972
|
+
ctx.loopCancelledRef.current = false;
|
|
973
|
+
ctx.addMessage('system', `\u{1F504} Agent Loop Started
|
|
974
|
+
Prompt: "${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}"
|
|
975
|
+
Max iterations: ${maxIterMatch ? maxIterMatch[1] : '100'}
|
|
976
|
+
${completionMatch ? `Completion promise: "${completionMatch[1]}"` : 'No completion promise (runs until max iterations)'}
|
|
977
|
+
Use /cancel-loop to stop`);
|
|
978
|
+
// Start the loop execution (non-blocking)
|
|
979
|
+
ctx.runLoop(prompt, maxIterMatch ? parseInt(maxIterMatch[1], 10) : 100, completionMatch?.[1]);
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
case '/cancel-loop':
|
|
983
|
+
case '/stop':
|
|
984
|
+
if (ctx.loopActive) {
|
|
985
|
+
ctx.loopCancelledRef.current = true;
|
|
986
|
+
ctx.setLoopActive(false);
|
|
987
|
+
ctx.addMessage('system', '\u{1F6D1} Loop cancelled');
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
ctx.addMessage('system', 'No active loop to cancel');
|
|
991
|
+
}
|
|
992
|
+
break;
|
|
993
|
+
case '/confirm':
|
|
994
|
+
if (parts[1] === 'on') {
|
|
995
|
+
ctx.setConfirmMode(true);
|
|
996
|
+
ctx.addMessage('system', '\u2713 Confirmation mode ON - will ask before risky operations');
|
|
997
|
+
}
|
|
998
|
+
else if (parts[1] === 'off') {
|
|
999
|
+
ctx.setConfirmMode(false);
|
|
1000
|
+
ctx.addMessage('system', '\u26A0\uFE0F Confirmation mode OFF - risky operations will auto-execute');
|
|
1001
|
+
}
|
|
1002
|
+
else {
|
|
1003
|
+
ctx.addMessage('system', `Confirm mode: ${ctx.confirmMode ? 'ON' : 'OFF'}\nUsage: /confirm [on|off]`);
|
|
1004
|
+
}
|
|
1005
|
+
break;
|
|
1006
|
+
case '/profile': {
|
|
1007
|
+
const subCmd = parts[1];
|
|
1008
|
+
if (subCmd === 'list' || !subCmd) {
|
|
1009
|
+
const profiles = config.listProfiles();
|
|
1010
|
+
const active = config.getActiveProfile();
|
|
1011
|
+
const list = profiles.map(p => {
|
|
1012
|
+
const marker = p.name === active ? '\u2192 ' : ' ';
|
|
1013
|
+
const tag = p.builtin ? '(built-in)' : '(custom)';
|
|
1014
|
+
return `${marker}${p.name}: ${p.profile.provider}/${p.profile.model || 'default'} ${tag}`;
|
|
1015
|
+
}).join('\n');
|
|
1016
|
+
ctx.addMessage('system', `Profiles:\n${list}\n\nUsage: /profile <name> | /profile save <name>`);
|
|
1017
|
+
}
|
|
1018
|
+
else if (subCmd === 'save' && parts[2]) {
|
|
1019
|
+
const name = parts[2];
|
|
1020
|
+
config.saveProfile(name, {
|
|
1021
|
+
provider: ctx.provider,
|
|
1022
|
+
model: ctx.model,
|
|
1023
|
+
persona: ctx.persona,
|
|
1024
|
+
confirmMode: ctx.confirmMode,
|
|
1025
|
+
});
|
|
1026
|
+
ctx.addMessage('system', `\u2713 Saved profile: ${name}`);
|
|
1027
|
+
}
|
|
1028
|
+
else if (subCmd === 'delete' && parts[2]) {
|
|
1029
|
+
const name = parts[2];
|
|
1030
|
+
if (config.deleteProfile(name)) {
|
|
1031
|
+
ctx.addMessage('system', `\u2713 Deleted profile: ${name}`);
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
ctx.addMessage('error', `Cannot delete profile: ${name} (built-in or not found)`);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
// Load profile
|
|
1039
|
+
const profile = config.getProfile(subCmd);
|
|
1040
|
+
if (profile) {
|
|
1041
|
+
ctx.setProvider(profile.provider);
|
|
1042
|
+
if (profile.model)
|
|
1043
|
+
ctx.setModel(profile.model);
|
|
1044
|
+
ctx.setPersona(profile.persona);
|
|
1045
|
+
if (profile.confirmMode !== undefined)
|
|
1046
|
+
ctx.setConfirmMode(profile.confirmMode);
|
|
1047
|
+
config.setActiveProfile(subCmd);
|
|
1048
|
+
ctx.addMessage('system', `\u2713 Loaded profile: ${subCmd} (${profile.provider}/${profile.model || 'default'})`);
|
|
1049
|
+
}
|
|
1050
|
+
else {
|
|
1051
|
+
ctx.addMessage('error', `Profile not found: ${subCmd}\nBuilt-in: fast, smart, cheap, local`);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
break;
|
|
1055
|
+
}
|
|
1056
|
+
case '/mcp': {
|
|
1057
|
+
const subCmd = parts[1];
|
|
1058
|
+
if (subCmd === 'list' || !subCmd) {
|
|
1059
|
+
const servers = mcp.listServers();
|
|
1060
|
+
if (servers.length === 0) {
|
|
1061
|
+
ctx.addMessage('system', 'No MCP servers registered.\n\nUsage:\n /mcp add <url> - Register MCP server\n /mcp remove <id> - Remove server');
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
const list = servers.map(s => {
|
|
1065
|
+
const status = s.status === 'connected' ? '\u{1F7E2}' : s.status === 'error' ? '\u{1F534}' : '\u26AA';
|
|
1066
|
+
return `${status} ${s.name} (${s.tools.length} tools)\n ${s.url}`;
|
|
1067
|
+
}).join('\n\n');
|
|
1068
|
+
ctx.addMessage('system', `MCP Servers:\n\n${list}`);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
else if (subCmd === 'add' && parts[2]) {
|
|
1072
|
+
const url = parts[2];
|
|
1073
|
+
ctx.addMessage('system', `Registering MCP server: ${url}...`);
|
|
1074
|
+
try {
|
|
1075
|
+
const server = await mcp.registerServer(url);
|
|
1076
|
+
ctx.addMessage('system', `\u2713 Registered: ${server.name} (${server.tools.length} tools)`);
|
|
1077
|
+
}
|
|
1078
|
+
catch (e) {
|
|
1079
|
+
ctx.addMessage('error', `Failed to register: ${e instanceof Error ? e.message : String(e)}`);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
else if ((subCmd === 'remove' || subCmd === 'rm') && parts[2]) {
|
|
1083
|
+
if (mcp.unregisterServer(parts[2])) {
|
|
1084
|
+
ctx.addMessage('system', '\u2713 Server removed');
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
ctx.addMessage('error', 'Server not found');
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
else if (subCmd === 'refresh') {
|
|
1091
|
+
const servers = mcp.listServers();
|
|
1092
|
+
let connected = 0;
|
|
1093
|
+
for (const s of servers) {
|
|
1094
|
+
const updated = await mcp.refreshServer(s.id);
|
|
1095
|
+
if (updated?.status === 'connected')
|
|
1096
|
+
connected++;
|
|
1097
|
+
}
|
|
1098
|
+
ctx.addMessage('system', `Refreshed ${servers.length} servers (${connected} connected)`);
|
|
1099
|
+
}
|
|
1100
|
+
else if (subCmd === 'tools') {
|
|
1101
|
+
const tools = mcp.getMCPTools();
|
|
1102
|
+
if (tools.length === 0) {
|
|
1103
|
+
ctx.addMessage('system', 'No MCP tools available. Add servers with /mcp add <url>');
|
|
1104
|
+
}
|
|
1105
|
+
else {
|
|
1106
|
+
const list = tools.map(t => `\u2022 ${t.name}\n ${t.description}`).join('\n\n');
|
|
1107
|
+
ctx.addMessage('system', `MCP Tools:\n\n${list}`);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
else {
|
|
1111
|
+
ctx.addMessage('system', 'Usage: /mcp [list|add <url>|remove <id>|refresh|tools]');
|
|
1112
|
+
}
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
case '/skills': {
|
|
1116
|
+
const subCmd = parts[1];
|
|
1117
|
+
if (subCmd === 'list' || !subCmd) {
|
|
1118
|
+
const allSkills = skills.getSkills();
|
|
1119
|
+
if (allSkills.length === 0) {
|
|
1120
|
+
ctx.addMessage('system', 'No skills installed.\n\nUsage:\n /skills add <name> - Install from agentskills.io\n /skills add <github-url> - Install from GitHub\n /skills add <path> - Install from local directory');
|
|
1121
|
+
}
|
|
1122
|
+
else {
|
|
1123
|
+
const list = allSkills.map(s => {
|
|
1124
|
+
const src = s.source === 'github' ? '(GitHub)' : s.source === 'registry' ? '(agentskills.io)' : '(local)';
|
|
1125
|
+
return `\u2022 ${s.metadata.name} ${src}\n ${s.metadata.description.substring(0, 80)}...`;
|
|
1126
|
+
}).join('\n\n');
|
|
1127
|
+
ctx.addMessage('system', `Installed Skills:\n\n${list}`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
else if (subCmd === 'add' && parts[2]) {
|
|
1131
|
+
const source = parts[2];
|
|
1132
|
+
ctx.addMessage('system', `Installing skill: ${source}...`);
|
|
1133
|
+
try {
|
|
1134
|
+
let skill;
|
|
1135
|
+
if (source.startsWith('http')) {
|
|
1136
|
+
skill = await skills.installFromGithub(source);
|
|
1137
|
+
}
|
|
1138
|
+
else if (fs.existsSync(source)) {
|
|
1139
|
+
skill = skills.installLocalSkill(source);
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
skill = await skills.installFromRegistry(source);
|
|
1143
|
+
}
|
|
1144
|
+
if (skill) {
|
|
1145
|
+
ctx.addMessage('system', `\u2713 Installed: ${skill.metadata.name}`);
|
|
1146
|
+
}
|
|
1147
|
+
else {
|
|
1148
|
+
ctx.addMessage('error', 'Failed to install skill');
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
catch (e) {
|
|
1152
|
+
ctx.addMessage('error', `Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
else if ((subCmd === 'remove' || subCmd === 'rm') && parts[2]) {
|
|
1156
|
+
if (skills.uninstallSkill(parts[2])) {
|
|
1157
|
+
ctx.addMessage('system', '\u2713 Skill removed');
|
|
1158
|
+
}
|
|
1159
|
+
else {
|
|
1160
|
+
ctx.addMessage('error', 'Skill not found');
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
else if (subCmd === 'info' && parts[2]) {
|
|
1164
|
+
const skill = skills.getSkill(parts[2]);
|
|
1165
|
+
if (skill) {
|
|
1166
|
+
let info = `# ${skill.metadata.name}\n\n`;
|
|
1167
|
+
info += `${skill.metadata.description}\n\n`;
|
|
1168
|
+
if (skill.metadata.compatibility)
|
|
1169
|
+
info += `Compatibility: ${skill.metadata.compatibility}\n`;
|
|
1170
|
+
if (skill.metadata.license)
|
|
1171
|
+
info += `License: ${skill.metadata.license}\n`;
|
|
1172
|
+
if (skill.sourceUrl)
|
|
1173
|
+
info += `Source: ${skill.sourceUrl}\n`;
|
|
1174
|
+
ctx.addMessage('system', info);
|
|
1175
|
+
}
|
|
1176
|
+
else {
|
|
1177
|
+
ctx.addMessage('error', 'Skill not found');
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
else {
|
|
1181
|
+
ctx.addMessage('system', 'Usage: /skills [list|add <source>|remove <name>|info <name>]');
|
|
1182
|
+
}
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
case '/memory': {
|
|
1186
|
+
const memoryModule = await import('../memory.js');
|
|
1187
|
+
const subCmd = parts[1];
|
|
1188
|
+
const cwd = process.cwd();
|
|
1189
|
+
if (subCmd === 'init') {
|
|
1190
|
+
const memPath = memoryModule.initProjectMemory(cwd);
|
|
1191
|
+
ctx.addMessage('system', `Created: ${memPath}\nEdit the file to add context and preferences.`);
|
|
1192
|
+
}
|
|
1193
|
+
else if (subCmd === 'show' || !subCmd) {
|
|
1194
|
+
const memPath = memoryModule.findProjectMemory(cwd);
|
|
1195
|
+
if (!memPath) {
|
|
1196
|
+
ctx.addMessage('system', 'No CALLIOPE.md found.\nRun /memory init to create one.');
|
|
1197
|
+
}
|
|
1198
|
+
else {
|
|
1199
|
+
const mem = memoryModule.loadMemory(memPath);
|
|
1200
|
+
let info = `Memory: ${memPath}\n\n`;
|
|
1201
|
+
if (mem.context.length)
|
|
1202
|
+
info += `**Context:**\n${mem.context.map((c) => ` - ${c}`).join('\n')}\n\n`;
|
|
1203
|
+
if (mem.preferences.length)
|
|
1204
|
+
info += `**Preferences:**\n${mem.preferences.map((p) => ` - ${p}`).join('\n')}\n\n`;
|
|
1205
|
+
if (mem.history.length)
|
|
1206
|
+
info += `**History:**\n${mem.history.slice(-5).map((h) => ` - ${h}`).join('\n')}\n`;
|
|
1207
|
+
ctx.addMessage('system', info);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
else if (subCmd === 'add' && parts[2]) {
|
|
1211
|
+
const type = parts[2];
|
|
1212
|
+
const content = parts.slice(3).join(' ');
|
|
1213
|
+
if (!content) {
|
|
1214
|
+
ctx.addMessage('error', 'Usage: /memory add <type> <content>');
|
|
1215
|
+
}
|
|
1216
|
+
else {
|
|
1217
|
+
let memPath = memoryModule.findProjectMemory(cwd);
|
|
1218
|
+
if (!memPath) {
|
|
1219
|
+
memPath = memoryModule.initProjectMemory(cwd);
|
|
1220
|
+
}
|
|
1221
|
+
memoryModule.addMemoryEntry(memPath, {
|
|
1222
|
+
type,
|
|
1223
|
+
content,
|
|
1224
|
+
timestamp: new Date().toISOString().split('T')[0],
|
|
1225
|
+
});
|
|
1226
|
+
ctx.addMessage('system', `Added ${type}: ${content}`);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
else if (subCmd === 'remove' && parts[2]) {
|
|
1230
|
+
const type = parts[2];
|
|
1231
|
+
const content = parts.slice(3).join(' ');
|
|
1232
|
+
const memPath = memoryModule.findProjectMemory(cwd);
|
|
1233
|
+
if (memPath && memoryModule.removeMemoryEntry(memPath, type, content)) {
|
|
1234
|
+
ctx.addMessage('system', `Removed matching ${type}`);
|
|
1235
|
+
}
|
|
1236
|
+
else {
|
|
1237
|
+
ctx.addMessage('error', 'Entry not found');
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
else if (subCmd === 'global') {
|
|
1241
|
+
const globalMem = memoryModule.getGlobalMemory();
|
|
1242
|
+
let info = 'Global Memory:\n\n';
|
|
1243
|
+
if (globalMem.preferences.length)
|
|
1244
|
+
info += `**Preferences:**\n${globalMem.preferences.map((p) => ` - ${p}`).join('\n')}\n`;
|
|
1245
|
+
if (globalMem.notes.length)
|
|
1246
|
+
info += `**Notes:**\n${globalMem.notes.map((n) => ` - ${n}`).join('\n')}\n`;
|
|
1247
|
+
ctx.addMessage('system', info || 'No global memories yet.');
|
|
1248
|
+
}
|
|
1249
|
+
else {
|
|
1250
|
+
ctx.addMessage('system', 'Usage: /memory [init|show|add <type> <text>|remove <type> <text>|global]');
|
|
1251
|
+
}
|
|
1252
|
+
break;
|
|
1253
|
+
}
|
|
1254
|
+
case '/find': {
|
|
1255
|
+
const fuzzy = await import('../fuzzy-search.js');
|
|
1256
|
+
const query = parts.slice(1).join(' ');
|
|
1257
|
+
if (!query) {
|
|
1258
|
+
ctx.addMessage('system', 'Usage: /find <pattern>\nFuzzy search for files');
|
|
1259
|
+
}
|
|
1260
|
+
else {
|
|
1261
|
+
const results = fuzzy.searchWithHighlight(process.cwd(), query, { maxResults: 20 });
|
|
1262
|
+
if (results.length === 0) {
|
|
1263
|
+
ctx.addMessage('system', 'No files found');
|
|
1264
|
+
}
|
|
1265
|
+
else {
|
|
1266
|
+
const list = results.map((r, i) => `${i + 1}. ${r.highlighted}`).join('\n');
|
|
1267
|
+
ctx.addMessage('system', `Found ${results.length} files:\n\n${list}`);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
break;
|
|
1271
|
+
}
|
|
1272
|
+
case '/branch': {
|
|
1273
|
+
const branching = await import('../branching.js');
|
|
1274
|
+
const subCmd = parts[1];
|
|
1275
|
+
const sessionId = ctx.sessionRef.current?.id || `session_${Date.now()}`;
|
|
1276
|
+
if (subCmd === 'list' || !subCmd) {
|
|
1277
|
+
const tree = branching.getBranchTree(sessionId);
|
|
1278
|
+
ctx.addMessage('system', `Branches:\n${tree}`);
|
|
1279
|
+
}
|
|
1280
|
+
else if (subCmd === 'new' && parts[2]) {
|
|
1281
|
+
const branch = branching.createBranch(sessionId, parts[2], ctx.llmMessages.current, parts.slice(3).join(' '));
|
|
1282
|
+
ctx.addMessage('system', `Created branch: ${branch.name}`);
|
|
1283
|
+
}
|
|
1284
|
+
else if (subCmd === 'switch' && parts[2]) {
|
|
1285
|
+
const msgs = branching.switchBranch(sessionId, parts[2], ctx.llmMessages.current);
|
|
1286
|
+
if (msgs) {
|
|
1287
|
+
ctx.llmMessages.current = msgs;
|
|
1288
|
+
ctx.addMessage('system', `Switched to branch: ${parts[2]}`);
|
|
1289
|
+
}
|
|
1290
|
+
else {
|
|
1291
|
+
ctx.addMessage('error', 'Branch not found');
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
else if (subCmd === 'delete' && parts[2]) {
|
|
1295
|
+
if (branching.deleteBranch(sessionId, parts[2])) {
|
|
1296
|
+
ctx.addMessage('system', 'Branch deleted');
|
|
1297
|
+
}
|
|
1298
|
+
else {
|
|
1299
|
+
ctx.addMessage('error', 'Cannot delete branch');
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
else {
|
|
1303
|
+
ctx.addMessage('system', 'Usage: /branch [list|new <name>|switch <name>|delete <name>]');
|
|
1304
|
+
}
|
|
1305
|
+
break;
|
|
1306
|
+
}
|
|
1307
|
+
case '/theme': {
|
|
1308
|
+
const subCmd = parts[1];
|
|
1309
|
+
if (!subCmd) {
|
|
1310
|
+
// Open interactive theme picker
|
|
1311
|
+
ctx.setModalMode('theme-picker');
|
|
1312
|
+
}
|
|
1313
|
+
else if (subCmd === 'list') {
|
|
1314
|
+
const themes = await import('../themes.js');
|
|
1315
|
+
const list = themes.listThemes();
|
|
1316
|
+
const current = themes.getCurrentThemeName();
|
|
1317
|
+
const formatted = list.map((t) => {
|
|
1318
|
+
const marker = t.name === current ? ' *' : '';
|
|
1319
|
+
const custom = t.custom ? ' (custom)' : '';
|
|
1320
|
+
return ` ${t.name}${marker}${custom} - ${t.description || 'No description'}`;
|
|
1321
|
+
}).join('\n');
|
|
1322
|
+
ctx.addMessage('system', `Available themes:\n${formatted}`);
|
|
1323
|
+
}
|
|
1324
|
+
else {
|
|
1325
|
+
const themes = await import('../themes.js');
|
|
1326
|
+
if (themes.setCurrentTheme(subCmd)) {
|
|
1327
|
+
themes.clearThemeCache();
|
|
1328
|
+
ctx.addMessage('system', `Theme set to: ${subCmd}`);
|
|
1329
|
+
}
|
|
1330
|
+
else {
|
|
1331
|
+
ctx.addMessage('error', `Theme not found: ${subCmd}`);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
break;
|
|
1335
|
+
}
|
|
1336
|
+
case '/skin': {
|
|
1337
|
+
const subCmd = parts[1];
|
|
1338
|
+
if (subCmd === 'list' || !subCmd) {
|
|
1339
|
+
const skins = listSkins();
|
|
1340
|
+
const current = getCurrentSkin();
|
|
1341
|
+
const formatted = skins.map((s) => {
|
|
1342
|
+
const marker = s.name === current.name ? ' *' : '';
|
|
1343
|
+
const custom = s.custom ? ' (custom)' : '';
|
|
1344
|
+
return ` ${s.name}${marker}${custom} - ${s.description}`;
|
|
1345
|
+
}).join('\n');
|
|
1346
|
+
ctx.addMessage('system', `Active: ${current.name}\nAvailable skins:\n${formatted}`);
|
|
1347
|
+
}
|
|
1348
|
+
else {
|
|
1349
|
+
applySkin(subCmd);
|
|
1350
|
+
const newSkin = getCurrentSkin();
|
|
1351
|
+
if (newSkin.name === subCmd) {
|
|
1352
|
+
config.set('activeSkin', subCmd);
|
|
1353
|
+
ctx.addMessage('system', `Skin set to: ${subCmd} \u2014 ${newSkin.description}`);
|
|
1354
|
+
}
|
|
1355
|
+
else {
|
|
1356
|
+
ctx.addMessage('error', `Skin not found: ${subCmd}. Available: ${listSkins().map((s) => s.name).join(', ')}`);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
break;
|
|
1360
|
+
}
|
|
1361
|
+
case '/palette': {
|
|
1362
|
+
const subCmd = parts[1];
|
|
1363
|
+
if (subCmd === 'list' || !subCmd) {
|
|
1364
|
+
const palettes = listPalettes();
|
|
1365
|
+
const current = getCurrentPalette();
|
|
1366
|
+
const formatted = palettes.map((p) => {
|
|
1367
|
+
const marker = p.name === current.name ? ' *' : '';
|
|
1368
|
+
const custom = p.custom ? ' (custom)' : '';
|
|
1369
|
+
return ` ${p.name}${marker}${custom} - ${p.description}`;
|
|
1370
|
+
}).join('\n');
|
|
1371
|
+
ctx.addMessage('system', `Active: ${current.name}\nAvailable palettes:\n${formatted}`);
|
|
1372
|
+
}
|
|
1373
|
+
else {
|
|
1374
|
+
applyPalette(subCmd);
|
|
1375
|
+
const newPal = getCurrentPalette();
|
|
1376
|
+
if (newPal.name === subCmd) {
|
|
1377
|
+
config.set('activePalette', subCmd);
|
|
1378
|
+
ctx.addMessage('system', `Palette set to: ${subCmd} \u2014 ${newPal.description}`);
|
|
1379
|
+
}
|
|
1380
|
+
else {
|
|
1381
|
+
ctx.addMessage('error', `Palette not found: ${subCmd}. Available: ${listPalettes().map((p) => p.name).join(', ')}`);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
break;
|
|
1385
|
+
}
|
|
1386
|
+
case '/companion': {
|
|
1387
|
+
const subCmd = parts[1];
|
|
1388
|
+
if (subCmd === 'list' || !subCmd) {
|
|
1389
|
+
const companions = listCompanions();
|
|
1390
|
+
const current = getCurrentCompanion();
|
|
1391
|
+
const formatted = companions.map((comp) => {
|
|
1392
|
+
const marker = comp.name === current.name ? ' *' : '';
|
|
1393
|
+
return ` ${comp.name}${marker} - ${comp.description}`;
|
|
1394
|
+
}).join('\n');
|
|
1395
|
+
ctx.addMessage('system', `Active: ${current.name}\nAvailable companions:\n${formatted}`);
|
|
1396
|
+
}
|
|
1397
|
+
else {
|
|
1398
|
+
applyCompanion(subCmd);
|
|
1399
|
+
const newComp = getCurrentCompanion();
|
|
1400
|
+
if (newComp.name === subCmd) {
|
|
1401
|
+
config.set('activeCompanion', subCmd);
|
|
1402
|
+
ctx.llmMessages.current = [{ role: 'system', content: getSystemPrompt(ctx.persona) }];
|
|
1403
|
+
ctx.addMessage('system', `Companion set to: ${subCmd} \u2014 "${newComp.greeting}"`);
|
|
1404
|
+
}
|
|
1405
|
+
else {
|
|
1406
|
+
ctx.addMessage('error', `Companion not found: ${subCmd}. Available: ${listCompanions().map((c) => c.name).join(', ')}`);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
break;
|
|
1410
|
+
}
|
|
1411
|
+
case '/banner': {
|
|
1412
|
+
const bannerSkin = getCurrentSkin();
|
|
1413
|
+
const bannerPalette = getCurrentPalette();
|
|
1414
|
+
const bannerColor = bannerPalette.colors.primary;
|
|
1415
|
+
const imgInfo = getTerminalImageInfo();
|
|
1416
|
+
const rendered = renderSkinBanner(bannerSkin.banner.art, bannerColor, bannerSkin.banner.tagline ?? undefined, imgInfo.mode);
|
|
1417
|
+
ctx.addMessage('system', `${rendered}\n\nSkin: ${bannerSkin.name} | Terminal: ${getImageModeLabel(imgInfo.mode)}${imgInfo.truecolor ? ' (truecolor)' : ''}`);
|
|
1418
|
+
break;
|
|
1419
|
+
}
|
|
1420
|
+
case '/hud': {
|
|
1421
|
+
const hudSkin = getCurrentSkin();
|
|
1422
|
+
const hudPalette = getCurrentPalette();
|
|
1423
|
+
const hudCompanion = getCurrentCompanion();
|
|
1424
|
+
const hudPack = getCurrentPack();
|
|
1425
|
+
const hudIntensity = getCompanionMode();
|
|
1426
|
+
ctx.addMessage('system', `HUD Configuration\n` +
|
|
1427
|
+
(hudPack ? ` Pack: ${hudPack.name} — ${hudPack.description}\n` : '') +
|
|
1428
|
+
` Skin: ${hudSkin.name} — ${hudSkin.description}\n` +
|
|
1429
|
+
` Palette: ${hudPalette.name} — ${hudPalette.description}\n` +
|
|
1430
|
+
` Companion: ${hudCompanion.name} — ${hudCompanion.description}\n` +
|
|
1431
|
+
` Intensity: ${hudIntensity}\n` +
|
|
1432
|
+
` Emojis: ${config.get('useEmojis') !== false ? 'ON' : 'OFF'}\n` +
|
|
1433
|
+
` Mood: ${getMoodText()}\n\n` +
|
|
1434
|
+
` /pack <name> /intensity <pro|immersive> /emoji [on|off]\n` +
|
|
1435
|
+
` /skin <name> /palette <name> /companion <name>`);
|
|
1436
|
+
break;
|
|
1437
|
+
}
|
|
1438
|
+
case '/pack': {
|
|
1439
|
+
const subCmd = parts[1];
|
|
1440
|
+
if (!subCmd) {
|
|
1441
|
+
ctx.setModalMode('pack-picker');
|
|
1442
|
+
break;
|
|
1443
|
+
}
|
|
1444
|
+
if (subCmd === 'list') {
|
|
1445
|
+
const category = parts[2];
|
|
1446
|
+
const packs = listThemePacks(category || undefined);
|
|
1447
|
+
const currentP = getCurrentPack();
|
|
1448
|
+
// Group by category
|
|
1449
|
+
const grouped = new Map();
|
|
1450
|
+
for (const p of packs) {
|
|
1451
|
+
const group = grouped.get(p.category) || [];
|
|
1452
|
+
group.push(p);
|
|
1453
|
+
grouped.set(p.category, group);
|
|
1454
|
+
}
|
|
1455
|
+
let output = 'Theme Packs:\n';
|
|
1456
|
+
for (const [cat, catPacks] of grouped) {
|
|
1457
|
+
output += `\n [${cat}]\n`;
|
|
1458
|
+
for (const p of catPacks) {
|
|
1459
|
+
const marker = currentP && p.name === currentP.name ? ' *' : '';
|
|
1460
|
+
output += ` ${p.name}${marker} — ${p.description}\n`;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
output += '\nUse: /pack <name>';
|
|
1464
|
+
ctx.addMessage('system', output);
|
|
1465
|
+
}
|
|
1466
|
+
else {
|
|
1467
|
+
// Run theme transition animation before applying
|
|
1468
|
+
const targetPack = getThemePack(subCmd);
|
|
1469
|
+
if (targetPack?.skin.splash?.transition) {
|
|
1470
|
+
await renderTransition(targetPack.skin.splash.transition);
|
|
1471
|
+
}
|
|
1472
|
+
const success = applyThemePack(subCmd, getCompanionMode());
|
|
1473
|
+
if (success) {
|
|
1474
|
+
const pack = getCurrentPack();
|
|
1475
|
+
config.set('activeThemePack', subCmd);
|
|
1476
|
+
config.set('activeSkin', pack.skin.name);
|
|
1477
|
+
config.set('activePalette', pack.palette.name);
|
|
1478
|
+
const companion = getCompanionMode() === 'professional'
|
|
1479
|
+
? pack.companions.professional
|
|
1480
|
+
: pack.companions.immersive;
|
|
1481
|
+
config.set('activeCompanion', companion.name);
|
|
1482
|
+
// Reset LLM system prompt to use the companion's persona
|
|
1483
|
+
ctx.llmMessages.current = [{ role: 'system', content: getSystemPrompt(ctx.persona) }];
|
|
1484
|
+
ctx.addMessage('system', `Theme pack: ${subCmd}\n` +
|
|
1485
|
+
` Skin: ${pack.skin.name}, Palette: ${pack.palette.name}, Companion: ${companion.name}\n` +
|
|
1486
|
+
` "${companion.greeting}"`);
|
|
1487
|
+
}
|
|
1488
|
+
else {
|
|
1489
|
+
ctx.addMessage('error', `Theme pack not found: ${subCmd}. Use /pack list to see available packs.`);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
break;
|
|
1493
|
+
}
|
|
1494
|
+
case '/intensity': {
|
|
1495
|
+
const intensity = parts[1];
|
|
1496
|
+
if (intensity === 'professional' || intensity === 'pro') {
|
|
1497
|
+
const success = setCompanionMode('professional');
|
|
1498
|
+
if (success) {
|
|
1499
|
+
const pack = getCurrentPack();
|
|
1500
|
+
config.set('companionIntensity', 'professional');
|
|
1501
|
+
config.set('activeCompanion', pack.companions.professional.name);
|
|
1502
|
+
ctx.llmMessages.current = [{ role: 'system', content: getSystemPrompt(ctx.persona) }];
|
|
1503
|
+
ctx.addMessage('system', `Switched to professional mode — ${pack.companions.professional.description}`);
|
|
1504
|
+
}
|
|
1505
|
+
else {
|
|
1506
|
+
ctx.addMessage('error', 'No theme pack active. Use /pack <name> first.');
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
else if (intensity === 'immersive' || intensity === 'imm') {
|
|
1510
|
+
const success = setCompanionMode('immersive');
|
|
1511
|
+
if (success) {
|
|
1512
|
+
const pack = getCurrentPack();
|
|
1513
|
+
config.set('companionIntensity', 'immersive');
|
|
1514
|
+
config.set('activeCompanion', pack.companions.immersive.name);
|
|
1515
|
+
ctx.llmMessages.current = [{ role: 'system', content: getSystemPrompt(ctx.persona) }];
|
|
1516
|
+
ctx.addMessage('system', `Switched to immersive mode — ${pack.companions.immersive.description}`);
|
|
1517
|
+
}
|
|
1518
|
+
else {
|
|
1519
|
+
ctx.addMessage('error', 'No theme pack active. Use /pack <name> first.');
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
else {
|
|
1523
|
+
const currentIntensity = getCompanionMode();
|
|
1524
|
+
ctx.addMessage('system', `Intensity: ${currentIntensity}\nOptions: /intensity professional (pro), /intensity immersive (imm)`);
|
|
1525
|
+
}
|
|
1526
|
+
break;
|
|
1527
|
+
}
|
|
1528
|
+
case '/emoji': {
|
|
1529
|
+
const emojiArg = parts[1];
|
|
1530
|
+
const current = config.get('useEmojis') !== false;
|
|
1531
|
+
if (emojiArg === 'on') {
|
|
1532
|
+
config.set('useEmojis', true);
|
|
1533
|
+
ctx.addMessage('system', '\u2713 Emojis enabled');
|
|
1534
|
+
}
|
|
1535
|
+
else if (emojiArg === 'off') {
|
|
1536
|
+
config.set('useEmojis', false);
|
|
1537
|
+
ctx.addMessage('system', '\u2713 Emojis disabled — text fallbacks will be used');
|
|
1538
|
+
}
|
|
1539
|
+
else if (emojiArg === 'toggle') {
|
|
1540
|
+
config.set('useEmojis', !current);
|
|
1541
|
+
ctx.addMessage('system', `\u2713 Emojis ${!current ? 'enabled' : 'disabled'}`);
|
|
1542
|
+
}
|
|
1543
|
+
else {
|
|
1544
|
+
ctx.addMessage('system', `Emojis: ${current ? 'ON' : 'OFF'}\nUsage: /emoji [on|off|toggle]`);
|
|
1545
|
+
}
|
|
1546
|
+
break;
|
|
1547
|
+
}
|
|
1548
|
+
case '/hooks': {
|
|
1549
|
+
const hooksModule = await import('../hooks.js');
|
|
1550
|
+
const subCmd = parts[1];
|
|
1551
|
+
if (subCmd === 'list' || !subCmd) {
|
|
1552
|
+
ctx.addMessage('system', hooksModule.listHooksFormatted());
|
|
1553
|
+
}
|
|
1554
|
+
else if (subCmd === 'add' && parts[2]) {
|
|
1555
|
+
const event = parts[2];
|
|
1556
|
+
const hookCommand = parts.slice(3).join(' ');
|
|
1557
|
+
if (!hookCommand) {
|
|
1558
|
+
ctx.addMessage('system', 'Usage: /hooks add <event> <command>');
|
|
1559
|
+
}
|
|
1560
|
+
else {
|
|
1561
|
+
hooksModule.addHook({ event, name: `Hook for ${event}`, command: hookCommand, enabled: true, async: false });
|
|
1562
|
+
ctx.addMessage('system', 'Hook added');
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
else if (subCmd === 'init') {
|
|
1566
|
+
hooksModule.initDefaultHooks();
|
|
1567
|
+
ctx.addMessage('system', 'Default hooks initialized');
|
|
1568
|
+
}
|
|
1569
|
+
else {
|
|
1570
|
+
ctx.addMessage('system', 'Usage: /hooks [list|add <event> <command>|init]');
|
|
1571
|
+
}
|
|
1572
|
+
break;
|
|
1573
|
+
}
|
|
1574
|
+
case '/search': {
|
|
1575
|
+
const query = parts.slice(1).join(' ');
|
|
1576
|
+
if (!query) {
|
|
1577
|
+
ctx.addMessage('system', 'Usage: /search <query>\nSearch conversation history');
|
|
1578
|
+
}
|
|
1579
|
+
else {
|
|
1580
|
+
const lower = query.toLowerCase();
|
|
1581
|
+
const matches = ctx.messages.filter(m => m.content.toLowerCase().includes(lower));
|
|
1582
|
+
if (matches.length === 0) {
|
|
1583
|
+
ctx.addMessage('system', 'No matches found');
|
|
1584
|
+
}
|
|
1585
|
+
else {
|
|
1586
|
+
const results = matches.slice(-10).map(m => {
|
|
1587
|
+
const preview = m.content.slice(0, 100).replace(/\n/g, ' ');
|
|
1588
|
+
return `[${m.type}] ${preview}...`;
|
|
1589
|
+
}).join('\n\n');
|
|
1590
|
+
ctx.addMessage('system', `Found ${matches.length} matches:\n\n${results}`);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
break;
|
|
1594
|
+
}
|
|
1595
|
+
case '/project': {
|
|
1596
|
+
const projectConfig = await import('../project-config.js');
|
|
1597
|
+
const subCmd = parts[1];
|
|
1598
|
+
const cwd = process.cwd();
|
|
1599
|
+
if (subCmd === 'init') {
|
|
1600
|
+
const configPath = projectConfig.createProjectConfig(cwd);
|
|
1601
|
+
ctx.addMessage('system', `Created project config: ${configPath}\nEdit the file to customize settings.`);
|
|
1602
|
+
}
|
|
1603
|
+
else if (subCmd === 'show' || !subCmd) {
|
|
1604
|
+
const configPath = projectConfig.findProjectConfig(cwd);
|
|
1605
|
+
if (!configPath) {
|
|
1606
|
+
ctx.addMessage('system', 'No project config found.\nRun /project init to create one.');
|
|
1607
|
+
}
|
|
1608
|
+
else {
|
|
1609
|
+
const cfg = projectConfig.loadProjectConfig(configPath);
|
|
1610
|
+
if (cfg) {
|
|
1611
|
+
let info = `Config: ${configPath}\n\n`;
|
|
1612
|
+
if (cfg.project)
|
|
1613
|
+
info += `Project: ${cfg.project}\n`;
|
|
1614
|
+
if (cfg.provider)
|
|
1615
|
+
info += `Provider: ${cfg.provider}\n`;
|
|
1616
|
+
if (cfg.model)
|
|
1617
|
+
info += `Model: ${cfg.model}\n`;
|
|
1618
|
+
if (cfg.tech?.length)
|
|
1619
|
+
info += `Tech: ${cfg.tech.join(', ')}\n`;
|
|
1620
|
+
if (cfg.conventions?.length)
|
|
1621
|
+
info += `\nConventions:\n${cfg.conventions.map((c) => ` - ${c}`).join('\n')}\n`;
|
|
1622
|
+
if (cfg.commands)
|
|
1623
|
+
info += `\nCommands: ${Object.keys(cfg.commands).join(', ')}\n`;
|
|
1624
|
+
ctx.addMessage('system', info);
|
|
1625
|
+
}
|
|
1626
|
+
else {
|
|
1627
|
+
ctx.addMessage('error', 'Failed to parse config');
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
else if (subCmd === 'run' && parts[2]) {
|
|
1632
|
+
const configPath = projectConfig.findProjectConfig(cwd);
|
|
1633
|
+
const cfg = configPath ? projectConfig.loadProjectConfig(configPath) : null;
|
|
1634
|
+
const cmdName = parts[2];
|
|
1635
|
+
if (cfg?.commands?.[cmdName]) {
|
|
1636
|
+
const commandToRun = cfg.commands[cmdName];
|
|
1637
|
+
// Show the command and source for user awareness
|
|
1638
|
+
ctx.addMessage('system', `Project command "${cmdName}" from ${configPath}:\n` +
|
|
1639
|
+
` $ ${commandToRun}\n\n` +
|
|
1640
|
+
`Type "/project run-confirm ${cmdName}" to execute, or review the .calliope config first.`);
|
|
1641
|
+
}
|
|
1642
|
+
else {
|
|
1643
|
+
ctx.addMessage('error', `Command not found: ${cmdName}`);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
else if (subCmd === 'run-confirm' && parts[2]) {
|
|
1647
|
+
const configPath = projectConfig.findProjectConfig(cwd);
|
|
1648
|
+
const cfg = configPath ? projectConfig.loadProjectConfig(configPath) : null;
|
|
1649
|
+
const cmdName = parts[2];
|
|
1650
|
+
if (cfg?.commands?.[cmdName]) {
|
|
1651
|
+
const commandToRun = cfg.commands[cmdName];
|
|
1652
|
+
ctx.addMessage('system', `Running: ${commandToRun}`);
|
|
1653
|
+
const { spawn } = await import('child_process');
|
|
1654
|
+
const proc = spawn('sh', ['-c', commandToRun], { cwd, stdio: 'pipe' });
|
|
1655
|
+
let output = '';
|
|
1656
|
+
proc.stdout?.on('data', (d) => output += d.toString());
|
|
1657
|
+
proc.stderr?.on('data', (d) => output += d.toString());
|
|
1658
|
+
proc.on('close', (code) => {
|
|
1659
|
+
ctx.addMessage('system', `Exit ${code}\n${output}`);
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
else {
|
|
1663
|
+
ctx.addMessage('error', `Command not found: ${cmdName}`);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
else {
|
|
1667
|
+
ctx.addMessage('system', 'Usage: /project [init|show|run <cmd>|run-confirm <cmd>]');
|
|
1668
|
+
}
|
|
1669
|
+
break;
|
|
1670
|
+
}
|
|
1671
|
+
case '/route':
|
|
1672
|
+
case '/autoroute': {
|
|
1673
|
+
if (parts[1] === 'on') {
|
|
1674
|
+
ctx.setAutoRoute(true);
|
|
1675
|
+
ctx.addMessage('system', '\u2713 Auto-routing ON - model selected based on task complexity');
|
|
1676
|
+
}
|
|
1677
|
+
else if (parts[1] === 'off') {
|
|
1678
|
+
ctx.setAutoRoute(false);
|
|
1679
|
+
ctx.addMessage('system', '\u2713 Auto-routing OFF - using fixed model');
|
|
1680
|
+
}
|
|
1681
|
+
else if (parts[1] === 'test' && parts[2]) {
|
|
1682
|
+
const testMsg = parts.slice(2).join(' ');
|
|
1683
|
+
const decision = modelRouter.routeRequest(testMsg, ctx.actualProvider);
|
|
1684
|
+
ctx.addMessage('system', `Route test: ${decision.tier} tier (${decision.complexity})\nModel: ${decision.model.model}\nReason: ${decision.reason}\nConfidence: ${Math.round(decision.confidence * 100)}%`);
|
|
1685
|
+
}
|
|
1686
|
+
else {
|
|
1687
|
+
const tiers = modelRouter.getAllTiers(ctx.actualProvider);
|
|
1688
|
+
ctx.addMessage('system', `Auto-route: ${ctx.autoRoute ? 'ON' : 'OFF'}\n\nModel tiers for ${ctx.actualProvider}:\n fast: ${tiers.fast.model}\n balanced: ${tiers.balanced.model}\n smart: ${tiers.smart.model}\n\nUsage: /route [on|off|test <message>]`);
|
|
1689
|
+
}
|
|
1690
|
+
break;
|
|
1691
|
+
}
|
|
1692
|
+
case '/summarize': {
|
|
1693
|
+
const subCmd = parts[1];
|
|
1694
|
+
if (subCmd === 'context' || !subCmd) {
|
|
1695
|
+
const msgCount = ctx.llmMessages.current.length;
|
|
1696
|
+
if (msgCount < 5) {
|
|
1697
|
+
ctx.addMessage('system', 'Not enough messages to summarize.');
|
|
1698
|
+
}
|
|
1699
|
+
else {
|
|
1700
|
+
const summary = summarization.extractKeyInfo(ctx.llmMessages.current);
|
|
1701
|
+
let info = 'Context Summary:\n\n';
|
|
1702
|
+
if (summary.topics.length)
|
|
1703
|
+
info += `**Topics:** ${summary.topics.join(', ')}\n`;
|
|
1704
|
+
if (summary.decisions.length)
|
|
1705
|
+
info += `**Decisions:**\n${summary.decisions.map((d) => ` - ${d}`).join('\n')}\n`;
|
|
1706
|
+
if (summary.actions.length)
|
|
1707
|
+
info += `**Actions:**\n${summary.actions.map((a) => ` - ${a}`).join('\n')}\n`;
|
|
1708
|
+
if (summary.codeChanges.length)
|
|
1709
|
+
info += `**Code Changes:**\n${summary.codeChanges.slice(0, 5).map((c) => ` - ${c}`).join('\n')}\n`;
|
|
1710
|
+
ctx.addMessage('system', info || 'No key information extracted.');
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
else if (subCmd === 'compact') {
|
|
1714
|
+
// Summarize and compact the conversation
|
|
1715
|
+
const result = summarization.summarizeConversation(ctx.llmMessages.current, { maxTokens: 50000 });
|
|
1716
|
+
if (result.summarizedCount > 0) {
|
|
1717
|
+
ctx.llmMessages.current = result.messages;
|
|
1718
|
+
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
1719
|
+
ctx.addMessage('system', `\u2713 Compacted ${result.summarizedCount} messages (${result.originalTokens} \u2192 ${result.reducedTokens} tokens)`);
|
|
1720
|
+
}
|
|
1721
|
+
else {
|
|
1722
|
+
ctx.addMessage('system', 'Context already within limits, no compaction needed.');
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
else {
|
|
1726
|
+
ctx.addMessage('system', 'Usage: /summarize [context|compact]');
|
|
1727
|
+
}
|
|
1728
|
+
break;
|
|
1729
|
+
}
|
|
1730
|
+
case '/upgrade':
|
|
1731
|
+
ctx.addMessage('system', 'Checking for updates...');
|
|
1732
|
+
try {
|
|
1733
|
+
const current = getVersion();
|
|
1734
|
+
const latest = await getLatestVersion();
|
|
1735
|
+
if (!latest) {
|
|
1736
|
+
ctx.addMessage('error', 'Could not check for updates');
|
|
1737
|
+
break;
|
|
1738
|
+
}
|
|
1739
|
+
const [cMaj, cMin, cPat] = current.split('.').map(Number);
|
|
1740
|
+
const [lMaj, lMin, lPat] = latest.split('.').map(Number);
|
|
1741
|
+
const hasUpdate = lMaj > cMaj || (lMaj === cMaj && lMin > cMin) || (lMaj === cMaj && lMin === cMin && lPat > cPat);
|
|
1742
|
+
if (hasUpdate) {
|
|
1743
|
+
ctx.setLatestVersion(latest);
|
|
1744
|
+
ctx.setModalMode('upgrade');
|
|
1745
|
+
}
|
|
1746
|
+
else {
|
|
1747
|
+
ctx.addMessage('system', `You're on the latest version (v${current})`);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
catch (e) {
|
|
1751
|
+
ctx.addMessage('error', `Failed to check for updates: ${e instanceof Error ? e.message : String(e)}`);
|
|
1752
|
+
}
|
|
1753
|
+
break;
|
|
1754
|
+
case '/session':
|
|
1755
|
+
case '/sessions':
|
|
1756
|
+
if (parts[1] === 'list' || !parts[1]) {
|
|
1757
|
+
const sessions = storage.listSessions(20);
|
|
1758
|
+
if (sessions.length === 0) {
|
|
1759
|
+
ctx.addMessage('system', 'No previous sessions found.');
|
|
1760
|
+
}
|
|
1761
|
+
else {
|
|
1762
|
+
ctx.setAvailableSessions(sessions);
|
|
1763
|
+
ctx.setModalMode('sessions');
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
else if (parts[1] === 'info') {
|
|
1767
|
+
const session = ctx.sessionRef.current;
|
|
1768
|
+
if (session) {
|
|
1769
|
+
const savedMessages = storage.loadMessageHistory();
|
|
1770
|
+
const savedCount = savedMessages ? savedMessages.length : 0;
|
|
1771
|
+
ctx.addMessage('system', `Session: ${session.projectName}\nID: ${session.id}\nCreated: ${new Date(session.createdAt).toLocaleString()}\nMessages: ${session.messageCount}\nSaved LLM messages: ${savedCount}`);
|
|
1772
|
+
}
|
|
1773
|
+
else {
|
|
1774
|
+
ctx.addMessage('system', 'No active session.');
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
else if (parts[1] === 'fork') {
|
|
1778
|
+
const session = ctx.sessionRef.current;
|
|
1779
|
+
if (!session) {
|
|
1780
|
+
ctx.addMessage('error', 'No active session to fork.');
|
|
1781
|
+
}
|
|
1782
|
+
else {
|
|
1783
|
+
// Save current messages before forking
|
|
1784
|
+
storage.saveMessageHistory(ctx.llmMessages.current);
|
|
1785
|
+
const forked = storage.forkSession(session.projectPath);
|
|
1786
|
+
if (forked) {
|
|
1787
|
+
ctx.sessionRef.current = forked;
|
|
1788
|
+
ctx.addMessage('system', `Forked session: ${forked.id}\nMessages carried over: ${ctx.llmMessages.current.length}\n\nYou are now on the forked session. The original session is preserved.`);
|
|
1789
|
+
}
|
|
1790
|
+
else {
|
|
1791
|
+
ctx.addMessage('error', 'Failed to fork session. No saved messages found.');
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
else if (parts[1] === 'save') {
|
|
1796
|
+
storage.saveMessageHistory(ctx.llmMessages.current);
|
|
1797
|
+
ctx.addMessage('system', `Saved ${ctx.llmMessages.current.length} LLM messages to session.`);
|
|
1798
|
+
}
|
|
1799
|
+
else {
|
|
1800
|
+
ctx.addMessage('system', 'Usage: /session [list|info|fork|save] or just /sessions');
|
|
1801
|
+
}
|
|
1802
|
+
break;
|
|
1803
|
+
case '/todo': {
|
|
1804
|
+
const subCommand = parts[1];
|
|
1805
|
+
if (subCommand === 'add' && parts.length > 2) {
|
|
1806
|
+
const content = parts.slice(2).join(' ');
|
|
1807
|
+
const isGlobal = content.includes('--global');
|
|
1808
|
+
const isHigh = content.includes('--priority') && content.includes('high');
|
|
1809
|
+
const cleanContent = content.replace(/--global|--priority\s*\w+/g, '').trim();
|
|
1810
|
+
const todo = storage.addTodo(cleanContent, {
|
|
1811
|
+
global: isGlobal,
|
|
1812
|
+
priority: isHigh ? 'high' : 'normal',
|
|
1813
|
+
});
|
|
1814
|
+
ctx.addMessage('system', `\u2713 TODO added (#${todo.id.slice(-4)}${isGlobal ? ', global' : ''})`);
|
|
1815
|
+
}
|
|
1816
|
+
else if (subCommand === 'done' && parts[2]) {
|
|
1817
|
+
const id = parts[2];
|
|
1818
|
+
const todos = [...storage.getSessionTodos(), ...storage.getGlobalTodos()];
|
|
1819
|
+
const todo = todos.find(t => t.id.endsWith(id) || t.id === id);
|
|
1820
|
+
if (todo) {
|
|
1821
|
+
storage.updateTodo(todo.id, { status: 'completed' });
|
|
1822
|
+
ctx.addMessage('system', `\u2713 TODO #${id} marked done`);
|
|
1823
|
+
}
|
|
1824
|
+
else {
|
|
1825
|
+
ctx.addMessage('error', `TODO #${id} not found`);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
else if (subCommand === 'list' || !subCommand) {
|
|
1829
|
+
const sessionTodos = storage.getSessionTodos();
|
|
1830
|
+
const globalTodos = storage.getGlobalTodos();
|
|
1831
|
+
const pending = [...sessionTodos, ...globalTodos].filter(t => t.status !== 'completed');
|
|
1832
|
+
const completed = [...sessionTodos, ...globalTodos].filter(t => t.status === 'completed').slice(-3);
|
|
1833
|
+
if (pending.length === 0 && completed.length === 0) {
|
|
1834
|
+
ctx.addMessage('system', 'No TODOs. Use /todo add <task> to create one.');
|
|
1835
|
+
}
|
|
1836
|
+
else {
|
|
1837
|
+
let output = '\u{1F4CB} TODOs:\n';
|
|
1838
|
+
if (pending.length > 0) {
|
|
1839
|
+
output += pending.map(t => ` ${t.priority === 'high' ? '!' : '\u25A1'} #${t.id.slice(-4)} ${t.content}`).join('\n');
|
|
1840
|
+
}
|
|
1841
|
+
if (completed.length > 0) {
|
|
1842
|
+
output += '\n\nCompleted:\n' + completed.map(t => ` \u2713 #${t.id.slice(-4)} ${t.content}`).join('\n');
|
|
1843
|
+
}
|
|
1844
|
+
ctx.addMessage('system', output);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
else if (subCommand === 'work' && parts[2]) {
|
|
1848
|
+
const id = parts[2];
|
|
1849
|
+
const todos = [...storage.getSessionTodos(), ...storage.getGlobalTodos()];
|
|
1850
|
+
const todo = todos.find(t => t.id.endsWith(id) || t.id === id);
|
|
1851
|
+
if (todo) {
|
|
1852
|
+
storage.setActiveTodo(todo.id);
|
|
1853
|
+
storage.updateTodo(todo.id, { status: 'in_progress' });
|
|
1854
|
+
ctx.addMessage('system', `\u2713 Working on: ${todo.content}\n\nTip: I'll help you complete this task. Describe what you need.`);
|
|
1855
|
+
}
|
|
1856
|
+
else {
|
|
1857
|
+
ctx.addMessage('error', `TODO #${id} not found`);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
else if (subCommand === 'clear') {
|
|
1861
|
+
storage.setActiveTodo(null);
|
|
1862
|
+
ctx.addMessage('system', '\u2713 Active TODO cleared');
|
|
1863
|
+
}
|
|
1864
|
+
else {
|
|
1865
|
+
ctx.addMessage('system', 'Usage: /todo [add <task>|done <id>|work <id>|clear|list]');
|
|
1866
|
+
}
|
|
1867
|
+
break;
|
|
1868
|
+
}
|
|
1869
|
+
case '/plans': {
|
|
1870
|
+
const subCommand = parts[1];
|
|
1871
|
+
if (subCommand === 'list' || !subCommand) {
|
|
1872
|
+
const plans = storage.getPlans();
|
|
1873
|
+
if (plans.length === 0) {
|
|
1874
|
+
ctx.addMessage('system', 'No plans yet. Plans are created in hybrid mode.');
|
|
1875
|
+
}
|
|
1876
|
+
else {
|
|
1877
|
+
const list = plans.slice(0, 5).map((p) => `${p.status === 'completed' ? '\u2713' : '\u25CB'} ${p.id.slice(-4)}: ${p.title}`).join('\n');
|
|
1878
|
+
ctx.addMessage('system', `\u{1F4CB} Plans:\n${list}`);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
else if (subCommand === 'view' && parts[2]) {
|
|
1882
|
+
const plans = storage.getPlans();
|
|
1883
|
+
const plan = plans.find((p) => p.id.endsWith(parts[2]) || p.id === parts[2]);
|
|
1884
|
+
if (plan) {
|
|
1885
|
+
const phases = plan.phases.map((ph) => ` ${ph.status === 'completed' ? '\u2713' : '\u25CB'} ${ph.name} (${ph.risk} risk)`).join('\n');
|
|
1886
|
+
ctx.addMessage('system', `Plan: ${plan.title}\nStatus: ${plan.status}\n\nPhases:\n${phases}`);
|
|
1887
|
+
}
|
|
1888
|
+
else {
|
|
1889
|
+
ctx.addMessage('error', `Plan #${parts[2]} not found`);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
else if (subCommand === 'rerun' && parts[2]) {
|
|
1893
|
+
const plans = storage.getPlans();
|
|
1894
|
+
const plan = plans.find((p) => p.id.endsWith(parts[2]) || p.id === parts[2]);
|
|
1895
|
+
if (plan) {
|
|
1896
|
+
// Reset plan status and activate
|
|
1897
|
+
plan.status = 'in_progress';
|
|
1898
|
+
plan.phases.forEach((ph) => ph.status = 'pending');
|
|
1899
|
+
storage.savePlan(plan);
|
|
1900
|
+
storage.setActivePlan(plan);
|
|
1901
|
+
// Generate prompt for re-execution
|
|
1902
|
+
const phaseList = plan.phases.map((ph) => `- ${ph.name}`).join('\n');
|
|
1903
|
+
const prompt = `Please help me execute this plan:\n\n**${plan.title}**\n\nPhases:\n${phaseList}\n\nStart with the first phase.`;
|
|
1904
|
+
ctx.setInput(prompt);
|
|
1905
|
+
ctx.addMessage('system', `\u2713 Plan loaded: ${plan.title}\nPress Enter to start execution.`);
|
|
1906
|
+
}
|
|
1907
|
+
else {
|
|
1908
|
+
ctx.addMessage('error', `Plan #${parts[2]} not found`);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
else {
|
|
1912
|
+
ctx.addMessage('system', 'Usage: /plans [list|view <id>|rerun <id>]');
|
|
1913
|
+
}
|
|
1914
|
+
break;
|
|
1915
|
+
}
|
|
1916
|
+
case '/history': {
|
|
1917
|
+
const subCommand = parts[1];
|
|
1918
|
+
if (subCommand === 'search' && parts[2]) {
|
|
1919
|
+
const query = parts.slice(2).join(' ');
|
|
1920
|
+
const results = storage.searchChatHistory(query);
|
|
1921
|
+
if (results.length === 0) {
|
|
1922
|
+
ctx.addMessage('system', `No matches for "${query}"`);
|
|
1923
|
+
}
|
|
1924
|
+
else {
|
|
1925
|
+
const list = results.slice(-5).map((m) => `${new Date(m.timestamp).toLocaleTimeString()}: ${m.content.substring(0, 60)}...`).join('\n');
|
|
1926
|
+
ctx.addMessage('system', `\u{1F50D} Found ${results.length} matches:\n${list}`);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
else if (subCommand === 'clear') {
|
|
1930
|
+
ctx.addMessage('system', 'History is preserved per session. Start a new session for fresh history.');
|
|
1931
|
+
}
|
|
1932
|
+
else {
|
|
1933
|
+
const history = storage.getChatHistory(5);
|
|
1934
|
+
if (history.length === 0) {
|
|
1935
|
+
ctx.addMessage('system', 'No chat history yet.');
|
|
1936
|
+
}
|
|
1937
|
+
else {
|
|
1938
|
+
const list = history.map((m) => `${m.role}: ${m.content.substring(0, 50)}...`).join('\n');
|
|
1939
|
+
ctx.addMessage('system', `Recent history:\n${list}\n\nUse /history search <query> to search.`);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
break;
|
|
1943
|
+
}
|
|
1944
|
+
case '/context': {
|
|
1945
|
+
const subCommand = parts[1];
|
|
1946
|
+
if (subCommand === 'load') {
|
|
1947
|
+
const limit = parseInt(parts[2]) || 20;
|
|
1948
|
+
const history = storage.getChatHistory(limit);
|
|
1949
|
+
if (history.length > 0) {
|
|
1950
|
+
// Load history into LLM context
|
|
1951
|
+
for (const msg of history) {
|
|
1952
|
+
if (msg.role === 'user' || msg.role === 'assistant') {
|
|
1953
|
+
ctx.llmMessages.current.push({
|
|
1954
|
+
role: msg.role,
|
|
1955
|
+
content: msg.content,
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
ctx.addMessage('system', `\u2713 Loaded ${history.length} messages into context`);
|
|
1960
|
+
}
|
|
1961
|
+
else {
|
|
1962
|
+
ctx.addMessage('system', 'No history to load.');
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
else if (subCommand === 'summary' || !subCommand) {
|
|
1966
|
+
// Enhanced context summary with model limits
|
|
1967
|
+
const msgCount = ctx.llmMessages.current.length;
|
|
1968
|
+
const estTokens = ctx.estimateContextTokens();
|
|
1969
|
+
const modelLimit = getModelContextLimit(ctx.actualProvider, ctx.actualModel);
|
|
1970
|
+
const percentage = Math.round((estTokens / modelLimit) * 100);
|
|
1971
|
+
const formatK = (n) => n >= 1000 ? `${Math.round(n / 1000)}K` : String(n);
|
|
1972
|
+
let status = '\u{1F7E2} Healthy';
|
|
1973
|
+
if (percentage > 90)
|
|
1974
|
+
status = '\u{1F534} Critical';
|
|
1975
|
+
else if (percentage > 80)
|
|
1976
|
+
status = '\u{1F7E1} Warning';
|
|
1977
|
+
else if (percentage > 60)
|
|
1978
|
+
status = '\u{1F7E0} Caution';
|
|
1979
|
+
ctx.addMessage('system', `**Context Status: ${status}**
|
|
1980
|
+
|
|
1981
|
+
**Usage:** ${formatK(estTokens)} / ${formatK(modelLimit)} tokens (${percentage}%)
|
|
1982
|
+
**Messages:** ${msgCount}
|
|
1983
|
+
**Provider:** ${ctx.actualProvider}
|
|
1984
|
+
**Model:** ${ctx.actualModel}
|
|
1985
|
+
|
|
1986
|
+
**Commands:**
|
|
1987
|
+
/summarize compact - Auto-compress context
|
|
1988
|
+
/context load [n] - Load n messages from history
|
|
1989
|
+
/clear - Start fresh`);
|
|
1990
|
+
}
|
|
1991
|
+
else {
|
|
1992
|
+
ctx.addMessage('system', 'Usage: /context [load [n]|summary]\n\nShow context status or load history.');
|
|
1993
|
+
}
|
|
1994
|
+
break;
|
|
1995
|
+
}
|
|
1996
|
+
case '/scope':
|
|
1997
|
+
case '/dirs': {
|
|
1998
|
+
const subCmd = parts[1];
|
|
1999
|
+
if (subCmd === 'details' || subCmd === 'full') {
|
|
2000
|
+
ctx.addMessage('system', getScopeDetails());
|
|
2001
|
+
}
|
|
2002
|
+
else if (subCmd === 'reset') {
|
|
2003
|
+
resetScope(process.cwd());
|
|
2004
|
+
ctx.addMessage('system', '\u2713 Scope reset to current directory only');
|
|
2005
|
+
}
|
|
2006
|
+
else {
|
|
2007
|
+
ctx.addMessage('system', getScopeSummary());
|
|
2008
|
+
}
|
|
2009
|
+
break;
|
|
2010
|
+
}
|
|
2011
|
+
case '/add-dir': {
|
|
2012
|
+
const dirPath = parts.slice(1).join(' ').replace(/^["']|["']$/g, '');
|
|
2013
|
+
if (!dirPath) {
|
|
2014
|
+
ctx.addMessage('system', 'Usage: /add-dir <path>\n\nAdd a directory to the allowed scope.\nThe agent can only access files within scope.');
|
|
2015
|
+
}
|
|
2016
|
+
else {
|
|
2017
|
+
const result = addToScope(dirPath);
|
|
2018
|
+
if (result.success) {
|
|
2019
|
+
ctx.addMessage('system', `\u2713 ${result.message}`);
|
|
2020
|
+
}
|
|
2021
|
+
else {
|
|
2022
|
+
ctx.addMessage('error', result.message);
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
break;
|
|
2026
|
+
}
|
|
2027
|
+
case '/remove-dir': {
|
|
2028
|
+
const dirPath = parts.slice(1).join(' ').replace(/^["']|["']$/g, '');
|
|
2029
|
+
if (!dirPath) {
|
|
2030
|
+
ctx.addMessage('system', 'Usage: /remove-dir <path>\n\nRemove a directory from the allowed scope.');
|
|
2031
|
+
}
|
|
2032
|
+
else {
|
|
2033
|
+
const result = removeFromScope(dirPath);
|
|
2034
|
+
if (result.success) {
|
|
2035
|
+
ctx.addMessage('system', `\u2713 ${result.message}`);
|
|
2036
|
+
}
|
|
2037
|
+
else {
|
|
2038
|
+
ctx.addMessage('error', result.message);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
break;
|
|
2042
|
+
}
|
|
2043
|
+
case '/trust':
|
|
2044
|
+
case '/untrust': {
|
|
2045
|
+
const { checkTrust, trustProject, untrustProject, listTrustedProjects, removeFromRegistry } = await import('../trust.js');
|
|
2046
|
+
const trustSubCmd = command === '/untrust' ? 'remove' : (parts[1] || 'status');
|
|
2047
|
+
if (trustSubCmd === 'status') {
|
|
2048
|
+
const trust = checkTrust(process.cwd());
|
|
2049
|
+
ctx.addMessage('system', `Trust: ${trust.trusted ? '✓ Trusted' : '✗ Untrusted'}\n${trust.reason}${trust.changed ? '\n⚠️ CALLIOPE.md has changed since trust was granted' : ''}`);
|
|
2050
|
+
}
|
|
2051
|
+
else if (trustSubCmd === 'add' || trustSubCmd === 'yes') {
|
|
2052
|
+
const dir = parts[2] || process.cwd();
|
|
2053
|
+
trustProject(dir, parts.slice(3).join(' ') || undefined);
|
|
2054
|
+
ctx.addMessage('system', `✓ Trusted: ${dir}`);
|
|
2055
|
+
}
|
|
2056
|
+
else if (trustSubCmd === 'remove' || trustSubCmd === 'no') {
|
|
2057
|
+
const dir = parts[2] || process.cwd();
|
|
2058
|
+
if (command === '/untrust') {
|
|
2059
|
+
untrustProject(parts[1] || process.cwd());
|
|
2060
|
+
}
|
|
2061
|
+
else {
|
|
2062
|
+
untrustProject(dir);
|
|
2063
|
+
}
|
|
2064
|
+
ctx.addMessage('system', `✗ Untrusted: ${command === '/untrust' ? (parts[1] || process.cwd()) : dir}`);
|
|
2065
|
+
}
|
|
2066
|
+
else if (trustSubCmd === 'list') {
|
|
2067
|
+
const projects = listTrustedProjects();
|
|
2068
|
+
if (projects.length === 0) {
|
|
2069
|
+
ctx.addMessage('system', 'No projects in trust registry.');
|
|
2070
|
+
}
|
|
2071
|
+
else {
|
|
2072
|
+
const list = projects.map(p => ` ${p.entry.trusted ? '✓' : '✗'} ${p.path}${p.entry.note ? ` (${p.entry.note})` : ''}`).join('\n');
|
|
2073
|
+
ctx.addMessage('system', `Trust registry:\n${list}`);
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
else if (trustSubCmd === 'clear') {
|
|
2077
|
+
const dir = parts[2] || process.cwd();
|
|
2078
|
+
removeFromRegistry(dir);
|
|
2079
|
+
ctx.addMessage('system', `Removed from trust registry: ${dir}`);
|
|
2080
|
+
}
|
|
2081
|
+
else {
|
|
2082
|
+
ctx.addMessage('system', 'Usage: /trust [status|add|remove|list|clear]\n /trust add [path] - trust a project\n /trust remove [path] - untrust a project\n /untrust [path] - shortcut for /trust remove');
|
|
2083
|
+
}
|
|
2084
|
+
break;
|
|
2085
|
+
}
|
|
2086
|
+
case '/template':
|
|
2087
|
+
case '/t': {
|
|
2088
|
+
const subCmd = parts[1];
|
|
2089
|
+
if (subCmd === 'list' || !subCmd) {
|
|
2090
|
+
if (ctx.templates.length === 0) {
|
|
2091
|
+
ctx.addMessage('system', 'No templates saved.\n\nUsage:\n /template save <name> <prompt>\n /template use <name>\n /template delete <name>');
|
|
2092
|
+
}
|
|
2093
|
+
else {
|
|
2094
|
+
const list = ctx.templates.map((t, i) => ` ${i + 1}. ${t.name}: "${t.prompt.substring(0, 50)}${t.prompt.length > 50 ? '...' : ''}"`).join('\n');
|
|
2095
|
+
ctx.addMessage('system', `Templates:\n${list}`);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
else if (subCmd === 'save' && parts[2]) {
|
|
2099
|
+
const name = parts[2];
|
|
2100
|
+
const prompt = parts.slice(3).join(' ').replace(/^["']|["']$/g, '');
|
|
2101
|
+
if (!prompt) {
|
|
2102
|
+
ctx.addMessage('error', 'Usage: /template save <name> "<prompt>"');
|
|
2103
|
+
}
|
|
2104
|
+
else {
|
|
2105
|
+
storage.saveTemplate(name, prompt);
|
|
2106
|
+
ctx.setTemplates(prev => {
|
|
2107
|
+
const filtered = prev.filter(t => t.name !== name);
|
|
2108
|
+
return [...filtered, { name, prompt, createdAt: new Date() }];
|
|
2109
|
+
});
|
|
2110
|
+
ctx.addMessage('system', `\u2713 Template saved: ${name}`);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
else if (subCmd === 'use' && parts[2]) {
|
|
2114
|
+
const name = parts[2];
|
|
2115
|
+
const template = ctx.templates.find(t => t.name === name);
|
|
2116
|
+
if (template) {
|
|
2117
|
+
ctx.setInput(template.prompt);
|
|
2118
|
+
ctx.addMessage('system', `\u2713 Template loaded: ${name} (press Enter to send)`);
|
|
2119
|
+
}
|
|
2120
|
+
else {
|
|
2121
|
+
ctx.addMessage('error', `Template not found: ${name}`);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
else if (subCmd === 'delete' && parts[2]) {
|
|
2125
|
+
const name = parts[2];
|
|
2126
|
+
const found = ctx.templates.find(t => t.name === name);
|
|
2127
|
+
if (found) {
|
|
2128
|
+
storage.deleteTemplate(name);
|
|
2129
|
+
ctx.setTemplates(prev => prev.filter(t => t.name !== name));
|
|
2130
|
+
ctx.addMessage('system', `\u2713 Template deleted: ${name}`);
|
|
2131
|
+
}
|
|
2132
|
+
else {
|
|
2133
|
+
ctx.addMessage('error', `Template not found: ${name}`);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
else {
|
|
2137
|
+
ctx.addMessage('system', 'Usage: /template [list|save <name> <prompt>|use <name>|delete <name>]');
|
|
2138
|
+
}
|
|
2139
|
+
break;
|
|
2140
|
+
}
|
|
2141
|
+
case '/cost':
|
|
2142
|
+
case '/costs': {
|
|
2143
|
+
const subCmd = parts[1];
|
|
2144
|
+
if (subCmd === 'reset') {
|
|
2145
|
+
storage.resetCosts();
|
|
2146
|
+
ctx.addMessage('system', '\u2713 Cost tracking reset');
|
|
2147
|
+
}
|
|
2148
|
+
else {
|
|
2149
|
+
ctx.addMessage('system', storage.getCostSummary());
|
|
2150
|
+
}
|
|
2151
|
+
break;
|
|
2152
|
+
}
|
|
2153
|
+
case '/bookmark':
|
|
2154
|
+
case '/bm': {
|
|
2155
|
+
const subCmd = parts[1];
|
|
2156
|
+
if (!subCmd || subCmd === 'list') {
|
|
2157
|
+
// List bookmarks
|
|
2158
|
+
if (ctx.bookmarks.length === 0) {
|
|
2159
|
+
ctx.addMessage('system', 'No bookmarks. Use /bookmark "name" to create one.');
|
|
2160
|
+
}
|
|
2161
|
+
else {
|
|
2162
|
+
const list = ctx.bookmarks.map((b, i) => ` ${i + 1}. \u{1F516} ${b.name} (message #${b.messageIndex})`).join('\n');
|
|
2163
|
+
ctx.addMessage('system', `Bookmarks:\n${list}\n\nUse /bookmark goto <number> to jump.`);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
else if (subCmd === 'goto' && parts[2]) {
|
|
2167
|
+
const idx = parseInt(parts[2]) - 1;
|
|
2168
|
+
if (idx >= 0 && idx < ctx.bookmarks.length) {
|
|
2169
|
+
const bm = ctx.bookmarks[idx];
|
|
2170
|
+
// Save current state for undo
|
|
2171
|
+
ctx.saveUndoState();
|
|
2172
|
+
// Restore to bookmark point
|
|
2173
|
+
ctx.setMessages(ctx.messages.slice(0, bm.messageIndex + 1));
|
|
2174
|
+
ctx.llmMessages.current = ctx.llmMessages.current.slice(0, bm.llmMessageIndex + 1);
|
|
2175
|
+
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
2176
|
+
ctx.addMessage('system', `\u2713 Jumped to bookmark: ${bm.name}`);
|
|
2177
|
+
}
|
|
2178
|
+
else {
|
|
2179
|
+
ctx.addMessage('error', `Invalid bookmark number. Use /bookmark list to see available.`);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
else if (subCmd === 'delete' && parts[2]) {
|
|
2183
|
+
const idx = parseInt(parts[2]) - 1;
|
|
2184
|
+
if (idx >= 0 && idx < ctx.bookmarks.length) {
|
|
2185
|
+
const removed = ctx.bookmarks[idx];
|
|
2186
|
+
ctx.setBookmarks(prev => prev.filter((_, i) => i !== idx));
|
|
2187
|
+
ctx.addMessage('system', `\u2713 Deleted bookmark: ${removed.name}`);
|
|
2188
|
+
}
|
|
2189
|
+
else {
|
|
2190
|
+
ctx.addMessage('error', 'Invalid bookmark number.');
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
else {
|
|
2194
|
+
// Create bookmark with given name
|
|
2195
|
+
const name = parts.slice(1).join(' ').replace(/^["']|["']$/g, '');
|
|
2196
|
+
const bm = {
|
|
2197
|
+
id: `bm_${Date.now()}`,
|
|
2198
|
+
name,
|
|
2199
|
+
messageIndex: ctx.messages.length - 1,
|
|
2200
|
+
llmMessageIndex: ctx.llmMessages.current.length - 1,
|
|
2201
|
+
timestamp: new Date(),
|
|
2202
|
+
};
|
|
2203
|
+
ctx.setBookmarks(prev => [...prev, bm]);
|
|
2204
|
+
ctx.addMessage('system', `\u{1F516} Bookmark created: "${name}"`);
|
|
2205
|
+
}
|
|
2206
|
+
break;
|
|
2207
|
+
}
|
|
2208
|
+
case '/queue':
|
|
2209
|
+
case '/q': {
|
|
2210
|
+
// /q is now queue, use /exit to quit
|
|
2211
|
+
if (command === '/q' && !parts[1]) {
|
|
2212
|
+
// Just /q with no args shows queue
|
|
2213
|
+
if (ctx.queuedMessages.length === 0) {
|
|
2214
|
+
ctx.addMessage('system', 'No messages queued. Type while agent is processing to queue feedback.');
|
|
2215
|
+
}
|
|
2216
|
+
else {
|
|
2217
|
+
const list = ctx.queuedMessages.map((m, i) => ` ${i + 1}. ${m.substring(0, 60)}${m.length > 60 ? '...' : ''}`).join('\n');
|
|
2218
|
+
ctx.addMessage('system', `\u{1F4E8} Queued messages (${ctx.queuedMessages.length}):\n${list}\n\nUse /queue clear to remove all.`);
|
|
2219
|
+
}
|
|
2220
|
+
break;
|
|
2221
|
+
}
|
|
2222
|
+
const subCmd = parts[1];
|
|
2223
|
+
if (subCmd === 'clear') {
|
|
2224
|
+
const count = ctx.queuedMessages.length;
|
|
2225
|
+
ctx.setQueuedMessages([]);
|
|
2226
|
+
ctx.addMessage('system', `\u2713 Cleared ${count} queued message${count !== 1 ? 's' : ''}`);
|
|
2227
|
+
}
|
|
2228
|
+
else if (subCmd === 'show' || !subCmd) {
|
|
2229
|
+
if (ctx.queuedMessages.length === 0) {
|
|
2230
|
+
ctx.addMessage('system', 'No messages queued.');
|
|
2231
|
+
}
|
|
2232
|
+
else {
|
|
2233
|
+
const list = ctx.queuedMessages.map((m, i) => ` ${i + 1}. ${m}`).join('\n');
|
|
2234
|
+
ctx.addMessage('system', `\u{1F4E8} Queued messages:\n${list}`);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
else if (subCmd === 'flush') {
|
|
2238
|
+
// Force-process queued messages even if stuck
|
|
2239
|
+
if (ctx.queuedMessages.length === 0) {
|
|
2240
|
+
ctx.addMessage('system', 'No messages to flush.');
|
|
2241
|
+
}
|
|
2242
|
+
else {
|
|
2243
|
+
const queued = [...ctx.queuedMessages];
|
|
2244
|
+
ctx.setQueuedMessages([]);
|
|
2245
|
+
ctx.setIsProcessing(false); // Force reset processing state
|
|
2246
|
+
ctx.setThinkingState(null);
|
|
2247
|
+
ctx.setStreamingResponse('');
|
|
2248
|
+
ctx.addMessage('system', `\u{1F504} Flushing ${queued.length} queued message(s)...`);
|
|
2249
|
+
const followUp = queued.length === 1
|
|
2250
|
+
? queued[0]
|
|
2251
|
+
: `[Multiple follow-up messages:]\n${queued.map((m, i) => `${i + 1}. ${m}`).join('\n')}`;
|
|
2252
|
+
setTimeout(() => {
|
|
2253
|
+
ctx.setIsProcessing(true);
|
|
2254
|
+
ctx.runAgent(followUp).finally(() => {
|
|
2255
|
+
ctx.setIsProcessing(false);
|
|
2256
|
+
ctx.setThinkingState(null);
|
|
2257
|
+
ctx.setStreamingResponse('');
|
|
2258
|
+
});
|
|
2259
|
+
}, 50);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
else {
|
|
2263
|
+
ctx.addMessage('system', 'Usage: /queue [show|clear|flush]\n\nTip: Type while agent is processing to queue follow-up messages.');
|
|
2264
|
+
}
|
|
2265
|
+
break;
|
|
2266
|
+
}
|
|
2267
|
+
case '/flush': {
|
|
2268
|
+
// Shortcut for /queue flush - force-process queued messages
|
|
2269
|
+
if (ctx.queuedMessages.length === 0) {
|
|
2270
|
+
ctx.addMessage('system', 'No messages to flush. Use /debug to see current state.');
|
|
2271
|
+
}
|
|
2272
|
+
else {
|
|
2273
|
+
const queued = [...ctx.queuedMessages];
|
|
2274
|
+
ctx.setQueuedMessages([]);
|
|
2275
|
+
ctx.setIsProcessing(false); // Force reset processing state
|
|
2276
|
+
ctx.setThinkingState(null);
|
|
2277
|
+
ctx.setStreamingResponse('');
|
|
2278
|
+
ctx.addMessage('system', `\u{1F504} Flushing ${queued.length} queued message(s)...`);
|
|
2279
|
+
const followUp = queued.length === 1
|
|
2280
|
+
? queued[0]
|
|
2281
|
+
: `[Multiple follow-up messages:]\n${queued.map((m, i) => `${i + 1}. ${m}`).join('\n')}`;
|
|
2282
|
+
setTimeout(() => {
|
|
2283
|
+
ctx.setIsProcessing(true);
|
|
2284
|
+
ctx.runAgent(followUp).finally(() => {
|
|
2285
|
+
ctx.setIsProcessing(false);
|
|
2286
|
+
ctx.setThinkingState(null);
|
|
2287
|
+
ctx.setStreamingResponse('');
|
|
2288
|
+
});
|
|
2289
|
+
}, 50);
|
|
2290
|
+
}
|
|
2291
|
+
break;
|
|
2292
|
+
}
|
|
2293
|
+
case '/debug': {
|
|
2294
|
+
const subCmd = parts[1];
|
|
2295
|
+
if (subCmd === 'on') {
|
|
2296
|
+
ctx.setDebugEnabled(true);
|
|
2297
|
+
ctx.addMessage('system', '\u{1F50D} Debug logging ON (output to stderr). Use /debug off to disable.');
|
|
2298
|
+
}
|
|
2299
|
+
else if (subCmd === 'off') {
|
|
2300
|
+
ctx.setDebugEnabled(false);
|
|
2301
|
+
ctx.addMessage('system', '\u{1F50D} Debug logging OFF');
|
|
2302
|
+
}
|
|
2303
|
+
else {
|
|
2304
|
+
// Show internal state for debugging stuck issues
|
|
2305
|
+
const debugInfo = [
|
|
2306
|
+
`isProcessing: ${ctx.isProcessing}`,
|
|
2307
|
+
`queuedMessages: ${ctx.queuedMessages.length}`,
|
|
2308
|
+
`modalMode: ${ctx.modalMode}`,
|
|
2309
|
+
`confirmMode: ${ctx.confirmMode}`,
|
|
2310
|
+
`loopActive: ${ctx.loopActive}`,
|
|
2311
|
+
`thinkingState: ${ctx.thinkingState ? JSON.stringify(ctx.thinkingState) : 'null'}`,
|
|
2312
|
+
`streamingResponse length: ${ctx.streamingResponse.length}`,
|
|
2313
|
+
`llmMessages count: ${ctx.llmMessages.current.length}`,
|
|
2314
|
+
`mode: ${ctx.mode}`,
|
|
2315
|
+
`debugEnabled: ${ctx.debugEnabled}`,
|
|
2316
|
+
];
|
|
2317
|
+
ctx.addMessage('system', `\u{1F50D} Debug State:\n${debugInfo.join('\n')}\n\nUse /debug on|off to toggle logging.`);
|
|
2318
|
+
}
|
|
2319
|
+
break;
|
|
2320
|
+
}
|
|
2321
|
+
case '/unstick': {
|
|
2322
|
+
// Emergency reset of processing state
|
|
2323
|
+
ctx.setIsProcessing(false);
|
|
2324
|
+
ctx.setThinkingState(null);
|
|
2325
|
+
ctx.setStreamingResponse('');
|
|
2326
|
+
ctx.setLoopActive(false);
|
|
2327
|
+
ctx.setModalMode('none');
|
|
2328
|
+
ctx.setPendingComplexPrompt(null);
|
|
2329
|
+
// Also reset to hybrid mode if stuck in plan mode
|
|
2330
|
+
if (ctx.mode === 'plan') {
|
|
2331
|
+
ctx.setMode('hybrid');
|
|
2332
|
+
ctx.addMessage('system', '\u{1F527} Reset processing state + switched from plan to hybrid mode.');
|
|
2333
|
+
}
|
|
2334
|
+
else {
|
|
2335
|
+
ctx.addMessage('system', '\u{1F527} Reset processing state. You can now submit new messages.');
|
|
2336
|
+
}
|
|
2337
|
+
break;
|
|
2338
|
+
}
|
|
2339
|
+
case '/keys':
|
|
2340
|
+
case '/?': {
|
|
2341
|
+
// Show keybindings modal
|
|
2342
|
+
ctx.setModalMode('keys');
|
|
2343
|
+
break;
|
|
2344
|
+
}
|
|
2345
|
+
case '/work': {
|
|
2346
|
+
// Quick shortcut to enter work mode
|
|
2347
|
+
ctx.setMode('work');
|
|
2348
|
+
ctx.addMessage('system', `Mode: ${MODE_CONFIG['work'].icon} ${MODE_CONFIG['work'].label} - ${MODE_CONFIG['work'].description}`);
|
|
2349
|
+
break;
|
|
2350
|
+
}
|
|
2351
|
+
case '/plan': {
|
|
2352
|
+
// Quick shortcut to enter plan mode
|
|
2353
|
+
ctx.setMode('plan');
|
|
2354
|
+
ctx.addMessage('system', `Mode: ${MODE_CONFIG['plan'].icon} ${MODE_CONFIG['plan'].label} - ${MODE_CONFIG['plan'].description}`);
|
|
2355
|
+
break;
|
|
2356
|
+
}
|
|
2357
|
+
case '/approve': {
|
|
2358
|
+
// Approve a pending plan and start execution (#19)
|
|
2359
|
+
const approveMsg = parts.length > 1
|
|
2360
|
+
? `Plan approved with notes: ${parts.slice(1).join(' ')}. Execute it step by step, updating progress as you complete each step.`
|
|
2361
|
+
: 'Plan approved. Execute it step by step, updating progress as you complete each step.';
|
|
2362
|
+
// Switch to work mode for execution
|
|
2363
|
+
ctx.setMode('work');
|
|
2364
|
+
ctx.addMessage('system', `${MODE_CONFIG['work'].icon} Switched to work mode for plan execution`);
|
|
2365
|
+
// Send approval as user message to the agent
|
|
2366
|
+
ctx.addMessage('user', approveMsg);
|
|
2367
|
+
await ctx.runAgent(approveMsg);
|
|
2368
|
+
break;
|
|
2369
|
+
}
|
|
2370
|
+
case '/resume': {
|
|
2371
|
+
// Resume a session by loading saved LLM message history
|
|
2372
|
+
// Usage: /resume [sessionId] - resume a specific session, or current session if no ID
|
|
2373
|
+
const targetSessionId = parts[1];
|
|
2374
|
+
// Try loading full message history first (preferred - preserves tool calls etc.)
|
|
2375
|
+
const savedMessages = storage.loadMessageHistory(targetSessionId);
|
|
2376
|
+
if (savedMessages && savedMessages.length > 0) {
|
|
2377
|
+
// Replace current LLM messages with saved ones
|
|
2378
|
+
ctx.llmMessages.current.length = 0;
|
|
2379
|
+
for (const msg of savedMessages) {
|
|
2380
|
+
ctx.llmMessages.current.push(msg);
|
|
2381
|
+
}
|
|
2382
|
+
ctx.addMessage('system', `Restored ${savedMessages.length} messages from saved session${targetSessionId ? ` (${targetSessionId})` : ''}`);
|
|
2383
|
+
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
2384
|
+
}
|
|
2385
|
+
else {
|
|
2386
|
+
// Fall back to chat.log history (legacy format, user/assistant only)
|
|
2387
|
+
const history = storage.getChatHistory(20);
|
|
2388
|
+
if (history.length === 0) {
|
|
2389
|
+
ctx.addMessage('system', 'No previous messages to resume. Start a conversation first, messages are auto-saved.');
|
|
2390
|
+
}
|
|
2391
|
+
else {
|
|
2392
|
+
for (const msg of history) {
|
|
2393
|
+
if (msg.role === 'user' || msg.role === 'assistant') {
|
|
2394
|
+
ctx.llmMessages.current.push({
|
|
2395
|
+
role: msg.role,
|
|
2396
|
+
content: msg.content,
|
|
2397
|
+
});
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
ctx.addMessage('system', `Loaded ${history.length} messages from chat log (legacy format, tool context not preserved)`);
|
|
2401
|
+
ctx.setContextTokens(ctx.estimateContextTokens());
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
break;
|
|
2405
|
+
}
|
|
2406
|
+
// ================================================================
|
|
2407
|
+
// Circuit Breaker
|
|
2408
|
+
// ================================================================
|
|
2409
|
+
case '/breaker':
|
|
2410
|
+
case '/cb': {
|
|
2411
|
+
const subCmd = parts[1];
|
|
2412
|
+
if (!ctx.circuitBreaker) {
|
|
2413
|
+
ctx.addMessage('system', 'Circuit breakers not initialized. They activate automatically during agent runs.');
|
|
2414
|
+
break;
|
|
2415
|
+
}
|
|
2416
|
+
if (subCmd === 'status' || !subCmd) {
|
|
2417
|
+
const statuses = ctx.circuitBreaker.getStatus();
|
|
2418
|
+
const stats = ctx.circuitBreaker.getTrackingStats();
|
|
2419
|
+
const health = ctx.circuitBreaker.getHealth();
|
|
2420
|
+
let msg = `Circuit Breaker Health: ${health.toUpperCase()}\n\n`;
|
|
2421
|
+
for (const s of statuses) {
|
|
2422
|
+
const icon = s.state === 'open' ? '\u{1f534}' : s.state === 'half-open' ? '\u{1f7e1}' : '\u{1f7e2}';
|
|
2423
|
+
msg += `${icon} ${s.type}: ${s.state} (tripped ${s.tripCount}x)`;
|
|
2424
|
+
if (s.lastEvent)
|
|
2425
|
+
msg += ` - ${s.lastEvent.message}`;
|
|
2426
|
+
msg += '\n';
|
|
2427
|
+
}
|
|
2428
|
+
msg += `\nTracking: ${stats.consecutiveErrors} consecutive errors, ${stats.totalTokens.toLocaleString()} total tokens, $${stats.totalCost.toFixed(2)} cost, ${stats.idleCount} idle`;
|
|
2429
|
+
ctx.addMessage('system', msg);
|
|
2430
|
+
}
|
|
2431
|
+
else if (subCmd === 'resume') {
|
|
2432
|
+
const breakerType = parts[2];
|
|
2433
|
+
ctx.circuitBreaker.resume(breakerType);
|
|
2434
|
+
ctx.setBreakerHealth(ctx.circuitBreaker.getHealth());
|
|
2435
|
+
ctx.addMessage('system', `\u2713 Circuit breaker${breakerType ? ` "${breakerType}"` : 's'} resumed (half-open mode, 50% more generous thresholds)`);
|
|
2436
|
+
}
|
|
2437
|
+
else if (subCmd === 'reset') {
|
|
2438
|
+
const breakerType = parts[2];
|
|
2439
|
+
ctx.circuitBreaker.reset(breakerType);
|
|
2440
|
+
ctx.setBreakerHealth(ctx.circuitBreaker.getHealth());
|
|
2441
|
+
ctx.addMessage('system', `\u2713 Circuit breaker${breakerType ? ` "${breakerType}"` : 's'} reset to closed`);
|
|
2442
|
+
}
|
|
2443
|
+
else if (subCmd === 'off') {
|
|
2444
|
+
config.set('circuitBreakersEnabled', false);
|
|
2445
|
+
ctx.addMessage('system', '\u2713 Circuit breakers disabled (will take effect on next agent run)');
|
|
2446
|
+
}
|
|
2447
|
+
else if (subCmd === 'on') {
|
|
2448
|
+
config.set('circuitBreakersEnabled', true);
|
|
2449
|
+
ctx.addMessage('system', '\u2713 Circuit breakers enabled');
|
|
2450
|
+
}
|
|
2451
|
+
else {
|
|
2452
|
+
ctx.addMessage('system', `Usage: /breaker [status|resume|reset|on|off]
|
|
2453
|
+
/breaker resume [type] - Resume tripped breaker (half-open)
|
|
2454
|
+
/breaker reset [type] - Reset breaker to closed
|
|
2455
|
+
/breaker on|off - Enable/disable circuit breakers
|
|
2456
|
+
|
|
2457
|
+
Breaker types: repeated-failure, cost-runaway, infinite-loop, token-burn, stall`);
|
|
2458
|
+
}
|
|
2459
|
+
break;
|
|
2460
|
+
}
|
|
2461
|
+
// ================================================================
|
|
2462
|
+
// Smart Routing
|
|
2463
|
+
// ================================================================
|
|
2464
|
+
case '/smart': {
|
|
2465
|
+
const subCmd = parts[1];
|
|
2466
|
+
if (subCmd === 'on') {
|
|
2467
|
+
ctx.setSmartRouteActive(true);
|
|
2468
|
+
config.set('smartRoutingEnabled', true);
|
|
2469
|
+
ctx.addMessage('system', '\u2713 Smart routing ON - best model selected across all providers');
|
|
2470
|
+
}
|
|
2471
|
+
else if (subCmd === 'off') {
|
|
2472
|
+
ctx.setSmartRouteActive(false);
|
|
2473
|
+
config.set('smartRoutingEnabled', false);
|
|
2474
|
+
ctx.addMessage('system', '\u2713 Smart routing OFF - using fixed provider/model');
|
|
2475
|
+
}
|
|
2476
|
+
else if (subCmd === 'cost' && parts[2]) {
|
|
2477
|
+
const sensitivity = parseFloat(parts[2]);
|
|
2478
|
+
if (isNaN(sensitivity) || sensitivity < 0 || sensitivity > 1) {
|
|
2479
|
+
ctx.addMessage('error', 'Cost sensitivity must be between 0 (best quality) and 1 (cheapest)');
|
|
2480
|
+
}
|
|
2481
|
+
else {
|
|
2482
|
+
config.set('smartRoutingCostSensitivity', sensitivity);
|
|
2483
|
+
ctx.addMessage('system', `\u2713 Smart routing cost sensitivity set to ${sensitivity} (0=quality, 1=cost)`);
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
else if (subCmd === 'test' && parts[2]) {
|
|
2487
|
+
const testMsg = parts.slice(2).join(' ');
|
|
2488
|
+
const routingConfig = ctx.smartRoutingConfig || {
|
|
2489
|
+
...getDefaultSmartRoutingConfig(),
|
|
2490
|
+
enabled: true,
|
|
2491
|
+
costSensitivity: config.get('smartRoutingCostSensitivity') ?? 0.3,
|
|
2492
|
+
};
|
|
2493
|
+
const decision = smartRoute(testMsg, routingConfig);
|
|
2494
|
+
const taskInfo = detectTaskType(testMsg);
|
|
2495
|
+
let msg = `Smart Route Test:\n`;
|
|
2496
|
+
msg += ` Task type: ${taskInfo.taskType} (${Math.round(taskInfo.confidence * 100)}% confidence)\n`;
|
|
2497
|
+
msg += ` Complexity: ${decision.complexity}\n`;
|
|
2498
|
+
msg += ` Selected: ${decision.selected.provider}/${decision.selected.model} (${decision.selected.tier} tier)\n`;
|
|
2499
|
+
msg += ` Score: ${(decision.selected.score * 100).toFixed(1)}\n`;
|
|
2500
|
+
if (decision.alternatives.length > 0) {
|
|
2501
|
+
msg += ` Alternatives:\n`;
|
|
2502
|
+
for (const alt of decision.alternatives.slice(0, 3)) {
|
|
2503
|
+
msg += ` - ${alt.provider}/${alt.model} (score: ${(alt.score * 100).toFixed(1)})\n`;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
ctx.addMessage('system', msg);
|
|
2507
|
+
}
|
|
2508
|
+
else {
|
|
2509
|
+
ctx.addMessage('system', `Smart routing: ${ctx.smartRouteActive ? 'ON' : 'OFF'}
|
|
2510
|
+
Cost sensitivity: ${config.get('smartRoutingCostSensitivity') ?? 0.3}
|
|
2511
|
+
|
|
2512
|
+
Usage: /smart [on|off|cost <0-1>|test <message>]
|
|
2513
|
+
/smart on - Enable cross-provider routing
|
|
2514
|
+
/smart off - Disable, use fixed provider
|
|
2515
|
+
/smart cost <0-1> - Set cost sensitivity (0=quality, 1=cheapest)
|
|
2516
|
+
/smart test <msg> - Test routing decision for a message`);
|
|
2517
|
+
}
|
|
2518
|
+
break;
|
|
2519
|
+
}
|
|
2520
|
+
// ================================================================
|
|
2521
|
+
// Swarm Mode
|
|
2522
|
+
// ================================================================
|
|
2523
|
+
case '/swarm':
|
|
2524
|
+
case '/council': {
|
|
2525
|
+
const subCmd = parts[1];
|
|
2526
|
+
if (!ctx.agtermEnabled) {
|
|
2527
|
+
ctx.addMessage('system', 'Agents mode not enabled. Start with --agents flag to use agent swarms.');
|
|
2528
|
+
break;
|
|
2529
|
+
}
|
|
2530
|
+
// /swarm coordinate — multi-agent coordination (formerly /council)
|
|
2531
|
+
if (subCmd === 'coordinate' || subCmd === 'coord') {
|
|
2532
|
+
const coordSubCmd = parts[2];
|
|
2533
|
+
if (coordSubCmd === 'templates') {
|
|
2534
|
+
const templates = Object.values(COUNCIL_TEMPLATES);
|
|
2535
|
+
let msg = 'Swarm Coordination Templates:\n';
|
|
2536
|
+
for (const t of templates) {
|
|
2537
|
+
msg += `\n ${t.name} - ${t.description}`;
|
|
2538
|
+
msg += `\n Mode: ${t.mode}, Agents: ${t.members.map(m => m.name).join(', ')}`;
|
|
2539
|
+
}
|
|
2540
|
+
ctx.addMessage('system', msg);
|
|
2541
|
+
}
|
|
2542
|
+
else if (coordSubCmd === 'status') {
|
|
2543
|
+
const sessionId = parts[3];
|
|
2544
|
+
if (sessionId) {
|
|
2545
|
+
const session = councilManager.getAllSessions().find(s => s.id.startsWith(sessionId));
|
|
2546
|
+
if (session) {
|
|
2547
|
+
let msg = councilManager.formatSessionStatus(session);
|
|
2548
|
+
if (session.status === 'completed' && session.result) {
|
|
2549
|
+
msg += `\n\nResult:\n${session.result.slice(0, 500)}${(session.result.length > 500) ? '...' : ''}`;
|
|
2550
|
+
}
|
|
2551
|
+
ctx.addMessage('system', msg);
|
|
2552
|
+
}
|
|
2553
|
+
else {
|
|
2554
|
+
ctx.addMessage('system', `Coordination session not found: ${sessionId}`);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
else {
|
|
2558
|
+
const sessions = councilManager.getAllSessions();
|
|
2559
|
+
if (sessions.length === 0) {
|
|
2560
|
+
ctx.addMessage('system', 'No coordination sessions.');
|
|
2561
|
+
}
|
|
2562
|
+
else {
|
|
2563
|
+
const lines = sessions.map(s => ` ${s.id.slice(0, 8)} [${s.config.mode}] ${s.status} - ${s.prompt.slice(0, 50)}`);
|
|
2564
|
+
ctx.addMessage('system', `Coordination Sessions:\n${lines.join('\n')}`);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
else if (coordSubCmd === 'list') {
|
|
2569
|
+
const sessions = councilManager.getAllSessions();
|
|
2570
|
+
const stats = councilManager.getStats();
|
|
2571
|
+
let msg = `Coordination Stats: ${stats.totalSessions} total, ${stats.activeSessions} active, ${stats.completedSessions} completed, ${stats.failedSessions} failed\n`;
|
|
2572
|
+
for (const s of sessions) {
|
|
2573
|
+
msg += `\n ${s.id.slice(0, 8)} [${s.config.mode}] ${s.status} - ${s.prompt.slice(0, 60)}`;
|
|
2574
|
+
}
|
|
2575
|
+
ctx.addMessage('system', msg);
|
|
2576
|
+
}
|
|
2577
|
+
else if (coordSubCmd === 'cancel' && parts[3]) {
|
|
2578
|
+
const session = councilManager.getAllSessions().find(s => s.id.startsWith(parts[3]));
|
|
2579
|
+
if (session) {
|
|
2580
|
+
await councilManager.cancelCouncil(session.id);
|
|
2581
|
+
ctx.addMessage('system', `\u2713 Coordination ${parts[3]} cancelled.`);
|
|
2582
|
+
}
|
|
2583
|
+
else {
|
|
2584
|
+
ctx.addMessage('system', `Coordination session not found: ${parts[3]}`);
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
else if (coordSubCmd && !['help'].includes(coordSubCmd)) {
|
|
2588
|
+
// /swarm coordinate <objective>
|
|
2589
|
+
const prompt = parts.slice(2).join(' ');
|
|
2590
|
+
let cleanPrompt = prompt;
|
|
2591
|
+
let template;
|
|
2592
|
+
let mode = 'competitive';
|
|
2593
|
+
const templateMatch = prompt.match(/--template\s+(\S+)/);
|
|
2594
|
+
if (templateMatch) {
|
|
2595
|
+
template = templateMatch[1];
|
|
2596
|
+
cleanPrompt = cleanPrompt.replace(templateMatch[0], '').trim();
|
|
2597
|
+
}
|
|
2598
|
+
const modeMatch = prompt.match(/--mode\s+(competitive|collaborative|consensus|overseer)/);
|
|
2599
|
+
if (modeMatch) {
|
|
2600
|
+
mode = modeMatch[1];
|
|
2601
|
+
cleanPrompt = cleanPrompt.replace(modeMatch[0], '').trim();
|
|
2602
|
+
}
|
|
2603
|
+
try {
|
|
2604
|
+
let session;
|
|
2605
|
+
if (template) {
|
|
2606
|
+
session = await councilManager.startFromTemplate(template, cleanPrompt);
|
|
2607
|
+
}
|
|
2608
|
+
else {
|
|
2609
|
+
const { randomUUID } = await import('crypto');
|
|
2610
|
+
const members = [
|
|
2611
|
+
{ id: randomUUID(), name: 'Agent A', agent: 'claude', weight: 1.0 },
|
|
2612
|
+
{ id: randomUUID(), name: 'Agent B', agent: 'claude', weight: 1.0 },
|
|
2613
|
+
{ id: randomUUID(), name: 'Agent C', agent: 'claude', weight: 1.0 },
|
|
2614
|
+
];
|
|
2615
|
+
session = await councilManager.startCouncil(cleanPrompt, { mode, members });
|
|
2616
|
+
}
|
|
2617
|
+
ctx.addMessage('system', `\u2713 Swarm coordination started: ${session.id.slice(0, 8)}\nMode: ${session.config.mode}\nAgents: ${session.config.members.map(m => m.name).join(', ')}\n\nUse /swarm coord status ${session.id.slice(0, 8)} to check progress.`);
|
|
2618
|
+
}
|
|
2619
|
+
catch (err) {
|
|
2620
|
+
ctx.addMessage('error', `Failed to start coordination: ${err instanceof Error ? err.message : String(err)}`);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
else {
|
|
2624
|
+
ctx.addMessage('system', `Swarm Coordination: Multi-agent teams working toward shared objectives.
|
|
2625
|
+
|
|
2626
|
+
Modes:
|
|
2627
|
+
competitive — Agents work independently, best result wins
|
|
2628
|
+
collaborative — Pipeline: each agent builds on previous work
|
|
2629
|
+
consensus — Agents vote on approach, supermajority decides
|
|
2630
|
+
overseer — Lead decomposes, delegates, reviews, coordinates
|
|
2631
|
+
|
|
2632
|
+
Usage:
|
|
2633
|
+
/swarm coord <objective> Start with default competitive mode
|
|
2634
|
+
/swarm coord <objective> [options] Start with explicit options
|
|
2635
|
+
/swarm coord status [id] Show session status
|
|
2636
|
+
/swarm coord list List all sessions
|
|
2637
|
+
/swarm coord templates Show available templates
|
|
2638
|
+
/swarm coord cancel <id> Cancel a running session
|
|
2639
|
+
|
|
2640
|
+
Options:
|
|
2641
|
+
--template code-review|architecture|security-audit|brainstorm|debate
|
|
2642
|
+
--mode competitive|collaborative|consensus|overseer`);
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
else if (subCmd === 'templates') {
|
|
2646
|
+
// Shortcut: /swarm templates → show coordination templates
|
|
2647
|
+
const templates = Object.values(COUNCIL_TEMPLATES);
|
|
2648
|
+
let msg = 'Swarm Coordination Templates:\n';
|
|
2649
|
+
for (const t of templates) {
|
|
2650
|
+
msg += `\n ${t.name} - ${t.description}`;
|
|
2651
|
+
msg += `\n Mode: ${t.mode}, Agents: ${t.members.map(m => m.name).join(', ')}`;
|
|
2652
|
+
}
|
|
2653
|
+
ctx.addMessage('system', msg);
|
|
2654
|
+
}
|
|
2655
|
+
else if (subCmd === 'start' || (subCmd && !['status', 'list', 'cancel', 'templates', 'coordinate', 'coord', 'help'].includes(subCmd))) {
|
|
2656
|
+
// /swarm start <prompt> or /swarm <prompt> — task decomposition
|
|
2657
|
+
const promptStart = subCmd === 'start' ? 2 : 1;
|
|
2658
|
+
const prompt = parts.slice(promptStart).join(' ');
|
|
2659
|
+
if (!prompt) {
|
|
2660
|
+
ctx.addMessage('system', 'Usage: /swarm <task description>\n /swarm start <task> [--strategy parallel|sequential|map-reduce|pipeline] [--aggregation concatenate|merge-dedupe|summarize|structured]');
|
|
2661
|
+
break;
|
|
2662
|
+
}
|
|
2663
|
+
let strategy = 'parallel';
|
|
2664
|
+
let aggregation = 'concatenate';
|
|
2665
|
+
let cleanPrompt = prompt;
|
|
2666
|
+
const strategyMatch = prompt.match(/--strategy\s+(parallel|sequential|map-reduce|pipeline)/);
|
|
2667
|
+
if (strategyMatch) {
|
|
2668
|
+
strategy = strategyMatch[1];
|
|
2669
|
+
cleanPrompt = cleanPrompt.replace(strategyMatch[0], '').trim();
|
|
2670
|
+
}
|
|
2671
|
+
const aggMatch = prompt.match(/--aggregation\s+(concatenate|merge-dedupe|summarize|structured)/);
|
|
2672
|
+
if (aggMatch) {
|
|
2673
|
+
aggregation = aggMatch[1];
|
|
2674
|
+
cleanPrompt = cleanPrompt.replace(aggMatch[0], '').trim();
|
|
2675
|
+
}
|
|
2676
|
+
try {
|
|
2677
|
+
const session = await swarmManager.startSwarm(cleanPrompt, { decomposition: strategy, aggregation });
|
|
2678
|
+
ctx.addMessage('system', `\u2713 Swarm started: ${session.id.slice(0, 8)}\nStrategy: ${strategy} \u2192 ${aggregation}\nStatus: ${session.status}\n\nUse /swarm status ${session.id.slice(0, 8)} to check progress.`);
|
|
2679
|
+
}
|
|
2680
|
+
catch (err) {
|
|
2681
|
+
ctx.addMessage('error', `Failed to start swarm: ${err instanceof Error ? err.message : String(err)}`);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
else if (subCmd === 'status') {
|
|
2685
|
+
// Show both swarm and coordination sessions
|
|
2686
|
+
const sessionId = parts[2];
|
|
2687
|
+
if (sessionId) {
|
|
2688
|
+
// Check decomposition swarms first, then coordination
|
|
2689
|
+
const swarmSession = swarmManager.getAllSessions().find(s => s.id.startsWith(sessionId));
|
|
2690
|
+
const coordSession = councilManager.getAllSessions().find(s => s.id.startsWith(sessionId));
|
|
2691
|
+
if (swarmSession) {
|
|
2692
|
+
let msg = swarmManager.formatSessionStatus(swarmSession);
|
|
2693
|
+
if (swarmSession.status === 'completed' && swarmSession.result) {
|
|
2694
|
+
msg += `\n\nResult:\n${swarmSession.result.slice(0, 500)}${(swarmSession.result.length > 500) ? '...' : ''}`;
|
|
2695
|
+
}
|
|
2696
|
+
ctx.addMessage('system', msg);
|
|
2697
|
+
}
|
|
2698
|
+
else if (coordSession) {
|
|
2699
|
+
let msg = councilManager.formatSessionStatus(coordSession);
|
|
2700
|
+
if (coordSession.status === 'completed' && coordSession.result) {
|
|
2701
|
+
msg += `\n\nResult:\n${coordSession.result.slice(0, 500)}${(coordSession.result.length > 500) ? '...' : ''}`;
|
|
2702
|
+
}
|
|
2703
|
+
ctx.addMessage('system', msg);
|
|
2704
|
+
}
|
|
2705
|
+
else {
|
|
2706
|
+
ctx.addMessage('system', `Swarm session not found: ${sessionId}`);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
else {
|
|
2710
|
+
const swarmSessions = swarmManager.getAllSessions();
|
|
2711
|
+
const coordSessions = councilManager.getAllSessions();
|
|
2712
|
+
if (swarmSessions.length === 0 && coordSessions.length === 0) {
|
|
2713
|
+
ctx.addMessage('system', 'No swarm sessions.');
|
|
2714
|
+
}
|
|
2715
|
+
else {
|
|
2716
|
+
const lines = [];
|
|
2717
|
+
for (const s of swarmSessions) {
|
|
2718
|
+
const subtaskInfo = s.subtasks.length > 0
|
|
2719
|
+
? ` (${s.subtasks.filter(st => st.status === 'completed').length}/${s.subtasks.length} done)`
|
|
2720
|
+
: '';
|
|
2721
|
+
lines.push(` ${s.id.slice(0, 8)} [decompose] ${s.status}${subtaskInfo} - ${s.prompt.slice(0, 50)}`);
|
|
2722
|
+
}
|
|
2723
|
+
for (const s of coordSessions) {
|
|
2724
|
+
lines.push(` ${s.id.slice(0, 8)} [${s.config.mode}] ${s.status} - ${s.prompt.slice(0, 50)}`);
|
|
2725
|
+
}
|
|
2726
|
+
ctx.addMessage('system', `Swarm Sessions:\n${lines.join('\n')}`);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
else if (subCmd === 'list') {
|
|
2731
|
+
const swarmSessions = swarmManager.getAllSessions();
|
|
2732
|
+
const swarmStats = swarmManager.getStats();
|
|
2733
|
+
const coordSessions = councilManager.getAllSessions();
|
|
2734
|
+
const coordStats = councilManager.getStats();
|
|
2735
|
+
let msg = `Decomposition: ${swarmStats.totalSessions} total, ${swarmStats.activeSessions} active, ${swarmStats.completedSessions} completed\n`;
|
|
2736
|
+
msg += `Coordination: ${coordStats.totalSessions} total, ${coordStats.activeSessions} active, ${coordStats.completedSessions} completed\n`;
|
|
2737
|
+
for (const s of swarmSessions) {
|
|
2738
|
+
msg += `\n ${s.id.slice(0, 8)} [decompose] ${s.status} - ${s.prompt.slice(0, 60)}`;
|
|
2739
|
+
}
|
|
2740
|
+
for (const s of coordSessions) {
|
|
2741
|
+
msg += `\n ${s.id.slice(0, 8)} [${s.config.mode}] ${s.status} - ${s.prompt.slice(0, 60)}`;
|
|
2742
|
+
}
|
|
2743
|
+
ctx.addMessage('system', msg);
|
|
2744
|
+
}
|
|
2745
|
+
else if (subCmd === 'cancel' && parts[2]) {
|
|
2746
|
+
const swarmSession = swarmManager.getAllSessions().find(s => s.id.startsWith(parts[2]));
|
|
2747
|
+
const coordSession = councilManager.getAllSessions().find(s => s.id.startsWith(parts[2]));
|
|
2748
|
+
if (swarmSession) {
|
|
2749
|
+
await swarmManager.cancelSwarm(swarmSession.id);
|
|
2750
|
+
ctx.addMessage('system', `\u2713 Swarm ${parts[2]} cancelled.`);
|
|
2751
|
+
}
|
|
2752
|
+
else if (coordSession) {
|
|
2753
|
+
await councilManager.cancelCouncil(coordSession.id);
|
|
2754
|
+
ctx.addMessage('system', `\u2713 Coordination ${parts[2]} cancelled.`);
|
|
2755
|
+
}
|
|
2756
|
+
else {
|
|
2757
|
+
ctx.addMessage('system', `Swarm session not found: ${parts[2]}`);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
else {
|
|
2761
|
+
ctx.addMessage('system', `Agent Swarms: Decompose tasks and coordinate multi-agent teams.
|
|
2762
|
+
|
|
2763
|
+
Decomposition (parallel workers):
|
|
2764
|
+
/swarm <task> Decompose and execute in parallel
|
|
2765
|
+
/swarm start <task> [options] Start with explicit strategy
|
|
2766
|
+
|
|
2767
|
+
Coordination (multi-agent teamwork):
|
|
2768
|
+
/swarm coord <objective> Start coordinated agents
|
|
2769
|
+
/swarm coord <objective> [options] Start with explicit mode
|
|
2770
|
+
/swarm coord templates Show coordination templates
|
|
2771
|
+
|
|
2772
|
+
Management:
|
|
2773
|
+
/swarm status [id] Show session status
|
|
2774
|
+
/swarm list List all sessions
|
|
2775
|
+
/swarm cancel <id> Cancel a session
|
|
2776
|
+
|
|
2777
|
+
Decomposition options:
|
|
2778
|
+
--strategy parallel|sequential|map-reduce|pipeline
|
|
2779
|
+
--aggregation concatenate|merge-dedupe|summarize|structured
|
|
2780
|
+
|
|
2781
|
+
Coordination options:
|
|
2782
|
+
--mode competitive|collaborative|consensus|overseer
|
|
2783
|
+
--template code-review|architecture|security-audit|brainstorm|debate
|
|
2784
|
+
|
|
2785
|
+
Requires --agents flag.`);
|
|
2786
|
+
}
|
|
2787
|
+
break;
|
|
2788
|
+
}
|
|
2789
|
+
case '/checkpoint':
|
|
2790
|
+
case '/cp': {
|
|
2791
|
+
const { listCheckpoints, clearCheckpoints } = await import('../checkpoint.js');
|
|
2792
|
+
const cpSubCmd = parts[1] || 'list';
|
|
2793
|
+
if (cpSubCmd === 'list') {
|
|
2794
|
+
const filterPath = parts[2];
|
|
2795
|
+
const checkpoints = listCheckpoints(filterPath);
|
|
2796
|
+
if (checkpoints.length === 0) {
|
|
2797
|
+
ctx.addMessage('system', filterPath
|
|
2798
|
+
? `No checkpoints found for: ${filterPath}`
|
|
2799
|
+
: 'No checkpoints found. Checkpoints are created automatically when files are overwritten.');
|
|
2800
|
+
}
|
|
2801
|
+
else {
|
|
2802
|
+
const list = checkpoints.slice(0, 20).map((cp, i) => {
|
|
2803
|
+
const relPath = path.relative(process.cwd(), cp.filePath);
|
|
2804
|
+
const time = new Date(cp.timestamp).toLocaleString();
|
|
2805
|
+
const size = cp.size > 1024 ? `${(cp.size / 1024).toFixed(1)}KB` : `${cp.size}B`;
|
|
2806
|
+
return ` ${i}. ${relPath} (${size}) - ${time}`;
|
|
2807
|
+
}).join('\n');
|
|
2808
|
+
ctx.addMessage('system', `Checkpoints (newest first):\n${list}${checkpoints.length > 20 ? `\n ... and ${checkpoints.length - 20} more` : ''}`);
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
else if (cpSubCmd === 'clear') {
|
|
2812
|
+
const days = parts[2] ? parseInt(parts[2]) : undefined;
|
|
2813
|
+
const removed = clearCheckpoints(days);
|
|
2814
|
+
ctx.addMessage('system', `Cleared ${removed} checkpoint${removed !== 1 ? 's' : ''}${days ? ` older than ${days} days` : ''}.`);
|
|
2815
|
+
}
|
|
2816
|
+
else {
|
|
2817
|
+
ctx.addMessage('system', 'Usage: /checkpoint [list|clear]\n /checkpoint list [path] - list checkpoints\n /checkpoint clear [days] - clear old checkpoints\n /restore <path> [index] - restore a file');
|
|
2818
|
+
}
|
|
2819
|
+
break;
|
|
2820
|
+
}
|
|
2821
|
+
case '/restore': {
|
|
2822
|
+
const { restoreCheckpoint, listCheckpoints } = await import('../checkpoint.js');
|
|
2823
|
+
const restorePath = parts[1];
|
|
2824
|
+
if (!restorePath) {
|
|
2825
|
+
ctx.addMessage('error', 'Usage: /restore <path> [index]\n Restores a file from its most recent checkpoint.\n Use /checkpoint list to see available checkpoints.');
|
|
2826
|
+
break;
|
|
2827
|
+
}
|
|
2828
|
+
const idx = parts[2] ? parseInt(parts[2]) : 0;
|
|
2829
|
+
const absRestorePath = path.resolve(restorePath);
|
|
2830
|
+
const checkpoints = listCheckpoints(absRestorePath);
|
|
2831
|
+
if (checkpoints.length === 0) {
|
|
2832
|
+
ctx.addMessage('error', `No checkpoints found for: ${restorePath}`);
|
|
2833
|
+
break;
|
|
2834
|
+
}
|
|
2835
|
+
const restored = restoreCheckpoint(absRestorePath, idx);
|
|
2836
|
+
if (restored !== undefined) {
|
|
2837
|
+
const relPath = path.relative(process.cwd(), absRestorePath);
|
|
2838
|
+
const cp = checkpoints[idx];
|
|
2839
|
+
ctx.addMessage('system', `✓ Restored ${relPath} from checkpoint (${new Date(cp.timestamp).toLocaleString()})`);
|
|
2840
|
+
}
|
|
2841
|
+
else {
|
|
2842
|
+
ctx.addMessage('error', `Failed to restore: checkpoint index ${idx} not found for ${restorePath}`);
|
|
2843
|
+
}
|
|
2844
|
+
break;
|
|
2845
|
+
}
|
|
2846
|
+
// ================================================================
|
|
2847
|
+
// Background Jobs
|
|
2848
|
+
// ================================================================
|
|
2849
|
+
case '/bg': {
|
|
2850
|
+
const bgPrompt = parts.slice(1).join(' ');
|
|
2851
|
+
if (!bgPrompt) {
|
|
2852
|
+
ctx.addMessage('error', 'Usage: /bg <prompt> — run a task in the background');
|
|
2853
|
+
break;
|
|
2854
|
+
}
|
|
2855
|
+
const bgJob = createJob(bgPrompt, { provider: ctx.actualProvider, model: ctx.actualModel });
|
|
2856
|
+
ctx.addMessage('system', `Background job ${bgJob.id} created: "${bgPrompt.length > 60 ? bgPrompt.slice(0, 57) + '...' : bgPrompt}"`);
|
|
2857
|
+
// Run the job using the agent
|
|
2858
|
+
runJob(bgJob.id, async (prompt, signal) => {
|
|
2859
|
+
const { chat } = await import('../providers/index.js');
|
|
2860
|
+
const { TOOLS } = await import('../tools.js');
|
|
2861
|
+
const { getSystemPrompt: getSysPrompt } = await import('../types.js');
|
|
2862
|
+
const bgMessages = [
|
|
2863
|
+
{ role: 'system', content: getSysPrompt(ctx.persona) },
|
|
2864
|
+
{ role: 'user', content: prompt },
|
|
2865
|
+
];
|
|
2866
|
+
let iterations = 0;
|
|
2867
|
+
let lastContent = '';
|
|
2868
|
+
while (iterations < 20 && !signal.aborted) {
|
|
2869
|
+
iterations++;
|
|
2870
|
+
const response = await chat(ctx.provider, bgMessages, TOOLS, ctx.model);
|
|
2871
|
+
if (!response.toolCalls?.length) {
|
|
2872
|
+
lastContent = response.content;
|
|
2873
|
+
break;
|
|
2874
|
+
}
|
|
2875
|
+
bgMessages.push({ role: 'assistant', content: response.content, toolCalls: response.toolCalls });
|
|
2876
|
+
const { executeTool: execTool } = await import('../tools.js');
|
|
2877
|
+
for (const tc of response.toolCalls) {
|
|
2878
|
+
const result = await execTool(tc, process.cwd());
|
|
2879
|
+
bgMessages.push({ role: 'tool', content: result.result, toolCallId: tc.id });
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
return { result: lastContent, iterations };
|
|
2883
|
+
}).then(completed => {
|
|
2884
|
+
ctx.addMessage('system', `Background job ${completed.id} ${completed.status}: ${completed.result?.slice(0, 200) || completed.error || 'done'}`);
|
|
2885
|
+
}).catch(() => { });
|
|
2886
|
+
break;
|
|
2887
|
+
}
|
|
2888
|
+
case '/jobs': {
|
|
2889
|
+
ctx.addMessage('system', formatJobsList());
|
|
2890
|
+
break;
|
|
2891
|
+
}
|
|
2892
|
+
case '/job': {
|
|
2893
|
+
const jobId = parts[1];
|
|
2894
|
+
if (!jobId) {
|
|
2895
|
+
ctx.addMessage('error', 'Usage: /job <id> — show job details');
|
|
2896
|
+
break;
|
|
2897
|
+
}
|
|
2898
|
+
const jobInfo = getJob(jobId);
|
|
2899
|
+
if (!jobInfo) {
|
|
2900
|
+
ctx.addMessage('error', `Job "${jobId}" not found.`);
|
|
2901
|
+
}
|
|
2902
|
+
else {
|
|
2903
|
+
ctx.addMessage('system', formatJob(jobInfo) + (jobInfo.result ? `\n\nResult:\n${jobInfo.result.slice(0, 2000)}` : ''));
|
|
2904
|
+
}
|
|
2905
|
+
break;
|
|
2906
|
+
}
|
|
2907
|
+
case '/cancel': {
|
|
2908
|
+
const cancelId = parts[1];
|
|
2909
|
+
if (!cancelId) {
|
|
2910
|
+
ctx.addMessage('error', 'Usage: /cancel <job-id>');
|
|
2911
|
+
break;
|
|
2912
|
+
}
|
|
2913
|
+
const cancelled = cancelJob(cancelId);
|
|
2914
|
+
ctx.addMessage('system', cancelled ? `Cancelled job ${cancelId}` : `Could not cancel ${cancelId} (not running or not found)`);
|
|
2915
|
+
break;
|
|
2916
|
+
}
|
|
2917
|
+
case '/clear-jobs': {
|
|
2918
|
+
const cleared = clearFinishedJobs();
|
|
2919
|
+
ctx.addMessage('system', cleared > 0 ? `Cleared ${cleared} finished job(s).` : 'No finished jobs to clear.');
|
|
2920
|
+
break;
|
|
2921
|
+
}
|
|
2922
|
+
// ================================================================
|
|
2923
|
+
// Recordings
|
|
2924
|
+
// ================================================================
|
|
2925
|
+
case '/recordings': {
|
|
2926
|
+
const recs = listRecordings();
|
|
2927
|
+
if (recs.length === 0) {
|
|
2928
|
+
ctx.addMessage('system', 'No recordings found.');
|
|
2929
|
+
}
|
|
2930
|
+
else {
|
|
2931
|
+
const lines = recs.slice(0, 20).map(r => {
|
|
2932
|
+
const dur = r.duration > 0 ? ` (${Math.floor(r.duration / 60000)}m${Math.floor((r.duration % 60000) / 1000)}s)` : '';
|
|
2933
|
+
return ` ${r.id} ${r.startTime} ${r.eventCount} events${dur}`;
|
|
2934
|
+
});
|
|
2935
|
+
ctx.addMessage('system', `Recordings (${recs.length} total):\n${lines.join('\n')}`);
|
|
2936
|
+
}
|
|
2937
|
+
break;
|
|
2938
|
+
}
|
|
2939
|
+
case '/recording': {
|
|
2940
|
+
const recId = parts[1];
|
|
2941
|
+
if (!recId) {
|
|
2942
|
+
ctx.addMessage('error', 'Usage: /recording <id> — show recording details');
|
|
2943
|
+
break;
|
|
2944
|
+
}
|
|
2945
|
+
const rec = loadRecording(recId);
|
|
2946
|
+
if (!rec) {
|
|
2947
|
+
ctx.addMessage('error', `Recording "${recId}" not found.`);
|
|
2948
|
+
}
|
|
2949
|
+
else {
|
|
2950
|
+
ctx.addMessage('system', formatRecording(rec));
|
|
2951
|
+
}
|
|
2952
|
+
break;
|
|
2953
|
+
}
|
|
2954
|
+
case '/delete-recording': {
|
|
2955
|
+
const delRecId = parts[1];
|
|
2956
|
+
if (!delRecId) {
|
|
2957
|
+
ctx.addMessage('error', 'Usage: /delete-recording <id>');
|
|
2958
|
+
break;
|
|
2959
|
+
}
|
|
2960
|
+
const delResult = deleteRecording(delRecId);
|
|
2961
|
+
ctx.addMessage('system', delResult ? `Deleted recording ${delRecId}` : `Recording "${delRecId}" not found.`);
|
|
2962
|
+
break;
|
|
2963
|
+
}
|
|
2964
|
+
// ================================================================
|
|
2965
|
+
// API Server
|
|
2966
|
+
// ================================================================
|
|
2967
|
+
case '/serve': {
|
|
2968
|
+
if (isApiServerRunning()) {
|
|
2969
|
+
ctx.addMessage('system', 'API server is already running.');
|
|
2970
|
+
break;
|
|
2971
|
+
}
|
|
2972
|
+
const servePort = parts[1] ? parseInt(parts[1], 10) : 3100;
|
|
2973
|
+
if (isNaN(servePort) || servePort < 1 || servePort > 65535) {
|
|
2974
|
+
ctx.addMessage('error', 'Invalid port. Usage: /serve [port]');
|
|
2975
|
+
break;
|
|
2976
|
+
}
|
|
2977
|
+
try {
|
|
2978
|
+
const info = await startApiServer({ port: servePort, host: '127.0.0.1' });
|
|
2979
|
+
ctx.addMessage('system', `API server started on http://${info.host}:${info.port}`);
|
|
2980
|
+
}
|
|
2981
|
+
catch (err) {
|
|
2982
|
+
ctx.addMessage('error', `Failed to start API server: ${err instanceof Error ? err.message : String(err)}`);
|
|
2983
|
+
}
|
|
2984
|
+
break;
|
|
2985
|
+
}
|
|
2986
|
+
case '/serve-stop': {
|
|
2987
|
+
if (!isApiServerRunning()) {
|
|
2988
|
+
ctx.addMessage('system', 'API server is not running.');
|
|
2989
|
+
break;
|
|
2990
|
+
}
|
|
2991
|
+
await stopApiServer();
|
|
2992
|
+
ctx.addMessage('system', 'API server stopped.');
|
|
2993
|
+
break;
|
|
2994
|
+
}
|
|
2995
|
+
case '/exit':
|
|
2996
|
+
case '/quit':
|
|
2997
|
+
if (isApiServerRunning()) {
|
|
2998
|
+
await stopApiServer();
|
|
2999
|
+
}
|
|
3000
|
+
ctx.exit();
|
|
3001
|
+
break;
|
|
3002
|
+
default:
|
|
3003
|
+
ctx.addMessage('error', `Unknown command: ${command}. Type /help for help.`);
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
//# sourceMappingURL=commands.js.map
|