@codemieai/code 0.0.2 ā 0.0.4
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/README.md +357 -719
- package/bin/codemie-claude.js +145 -0
- package/bin/codemie-code.js +128 -15
- package/bin/codemie-codex.js +137 -0
- package/bin/codemie.js +1 -1
- package/dist/agents/adapters/claude-code.d.ts +7 -2
- package/dist/agents/adapters/claude-code.d.ts.map +1 -1
- package/dist/agents/adapters/claude-code.js +94 -58
- package/dist/agents/adapters/claude-code.js.map +1 -1
- package/dist/agents/adapters/codemie-code.d.ts +11 -2
- package/dist/agents/adapters/codemie-code.d.ts.map +1 -1
- package/dist/agents/adapters/codemie-code.js +93 -25
- package/dist/agents/adapters/codemie-code.js.map +1 -1
- package/dist/agents/adapters/codex.d.ts +7 -2
- package/dist/agents/adapters/codex.d.ts.map +1 -1
- package/dist/agents/adapters/codex.js +104 -39
- package/dist/agents/adapters/codex.js.map +1 -1
- package/dist/agents/codemie-code/agent.d.ts +89 -0
- package/dist/agents/codemie-code/agent.d.ts.map +1 -0
- package/dist/agents/codemie-code/agent.js +689 -0
- package/dist/agents/codemie-code/agent.js.map +1 -0
- package/dist/agents/codemie-code/config.d.ts +40 -0
- package/dist/agents/codemie-code/config.d.ts.map +1 -0
- package/dist/agents/codemie-code/config.js +278 -0
- package/dist/agents/codemie-code/config.js.map +1 -0
- package/dist/agents/codemie-code/filters.d.ts +91 -0
- package/dist/agents/codemie-code/filters.d.ts.map +1 -0
- package/dist/agents/codemie-code/filters.js +328 -0
- package/dist/agents/codemie-code/filters.js.map +1 -0
- package/dist/agents/codemie-code/index.d.ts +92 -0
- package/dist/agents/codemie-code/index.d.ts.map +1 -0
- package/dist/agents/codemie-code/index.js +327 -0
- package/dist/agents/codemie-code/index.js.map +1 -0
- package/dist/agents/codemie-code/modes/contextAwarePlanning.d.ts +87 -0
- package/dist/agents/codemie-code/modes/contextAwarePlanning.d.ts.map +1 -0
- package/dist/agents/codemie-code/modes/contextAwarePlanning.js +957 -0
- package/dist/agents/codemie-code/modes/contextAwarePlanning.js.map +1 -0
- package/dist/agents/codemie-code/modes/planMode.d.ts +116 -0
- package/dist/agents/codemie-code/modes/planMode.d.ts.map +1 -0
- package/dist/agents/codemie-code/modes/planMode.js +537 -0
- package/dist/agents/codemie-code/modes/planMode.js.map +1 -0
- package/dist/agents/codemie-code/prompts.d.ts +40 -0
- package/dist/agents/codemie-code/prompts.d.ts.map +1 -0
- package/dist/agents/codemie-code/prompts.js +160 -0
- package/dist/agents/codemie-code/prompts.js.map +1 -0
- package/dist/agents/codemie-code/storage/todoStorage.d.ts +78 -0
- package/dist/agents/codemie-code/storage/todoStorage.d.ts.map +1 -0
- package/dist/agents/codemie-code/storage/todoStorage.js +225 -0
- package/dist/agents/codemie-code/storage/todoStorage.js.map +1 -0
- package/dist/agents/codemie-code/tokenUtils.d.ts +108 -0
- package/dist/agents/codemie-code/tokenUtils.d.ts.map +1 -0
- package/dist/agents/codemie-code/tokenUtils.js +220 -0
- package/dist/agents/codemie-code/tokenUtils.js.map +1 -0
- package/dist/agents/codemie-code/toolMetadata.d.ts +15 -0
- package/dist/agents/codemie-code/toolMetadata.d.ts.map +1 -0
- package/dist/agents/codemie-code/toolMetadata.js +315 -0
- package/dist/agents/codemie-code/toolMetadata.js.map +1 -0
- package/dist/agents/codemie-code/tools/index.d.ts +45 -0
- package/dist/agents/codemie-code/tools/index.d.ts.map +1 -0
- package/dist/agents/codemie-code/tools/index.js +407 -0
- package/dist/agents/codemie-code/tools/index.js.map +1 -0
- package/dist/agents/codemie-code/tools/planning.d.ts +53 -0
- package/dist/agents/codemie-code/tools/planning.d.ts.map +1 -0
- package/dist/agents/codemie-code/tools/planning.js +224 -0
- package/dist/agents/codemie-code/tools/planning.js.map +1 -0
- package/dist/agents/codemie-code/types.d.ts +418 -0
- package/dist/agents/codemie-code/types.d.ts.map +1 -0
- package/dist/agents/codemie-code/types.js +35 -0
- package/dist/agents/codemie-code/types.js.map +1 -0
- package/dist/agents/codemie-code/ui/progressTracker.d.ts +125 -0
- package/dist/agents/codemie-code/ui/progressTracker.d.ts.map +1 -0
- package/dist/agents/codemie-code/ui/progressTracker.js +343 -0
- package/dist/agents/codemie-code/ui/progressTracker.js.map +1 -0
- package/dist/agents/codemie-code/ui/todoPanel.d.ts +112 -0
- package/dist/agents/codemie-code/ui/todoPanel.d.ts.map +1 -0
- package/dist/agents/codemie-code/ui/todoPanel.js +318 -0
- package/dist/agents/codemie-code/ui/todoPanel.js.map +1 -0
- package/dist/agents/codemie-code/ui.d.ts +179 -0
- package/dist/agents/codemie-code/ui.d.ts.map +1 -0
- package/dist/agents/codemie-code/ui.js +1408 -0
- package/dist/agents/codemie-code/ui.js.map +1 -0
- package/dist/agents/codemie-code/utils/progressionEnforcer.d.ts +87 -0
- package/dist/agents/codemie-code/utils/progressionEnforcer.d.ts.map +1 -0
- package/dist/agents/codemie-code/utils/progressionEnforcer.js +293 -0
- package/dist/agents/codemie-code/utils/progressionEnforcer.js.map +1 -0
- package/dist/agents/codemie-code/utils/todoParser.d.ts +41 -0
- package/dist/agents/codemie-code/utils/todoParser.d.ts.map +1 -0
- package/dist/agents/codemie-code/utils/todoParser.js +305 -0
- package/dist/agents/codemie-code/utils/todoParser.js.map +1 -0
- package/dist/agents/codemie-code/utils/todoValidator.d.ts +65 -0
- package/dist/agents/codemie-code/utils/todoValidator.d.ts.map +1 -0
- package/dist/agents/codemie-code/utils/todoValidator.js +249 -0
- package/dist/agents/codemie-code/utils/todoValidator.js.map +1 -0
- package/dist/agents/codemie-code/validators/planValidator.d.ts +94 -0
- package/dist/agents/codemie-code/validators/planValidator.d.ts.map +1 -0
- package/dist/agents/codemie-code/validators/planValidator.js +281 -0
- package/dist/agents/codemie-code/validators/planValidator.js.map +1 -0
- package/dist/agents/registry.d.ts +1 -1
- package/dist/agents/registry.d.ts.map +1 -1
- package/dist/agents/registry.js +11 -15
- package/dist/agents/registry.js.map +1 -1
- package/dist/cli/commands/auth.d.ts +3 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +170 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +350 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +308 -71
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/env.d.ts +3 -0
- package/dist/cli/commands/env.d.ts.map +1 -0
- package/dist/cli/commands/env.js +19 -0
- package/dist/cli/commands/env.js.map +1 -0
- package/dist/cli/commands/install.d.ts.map +1 -1
- package/dist/cli/commands/install.js +28 -33
- package/dist/cli/commands/install.js.map +1 -1
- package/dist/cli/commands/list.js +18 -24
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +297 -31
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/setup.d.ts +3 -0
- package/dist/cli/commands/setup.d.ts.map +1 -0
- package/dist/cli/commands/setup.js +523 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/tools.d.ts +6 -0
- package/dist/cli/commands/tools.d.ts.map +1 -0
- package/dist/cli/commands/tools.js +244 -0
- package/dist/cli/commands/tools.js.map +1 -0
- package/dist/cli/commands/uninstall.js +24 -30
- package/dist/cli/commands/uninstall.js.map +1 -1
- package/dist/cli/commands/version.d.ts.map +1 -1
- package/dist/cli/commands/version.js +11 -16
- package/dist/cli/commands/version.js.map +1 -1
- package/dist/cli/commands/workflow.d.ts +6 -0
- package/dist/cli/commands/workflow.d.ts.map +1 -0
- package/dist/cli/commands/workflow.js +424 -0
- package/dist/cli/commands/workflow.js.map +1 -0
- package/dist/cli/index.js +85 -35
- package/dist/cli/index.js.map +1 -1
- package/dist/clients/adapters/github.d.ts +17 -0
- package/dist/clients/adapters/github.d.ts.map +1 -0
- package/dist/clients/adapters/github.js +150 -0
- package/dist/clients/adapters/github.js.map +1 -0
- package/dist/clients/adapters/gitlab.d.ts +17 -0
- package/dist/clients/adapters/gitlab.d.ts.map +1 -0
- package/dist/clients/adapters/gitlab.js +147 -0
- package/dist/clients/adapters/gitlab.js.map +1 -0
- package/dist/clients/registry.d.ts +20 -0
- package/dist/clients/registry.d.ts.map +1 -0
- package/dist/clients/registry.js +27 -0
- package/dist/clients/registry.js.map +1 -0
- package/dist/env/manager.js +9 -46
- package/dist/env/manager.js.map +1 -1
- package/dist/index.d.ts +6 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -43
- package/dist/index.js.map +1 -1
- package/dist/tools/detector.d.ts +33 -0
- package/dist/tools/detector.d.ts.map +1 -0
- package/dist/tools/detector.js +145 -0
- package/dist/tools/detector.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/manager.d.ts +21 -0
- package/dist/tools/manager.d.ts.map +1 -0
- package/dist/tools/manager.js +104 -0
- package/dist/tools/manager.js.map +1 -0
- package/dist/tools/registry.d.ts +8 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +36 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +41 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +5 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/types/sso.d.ts +42 -0
- package/dist/types/sso.d.ts.map +1 -0
- package/dist/types/sso.js +2 -0
- package/dist/types/sso.js.map +1 -0
- package/dist/utils/agent-compatibility.d.ts +32 -0
- package/dist/utils/agent-compatibility.d.ts.map +1 -0
- package/dist/utils/agent-compatibility.js +140 -0
- package/dist/utils/agent-compatibility.js.map +1 -0
- package/dist/utils/async-tips.d.ts.map +1 -1
- package/dist/utils/async-tips.js +16 -55
- package/dist/utils/async-tips.js.map +1 -1
- package/dist/utils/clipboard.d.ts +16 -0
- package/dist/utils/clipboard.d.ts.map +1 -0
- package/dist/utils/clipboard.js +179 -0
- package/dist/utils/clipboard.js.map +1 -0
- package/dist/utils/codemie-integration-validator.d.ts +17 -0
- package/dist/utils/codemie-integration-validator.d.ts.map +1 -0
- package/dist/utils/codemie-integration-validator.js +105 -0
- package/dist/utils/codemie-integration-validator.js.map +1 -0
- package/dist/utils/codemie-model-fetcher.d.ts +11 -0
- package/dist/utils/codemie-model-fetcher.d.ts.map +1 -0
- package/dist/utils/codemie-model-fetcher.js +242 -0
- package/dist/utils/codemie-model-fetcher.js.map +1 -0
- package/dist/utils/config-loader.d.ts +118 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-loader.js +397 -0
- package/dist/utils/config-loader.js.map +1 -0
- package/dist/utils/credential-store.d.ts +16 -0
- package/dist/utils/credential-store.d.ts.map +1 -0
- package/dist/utils/credential-store.js +109 -0
- package/dist/utils/credential-store.js.map +1 -0
- package/dist/utils/dirname.d.ts +7 -0
- package/dist/utils/dirname.d.ts.map +1 -0
- package/dist/utils/dirname.js +11 -0
- package/dist/utils/dirname.js.map +1 -0
- package/dist/utils/errors.js +7 -17
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/exec.js +3 -6
- package/dist/utils/exec.js.map +1 -1
- package/dist/utils/first-time.d.ts +34 -0
- package/dist/utils/first-time.d.ts.map +1 -0
- package/dist/utils/first-time.js +226 -0
- package/dist/utils/first-time.js.map +1 -0
- package/dist/utils/health-checker.d.ts +20 -0
- package/dist/utils/health-checker.d.ts.map +1 -0
- package/dist/utils/health-checker.js +172 -0
- package/dist/utils/health-checker.js.map +1 -0
- package/dist/utils/logger.js +12 -18
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/model-fetcher.d.ts +21 -0
- package/dist/utils/model-fetcher.d.ts.map +1 -0
- package/dist/utils/model-fetcher.js +150 -0
- package/dist/utils/model-fetcher.js.map +1 -0
- package/dist/utils/sso-auth.d.ts +15 -0
- package/dist/utils/sso-auth.d.ts.map +1 -0
- package/dist/utils/sso-auth.js +207 -0
- package/dist/utils/sso-auth.js.map +1 -0
- package/dist/utils/sso-gateway.d.ts +47 -0
- package/dist/utils/sso-gateway.d.ts.map +1 -0
- package/dist/utils/sso-gateway.js +298 -0
- package/dist/utils/sso-gateway.js.map +1 -0
- package/dist/utils/tips.d.ts.map +1 -1
- package/dist/utils/tips.js +13 -52
- package/dist/utils/tips.js.map +1 -1
- package/dist/workflows/detector.d.ts +37 -0
- package/dist/workflows/detector.d.ts.map +1 -0
- package/dist/workflows/detector.js +160 -0
- package/dist/workflows/detector.js.map +1 -0
- package/dist/workflows/index.d.ts +8 -0
- package/dist/workflows/index.d.ts.map +1 -0
- package/dist/workflows/index.js +8 -0
- package/dist/workflows/index.js.map +1 -0
- package/dist/workflows/installer.d.ts +24 -0
- package/dist/workflows/installer.d.ts.map +1 -0
- package/dist/workflows/installer.js +105 -0
- package/dist/workflows/installer.js.map +1 -0
- package/dist/workflows/registry.d.ts +29 -0
- package/dist/workflows/registry.d.ts.map +1 -0
- package/dist/workflows/registry.js +54 -0
- package/dist/workflows/registry.js.map +1 -0
- package/dist/workflows/templates/github/metadata.d.ts +6 -0
- package/dist/workflows/templates/github/metadata.d.ts.map +1 -0
- package/dist/workflows/templates/github/metadata.js +111 -0
- package/dist/workflows/templates/github/metadata.js.map +1 -0
- package/dist/workflows/templates/gitlab/metadata.d.ts +6 -0
- package/dist/workflows/templates/gitlab/metadata.d.ts.map +1 -0
- package/dist/workflows/templates/gitlab/metadata.js +14 -0
- package/dist/workflows/templates/gitlab/metadata.js.map +1 -0
- package/dist/workflows/types.d.ts +71 -0
- package/dist/workflows/types.d.ts.map +1 -0
- package/dist/workflows/types.js +5 -0
- package/dist/workflows/types.js.map +1 -0
- package/package.json +32 -25
- package/src/workflows/templates/github/code-ci.yml +529 -0
- package/src/workflows/templates/github/inline-fix.yml +665 -0
- package/src/workflows/templates/github/pr-review.yml +677 -0
- package/.claude/agents/README.md +0 -298
- package/.claude/agents/release-manager.md +0 -857
- package/.codemie/guides/git-workflow.md +0 -493
- package/CLAUDE.md +0 -855
- package/dist/agents/adapters/aider.d.ts +0 -12
- package/dist/agents/adapters/aider.d.ts.map +0 -1
- package/dist/agents/adapters/aider.js +0 -80
- package/dist/agents/adapters/aider.js.map +0 -1
- package/dist/cli/cli.d.ts +0 -4
- package/dist/cli/cli.d.ts.map +0 -1
- package/dist/cli/cli.js +0 -107
- package/dist/cli/cli.js.map +0 -1
- package/dist/cli/commands/mcp.d.ts +0 -3
- package/dist/cli/commands/mcp.d.ts.map +0 -1
- package/dist/cli/commands/mcp.js +0 -459
- package/dist/cli/commands/mcp.js.map +0 -1
- package/dist/code/agent-events.d.ts +0 -39
- package/dist/code/agent-events.d.ts.map +0 -1
- package/dist/code/agent-events.js +0 -4
- package/dist/code/agent-events.js.map +0 -1
- package/dist/code/agent.d.ts +0 -19
- package/dist/code/agent.d.ts.map +0 -1
- package/dist/code/agent.js +0 -144
- package/dist/code/agent.js.map +0 -1
- package/dist/code/config.d.ts +0 -13
- package/dist/code/config.d.ts.map +0 -1
- package/dist/code/config.js +0 -41
- package/dist/code/config.js.map +0 -1
- package/dist/code/index.d.ts +0 -19
- package/dist/code/index.d.ts.map +0 -1
- package/dist/code/index.js +0 -400
- package/dist/code/index.js.map +0 -1
- package/dist/code/prompts.d.ts +0 -2
- package/dist/code/prompts.d.ts.map +0 -1
- package/dist/code/prompts.js +0 -45
- package/dist/code/prompts.js.map +0 -1
- package/dist/code/tools/command.d.ts +0 -8
- package/dist/code/tools/command.d.ts.map +0 -1
- package/dist/code/tools/command.js +0 -83
- package/dist/code/tools/command.js.map +0 -1
- package/dist/code/tools/diff-utils.d.ts +0 -2
- package/dist/code/tools/diff-utils.d.ts.map +0 -1
- package/dist/code/tools/diff-utils.js +0 -45
- package/dist/code/tools/diff-utils.js.map +0 -1
- package/dist/code/tools/filesystem.d.ts +0 -11
- package/dist/code/tools/filesystem.d.ts.map +0 -1
- package/dist/code/tools/filesystem.js +0 -442
- package/dist/code/tools/filesystem.js.map +0 -1
- package/dist/code/tools/git.d.ts +0 -7
- package/dist/code/tools/git.d.ts.map +0 -1
- package/dist/code/tools/git.js +0 -111
- package/dist/code/tools/git.js.map +0 -1
- package/dist/code/tools/mcp.d.ts +0 -13
- package/dist/code/tools/mcp.d.ts.map +0 -1
- package/dist/code/tools/mcp.js +0 -230
- package/dist/code/tools/mcp.js.map +0 -1
- package/dist/data/tips.json +0 -118
- package/dist/ui/terminal-ui.d.ts +0 -73
- package/dist/ui/terminal-ui.d.ts.map +0 -1
- package/dist/ui/terminal-ui.js +0 -900
- package/dist/ui/terminal-ui.js.map +0 -1
- package/dist/utils/env-mapper.d.ts +0 -40
- package/dist/utils/env-mapper.d.ts.map +0 -1
- package/dist/utils/env-mapper.js +0 -122
- package/dist/utils/env-mapper.js.map +0 -1
- package/docs/USER_GUIDE.md +0 -573
- package/eslint.config.mjs +0 -43
- package/tests/agent-direct.test.mjs +0 -45
- package/tests/agent-output.test.mjs +0 -64
- package/tests/codemie-code.test.mjs +0 -42
- package/tests/context7-only.test.mjs +0 -42
- package/tests/conversation-flow.test.mjs +0 -63
- package/tests/interactive-simulation.test.mjs +0 -60
- package/tests/live-output.test.mjs +0 -53
- package/tests/mcp-context7.test.mjs +0 -105
- package/tests/mcp-e2e.test.mjs +0 -109
- package/tests/mcp-time-server.test.mjs +0 -58
- package/tests/streaming.test.mjs +0 -57
- package/tests/test-helpers.mjs +0 -94
- package/tests/text-wrapping.test.mjs +0 -33
- package/tests/tool-count.test.mjs +0 -81
- package/tests/ui-format.test.mjs +0 -39
- package/tests/ui-state.test.mjs +0 -72
|
@@ -0,0 +1,1408 @@
|
|
|
1
|
+
import { intro, outro, text, spinner, note, isCancel } from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { formatToolMetadata } from './toolMetadata.js';
|
|
4
|
+
import { formatCost, formatTokens, formatTokenUsageSummary } from './tokenUtils.js';
|
|
5
|
+
import { hasClipboardImage, getClipboardImage } from '../../utils/clipboard.js';
|
|
6
|
+
import { TodoPanel } from './ui/todoPanel.js';
|
|
7
|
+
import { getProgressTracker } from './ui/progressTracker.js';
|
|
8
|
+
import { TodoStateManager } from './tools/planning.js';
|
|
9
|
+
/**
|
|
10
|
+
* Terminal UI interface for CodeMie Agent using Clack
|
|
11
|
+
*/
|
|
12
|
+
export class CodeMieTerminalUI {
|
|
13
|
+
agent;
|
|
14
|
+
currentSpinner;
|
|
15
|
+
todoPanel;
|
|
16
|
+
progressTracker;
|
|
17
|
+
planMode = false;
|
|
18
|
+
activePlanningPhase = null;
|
|
19
|
+
constructor(agent) {
|
|
20
|
+
this.agent = agent;
|
|
21
|
+
this.todoPanel = new TodoPanel({
|
|
22
|
+
showProgress: true,
|
|
23
|
+
compact: false
|
|
24
|
+
});
|
|
25
|
+
this.progressTracker = getProgressTracker({
|
|
26
|
+
realTimeUpdates: true,
|
|
27
|
+
showCelebrations: true,
|
|
28
|
+
compact: true
|
|
29
|
+
});
|
|
30
|
+
// Register for todo update events
|
|
31
|
+
TodoStateManager.addEventCallback(this.handleTodoUpdate.bind(this));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Start interactive terminal session
|
|
35
|
+
*/
|
|
36
|
+
async startInteractive() {
|
|
37
|
+
// Welcome message
|
|
38
|
+
intro(chalk.cyan('š¤ CodeMie Native Agent'));
|
|
39
|
+
const config = this.agent.getConfig();
|
|
40
|
+
if (config) {
|
|
41
|
+
// Use displayProvider for user-facing output, or fall back to normalized provider
|
|
42
|
+
const displayProvider = config.displayProvider || config.provider;
|
|
43
|
+
console.log(chalk.cyan('ā Configuration'));
|
|
44
|
+
console.log(` Provider: ${chalk.yellow(displayProvider)}`);
|
|
45
|
+
console.log(` Model: ${chalk.cyan(config.model)}`);
|
|
46
|
+
console.log(` Working Directory: ${chalk.dim(config.workingDirectory)}`);
|
|
47
|
+
console.log(` Mode: ${this.planMode ? chalk.green('Plan Mode') : chalk.yellow('Direct Mode')}`);
|
|
48
|
+
console.log('');
|
|
49
|
+
}
|
|
50
|
+
console.log(chalk.dim('Type /help for commands, /exit to quit'));
|
|
51
|
+
console.log(chalk.dim('Enter = send, Shift+Enter = new line, Cmd+V = paste text'));
|
|
52
|
+
console.log(chalk.dim('šø Tab = insert clipboard image ⢠Multiple images supported'));
|
|
53
|
+
console.log(chalk.dim('š” Press Ctrl+H for hotkeys, Ctrl+P to toggle plan mode\n'));
|
|
54
|
+
// Main interaction loop
|
|
55
|
+
while (true) {
|
|
56
|
+
const input = await this.getMultilineInput();
|
|
57
|
+
if (input === null) {
|
|
58
|
+
outro(chalk.dim('Goodbye! š'));
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
const trimmed = input.text.trim();
|
|
62
|
+
// Handle special commands
|
|
63
|
+
if (trimmed.startsWith('/')) {
|
|
64
|
+
const handled = await this.handleCommand(trimmed);
|
|
65
|
+
if (handled === 'exit')
|
|
66
|
+
break;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (trimmed === '')
|
|
70
|
+
continue;
|
|
71
|
+
// Execute the task with streaming UI (respecting current mode)
|
|
72
|
+
await this.executeTaskWithCurrentMode(trimmed, input.images);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get input from user with Shift+Enter multiline support and image pasting
|
|
77
|
+
*/
|
|
78
|
+
async getMultilineInput() {
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
if (!process.stdin.setRawMode) {
|
|
81
|
+
// Fallback for environments without raw mode
|
|
82
|
+
this.getFallbackInput().then(resolve).catch(() => resolve(null));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
let cleanupDone = false;
|
|
86
|
+
const performCleanup = () => {
|
|
87
|
+
if (cleanupDone)
|
|
88
|
+
return;
|
|
89
|
+
cleanupDone = true;
|
|
90
|
+
try {
|
|
91
|
+
if (process.stdin.setRawMode) {
|
|
92
|
+
process.stdin.setRawMode(false);
|
|
93
|
+
}
|
|
94
|
+
process.stdin.pause();
|
|
95
|
+
process.stdin.removeAllListeners('data');
|
|
96
|
+
process.stdin.removeAllListeners('error');
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Ignore cleanup errors
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
// Set up error handling first
|
|
103
|
+
process.stdin.once('error', (error) => {
|
|
104
|
+
performCleanup();
|
|
105
|
+
const config = this.agent.getConfig();
|
|
106
|
+
if (config?.debug) {
|
|
107
|
+
console.error('[DEBUG] Stdin error:', error);
|
|
108
|
+
}
|
|
109
|
+
resolve(null);
|
|
110
|
+
});
|
|
111
|
+
try {
|
|
112
|
+
process.stdin.setRawMode(true);
|
|
113
|
+
process.stdin.resume();
|
|
114
|
+
process.stdin.setEncoding('utf8');
|
|
115
|
+
let lines = [];
|
|
116
|
+
let currentLine = '';
|
|
117
|
+
let isFirstLine = true;
|
|
118
|
+
let escapeSequence = '';
|
|
119
|
+
let images = [];
|
|
120
|
+
let imageCounter = 0;
|
|
121
|
+
const writePrompt = () => {
|
|
122
|
+
try {
|
|
123
|
+
const prompt = isFirstLine ? '> ' : '... ';
|
|
124
|
+
process.stdout.write(prompt);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Ignore prompt write errors
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
// Add timeout to prevent hanging indefinitely
|
|
131
|
+
const inputTimeout = setTimeout(() => {
|
|
132
|
+
performCleanup();
|
|
133
|
+
resolve(null);
|
|
134
|
+
}, 30000); // 30 second timeout
|
|
135
|
+
writePrompt();
|
|
136
|
+
process.stdin.on('data', (key) => {
|
|
137
|
+
if (cleanupDone)
|
|
138
|
+
return; // Prevent processing after cleanup
|
|
139
|
+
try {
|
|
140
|
+
const data = key.toString('utf8');
|
|
141
|
+
// Handle escape sequences
|
|
142
|
+
if (data.startsWith('\x1b')) {
|
|
143
|
+
escapeSequence += data;
|
|
144
|
+
// Wait for complete escape sequence (timeout after short delay)
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
escapeSequence = '';
|
|
147
|
+
}, 10);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Check for Shift+Enter patterns on macOS
|
|
151
|
+
// Shift+Enter in macOS Terminal typically sends: \r\n or \n\r
|
|
152
|
+
if (data === '\r\n' || data === '\n\r' || (escapeSequence && data === '\r')) {
|
|
153
|
+
// This is Shift+Enter - add line and continue
|
|
154
|
+
lines.push(currentLine);
|
|
155
|
+
currentLine = '';
|
|
156
|
+
isFirstLine = false;
|
|
157
|
+
process.stdout.write('\n');
|
|
158
|
+
writePrompt();
|
|
159
|
+
escapeSequence = '';
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// Regular Enter - send message
|
|
163
|
+
if (data === '\r' || data === '\n') {
|
|
164
|
+
if (currentLine.trim() === '' && lines.length === 0) {
|
|
165
|
+
// Empty input, continue asking
|
|
166
|
+
writePrompt();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
// Send the message
|
|
170
|
+
if (currentLine.trim() !== '') {
|
|
171
|
+
lines.push(currentLine);
|
|
172
|
+
}
|
|
173
|
+
process.stdout.write('\n');
|
|
174
|
+
clearTimeout(inputTimeout);
|
|
175
|
+
performCleanup();
|
|
176
|
+
resolve({
|
|
177
|
+
text: lines.join('\n'),
|
|
178
|
+
images: images
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Ctrl+C
|
|
183
|
+
if (data === '\u0003') {
|
|
184
|
+
clearTimeout(inputTimeout);
|
|
185
|
+
performCleanup();
|
|
186
|
+
resolve(null);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// Hotkeys for mode switching
|
|
190
|
+
// Ctrl+P - Toggle plan mode
|
|
191
|
+
if (data === '\u0010') {
|
|
192
|
+
this.handleHotkey('toggle-plan-mode');
|
|
193
|
+
// Mode change notification is handled in showModeChangeNotification()
|
|
194
|
+
writePrompt();
|
|
195
|
+
process.stdout.write(currentLine);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// Ctrl+H - Show hotkey help
|
|
199
|
+
if (data === '\u0008') {
|
|
200
|
+
this.showHotkeyHelp();
|
|
201
|
+
writePrompt();
|
|
202
|
+
process.stdout.write(currentLine);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Ctrl+T - Show current todos
|
|
206
|
+
if (data === '\u0014') {
|
|
207
|
+
this.handleHotkey('show-todos');
|
|
208
|
+
writePrompt();
|
|
209
|
+
process.stdout.write(currentLine);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
// Ctrl+S - Show current mode status (changed from Ctrl+M to avoid Enter conflict)
|
|
213
|
+
if (data === '\u0013') {
|
|
214
|
+
this.showModeStatus();
|
|
215
|
+
writePrompt();
|
|
216
|
+
process.stdout.write(currentLine);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Alt+M - Show mode status (Alt key sequences start with \x1b)
|
|
220
|
+
if (data === 'm' && escapeSequence.includes('\x1b')) {
|
|
221
|
+
this.showModeStatus();
|
|
222
|
+
writePrompt();
|
|
223
|
+
process.stdout.write(currentLine);
|
|
224
|
+
escapeSequence = '';
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// Ctrl+I - Insert image from clipboard (Tab key)
|
|
228
|
+
if (data === '\u0009') {
|
|
229
|
+
// Check if there's an image in clipboard
|
|
230
|
+
hasClipboardImage().then(hasImage => {
|
|
231
|
+
if (hasImage) {
|
|
232
|
+
getClipboardImage().then(clipboardImage => {
|
|
233
|
+
if (clipboardImage) {
|
|
234
|
+
imageCounter++;
|
|
235
|
+
images.push(clipboardImage);
|
|
236
|
+
// Insert visual indicator in current line
|
|
237
|
+
const imageIndicator = chalk.blueBright(`[Image #${imageCounter}]`);
|
|
238
|
+
currentLine += imageIndicator;
|
|
239
|
+
process.stdout.write(imageIndicator);
|
|
240
|
+
console.log(chalk.green(`\nšø Image #${imageCounter} added from clipboard (${clipboardImage.mimeType})`));
|
|
241
|
+
writePrompt();
|
|
242
|
+
process.stdout.write(currentLine);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
console.log(chalk.yellow('\nā ļø No image found in clipboard'));
|
|
248
|
+
writePrompt();
|
|
249
|
+
process.stdout.write(currentLine);
|
|
250
|
+
}
|
|
251
|
+
}).catch(() => {
|
|
252
|
+
console.log(chalk.rgb(255, 120, 120)('\nā Error accessing clipboard'));
|
|
253
|
+
writePrompt();
|
|
254
|
+
process.stdout.write(currentLine);
|
|
255
|
+
});
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// Backspace
|
|
259
|
+
if (data === '\u007F' || data === '\b') {
|
|
260
|
+
if (currentLine.length > 0) {
|
|
261
|
+
currentLine = currentLine.slice(0, -1);
|
|
262
|
+
process.stdout.write('\b \b');
|
|
263
|
+
}
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// Handle clipboard paste (Cmd+V on macOS, Ctrl+V on Windows/Linux)
|
|
267
|
+
// Pasted content can be multiple characters, so handle any printable text
|
|
268
|
+
if (data.length > 1 || (data.length === 1 && (data.charCodeAt(0) >= 32 || data === '\t'))) {
|
|
269
|
+
// Filter out non-printable characters except tabs
|
|
270
|
+
const printableData = data.split('').filter(char => char.charCodeAt(0) >= 32 || char === '\t').join('');
|
|
271
|
+
if (printableData.length > 0) {
|
|
272
|
+
currentLine += printableData;
|
|
273
|
+
process.stdout.write(printableData);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
escapeSequence = '';
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
// Handle data processing errors gracefully
|
|
280
|
+
const config = this.agent.getConfig();
|
|
281
|
+
if (config?.debug) {
|
|
282
|
+
console.error('[DEBUG] Input processing error:', error);
|
|
283
|
+
}
|
|
284
|
+
// Continue processing - don't crash on single key errors
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
performCleanup();
|
|
290
|
+
const config = this.agent.getConfig();
|
|
291
|
+
if (config?.debug) {
|
|
292
|
+
console.error('[DEBUG] Input setup error:', error);
|
|
293
|
+
}
|
|
294
|
+
resolve(null);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Fallback input method for environments without raw mode
|
|
300
|
+
*/
|
|
301
|
+
async getFallbackInput() {
|
|
302
|
+
const input = await text({
|
|
303
|
+
message: '>',
|
|
304
|
+
placeholder: 'Type your message... (Tab to insert clipboard image)'
|
|
305
|
+
});
|
|
306
|
+
if (isCancel(input)) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
const textInput = input === undefined || input === null ? '' : String(input);
|
|
310
|
+
// In fallback mode, we can't handle interactive image insertion
|
|
311
|
+
// So just return text with empty images array
|
|
312
|
+
return {
|
|
313
|
+
text: textInput,
|
|
314
|
+
images: []
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Handle special commands
|
|
319
|
+
*/
|
|
320
|
+
async handleCommand(command) {
|
|
321
|
+
const [cmd] = command.slice(1).split(' ');
|
|
322
|
+
switch (cmd) {
|
|
323
|
+
case 'help':
|
|
324
|
+
note(`${chalk.cyan('/help')} - Show this help message\n` +
|
|
325
|
+
`${chalk.cyan('/clear')} - Clear conversation history\n` +
|
|
326
|
+
`${chalk.cyan('/stats')} - Show agent statistics\n` +
|
|
327
|
+
`${chalk.cyan('/todos')} - Show current todo list and progress\n` +
|
|
328
|
+
`${chalk.cyan('/config')} - Show configuration\n` +
|
|
329
|
+
`${chalk.cyan('/health')} - Run health check\n` +
|
|
330
|
+
`${chalk.cyan('/exit')} - Exit the agent\n\n` +
|
|
331
|
+
`${chalk.yellow('Hotkeys:')}\n` +
|
|
332
|
+
`- ${chalk.cyan('Ctrl+P')} - Toggle plan mode on/off\n` +
|
|
333
|
+
`- ${chalk.cyan('Ctrl+H')} - Show detailed hotkey help\n` +
|
|
334
|
+
`- ${chalk.cyan('Ctrl+T')} - Show current todo list\n` +
|
|
335
|
+
`- ${chalk.cyan('Ctrl+S')} - Show current mode status\n\n` +
|
|
336
|
+
`${chalk.yellow('Input Controls:')}\n` +
|
|
337
|
+
`- ${chalk.cyan('Enter')} - Send message\n` +
|
|
338
|
+
`- ${chalk.cyan('Shift+Enter')} - New line (multiline input)\n` +
|
|
339
|
+
`- ${chalk.cyan('Cmd+V / Ctrl+V')} - Paste text from clipboard\n` +
|
|
340
|
+
`- ${chalk.cyan('Tab')} - Insert image from clipboard\n` +
|
|
341
|
+
`- ${chalk.cyan('Ctrl+C')} - Cancel current input\n\n` +
|
|
342
|
+
`${chalk.yellow('Image Support:')}\n` +
|
|
343
|
+
`- Copy image/screenshot to clipboard\n` +
|
|
344
|
+
`- Press ${chalk.cyan('Tab')} to insert as ${chalk.blueBright('[Image #N]')}\n` +
|
|
345
|
+
`- Multiple images supported per message\n` +
|
|
346
|
+
`- AI analyzes both text and all images`, 'Available Commands');
|
|
347
|
+
break;
|
|
348
|
+
case 'clear':
|
|
349
|
+
this.agent.clearHistory();
|
|
350
|
+
note('Conversation history cleared', 'History');
|
|
351
|
+
break;
|
|
352
|
+
case 'stats':
|
|
353
|
+
await this.showStats();
|
|
354
|
+
break;
|
|
355
|
+
case 'todos':
|
|
356
|
+
await this.showTodos();
|
|
357
|
+
break;
|
|
358
|
+
case 'config':
|
|
359
|
+
await this.showConfig();
|
|
360
|
+
break;
|
|
361
|
+
case 'health':
|
|
362
|
+
await this.runHealthCheck();
|
|
363
|
+
break;
|
|
364
|
+
case 'exit':
|
|
365
|
+
outro(chalk.dim('Goodbye! š'));
|
|
366
|
+
return 'exit';
|
|
367
|
+
default:
|
|
368
|
+
note(chalk.rgb(255, 120, 120)(`Unknown command: ${command}\nType /help for available commands`), 'Error');
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Execute task with modern UI feedback
|
|
374
|
+
*/
|
|
375
|
+
async executeTaskWithUI(task, images = []) {
|
|
376
|
+
let hasStarted = false;
|
|
377
|
+
let toolCallCount = 0;
|
|
378
|
+
try {
|
|
379
|
+
// Show image summary if any images were provided
|
|
380
|
+
if (images.length > 0) {
|
|
381
|
+
const imageTypes = images.map(img => img.mimeType).join(', ');
|
|
382
|
+
note(chalk.green(`šø ${images.length} image${images.length > 1 ? 's' : ''} included: ${imageTypes}`), 'Image Input');
|
|
383
|
+
}
|
|
384
|
+
// Start streaming with visual feedback
|
|
385
|
+
await this.agent.chatStream(task, (event) => {
|
|
386
|
+
switch (event.type) {
|
|
387
|
+
case 'thinking_start':
|
|
388
|
+
// Show "Thinking..." spinner when the agent starts processing
|
|
389
|
+
if (!this.currentSpinner) {
|
|
390
|
+
this.currentSpinner = spinner();
|
|
391
|
+
}
|
|
392
|
+
this.currentSpinner.start(chalk.dim('Thinking...'));
|
|
393
|
+
break;
|
|
394
|
+
case 'content_chunk':
|
|
395
|
+
if (!hasStarted) {
|
|
396
|
+
// Stop any existing spinner and start response
|
|
397
|
+
if (this.currentSpinner) {
|
|
398
|
+
this.currentSpinner.stop();
|
|
399
|
+
this.currentSpinner = undefined;
|
|
400
|
+
}
|
|
401
|
+
console.log(chalk.cyan('\nCodeMie Thoughts:'));
|
|
402
|
+
hasStarted = true;
|
|
403
|
+
}
|
|
404
|
+
process.stdout.write(event.content || '');
|
|
405
|
+
break;
|
|
406
|
+
case 'tool_call_start':
|
|
407
|
+
toolCallCount++;
|
|
408
|
+
if (!this.currentSpinner) {
|
|
409
|
+
this.currentSpinner = spinner();
|
|
410
|
+
}
|
|
411
|
+
this.currentSpinner.start(chalk.yellow(`Using ${event.toolName}...`));
|
|
412
|
+
break;
|
|
413
|
+
case 'tool_call_progress':
|
|
414
|
+
if (this.currentSpinner && event.toolProgress) {
|
|
415
|
+
const { percentage, operation, details } = event.toolProgress;
|
|
416
|
+
const progressBar = this.createToolProgressBar(percentage);
|
|
417
|
+
let message = `${operation} ${Math.round(percentage)}% ${progressBar}`;
|
|
418
|
+
if (details) {
|
|
419
|
+
message += ` (${details})`;
|
|
420
|
+
}
|
|
421
|
+
this.currentSpinner.message(chalk.cyan(message));
|
|
422
|
+
}
|
|
423
|
+
break;
|
|
424
|
+
case 'tool_call_result':
|
|
425
|
+
if (this.currentSpinner) {
|
|
426
|
+
// Use enhanced metadata if available, otherwise fall back to basic message
|
|
427
|
+
const message = event.toolMetadata
|
|
428
|
+
? formatToolMetadata(event.toolName || 'tool', event.toolMetadata)
|
|
429
|
+
: `ā ${event.toolName} completed`;
|
|
430
|
+
this.currentSpinner.stop(chalk.green(message));
|
|
431
|
+
// Show additional details if available and not an error (but avoid duplication)
|
|
432
|
+
if (event.toolMetadata && event.toolMetadata.success && this.shouldShowDetails(event.toolName || '')) {
|
|
433
|
+
this.showToolDetails(event.toolName || '', event.toolMetadata);
|
|
434
|
+
}
|
|
435
|
+
this.currentSpinner = undefined;
|
|
436
|
+
}
|
|
437
|
+
break;
|
|
438
|
+
case 'complete':
|
|
439
|
+
if (this.currentSpinner) {
|
|
440
|
+
this.currentSpinner.stop();
|
|
441
|
+
this.currentSpinner = undefined;
|
|
442
|
+
}
|
|
443
|
+
if (hasStarted) {
|
|
444
|
+
console.log('\n'); // Add spacing after response
|
|
445
|
+
}
|
|
446
|
+
// Show detailed task summary
|
|
447
|
+
this.showTaskSummary(toolCallCount);
|
|
448
|
+
break;
|
|
449
|
+
case 'error':
|
|
450
|
+
if (this.currentSpinner) {
|
|
451
|
+
this.currentSpinner.stop();
|
|
452
|
+
this.currentSpinner = undefined;
|
|
453
|
+
}
|
|
454
|
+
note(chalk.red(`Error: ${event.error}`), 'Error');
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
}, images);
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
if (this.currentSpinner) {
|
|
461
|
+
this.currentSpinner.stop();
|
|
462
|
+
this.currentSpinner = undefined;
|
|
463
|
+
}
|
|
464
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
465
|
+
note(chalk.red(`Execution failed: ${errorMessage}`), 'Error');
|
|
466
|
+
}
|
|
467
|
+
finally {
|
|
468
|
+
// Cleanup complete
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Show agent statistics
|
|
473
|
+
*/
|
|
474
|
+
async showStats() {
|
|
475
|
+
const stats = this.agent.getStats();
|
|
476
|
+
if (!stats) {
|
|
477
|
+
note('No statistics available', 'Stats');
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const statsText = [
|
|
481
|
+
`${chalk.yellow('Token Usage:')}`,
|
|
482
|
+
` Input: ${chalk.cyan(formatTokens(stats.inputTokens))}`,
|
|
483
|
+
` Output: ${chalk.cyan(formatTokens(stats.outputTokens))}${stats.cachedTokens > 0 ? ` + ${chalk.green(formatTokens(stats.cachedTokens))} cached` : ''}`,
|
|
484
|
+
` Total: ${chalk.cyan(formatTokens(stats.totalTokens))}`,
|
|
485
|
+
` Cost: ${chalk.green(formatCost(stats.estimatedTotalCost))}`,
|
|
486
|
+
'',
|
|
487
|
+
`${chalk.yellow('Execution Stats:')}`,
|
|
488
|
+
` LLM Calls: ${chalk.cyan(stats.llmCalls)}`,
|
|
489
|
+
` Tool Calls: ${chalk.cyan(stats.toolCalls)} (${chalk.green(stats.successfulTools)} success, ${chalk.red(stats.failedTools)} failed)`,
|
|
490
|
+
` Total Steps: ${chalk.cyan(stats.executionSteps.length)}`,
|
|
491
|
+
` Duration: ${chalk.dim(stats.executionTime + 'ms')}`
|
|
492
|
+
].join('\n');
|
|
493
|
+
note(statsText, 'Agent Statistics');
|
|
494
|
+
// Show detailed step breakdown if available
|
|
495
|
+
if (stats.executionSteps.length > 0) {
|
|
496
|
+
this.showExecutionSteps(stats.executionSteps);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Show current configuration
|
|
501
|
+
*/
|
|
502
|
+
async showConfig() {
|
|
503
|
+
const config = this.agent.getConfig();
|
|
504
|
+
if (!config) {
|
|
505
|
+
note('No configuration available', 'Config');
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
// Use displayProvider for user-facing output, or fall back to normalized provider
|
|
509
|
+
const displayProvider = config.displayProvider || config.provider;
|
|
510
|
+
const configText = [
|
|
511
|
+
`Provider: ${chalk.yellow(displayProvider)}`,
|
|
512
|
+
`Model: ${chalk.cyan(config.model)}`,
|
|
513
|
+
`Base URL: ${chalk.dim(config.baseUrl)}`,
|
|
514
|
+
`Working Directory: ${chalk.dim(config.workingDirectory)}`,
|
|
515
|
+
`Debug Mode: ${config.debug ? chalk.green('enabled') : chalk.dim('disabled')}`,
|
|
516
|
+
`Timeout: ${chalk.dim(config.timeout + 'ms')}`
|
|
517
|
+
].join('\n');
|
|
518
|
+
note(configText, 'Configuration');
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Run health check with UI feedback
|
|
522
|
+
*/
|
|
523
|
+
async runHealthCheck() {
|
|
524
|
+
const healthSpinner = spinner();
|
|
525
|
+
healthSpinner.start('Running health check...');
|
|
526
|
+
try {
|
|
527
|
+
const health = await this.agent.healthCheck();
|
|
528
|
+
if (health.status === 'healthy') {
|
|
529
|
+
healthSpinner.stop(chalk.green('ā System is healthy'));
|
|
530
|
+
const healthText = [
|
|
531
|
+
`Status: ${chalk.green('Healthy')}`,
|
|
532
|
+
`Provider: ${chalk.yellow(health.provider)}`,
|
|
533
|
+
`Model: ${chalk.cyan(health.model)}`,
|
|
534
|
+
`Tools: ${chalk.cyan(health.toolCount)}`
|
|
535
|
+
].join('\n');
|
|
536
|
+
note(healthText, 'Health Check');
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
healthSpinner.stop(chalk.red('ā System is unhealthy'));
|
|
540
|
+
note(chalk.red(health.error || 'Unknown error'), 'Health Check Failed');
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
healthSpinner.stop(chalk.red('ā Health check failed'));
|
|
545
|
+
note(chalk.red(error instanceof Error ? error.message : String(error)), 'Error');
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Format uptime duration
|
|
550
|
+
*/
|
|
551
|
+
formatUptime(uptimeMs) {
|
|
552
|
+
const seconds = Math.floor(uptimeMs / 1000);
|
|
553
|
+
const minutes = Math.floor(seconds / 60);
|
|
554
|
+
const hours = Math.floor(minutes / 60);
|
|
555
|
+
if (hours > 0) {
|
|
556
|
+
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
|
|
557
|
+
}
|
|
558
|
+
else if (minutes > 0) {
|
|
559
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
return `${seconds}s`;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Execute a single task with minimal UI (for non-interactive mode)
|
|
567
|
+
*/
|
|
568
|
+
async executeSingleTask(task, images = []) {
|
|
569
|
+
const taskSpinner = spinner();
|
|
570
|
+
let response = '';
|
|
571
|
+
let toolCallCount = 0;
|
|
572
|
+
let hasStarted = false;
|
|
573
|
+
try {
|
|
574
|
+
await this.agent.chatStream(task, (event) => {
|
|
575
|
+
switch (event.type) {
|
|
576
|
+
case 'thinking_start':
|
|
577
|
+
// Show "Thinking..." when the agent starts processing
|
|
578
|
+
taskSpinner.start(chalk.dim('Thinking...'));
|
|
579
|
+
break;
|
|
580
|
+
case 'content_chunk':
|
|
581
|
+
if (!hasStarted) {
|
|
582
|
+
// Switch from "Thinking..." to "Processing task..." when content starts
|
|
583
|
+
taskSpinner.message(chalk.dim('Processing task...'));
|
|
584
|
+
hasStarted = true;
|
|
585
|
+
}
|
|
586
|
+
response += event.content || '';
|
|
587
|
+
break;
|
|
588
|
+
case 'tool_call_start':
|
|
589
|
+
toolCallCount++;
|
|
590
|
+
taskSpinner.message(chalk.yellow(`Using ${event.toolName}...`));
|
|
591
|
+
break;
|
|
592
|
+
case 'tool_call_progress':
|
|
593
|
+
if (event.toolProgress) {
|
|
594
|
+
const { percentage, operation, details } = event.toolProgress;
|
|
595
|
+
const progressBar = this.createToolProgressBar(percentage);
|
|
596
|
+
let message = `${operation} ${Math.round(percentage)}% ${progressBar}`;
|
|
597
|
+
if (details) {
|
|
598
|
+
message += ` (${details})`;
|
|
599
|
+
}
|
|
600
|
+
taskSpinner.message(chalk.cyan(message));
|
|
601
|
+
}
|
|
602
|
+
break;
|
|
603
|
+
case 'tool_call_result':
|
|
604
|
+
// Show enhanced tool info in single task mode too
|
|
605
|
+
if (event.toolMetadata) {
|
|
606
|
+
const message = formatToolMetadata(event.toolName || 'tool', event.toolMetadata);
|
|
607
|
+
taskSpinner.message(chalk.green(message));
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
taskSpinner.message(chalk.dim('Processing task...'));
|
|
611
|
+
}
|
|
612
|
+
break;
|
|
613
|
+
case 'complete':
|
|
614
|
+
taskSpinner.stop();
|
|
615
|
+
break;
|
|
616
|
+
case 'error':
|
|
617
|
+
taskSpinner.stop(chalk.red(`Error: ${event.error}`));
|
|
618
|
+
break;
|
|
619
|
+
}
|
|
620
|
+
}, images);
|
|
621
|
+
// Show success message with token usage
|
|
622
|
+
const stats = this.agent.getStats();
|
|
623
|
+
const summaryParts = [];
|
|
624
|
+
if (toolCallCount > 0) {
|
|
625
|
+
summaryParts.push(`Used ${toolCallCount} tool${toolCallCount > 1 ? 's' : ''}`);
|
|
626
|
+
}
|
|
627
|
+
if (stats.totalTokens > 0) {
|
|
628
|
+
summaryParts.push(`${formatTokens(stats.totalTokens)} tokens`);
|
|
629
|
+
}
|
|
630
|
+
if (stats.estimatedTotalCost > 0) {
|
|
631
|
+
summaryParts.push(`${formatCost(stats.estimatedTotalCost)}`);
|
|
632
|
+
}
|
|
633
|
+
if (summaryParts.length > 0) {
|
|
634
|
+
console.log(chalk.dim(`${summaryParts.join(' ⢠')}\n`));
|
|
635
|
+
}
|
|
636
|
+
return response;
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
taskSpinner.stop(chalk.red('Task failed'));
|
|
640
|
+
throw error;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Execute a task with planning mode and UI streaming (for plan mode)
|
|
645
|
+
*/
|
|
646
|
+
async executePlanningTask(task, _images = [], planOnly = false) {
|
|
647
|
+
const planSpinner = spinner();
|
|
648
|
+
this.currentSpinner = planSpinner; // Store reference to stop when progress starts
|
|
649
|
+
planSpinner.start(chalk.blueBright('š Starting planning phase...'));
|
|
650
|
+
try {
|
|
651
|
+
// Import PlanMode
|
|
652
|
+
const { PlanMode } = await import('./modes/planMode.js');
|
|
653
|
+
const planMode = new PlanMode(this.agent, {
|
|
654
|
+
requirePlanning: true,
|
|
655
|
+
enforceSequential: true,
|
|
656
|
+
showPlanningFeedback: true
|
|
657
|
+
});
|
|
658
|
+
// Create a UI-connected event callback that handles both planning and streaming events
|
|
659
|
+
const uiEventCallback = (event) => {
|
|
660
|
+
// Handle planning-specific events
|
|
661
|
+
this.handleStreamingEvent(event);
|
|
662
|
+
// Handle regular streaming events too
|
|
663
|
+
switch (event.type) {
|
|
664
|
+
case 'thinking_start':
|
|
665
|
+
planSpinner.message(chalk.dim('Thinking...'));
|
|
666
|
+
break;
|
|
667
|
+
case 'content_chunk':
|
|
668
|
+
// For planning phase, we might not want to show content chunks immediately
|
|
669
|
+
break;
|
|
670
|
+
case 'tool_call_start':
|
|
671
|
+
planSpinner.message(chalk.yellow(`Using ${event.toolName}...`));
|
|
672
|
+
break;
|
|
673
|
+
case 'tool_call_progress':
|
|
674
|
+
if (event.toolProgress) {
|
|
675
|
+
const { percentage, operation, details } = event.toolProgress;
|
|
676
|
+
const progressBar = this.createToolProgressBar(percentage);
|
|
677
|
+
let message = `${operation} ${Math.round(percentage)}% ${progressBar}`;
|
|
678
|
+
if (details) {
|
|
679
|
+
message += ` (${details})`;
|
|
680
|
+
}
|
|
681
|
+
planSpinner.message(chalk.cyan(message));
|
|
682
|
+
}
|
|
683
|
+
break;
|
|
684
|
+
case 'tool_call_result':
|
|
685
|
+
if (event.toolMetadata) {
|
|
686
|
+
const message = formatToolMetadata(event.toolName || 'tool', event.toolMetadata);
|
|
687
|
+
planSpinner.message(chalk.green(message));
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
planSpinner.message(chalk.dim('Processing...'));
|
|
691
|
+
}
|
|
692
|
+
break;
|
|
693
|
+
case 'complete':
|
|
694
|
+
planSpinner.stop();
|
|
695
|
+
break;
|
|
696
|
+
case 'error':
|
|
697
|
+
planSpinner.stop(chalk.red(`Error: ${event.error}`));
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
if (planOnly) {
|
|
702
|
+
// Only generate plan, don't execute
|
|
703
|
+
const planningResult = await planMode.planningPhase(task, uiEventCallback);
|
|
704
|
+
planSpinner.stop();
|
|
705
|
+
if (!planningResult.success) {
|
|
706
|
+
throw new Error(`Planning failed: ${planningResult.error}`);
|
|
707
|
+
}
|
|
708
|
+
return `š Plan generated successfully with ${planningResult.todos.length} steps:\n\n` +
|
|
709
|
+
planningResult.todos.map((todo, i) => `${i + 1}. ${todo.content}`).join('\n') +
|
|
710
|
+
`\n\nQuality Score: ${planningResult.qualityScore}/100\n` +
|
|
711
|
+
(planningResult.suggestions.length > 0 ?
|
|
712
|
+
`\nSuggestions:\n${planningResult.suggestions.map((s) => `⢠${s}`).join('\n')}` : '') +
|
|
713
|
+
`\n\nšÆ **Plan-only mode**: Plan created. Use --plan flag (without --plan-only) to execute this plan.`;
|
|
714
|
+
}
|
|
715
|
+
// Full planning + execution
|
|
716
|
+
const result = await planMode.executePlannedTask(task, uiEventCallback);
|
|
717
|
+
planSpinner.stop();
|
|
718
|
+
// Show success message with token usage
|
|
719
|
+
const stats = this.agent.getStats();
|
|
720
|
+
const summaryParts = [];
|
|
721
|
+
if (stats.totalTokens > 0) {
|
|
722
|
+
summaryParts.push(`${formatTokens(stats.totalTokens)} tokens`);
|
|
723
|
+
}
|
|
724
|
+
if (stats.estimatedTotalCost > 0) {
|
|
725
|
+
summaryParts.push(`${formatCost(stats.estimatedTotalCost)}`);
|
|
726
|
+
}
|
|
727
|
+
if (summaryParts.length > 0) {
|
|
728
|
+
console.log(chalk.dim(`${summaryParts.join(' ⢠')}\n`));
|
|
729
|
+
}
|
|
730
|
+
return result;
|
|
731
|
+
}
|
|
732
|
+
catch (error) {
|
|
733
|
+
planSpinner.stop(chalk.red('Planning failed'));
|
|
734
|
+
throw error;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Check if we should show detailed information for a tool
|
|
739
|
+
*/
|
|
740
|
+
shouldShowDetails(toolName) {
|
|
741
|
+
// Show details for these tools when they have interesting information
|
|
742
|
+
return ['read_file', 'list_directory', 'execute_command'].includes(toolName);
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Show additional tool details
|
|
746
|
+
*/
|
|
747
|
+
showToolDetails(toolName, metadata) {
|
|
748
|
+
if (!metadata || !metadata.success)
|
|
749
|
+
return;
|
|
750
|
+
let details = '';
|
|
751
|
+
switch (toolName) {
|
|
752
|
+
case 'read_file':
|
|
753
|
+
if (metadata.contentPreview) {
|
|
754
|
+
details = chalk.dim(`Preview:\n${metadata.contentPreview}`);
|
|
755
|
+
}
|
|
756
|
+
break;
|
|
757
|
+
case 'list_directory':
|
|
758
|
+
if (metadata.contentPreview && metadata.contentPreview !== 'Empty directory') {
|
|
759
|
+
// Parse the content preview and format each item on a new line
|
|
760
|
+
const preview = metadata.contentPreview;
|
|
761
|
+
if (preview.includes(' +') && preview.includes(' more')) {
|
|
762
|
+
// Extract items and remaining count: "item1, item2, item3 +15 more"
|
|
763
|
+
const [itemsStr, remainingStr] = preview.split(' +');
|
|
764
|
+
const items = itemsStr.split(', ');
|
|
765
|
+
// Format with each item on a new line
|
|
766
|
+
const formattedItems = items.map(item => ` ${item}`).join('\n');
|
|
767
|
+
details = chalk.dim(`${formattedItems}\n +${remainingStr}`);
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
// Fallback for simple lists without truncation
|
|
771
|
+
const items = preview.split(', ');
|
|
772
|
+
details = chalk.dim(items.map(item => ` ${item}`).join('\n'));
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
break;
|
|
776
|
+
case 'execute_command':
|
|
777
|
+
if (metadata.outputPreview && metadata.outputPreview !== 'No output') {
|
|
778
|
+
details = chalk.dim(`Output:\n${metadata.outputPreview}`);
|
|
779
|
+
}
|
|
780
|
+
break;
|
|
781
|
+
}
|
|
782
|
+
if (details) {
|
|
783
|
+
console.log(chalk.dim(' ' + details.replace(/\n/g, '\n ')));
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Show task completion summary with token usage
|
|
788
|
+
*/
|
|
789
|
+
showTaskSummary(toolCallCount) {
|
|
790
|
+
const stats = this.agent.getStats();
|
|
791
|
+
const summaryParts = [];
|
|
792
|
+
if (toolCallCount > 0) {
|
|
793
|
+
summaryParts.push(`Used ${toolCallCount} tool${toolCallCount > 1 ? 's' : ''}`);
|
|
794
|
+
}
|
|
795
|
+
if (stats.totalTokens > 0) {
|
|
796
|
+
summaryParts.push(`${formatTokens(stats.totalTokens)} tokens (${formatTokens(stats.inputTokens)} in, ${formatTokens(stats.outputTokens)} out)`);
|
|
797
|
+
}
|
|
798
|
+
if (stats.estimatedTotalCost > 0) {
|
|
799
|
+
summaryParts.push(`${formatCost(stats.estimatedTotalCost)} estimated cost`);
|
|
800
|
+
}
|
|
801
|
+
if (summaryParts.length > 0) {
|
|
802
|
+
note(summaryParts.join(' ⢠'), 'Task Summary');
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Show detailed execution steps breakdown
|
|
807
|
+
*/
|
|
808
|
+
showExecutionSteps(steps) {
|
|
809
|
+
const stepLines = [];
|
|
810
|
+
stepLines.push(chalk.yellow('Execution Steps:'));
|
|
811
|
+
for (const step of steps) {
|
|
812
|
+
const duration = step.duration ? `${step.duration}ms` : 'ongoing';
|
|
813
|
+
if (step.type === 'llm_call') {
|
|
814
|
+
const tokenInfo = step.tokenUsage
|
|
815
|
+
? ` (${formatTokenUsageSummary(step.tokenUsage)})`
|
|
816
|
+
: '';
|
|
817
|
+
// Create descriptive label based on LLM context
|
|
818
|
+
let llmLabel = 'LLM Call';
|
|
819
|
+
if (step.llmContext === 'initial_input') {
|
|
820
|
+
llmLabel = 'Processing Input';
|
|
821
|
+
}
|
|
822
|
+
else if (step.llmContext === 'processing_tool_result') {
|
|
823
|
+
llmLabel = 'Processing Tool Output';
|
|
824
|
+
}
|
|
825
|
+
else if (step.llmContext === 'final_response') {
|
|
826
|
+
llmLabel = 'Final Reasoning';
|
|
827
|
+
}
|
|
828
|
+
stepLines.push(` ${chalk.cyan(`${step.stepNumber}.`)} ${llmLabel} - ${chalk.dim(duration)}${tokenInfo}`);
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
const success = step.toolSuccess !== undefined
|
|
832
|
+
? (step.toolSuccess ? chalk.green('ā') : chalk.red('ā'))
|
|
833
|
+
: chalk.yellow('?');
|
|
834
|
+
stepLines.push(` ${chalk.cyan(`${step.stepNumber}.`)} ${success} ${step.toolName} - ${chalk.dim(duration)}`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
note(stepLines.join('\n'), 'Step Details');
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Handle todo update events
|
|
841
|
+
*/
|
|
842
|
+
handleTodoUpdate(event) {
|
|
843
|
+
// Update internal state
|
|
844
|
+
this.todoPanel.update(event.todos);
|
|
845
|
+
// Only show progress tracker updates when NOT in active planning phase
|
|
846
|
+
// This prevents duplicate progress displays during context-aware planning
|
|
847
|
+
if (!this.activePlanningPhase) {
|
|
848
|
+
this.progressTracker.updateTodos(event.todos, event);
|
|
849
|
+
// Show visual feedback based on change type
|
|
850
|
+
if (event.changeType === 'create' && event.todos.length > 0) {
|
|
851
|
+
this.progressTracker.showPlanningComplete(event.todos.length);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
else {
|
|
855
|
+
// During planning phase, update internal state but suppress visual feedback
|
|
856
|
+
this.progressTracker.updateTodos(event.todos, event, true);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Enable plan mode for structured planning
|
|
861
|
+
*/
|
|
862
|
+
enablePlanMode() {
|
|
863
|
+
this.planMode = true;
|
|
864
|
+
// Remove redundant messages - UI will show plan mode status in the configuration display
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Disable plan mode
|
|
868
|
+
*/
|
|
869
|
+
disablePlanMode() {
|
|
870
|
+
this.planMode = false;
|
|
871
|
+
this.progressTracker.stop();
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Show current todo status
|
|
875
|
+
*/
|
|
876
|
+
async showTodos() {
|
|
877
|
+
const _todos = this.todoPanel.getTodos();
|
|
878
|
+
if (_todos.length === 0) {
|
|
879
|
+
note('No todos found. Use a planning task to create todos automatically.', 'š Todo Status');
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
const todoDisplay = this.todoPanel.render();
|
|
883
|
+
note(todoDisplay, 'š Current Todo List');
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Show planning phase welcome
|
|
887
|
+
*/
|
|
888
|
+
showPlanningWelcome() {
|
|
889
|
+
if (this.planMode) {
|
|
890
|
+
console.log(chalk.cyan('š” Plan mode features: structured planning, progress tracking, sequential execution'));
|
|
891
|
+
console.log('');
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Show task welcome with planning context
|
|
896
|
+
*/
|
|
897
|
+
showTaskWelcome(task) {
|
|
898
|
+
intro(chalk.cyan('š¤ CodeMie Native Agent'));
|
|
899
|
+
if (this.planMode) {
|
|
900
|
+
console.log(chalk.cyan('ā Task Execution'));
|
|
901
|
+
console.log(` Task: ${chalk.yellow(task)}`);
|
|
902
|
+
console.log(` Mode: ${chalk.cyan('Plan Mode')} - Structured planning enabled`);
|
|
903
|
+
console.log('');
|
|
904
|
+
this.showPlanningWelcome();
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
console.log(chalk.cyan('ā Task Execution'));
|
|
908
|
+
console.log(` Task: ${chalk.yellow(task)}`);
|
|
909
|
+
console.log('');
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Show task completion with todo summary
|
|
914
|
+
*/
|
|
915
|
+
showTaskComplete() {
|
|
916
|
+
const _todos = this.todoPanel.getTodos();
|
|
917
|
+
const progressInfo = this.todoPanel.getProgressInfo();
|
|
918
|
+
if (progressInfo && progressInfo.total > 0) {
|
|
919
|
+
const stats = {
|
|
920
|
+
tasksCompleted: progressInfo.completed,
|
|
921
|
+
totalTime: undefined // Could track this if needed
|
|
922
|
+
};
|
|
923
|
+
this.progressTracker.showOverallCompletion(stats);
|
|
924
|
+
// Show final todo status if relevant
|
|
925
|
+
if (progressInfo.completed === progressInfo.total) {
|
|
926
|
+
note('All planned tasks completed successfully! š', 'ā
Task Complete');
|
|
927
|
+
}
|
|
928
|
+
else if (progressInfo.completed > 0) {
|
|
929
|
+
note(`Completed ${progressInfo.completed}/${progressInfo.total} planned tasks`, 'š Progress Summary');
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
outro(chalk.green('ā
Task completed!'));
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Show error with todo context
|
|
938
|
+
*/
|
|
939
|
+
showError(error) {
|
|
940
|
+
const _todos = this.todoPanel.getTodos();
|
|
941
|
+
const progressInfo = this.todoPanel.getProgressInfo();
|
|
942
|
+
let contextInfo = '';
|
|
943
|
+
if (progressInfo?.currentTodo) {
|
|
944
|
+
contextInfo = `\nš Error occurred while working on: ${progressInfo.currentTodo.content}`;
|
|
945
|
+
}
|
|
946
|
+
note(chalk.rgb(255, 120, 120)(`ā ${error}${contextInfo}`), 'Error');
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Execute task respecting current mode settings
|
|
950
|
+
*/
|
|
951
|
+
async executeTaskWithCurrentMode(task, images = []) {
|
|
952
|
+
if (this.planMode) {
|
|
953
|
+
// Use plan mode execution with confirmation
|
|
954
|
+
try {
|
|
955
|
+
const { PlanMode } = await import('./modes/planMode.js');
|
|
956
|
+
const planMode = new PlanMode(this.agent, {
|
|
957
|
+
requirePlanning: true,
|
|
958
|
+
enforceSequential: true,
|
|
959
|
+
showPlanningFeedback: true
|
|
960
|
+
});
|
|
961
|
+
// First, create the plan only - using UI-integrated planning
|
|
962
|
+
const planningResult = await planMode.planningPhase(task, (event) => {
|
|
963
|
+
// Handle planning-specific events with UI integration
|
|
964
|
+
this.handleStreamingEvent(event);
|
|
965
|
+
// Handle regular streaming events for spinner updates
|
|
966
|
+
switch (event.type) {
|
|
967
|
+
case 'thinking_start':
|
|
968
|
+
// Already handled in handleStreamingEvent
|
|
969
|
+
break;
|
|
970
|
+
case 'tool_call_start':
|
|
971
|
+
// Already handled in handleStreamingEvent
|
|
972
|
+
break;
|
|
973
|
+
case 'tool_call_result':
|
|
974
|
+
// Already handled in handleStreamingEvent
|
|
975
|
+
break;
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
if (!planningResult.success) {
|
|
979
|
+
this.showError(`Planning failed: ${planningResult.error}`);
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
// Show the plan to the user with proper formatting
|
|
983
|
+
const formattedPlan = this.formatPlanForDisplay(planningResult.todos, planningResult.qualityScore);
|
|
984
|
+
note(formattedPlan, 'Planning Complete');
|
|
985
|
+
// Ask for confirmation
|
|
986
|
+
const shouldExecute = await text({
|
|
987
|
+
message: 'Execute this plan?',
|
|
988
|
+
placeholder: 'Type "yes" to execute, or "no" to cancel',
|
|
989
|
+
validate: (value) => {
|
|
990
|
+
const normalized = value.toLowerCase().trim();
|
|
991
|
+
if (!['yes', 'y', 'no', 'n'].includes(normalized)) {
|
|
992
|
+
return 'Please type "yes" or "no"';
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
if (isCancel(shouldExecute)) {
|
|
997
|
+
note('Plan execution cancelled by user', 'Cancelled');
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
const response = shouldExecute.toLowerCase().trim();
|
|
1001
|
+
if (['yes', 'y'].includes(response)) {
|
|
1002
|
+
// Execute the plan
|
|
1003
|
+
await planMode.executePlannedTask(task, (event) => {
|
|
1004
|
+
this.handleStreamingEvent(event);
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
else {
|
|
1008
|
+
note('Plan execution cancelled by user', 'Cancelled');
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
catch (error) {
|
|
1012
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
else {
|
|
1016
|
+
// Use direct execution
|
|
1017
|
+
await this.executeTaskWithUI(task, images);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Handle streaming events from plan mode execution
|
|
1022
|
+
*/
|
|
1023
|
+
handleStreamingEvent(event) {
|
|
1024
|
+
switch (event.type) {
|
|
1025
|
+
case 'planning_start':
|
|
1026
|
+
this.activePlanningPhase = event.planningInfo?.phase || 'planning';
|
|
1027
|
+
// Don't duplicate the planning start message - handled by spinner
|
|
1028
|
+
break;
|
|
1029
|
+
case 'planning_complete':
|
|
1030
|
+
this.activePlanningPhase = null;
|
|
1031
|
+
console.log(chalk.green(`š Plan created with ${event.planningInfo?.totalSteps || 0} steps`));
|
|
1032
|
+
break;
|
|
1033
|
+
case 'planning_progress':
|
|
1034
|
+
this.handlePlanningProgress(event.planningProgress);
|
|
1035
|
+
break;
|
|
1036
|
+
case 'planning_tool_call':
|
|
1037
|
+
this.handlePlanningToolCall(event.planningToolCall);
|
|
1038
|
+
break;
|
|
1039
|
+
case 'content_chunk':
|
|
1040
|
+
if (event.content) {
|
|
1041
|
+
process.stdout.write(event.content);
|
|
1042
|
+
}
|
|
1043
|
+
break;
|
|
1044
|
+
case 'todo_update':
|
|
1045
|
+
// Todo updates are handled automatically by the TodoStateManager
|
|
1046
|
+
// During planning phase, we suppress duplicate visual feedback
|
|
1047
|
+
break;
|
|
1048
|
+
case 'error':
|
|
1049
|
+
this.activePlanningPhase = null;
|
|
1050
|
+
this.showError(event.error || 'Unknown error');
|
|
1051
|
+
break;
|
|
1052
|
+
default:
|
|
1053
|
+
// Handle other event types silently
|
|
1054
|
+
break;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Handle planning progress streaming events
|
|
1059
|
+
*/
|
|
1060
|
+
currentPhase = '';
|
|
1061
|
+
currentTool = '';
|
|
1062
|
+
currentToolCall = '';
|
|
1063
|
+
handlePlanningProgress(progressInfo) {
|
|
1064
|
+
if (!progressInfo)
|
|
1065
|
+
return;
|
|
1066
|
+
// Keep the spinner running and use it to display progress updates
|
|
1067
|
+
const phaseNames = {
|
|
1068
|
+
'context_gathering': 'Discovery',
|
|
1069
|
+
'task_analysis': 'Analysis',
|
|
1070
|
+
'plan_generation': 'Planning',
|
|
1071
|
+
'plan_validation': 'Validation'
|
|
1072
|
+
};
|
|
1073
|
+
this.currentPhase = phaseNames[progressInfo.phase] || 'Planning';
|
|
1074
|
+
this.updateGlobalProgress(progressInfo.overallProgress || 0);
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Handle planning tool call events
|
|
1078
|
+
*/
|
|
1079
|
+
handlePlanningToolCall(toolInfo) {
|
|
1080
|
+
if (!toolInfo)
|
|
1081
|
+
return;
|
|
1082
|
+
const toolNames = {
|
|
1083
|
+
'list_directory': 'Exploring',
|
|
1084
|
+
'read_file': 'Reading',
|
|
1085
|
+
'execute_command': 'Executing',
|
|
1086
|
+
'llm_analysis': 'Analyzing',
|
|
1087
|
+
'llm_plan_generation': 'Generating Plan',
|
|
1088
|
+
'plan_validation': 'Validating',
|
|
1089
|
+
'analyze_dependencies': 'Dependencies'
|
|
1090
|
+
};
|
|
1091
|
+
this.currentTool = toolNames[toolInfo.toolName] || 'Tool';
|
|
1092
|
+
// Format tool call details with arguments
|
|
1093
|
+
this.currentToolCall = this.formatToolCall(toolInfo.toolName, toolInfo.args);
|
|
1094
|
+
// Update progress with current tool info
|
|
1095
|
+
this.updateGlobalProgress();
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Format tool call with arguments for display
|
|
1099
|
+
*/
|
|
1100
|
+
formatToolCall(toolName, args) {
|
|
1101
|
+
if (!args || Object.keys(args).length === 0) {
|
|
1102
|
+
return toolName;
|
|
1103
|
+
}
|
|
1104
|
+
// Extract key arguments for concise display
|
|
1105
|
+
const formatArg = (key, value) => {
|
|
1106
|
+
if (typeof value === 'string') {
|
|
1107
|
+
// Truncate long strings and show just the relevant part
|
|
1108
|
+
if (key === 'path' || key === 'directory') {
|
|
1109
|
+
// Show just the last part of paths
|
|
1110
|
+
const parts = value.split('/');
|
|
1111
|
+
return parts[parts.length - 1] || value;
|
|
1112
|
+
}
|
|
1113
|
+
if (key === 'command') {
|
|
1114
|
+
// Show first word of commands
|
|
1115
|
+
return value.split(' ')[0];
|
|
1116
|
+
}
|
|
1117
|
+
if (value.length > 20) {
|
|
1118
|
+
return value.substring(0, 17) + '...';
|
|
1119
|
+
}
|
|
1120
|
+
return value;
|
|
1121
|
+
}
|
|
1122
|
+
if (Array.isArray(value)) {
|
|
1123
|
+
return `[${value.length} items]`;
|
|
1124
|
+
}
|
|
1125
|
+
if (typeof value === 'object') {
|
|
1126
|
+
return '{...}';
|
|
1127
|
+
}
|
|
1128
|
+
return String(value);
|
|
1129
|
+
};
|
|
1130
|
+
// Pick the most relevant arguments to show
|
|
1131
|
+
const relevantKeys = ['path', 'directory', 'file', 'command', 'query', 'pattern'];
|
|
1132
|
+
const argsToShow = [];
|
|
1133
|
+
for (const key of relevantKeys) {
|
|
1134
|
+
if (key in args) {
|
|
1135
|
+
argsToShow.push(formatArg(key, args[key]));
|
|
1136
|
+
if (argsToShow.length >= 2)
|
|
1137
|
+
break; // Show max 2 args
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
// If no relevant keys found, show first few keys
|
|
1141
|
+
if (argsToShow.length === 0) {
|
|
1142
|
+
const keys = Object.keys(args).slice(0, 2);
|
|
1143
|
+
for (const key of keys) {
|
|
1144
|
+
argsToShow.push(formatArg(key, args[key]));
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
return argsToShow.length > 0 ? `${toolName} (${argsToShow.join(', ')})` : toolName;
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Update global progress display
|
|
1151
|
+
*/
|
|
1152
|
+
currentProgress = 0;
|
|
1153
|
+
updateGlobalProgress(progress) {
|
|
1154
|
+
if (progress !== undefined) {
|
|
1155
|
+
this.currentProgress = progress;
|
|
1156
|
+
}
|
|
1157
|
+
const progressBar = this.createProgressBar(this.currentProgress);
|
|
1158
|
+
let displayText = `Progress ${Math.round(this.currentProgress)}% ${progressBar}`;
|
|
1159
|
+
// Show tool call details if available, otherwise fall back to tool name or phase
|
|
1160
|
+
// This provides detailed info like "(Discovery, calling list_dir (src))"
|
|
1161
|
+
if (this.currentToolCall) {
|
|
1162
|
+
displayText += ` (${this.currentPhase}, calling ${this.currentToolCall})`;
|
|
1163
|
+
}
|
|
1164
|
+
else if (this.currentTool) {
|
|
1165
|
+
displayText += ` (${this.currentTool})`;
|
|
1166
|
+
}
|
|
1167
|
+
else if (this.currentPhase) {
|
|
1168
|
+
displayText += ` (${this.currentPhase})`;
|
|
1169
|
+
}
|
|
1170
|
+
// Use clack-style spinner update for reliable real-time display
|
|
1171
|
+
if (this.currentSpinner) {
|
|
1172
|
+
this.currentSpinner.message(displayText);
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
// Fallback to direct output if no spinner
|
|
1176
|
+
process.stdout.write('\r' + displayText);
|
|
1177
|
+
}
|
|
1178
|
+
// Only add newline when planning is completely finished (100%)
|
|
1179
|
+
if (this.currentProgress >= 100) {
|
|
1180
|
+
if (this.currentSpinner) {
|
|
1181
|
+
this.currentSpinner.stop(displayText);
|
|
1182
|
+
this.currentSpinner = undefined;
|
|
1183
|
+
}
|
|
1184
|
+
else {
|
|
1185
|
+
process.stdout.write('\n');
|
|
1186
|
+
}
|
|
1187
|
+
this.currentPhase = '';
|
|
1188
|
+
this.currentTool = '';
|
|
1189
|
+
this.currentToolCall = '';
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Create a simple progress bar
|
|
1194
|
+
*/
|
|
1195
|
+
createProgressBar(percentage, width = 20) {
|
|
1196
|
+
const filled = Math.round((percentage / 100) * width);
|
|
1197
|
+
const empty = width - filled;
|
|
1198
|
+
const filledBar = chalk.cyan('ā'.repeat(filled));
|
|
1199
|
+
const emptyBar = chalk.dim('ā'.repeat(empty));
|
|
1200
|
+
return `[${filledBar}${emptyBar}]`;
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Create a tool progress bar (smaller for inline use)
|
|
1204
|
+
*/
|
|
1205
|
+
createToolProgressBar(percentage, width = 12) {
|
|
1206
|
+
const filled = Math.round((percentage / 100) * width);
|
|
1207
|
+
const empty = width - filled;
|
|
1208
|
+
const filledBar = chalk.green('ā'.repeat(filled));
|
|
1209
|
+
const emptyBar = chalk.dim('ā'.repeat(empty));
|
|
1210
|
+
return `[${filledBar}${emptyBar}]`;
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Handle hotkey actions
|
|
1214
|
+
*/
|
|
1215
|
+
handleHotkey(action) {
|
|
1216
|
+
switch (action) {
|
|
1217
|
+
case 'toggle-plan-mode':
|
|
1218
|
+
this.togglePlanMode();
|
|
1219
|
+
break;
|
|
1220
|
+
case 'show-todos':
|
|
1221
|
+
this.showTodosHotkey();
|
|
1222
|
+
break;
|
|
1223
|
+
case 'show-help':
|
|
1224
|
+
this.showHotkeyHelp();
|
|
1225
|
+
break;
|
|
1226
|
+
case 'show-status':
|
|
1227
|
+
this.showModeStatus();
|
|
1228
|
+
break;
|
|
1229
|
+
default:
|
|
1230
|
+
console.log(chalk.red(`\nā Unknown hotkey action: ${action}`));
|
|
1231
|
+
break;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Toggle plan mode on/off
|
|
1236
|
+
*/
|
|
1237
|
+
togglePlanMode() {
|
|
1238
|
+
const wasEnabled = this.planMode;
|
|
1239
|
+
if (this.planMode) {
|
|
1240
|
+
this.disablePlanMode();
|
|
1241
|
+
}
|
|
1242
|
+
else {
|
|
1243
|
+
this.enablePlanMode();
|
|
1244
|
+
}
|
|
1245
|
+
// Show enhanced visual feedback
|
|
1246
|
+
this.showModeChangeNotification(wasEnabled, this.planMode);
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Show enhanced visual feedback for mode changes
|
|
1250
|
+
*/
|
|
1251
|
+
showModeChangeNotification(wasEnabled, nowEnabled) {
|
|
1252
|
+
const modeIcon = nowEnabled ? 'š' : 'ā”';
|
|
1253
|
+
const statusText = nowEnabled ? 'enabled' : 'disabled';
|
|
1254
|
+
// Simple, concise mode change message
|
|
1255
|
+
console.log(chalk.blueBright(`\n${modeIcon} Plan mode ${statusText}`));
|
|
1256
|
+
// Start progress tracking when plan mode is enabled (but don't show 0/0 progress yet)
|
|
1257
|
+
if (nowEnabled) {
|
|
1258
|
+
this.progressTracker.start();
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Show todos via hotkey (non-blocking)
|
|
1263
|
+
*/
|
|
1264
|
+
showTodosHotkey() {
|
|
1265
|
+
const _todos = this.todoPanel.getTodos();
|
|
1266
|
+
if (_todos.length === 0) {
|
|
1267
|
+
console.log(chalk.dim('\nš No todos found'));
|
|
1268
|
+
}
|
|
1269
|
+
else {
|
|
1270
|
+
const todoDisplay = this.todoPanel.render();
|
|
1271
|
+
console.log(`\n${todoDisplay}`);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Show hotkey help
|
|
1276
|
+
*/
|
|
1277
|
+
showHotkeyHelp() {
|
|
1278
|
+
const helpText = `
|
|
1279
|
+
${chalk.bold.cyan('š„ Interactive Mode Hotkeys')}
|
|
1280
|
+
|
|
1281
|
+
Mode Control:
|
|
1282
|
+
${chalk.yellow('Ctrl+P')} Toggle plan mode on/off
|
|
1283
|
+
${chalk.yellow('Ctrl+S')} Show current mode status
|
|
1284
|
+
|
|
1285
|
+
Todo Management:
|
|
1286
|
+
${chalk.yellow('Ctrl+T')} Show current todo list and progress
|
|
1287
|
+
|
|
1288
|
+
General:
|
|
1289
|
+
${chalk.yellow('Ctrl+H')} Show this help
|
|
1290
|
+
${chalk.yellow('Tab')} Insert image from clipboard
|
|
1291
|
+
${chalk.yellow('Ctrl+C')} Cancel input / Exit
|
|
1292
|
+
|
|
1293
|
+
Input Controls:
|
|
1294
|
+
${chalk.yellow('Enter')} Send message
|
|
1295
|
+
${chalk.yellow('Shift+Enter')} New line (multiline input)
|
|
1296
|
+
|
|
1297
|
+
Chat Commands:
|
|
1298
|
+
${chalk.yellow('/help')} Show chat commands
|
|
1299
|
+
${chalk.yellow('/todos')} Show detailed todo information
|
|
1300
|
+
${chalk.yellow('/stats')} Show agent statistics
|
|
1301
|
+
${chalk.yellow('/exit')} Exit the session
|
|
1302
|
+
`;
|
|
1303
|
+
console.log(helpText);
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Show current mode status
|
|
1307
|
+
*/
|
|
1308
|
+
showModeStatus() {
|
|
1309
|
+
const _todos = this.todoPanel.getTodos();
|
|
1310
|
+
const progressInfo = this.todoPanel.getProgressInfo();
|
|
1311
|
+
let statusText = chalk.bold.blueBright('\nš Current Status\n');
|
|
1312
|
+
// Mode information
|
|
1313
|
+
statusText += `Mode: ${this.planMode ?
|
|
1314
|
+
chalk.green('Plan Mode (structured todos)') :
|
|
1315
|
+
chalk.yellow('Direct Mode (immediate execution)')}\n`;
|
|
1316
|
+
// Todo information
|
|
1317
|
+
if (progressInfo && progressInfo.total > 0) {
|
|
1318
|
+
statusText += `Todos: ${progressInfo.completed}/${progressInfo.total} completed (${progressInfo.percentage}%)\n`;
|
|
1319
|
+
if (progressInfo.currentTodo) {
|
|
1320
|
+
statusText += `Current: ${chalk.cyan(progressInfo.currentTodo.content)}\n`;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
else {
|
|
1324
|
+
statusText += 'Todos: None active\n';
|
|
1325
|
+
}
|
|
1326
|
+
// Agent status
|
|
1327
|
+
const agentStats = this.agent.getStats();
|
|
1328
|
+
if (agentStats) {
|
|
1329
|
+
statusText += `Session: ${agentStats.toolCalls} tool calls, ${agentStats.llmCalls} LLM calls\n`;
|
|
1330
|
+
}
|
|
1331
|
+
console.log(statusText);
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Format plan for display with proper text wrapping
|
|
1335
|
+
*/
|
|
1336
|
+
formatPlanForDisplay(todos, qualityScore) {
|
|
1337
|
+
const maxWidth = Math.min(process.stdout.columns - 10, 100); // Leave some margin
|
|
1338
|
+
let formatted = `š **Plan Created** (${todos.length} steps):\n\n`;
|
|
1339
|
+
todos.forEach((todo, index) => {
|
|
1340
|
+
const stepNumber = `${index + 1}. `;
|
|
1341
|
+
const content = todo.content;
|
|
1342
|
+
// Wrap long lines properly
|
|
1343
|
+
const wrappedContent = this.wrapText(content, maxWidth - stepNumber.length);
|
|
1344
|
+
const lines = wrappedContent.split('\n');
|
|
1345
|
+
// First line with step number
|
|
1346
|
+
formatted += stepNumber + lines[0] + '\n';
|
|
1347
|
+
// Subsequent lines indented to align with content
|
|
1348
|
+
for (let i = 1; i < lines.length; i++) {
|
|
1349
|
+
formatted += ' '.repeat(stepNumber.length) + lines[i] + '\n';
|
|
1350
|
+
}
|
|
1351
|
+
// Add spacing between steps
|
|
1352
|
+
if (index < todos.length - 1) {
|
|
1353
|
+
formatted += '\n';
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
formatted += `\n\nQuality Score: ${qualityScore}/100`;
|
|
1357
|
+
return formatted;
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Wrap text to specified width
|
|
1361
|
+
*/
|
|
1362
|
+
wrapText(text, maxWidth) {
|
|
1363
|
+
if (text.length <= maxWidth) {
|
|
1364
|
+
return text;
|
|
1365
|
+
}
|
|
1366
|
+
const words = text.split(' ');
|
|
1367
|
+
const lines = [];
|
|
1368
|
+
let currentLine = '';
|
|
1369
|
+
for (const word of words) {
|
|
1370
|
+
// If adding this word would exceed the width
|
|
1371
|
+
if (currentLine.length + word.length + 1 > maxWidth) {
|
|
1372
|
+
if (currentLine) {
|
|
1373
|
+
lines.push(currentLine.trim());
|
|
1374
|
+
currentLine = word;
|
|
1375
|
+
}
|
|
1376
|
+
else {
|
|
1377
|
+
// Single word is longer than maxWidth, force break
|
|
1378
|
+
lines.push(word);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
else {
|
|
1382
|
+
if (currentLine) {
|
|
1383
|
+
currentLine += ' ' + word;
|
|
1384
|
+
}
|
|
1385
|
+
else {
|
|
1386
|
+
currentLine = word;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
if (currentLine) {
|
|
1391
|
+
lines.push(currentLine.trim());
|
|
1392
|
+
}
|
|
1393
|
+
return lines.join('\n');
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Cleanup resources
|
|
1397
|
+
*/
|
|
1398
|
+
dispose() {
|
|
1399
|
+
if (this.currentSpinner) {
|
|
1400
|
+
this.currentSpinner.stop();
|
|
1401
|
+
this.currentSpinner = undefined;
|
|
1402
|
+
}
|
|
1403
|
+
// Clean up todo tracking
|
|
1404
|
+
this.progressTracker.stop();
|
|
1405
|
+
TodoStateManager.removeEventCallback(this.handleTodoUpdate.bind(this));
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
//# sourceMappingURL=ui.js.map
|