@phuetz/code-buddy 1.1.0 → 1.3.0
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 +211 -163
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +12 -12
- package/dist/agent/base-agent.d.ts +4 -0
- package/dist/agent/base-agent.js +6 -0
- package/dist/agent/codebuddy-agent.d.ts +1 -1
- package/dist/agent/codebuddy-agent.js +69 -14
- package/dist/agent/execution/agent-executor.js +122 -12
- package/dist/agent/execution/tool-dependency-graph.js +7 -0
- package/dist/agent/execution/tool-hooks.d.ts +6 -0
- package/dist/agent/execution/tool-hooks.js +13 -5
- package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
- package/dist/agent/execution/tool-selection-strategy.js +67 -10
- package/dist/agent/extended-thinking.d.ts +8 -0
- package/dist/agent/extended-thinking.js +27 -0
- package/dist/agent/facades/infrastructure-facade.d.ts +9 -2
- package/dist/agent/facades/infrastructure-facade.js +15 -6
- package/dist/agent/hermes-browser-backends.js +151 -11
- package/dist/agent/hermes-claw-migrate.d.ts +59 -0
- package/dist/agent/hermes-claw-migrate.js +427 -26
- package/dist/agent/hermes-parity-manifest.js +78 -71
- package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
- package/dist/agent/hermes-runtime-lifecycle.js +468 -0
- package/dist/agent/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -0
- package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
- package/dist/agent/middleware/visual-validation-middleware.js +67 -0
- package/dist/agent/model-benchmark.d.ts +77 -0
- package/dist/agent/model-benchmark.js +309 -0
- package/dist/agent/model-tier.d.ts +14 -0
- package/dist/agent/model-tier.js +71 -0
- package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
- package/dist/agent/multi-agent/multi-agent-system.js +3 -1
- package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
- package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
- package/dist/agent/multi-agent/session-tools.js +22 -5
- package/dist/agent/reasoning/mcts.js +20 -6
- package/dist/agent/self-improvement/authored-artifact-gate.d.ts +18 -0
- package/dist/agent/self-improvement/authored-artifact-gate.js +42 -0
- package/dist/agent/self-improvement/authored-tool-runtime.d.ts +27 -0
- package/dist/agent/self-improvement/authored-tool-runtime.js +57 -0
- package/dist/agent/self-improvement/authored-tool-store.d.ts +24 -0
- package/dist/agent/self-improvement/authored-tool-store.js +57 -0
- package/dist/agent/self-improvement/llm-tool-proposer.d.ts +41 -0
- package/dist/agent/self-improvement/llm-tool-proposer.js +136 -0
- package/dist/agent/self-improvement/sandbox-scorer.d.ts +17 -0
- package/dist/agent/self-improvement/sandbox-scorer.js +43 -0
- package/dist/agent/self-improvement/self-knowledge.d.ts +8 -0
- package/dist/agent/self-improvement/self-knowledge.js +24 -0
- package/dist/agent/self-improvement/skill-benchmark.d.ts +9 -0
- package/dist/agent/self-improvement/skill-benchmark.js +22 -0
- package/dist/agent/self-improvement/skill-consolidator.d.ts +71 -0
- package/dist/agent/self-improvement/skill-consolidator.js +137 -0
- package/dist/agent/self-improvement/skill-engine.d.ts +42 -0
- package/dist/agent/self-improvement/skill-engine.js +87 -0
- package/dist/agent/self-improvement/skill-gate.d.ts +19 -0
- package/dist/agent/self-improvement/skill-gate.js +62 -0
- package/dist/agent/self-improvement/skill-mutator.d.ts +74 -0
- package/dist/agent/self-improvement/skill-mutator.js +223 -0
- package/dist/agent/self-improvement/skill-proposer.d.ts +40 -0
- package/dist/agent/self-improvement/skill-proposer.js +82 -0
- package/dist/agent/self-improvement/skill-types.d.ts +41 -0
- package/dist/agent/self-improvement/skill-types.js +13 -0
- package/dist/agent/self-improvement/tool-benchmark.d.ts +10 -0
- package/dist/agent/self-improvement/tool-benchmark.js +37 -0
- package/dist/agent/self-improvement/tool-engine.d.ts +54 -0
- package/dist/agent/self-improvement/tool-engine.js +101 -0
- package/dist/agent/self-improvement/tool-gate.d.ts +20 -0
- package/dist/agent/self-improvement/tool-gate.js +78 -0
- package/dist/agent/self-improvement/tool-proposer.d.ts +31 -0
- package/dist/agent/self-improvement/tool-proposer.js +34 -0
- package/dist/agent/self-improvement/tool-skill-mutator.d.ts +40 -0
- package/dist/agent/self-improvement/tool-skill-mutator.js +79 -0
- package/dist/agent/self-improvement/tool-types.d.ts +48 -0
- package/dist/agent/self-improvement/tool-types.js +9 -0
- package/dist/agent/self-improvement/types.d.ts +3 -1
- package/dist/agent/session-end-flush.d.ts +6 -1
- package/dist/agent/session-end-flush.js +20 -4
- package/dist/agent/streaming/streaming-handler.d.ts +7 -0
- package/dist/agent/streaming/streaming-handler.js +8 -1
- package/dist/agent/tool-executor.js +1 -0
- package/dist/agent/tool-handler.d.ts +5 -0
- package/dist/agent/tool-handler.js +133 -4
- package/dist/browser-automation/browser-operator-executor.js +15 -0
- package/dist/browser-automation/browser-use-runner.d.ts +96 -0
- package/dist/browser-automation/browser-use-runner.js +492 -0
- package/dist/browser-automation/camofox-runner.d.ts +107 -0
- package/dist/browser-automation/camofox-runner.js +287 -0
- package/dist/channels/dingtalk/index.js +3 -0
- package/dist/channels/discord/client.d.ts +9 -0
- package/dist/channels/discord/client.js +12 -0
- package/dist/channels/feishu/index.d.ts +167 -1
- package/dist/channels/feishu/index.js +432 -7
- package/dist/channels/gateway-lifecycle.d.ts +132 -0
- package/dist/channels/gateway-lifecycle.js +219 -0
- package/dist/channels/google-chat/index.d.ts +5 -1
- package/dist/channels/google-chat/index.js +5 -1
- package/dist/channels/imessage/index.d.ts +14 -0
- package/dist/channels/imessage/index.js +64 -17
- package/dist/channels/index.d.ts +4 -0
- package/dist/channels/index.js +4 -0
- package/dist/channels/irc/index.d.ts +73 -3
- package/dist/channels/irc/index.js +446 -11
- package/dist/channels/line/index.js +3 -0
- package/dist/channels/mattermost/index.d.ts +51 -4
- package/dist/channels/mattermost/index.js +303 -20
- package/dist/channels/nextcloud-talk/index.d.ts +89 -8
- package/dist/channels/nextcloud-talk/index.js +367 -16
- package/dist/channels/nostr/index.d.ts +121 -1
- package/dist/channels/nostr/index.js +396 -8
- package/dist/channels/ntfy/index.js +3 -0
- package/dist/channels/qq/index.js +3 -0
- package/dist/channels/slash-parity.d.ts +117 -0
- package/dist/channels/slash-parity.js +185 -0
- package/dist/channels/synology-chat/index.js +3 -0
- package/dist/channels/teams/index.d.ts +3 -0
- package/dist/channels/teams/index.js +3 -0
- package/dist/channels/telegram/client.d.ts +9 -0
- package/dist/channels/telegram/client.js +12 -0
- package/dist/channels/twilio-voice/index.js +3 -0
- package/dist/channels/webchat/index.d.ts +7 -1
- package/dist/channels/webchat/index.js +7 -1
- package/dist/channels/wecom/index.js +3 -0
- package/dist/channels/weixin/index.js +3 -0
- package/dist/channels/zalo/index.js +3 -0
- package/dist/cli/config-loader.js +1 -1
- package/dist/codebuddy/client.d.ts +30 -1
- package/dist/codebuddy/client.js +167 -20
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +7 -1
- package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
- package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
- package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
- package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
- package/dist/codebuddy/tool-definitions/index.js +2 -1
- package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
- package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
- package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
- package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
- package/dist/codebuddy/tools.d.ts +8 -1
- package/dist/codebuddy/tools.js +67 -24
- package/dist/commands/cli/hermes-commands.js +108 -0
- package/dist/commands/cli/improve-command.js +123 -0
- package/dist/commands/cli/native-engine-commands.js +102 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +2 -2
- package/dist/commands/goal-cli.d.ts +30 -0
- package/dist/commands/goal-cli.js +210 -27
- package/dist/commands/handlers/goal-handler.d.ts +9 -2
- package/dist/commands/handlers/goal-handler.js +36 -9
- package/dist/commands/handlers/infra-handlers.js +36 -0
- package/dist/commands/handlers/memory-handlers.js +96 -1
- package/dist/commands/handlers/missing-handlers.d.ts +1 -1
- package/dist/commands/handlers/missing-handlers.js +26 -3
- package/dist/commands/headless-slash.d.ts +2 -0
- package/dist/commands/headless-slash.js +12 -0
- package/dist/commands/llm-provider-resolution.d.ts +5 -3
- package/dist/commands/llm-provider-resolution.js +87 -33
- package/dist/commands/ollama.d.ts +25 -0
- package/dist/commands/ollama.js +100 -0
- package/dist/commands/provider.d.ts +5 -0
- package/dist/commands/provider.js +106 -55
- package/dist/commands/skills-cli/index.js +123 -0
- package/dist/commands/slash/builtin-commands.js +1 -1
- package/dist/commands/spec-next.js +2 -1
- package/dist/commands/spec-plan.js +5 -16
- package/dist/commands/tunnel.d.ts +2 -0
- package/dist/commands/tunnel.js +48 -0
- package/dist/companion/percepts.js +11 -1
- package/dist/config/config-resolver.d.ts +2 -1
- package/dist/config/config-resolver.js +54 -41
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/env-schema.js +756 -0
- package/dist/config/model-tools.js +4 -4
- package/dist/config/toml-config.d.ts +16 -0
- package/dist/config/toml-config.js +3 -0
- package/dist/context/bootstrap-loader.js +6 -23
- package/dist/context/context-manager-v2.js +1 -0
- package/dist/context/import-directive-parser.d.ts +4 -0
- package/dist/context/import-directive-parser.js +51 -6
- package/dist/context/instruction-excludes.d.ts +30 -1
- package/dist/context/instruction-excludes.js +71 -1
- package/dist/context/jit-context.d.ts +8 -10
- package/dist/context/jit-context.js +28 -106
- package/dist/context/project-context.d.ts +90 -0
- package/dist/context/project-context.js +295 -0
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +3 -3
- package/dist/daemon/autonomous-loop.d.ts +31 -1
- package/dist/daemon/autonomous-loop.js +91 -4
- package/dist/daemon/colab-goal.js +9 -1
- package/dist/daemon/cron-agent-bridge.d.ts +12 -3
- package/dist/daemon/cron-agent-bridge.js +25 -9
- package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
- package/dist/desktop/codebuddy-engine-adapter.js +257 -80
- package/dist/desktop/engine-adapter.d.ts +14 -0
- package/dist/desktop-automation/automation-manager.js +16 -0
- package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
- package/dist/desktop-automation/omniparser-runner.js +115 -0
- package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
- package/dist/desktop-automation/smart-snapshot.js +72 -5
- package/dist/doctor/index.js +40 -23
- package/dist/events/types.d.ts +11 -0
- package/dist/fleet/colab-store.js +14 -2
- package/dist/fleet/peer-session-bridge.js +50 -12
- package/dist/fleet/peer-tool-bridge.js +14 -0
- package/dist/goals/goal-decomposer.d.ts +27 -0
- package/dist/goals/goal-decomposer.js +293 -0
- package/dist/goals/goal-judge-client.d.ts +15 -0
- package/dist/goals/goal-judge-client.js +45 -0
- package/dist/goals/goal-judge.d.ts +2 -0
- package/dist/goals/goal-judge.js +35 -6
- package/dist/goals/goal-loop.d.ts +14 -0
- package/dist/goals/goal-loop.js +47 -2
- package/dist/goals/goal-manager.d.ts +6 -0
- package/dist/goals/goal-manager.js +67 -14
- package/dist/goals/goal-state.d.ts +13 -0
- package/dist/goals/goal-state.js +83 -13
- package/dist/goals/goal-store.d.ts +3 -0
- package/dist/goals/goal-store.js +31 -15
- package/dist/harness/contract.d.ts +28 -28
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/identity/identity-manager.js +3 -2
- package/dist/index.js +481 -26
- package/dist/input/text-to-speech.d.ts +2 -6
- package/dist/input/text-to-speech.js +2 -27
- package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
- package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
- package/dist/integrations/tailscale.d.ts +13 -0
- package/dist/integrations/tailscale.js +87 -34
- package/dist/knowledge/workspace-indexer.js +53 -9
- package/dist/mcp/client.js +1 -0
- package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
- package/dist/mcp/mcp-desktop-tools.js +158 -0
- package/dist/mcp/mcp-resources.js +2 -3
- package/dist/mcp/mcp-server.js +2 -0
- package/dist/memory/enhanced-memory.d.ts +10 -0
- package/dist/memory/enhanced-memory.js +33 -2
- package/dist/memory/index.d.ts +3 -1
- package/dist/memory/index.js +4 -1
- package/dist/memory/memory-auto-proposer.d.ts +23 -0
- package/dist/memory/memory-auto-proposer.js +308 -0
- package/dist/memory/memory-candidate-queue.d.ts +92 -0
- package/dist/memory/memory-candidate-queue.js +261 -0
- package/dist/memory/persistent-memory.d.ts +45 -2
- package/dist/memory/persistent-memory.js +236 -40
- package/dist/ml/bayesian-qualifier.d.ts +1 -1
- package/dist/ml/bayesian-qualifier.js +35 -2
- package/dist/openclaw/gateway-bridge.js +5 -0
- package/dist/plugins/marketplace.d.ts +1 -0
- package/dist/plugins/marketplace.js +7 -0
- package/dist/prompts/execution-discipline.d.ts +14 -0
- package/dist/prompts/execution-discipline.js +29 -0
- package/dist/prompts/variation-injector.js +9 -2
- package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
- package/dist/protocols/acp/acp-agentic-runner.js +115 -18
- package/dist/protocols/acp/acp-session-store.d.ts +23 -0
- package/dist/protocols/acp/acp-session-store.js +77 -0
- package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
- package/dist/protocols/acp/acp-stdio-server.js +71 -16
- package/dist/providers/active-llm-registry.d.ts +37 -0
- package/dist/providers/active-llm-registry.js +186 -0
- package/dist/providers/auxiliary-provider.d.ts +25 -0
- package/dist/providers/auxiliary-provider.js +192 -0
- package/dist/providers/codex-oauth.d.ts +1 -1
- package/dist/providers/codex-oauth.js +27 -4
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/provider-catalog.d.ts +70 -0
- package/dist/providers/provider-catalog.js +738 -0
- package/dist/providers/provider-fallback.d.ts +35 -0
- package/dist/providers/provider-fallback.js +171 -0
- package/dist/providers/xai-oauth.d.ts +128 -0
- package/dist/providers/xai-oauth.js +735 -0
- package/dist/scheduler/cron-scheduler.d.ts +15 -3
- package/dist/scheduler/cron-scheduler.js +53 -7
- package/dist/scheduler/script-runner.d.ts +2 -0
- package/dist/scheduler/script-runner.js +19 -4
- package/dist/scheduler/watchdog-handlers.js +7 -20
- package/dist/search/usearch-index.js +7 -2
- package/dist/security/tool-policy/tool-groups.js +2 -0
- package/dist/sensory/dreaming.d.ts +45 -0
- package/dist/sensory/dreaming.js +114 -0
- package/dist/sensory/heartbeat-scheduler.d.ts +38 -0
- package/dist/sensory/heartbeat-scheduler.js +72 -0
- package/dist/sensory/reactions.d.ts +24 -0
- package/dist/sensory/reactions.js +31 -0
- package/dist/sensory/screen-reaction.d.ts +23 -0
- package/dist/sensory/screen-reaction.js +59 -0
- package/dist/sensory/sensory-bridge.d.ts +23 -0
- package/dist/sensory/sensory-bridge.js +85 -0
- package/dist/sensory/sensory-memory.d.ts +20 -0
- package/dist/sensory/sensory-memory.js +39 -0
- package/dist/sensory/speech-reaction.d.ts +21 -0
- package/dist/sensory/speech-reaction.js +83 -0
- package/dist/sensory/vision-reaction.d.ts +31 -0
- package/dist/sensory/vision-reaction.js +74 -0
- package/dist/server/index.js +106 -2
- package/dist/server/routes/mobile.d.ts +12 -4
- package/dist/server/routes/mobile.js +116 -1
- package/dist/server/tls-config.d.ts +35 -0
- package/dist/server/tls-config.js +142 -0
- package/dist/server/tunnel-manager.d.ts +20 -0
- package/dist/server/tunnel-manager.js +58 -0
- package/dist/server/websocket/fleet-bridge.js +5 -0
- package/dist/services/prompt-builder.d.ts +11 -0
- package/dist/services/prompt-builder.js +124 -17
- package/dist/shared/engine-types.d.ts +15 -1
- package/dist/sidecar/sidecar-bridge.d.ts +1 -0
- package/dist/sidecar/sidecar-bridge.js +21 -10
- package/dist/skills/parser.js +3 -0
- package/dist/skills/skill-importer.d.ts +58 -0
- package/dist/skills/skill-importer.js +261 -0
- package/dist/skills/skill-sources.d.ts +20 -0
- package/dist/skills/skill-sources.js +102 -0
- package/dist/skills/types.d.ts +6 -0
- package/dist/spec/spec-store.js +8 -1
- package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
- package/dist/talk-mode/providers/audioreader-tts.js +8 -8
- package/dist/templates/project-scaffolding.js +3 -2
- package/dist/themes/theme-schema.d.ts +10 -10
- package/dist/tools/application-profiles.js +38 -0
- package/dist/tools/bash/bash-tool.d.ts +1 -0
- package/dist/tools/bash/bash-tool.js +16 -2
- package/dist/tools/bash/command-validator.js +3 -0
- package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
- package/dist/tools/bash/rtk-rewrite.js +100 -0
- package/dist/tools/bash/streaming-executor.js +14 -1
- package/dist/tools/computer-control-tool.d.ts +16 -1
- package/dist/tools/computer-control-tool.js +317 -5
- package/dist/tools/document-generator.d.ts +14 -0
- package/dist/tools/document-generator.js +79 -0
- package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
- package/dist/tools/execute-code-rpc-invoker.js +46 -5
- package/dist/tools/metadata.js +28 -0
- package/dist/tools/office-macro-tool.d.ts +10 -0
- package/dist/tools/office-macro-tool.js +93 -0
- package/dist/tools/register-tool-handler.d.ts +25 -0
- package/dist/tools/register-tool-handler.js +100 -0
- package/dist/tools/registry/index.d.ts +2 -1
- package/dist/tools/registry/index.js +2 -1
- package/dist/tools/registry/memory-tools.d.ts +20 -0
- package/dist/tools/registry/memory-tools.js +225 -3
- package/dist/tools/registry/multimodal-tools.js +2 -2
- package/dist/tools/registry/vision-tools.d.ts +33 -0
- package/dist/tools/registry/vision-tools.js +308 -6
- package/dist/tools/registry/windows-tools.d.ts +2 -0
- package/dist/tools/registry/windows-tools.js +47 -0
- package/dist/tools/registry.d.ts +6 -0
- package/dist/tools/registry.js +8 -0
- package/dist/tools/route-peer-tool.js +14 -0
- package/dist/tools/screenshot-tool.js +14 -2
- package/dist/tools/text-to-speech-tool.d.ts +1 -1
- package/dist/tools/text-to-speech-tool.js +2 -12
- package/dist/tools/tool-selector.js +3 -59
- package/dist/utils/config-validation/schema.d.ts +40 -7
- package/dist/utils/config-validation/schema.js +130 -1
- package/dist/utils/cost-tracker.js +21 -1
- package/dist/utils/disk-guard.d.ts +180 -0
- package/dist/utils/disk-guard.js +404 -0
- package/dist/utils/init-project.d.ts +7 -0
- package/dist/utils/init-project.js +37 -0
- package/dist/utils/interactive-setup.js +35 -2
- package/dist/utils/model-utils.js +11 -2
- package/dist/utils/output-sanitizer.js +11 -0
- package/dist/utils/provider-detector.d.ts +8 -7
- package/dist/utils/provider-detector.js +19 -77
- package/dist/utils/settings-manager.d.ts +19 -0
- package/dist/utils/token-display.d.ts +3 -3
- package/dist/utils/token-display.js +22 -4
- package/dist/wizard/onboarding.d.ts +39 -0
- package/dist/wizard/onboarding.js +433 -21
- package/dist/wizard/provider-onboarding.d.ts +8 -23
- package/dist/wizard/provider-onboarding.js +39 -158
- package/package.json +4 -2
- package/dist/plugins/provider-onboarding.d.ts +0 -23
- package/dist/plugins/provider-onboarding.js +0 -116
- package/dist/utils/kokoro-tts.d.ts +0 -8
- package/dist/utils/kokoro-tts.js +0 -67
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ process.env.CODEBUDDY_CLI_VERSION = packageJson.version;
|
|
|
24
24
|
import { logger } from "./utils/logger.js";
|
|
25
25
|
// Import graceful shutdown for clean application termination
|
|
26
26
|
import { initializeGracefulShutdown, getShutdownManager, } from "./utils/graceful-shutdown.js";
|
|
27
|
+
import { sweepStaleCodebuddyTemp } from "./utils/disk-guard.js";
|
|
27
28
|
/**
|
|
28
29
|
* CLI output helper.
|
|
29
30
|
*
|
|
@@ -165,6 +166,10 @@ const _shutdownManager = initializeGracefulShutdown({
|
|
|
165
166
|
forceExitOnTimeout: true,
|
|
166
167
|
showProgress: true,
|
|
167
168
|
});
|
|
169
|
+
// Startup janitor: remove our own stale /tmp scratch dirs left by crashed or
|
|
170
|
+
// SIGKILL'd runs (the durable backstop for the disk-leak class that caused the
|
|
171
|
+
// 2026-06-17 ENOSPC — see src/utils/disk-guard.ts). Age-gated; never throws.
|
|
172
|
+
sweepStaleCodebuddyTemp();
|
|
168
173
|
// Note: SIGINT, SIGTERM, SIGHUP are now handled by GracefulShutdownManager
|
|
169
174
|
// The manager will:
|
|
170
175
|
// 1. Wait for pending operations to complete
|
|
@@ -352,11 +357,94 @@ async function getDetectedProvider() {
|
|
|
352
357
|
return cachedProvider;
|
|
353
358
|
await ensureEnvLoaded();
|
|
354
359
|
cachedProvider = detectProviderFromEnv();
|
|
360
|
+
// xAI subscription login (`buddy login xai`). Token load + refresh is async,
|
|
361
|
+
// so it can't live in the sync env detector. A valid login takes precedence
|
|
362
|
+
// over an ambient local/env provider (mirrors ChatGPT-login precedence) — but
|
|
363
|
+
// ONLY when the token actually resolves, so a stale login never strands a
|
|
364
|
+
// working provider. Without this, `buddy login xai` stored tokens the runtime
|
|
365
|
+
// never consumed.
|
|
366
|
+
{
|
|
367
|
+
const xaiOverride = process.env.CODEBUDDY_PROVIDER?.toLowerCase();
|
|
368
|
+
const xaiWanted = xaiOverride === 'xai' ||
|
|
369
|
+
(!xaiOverride &&
|
|
370
|
+
cachedProvider?.provider !== 'chatgpt' &&
|
|
371
|
+
!process.env.GROK_API_KEY &&
|
|
372
|
+
!process.env.XAI_API_KEY);
|
|
373
|
+
if (xaiWanted) {
|
|
374
|
+
try {
|
|
375
|
+
const { hasXaiCredentials, getValidXaiAccessToken } = await import('./providers/xai-oauth.js');
|
|
376
|
+
if (hasXaiCredentials()) {
|
|
377
|
+
const token = await getValidXaiAccessToken();
|
|
378
|
+
if (token) {
|
|
379
|
+
cachedProvider = {
|
|
380
|
+
provider: 'grok',
|
|
381
|
+
apiKey: token,
|
|
382
|
+
baseURL: 'https://api.x.ai/v1',
|
|
383
|
+
// grok-4-latest is an alias of the current flagship grok-4.3
|
|
384
|
+
// (verified accessible on the SuperGrok plan; Hermes defaults here too).
|
|
385
|
+
defaultModel: process.env.GROK_MODEL || 'grok-4-latest',
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
logger.warn('xAI login found but no valid access token — run `buddy login xai` again.');
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
logger.debug('xAI login resolution skipped', {
|
|
395
|
+
error: err instanceof Error ? err.message : String(err),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Honor an onboarded LOCAL provider (Ollama / LM Studio) persisted to
|
|
401
|
+
// user-settings.json by `buddy onboard`. Env detection only inspects
|
|
402
|
+
// OLLAMA_HOST/oauth files, so a user who completed the wizard and picked
|
|
403
|
+
// Ollama would otherwise dead-end on "No provider configured" on the next
|
|
404
|
+
// run — local providers legitimately carry no API key, so the guard that
|
|
405
|
+
// keys off apiKey presence misfires. Cloud/env providers are matched first
|
|
406
|
+
// above, so this only triggers when nothing else resolved.
|
|
407
|
+
if (!cachedProvider) {
|
|
408
|
+
cachedProvider = await detectOnboardedLocalProvider();
|
|
409
|
+
}
|
|
355
410
|
if (cachedProvider) {
|
|
356
411
|
logger.info(`Auto-detected provider: ${cachedProvider.provider} (model: ${cachedProvider.defaultModel})`);
|
|
357
412
|
}
|
|
358
413
|
return cachedProvider;
|
|
359
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* Resolve an onboarded local provider (Ollama / LM Studio) from
|
|
417
|
+
* user-settings.json. Returns a detection with a placeholder API key (local
|
|
418
|
+
* OpenAI-compatible servers ignore it) so the downstream "no provider" guard
|
|
419
|
+
* passes and the persisted baseURL/model are used.
|
|
420
|
+
*/
|
|
421
|
+
async function detectOnboardedLocalProvider() {
|
|
422
|
+
try {
|
|
423
|
+
const getSettingsManager = await lazyImport.settingsManager();
|
|
424
|
+
const settings = getSettingsManager().loadUserSettings();
|
|
425
|
+
const provider = (settings.provider || '').toLowerCase();
|
|
426
|
+
if (provider !== 'ollama' && provider !== 'lmstudio')
|
|
427
|
+
return null;
|
|
428
|
+
const fallbackBase = provider === 'ollama'
|
|
429
|
+
? 'http://localhost:11434/v1'
|
|
430
|
+
: 'http://localhost:1234/v1';
|
|
431
|
+
const baseURL = settings.baseURL || fallbackBase;
|
|
432
|
+
// Mirror the env path so anything else reading OLLAMA_HOST stays consistent.
|
|
433
|
+
if (provider === 'ollama' && !process.env.OLLAMA_HOST) {
|
|
434
|
+
process.env.OLLAMA_HOST = baseURL.replace(/\/v1\/?$/, '');
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
provider: provider,
|
|
438
|
+
apiKey: provider, // placeholder — ignored by local OpenAI-compat servers
|
|
439
|
+
baseURL,
|
|
440
|
+
defaultModel: settings.model || (provider === 'ollama' ? 'llama3' : 'default'),
|
|
441
|
+
source: 'environment',
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
360
448
|
// Load API key from environment, secure storage, or legacy settings
|
|
361
449
|
async function loadApiKey() {
|
|
362
450
|
await ensureEnvLoaded();
|
|
@@ -426,28 +514,77 @@ async function loadModel() {
|
|
|
426
514
|
// 1. Explicit env var takes highest priority
|
|
427
515
|
if (process.env.GROK_MODEL)
|
|
428
516
|
return process.env.GROK_MODEL;
|
|
429
|
-
|
|
517
|
+
const detected = await getDetectedProvider();
|
|
518
|
+
// 2. Project/user settings override auto-detection — UNLESS the saved model is
|
|
519
|
+
// incompatible with the detected provider. A `gpt-5.5` left in settings.json
|
|
520
|
+
// would 404 against xAI after `buddy login xai`; prefer the Grok default then.
|
|
430
521
|
try {
|
|
431
522
|
const getSettingsManager = await lazyImport.settingsManager();
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
523
|
+
const settingsModel = getSettingsManager().getCurrentModel();
|
|
524
|
+
if (settingsModel) {
|
|
525
|
+
const grokProvider = detected?.provider === 'grok';
|
|
526
|
+
if (!grokProvider || /grok/i.test(settingsModel)) {
|
|
527
|
+
return settingsModel;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
436
530
|
}
|
|
437
531
|
catch (_err) {
|
|
438
532
|
logger.debug('Failed to load model from settings manager', { error: _err });
|
|
439
533
|
}
|
|
440
534
|
// 3. Fallback to auto-detected provider's default model
|
|
441
|
-
const detected = await getDetectedProvider();
|
|
442
535
|
if (detected)
|
|
443
536
|
return detected.defaultModel;
|
|
444
537
|
return undefined;
|
|
445
538
|
}
|
|
539
|
+
/**
|
|
540
|
+
* Active-LLM auto-failover. When `[llm] enabled` (or CODEBUDDY_LLM_FAILOVER=1),
|
|
541
|
+
* build the registry of the user's live logins and inject it into the agent's
|
|
542
|
+
* client fallback list — so a failing primary transparently fails over to the
|
|
543
|
+
* next active LLM (resilience order by default: capable/subscription first,
|
|
544
|
+
* local last). OFF by default → single-provider behavior is unchanged.
|
|
545
|
+
*/
|
|
546
|
+
async function applyActiveLlmFailover(agent) {
|
|
547
|
+
try {
|
|
548
|
+
const { getConfigManager } = await import('./config/toml-config.js');
|
|
549
|
+
const llmCfg = getConfigManager().getConfig().llm;
|
|
550
|
+
const enabled = Boolean(llmCfg?.enabled) || process.env.CODEBUDDY_LLM_FAILOVER === '1';
|
|
551
|
+
if (!enabled)
|
|
552
|
+
return;
|
|
553
|
+
const primary = await getDetectedProvider();
|
|
554
|
+
const { buildActiveLlmRegistry } = await import('./providers/active-llm-registry.js');
|
|
555
|
+
const orderEnv = process.env.CODEBUDDY_LLM_ORDER;
|
|
556
|
+
const registry = await buildActiveLlmRegistry({
|
|
557
|
+
primary: primary
|
|
558
|
+
? {
|
|
559
|
+
provider: primary.provider,
|
|
560
|
+
apiKey: primary.apiKey,
|
|
561
|
+
baseURL: primary.baseURL,
|
|
562
|
+
model: primary.defaultModel,
|
|
563
|
+
}
|
|
564
|
+
: undefined,
|
|
565
|
+
policy: orderEnv || llmCfg?.order || 'resilience',
|
|
566
|
+
manualOrder: llmCfg?.manualOrder,
|
|
567
|
+
localOnly: llmCfg?.local_only,
|
|
568
|
+
});
|
|
569
|
+
if (registry.fallbacks.length > 0) {
|
|
570
|
+
agent.getClient().setRuntimeFallbackProviders(registry.fallbacks);
|
|
571
|
+
logger.info(`Active-LLM failover: ${primary?.provider ?? '?'} → ${registry.fallbacks
|
|
572
|
+
.map((f) => f.provider)
|
|
573
|
+
.join(' → ')}`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
catch (err) {
|
|
577
|
+
logger.debug('Active-LLM failover setup skipped', {
|
|
578
|
+
error: err instanceof Error ? err.message : String(err),
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
446
582
|
// Handle commit-and-push command in headless mode
|
|
447
583
|
async function handleCommitAndPushHeadless(apiKey, baseURL, model, maxToolRounds) {
|
|
448
584
|
try {
|
|
449
585
|
const CodeBuddyAgent = await lazyImport.CodeBuddyAgent();
|
|
450
586
|
const agent = new CodeBuddyAgent(apiKey, baseURL, model, maxToolRounds);
|
|
587
|
+
await applyActiveLlmFailover(agent);
|
|
451
588
|
// Configure confirmation service for headless mode (auto-approve all operations)
|
|
452
589
|
const { ConfirmationService } = await import("./utils/confirmation-service.js");
|
|
453
590
|
const confirmationService = ConfirmationService.getInstance();
|
|
@@ -688,14 +825,31 @@ async function applyCustomAgentToolFilter(agentConfig) {
|
|
|
688
825
|
async function processPromptHeadless(prompt, apiKey, baseURL, model, maxToolRounds, selfHealEnabled = true, outputFormat = 'json', outputSchemaPath, agentName) {
|
|
689
826
|
const previousDisableMCP = process.env.CODEBUDDY_DISABLE_MCP;
|
|
690
827
|
const previousHeadless = process.env.CODEBUDDY_HEADLESS;
|
|
691
|
-
|
|
828
|
+
// Headless defaults MCP OFF (startup cost / determinism), but respect an
|
|
829
|
+
// explicit opt-in so `CODEBUDDY_DISABLE_MCP=false buddy -p …` can use MCP
|
|
830
|
+
// servers (e.g. the Code Explorer / gitnexus bridge, or the benchmark's
|
|
831
|
+
// "with graph" condition). Mirrors the opt-in pattern in goal-cli.ts.
|
|
832
|
+
process.env.CODEBUDDY_DISABLE_MCP = process.env.CODEBUDDY_DISABLE_MCP ?? 'true';
|
|
692
833
|
process.env.CODEBUDDY_HEADLESS = 'true';
|
|
693
834
|
try {
|
|
694
835
|
const customAgentConfig = await loadCustomAgentForCli(agentName, false);
|
|
695
836
|
const modelToUse = customAgentConfig?.model ?? model;
|
|
696
837
|
const CodeBuddyAgent = await lazyImport.CodeBuddyAgent();
|
|
697
838
|
const agent = new CodeBuddyAgent(apiKey, baseURL, modelToUse, maxToolRounds);
|
|
839
|
+
await applyActiveLlmFailover(agent);
|
|
698
840
|
await agent.systemPromptReady;
|
|
841
|
+
// When MCP is opted in for this headless run, wait for the servers to finish
|
|
842
|
+
// connecting so their tools (e.g. the Code Explorer / gitnexus bridge) are
|
|
843
|
+
// registered before the first turn — otherwise the one-shot turn races init
|
|
844
|
+
// and the agent never sees the MCP tools.
|
|
845
|
+
if (process.env.CODEBUDDY_DISABLE_MCP !== 'true') {
|
|
846
|
+
try {
|
|
847
|
+
await agent.getMCPReady();
|
|
848
|
+
}
|
|
849
|
+
catch (e) {
|
|
850
|
+
logger.debug('MCP readiness wait failed (continuing without MCP tools)', { error: String(e) });
|
|
851
|
+
}
|
|
852
|
+
}
|
|
699
853
|
if (customAgentConfig?.systemPrompt) {
|
|
700
854
|
agent.setSystemPrompt(customAgentConfig.systemPrompt);
|
|
701
855
|
}
|
|
@@ -942,7 +1096,7 @@ program
|
|
|
942
1096
|
.option("--quiet", "suppress informational logs (only show errors and responses)")
|
|
943
1097
|
.option("--verbose", "enable verbose/debug output")
|
|
944
1098
|
.option("--speak", "enable automatic speech synthesis of agent responses using Text-to-Speech")
|
|
945
|
-
.option("--tts-provider <provider>", "TTS provider (edge-tts, espeak, say, piper, audioreader
|
|
1099
|
+
.option("--tts-provider <provider>", "TTS provider (edge-tts, espeak, say, piper, audioreader)")
|
|
946
1100
|
.action(async (message, options) => {
|
|
947
1101
|
// Apply --quiet / --verbose flags
|
|
948
1102
|
if (options.quiet) {
|
|
@@ -963,7 +1117,7 @@ program
|
|
|
963
1117
|
ttsManager.setAutoSpeak(true);
|
|
964
1118
|
}
|
|
965
1119
|
if (options.ttsProvider) {
|
|
966
|
-
const validProviders = ['edge-tts', 'espeak', 'say', 'piper', 'audioreader'
|
|
1120
|
+
const validProviders = ['edge-tts', 'espeak', 'say', 'piper', 'audioreader'];
|
|
967
1121
|
if (validProviders.includes(options.ttsProvider)) {
|
|
968
1122
|
ttsManager.updateConfig({ provider: options.ttsProvider });
|
|
969
1123
|
}
|
|
@@ -1143,13 +1297,56 @@ program
|
|
|
1143
1297
|
}
|
|
1144
1298
|
try {
|
|
1145
1299
|
// Get API key from options, environment, or user settings
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
|
|
1300
|
+
const explicitProvider = options.model && !options.apiKey && !options.baseUrl
|
|
1301
|
+
? (await import('./commands/llm-provider-resolution.js')).resolveCommandProvider({
|
|
1302
|
+
explicitModel: options.model,
|
|
1303
|
+
})
|
|
1304
|
+
: null;
|
|
1305
|
+
let apiKey = options.apiKey || explicitProvider?.apiKey || await loadApiKey();
|
|
1306
|
+
let baseURL = options.baseUrl || explicitProvider?.baseURL || await loadBaseURL();
|
|
1307
|
+
let model = options.model || explicitProvider?.model || await loadModel(); // let: can be overridden by --agent
|
|
1149
1308
|
const maxToolRounds = parseInt(options.maxToolRounds) || 400;
|
|
1150
1309
|
if (!apiKey) {
|
|
1151
|
-
|
|
1152
|
-
|
|
1310
|
+
// In an interactive terminal, offer the guided setup wizard right here
|
|
1311
|
+
// (like Hermes) instead of dead-ending on an error — then continue the
|
|
1312
|
+
// session with the credentials it captured.
|
|
1313
|
+
const interactive = Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) &&
|
|
1314
|
+
!options.prompt && !options.print &&
|
|
1315
|
+
process.env.CI !== 'true' && process.env.GITHUB_ACTIONS !== 'true';
|
|
1316
|
+
let recovered = false;
|
|
1317
|
+
if (interactive) {
|
|
1318
|
+
const readline = await import('readline');
|
|
1319
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1320
|
+
const answer = await new Promise((resolve) => rl.question('\n❔ No AI provider configured. Run guided setup now? [Y/n] ', resolve));
|
|
1321
|
+
rl.close();
|
|
1322
|
+
const yes = answer.trim() === '' || /^y(es)?$/i.test(answer.trim());
|
|
1323
|
+
if (yes) {
|
|
1324
|
+
try {
|
|
1325
|
+
const { runOnboarding } = await import('./wizard/onboarding.js');
|
|
1326
|
+
const result = await runOnboarding();
|
|
1327
|
+
// Bust the cached provider detection so the just-saved creds resolve.
|
|
1328
|
+
cachedProvider = undefined;
|
|
1329
|
+
apiKey = options.apiKey || await loadApiKey();
|
|
1330
|
+
baseURL = options.baseUrl || await loadBaseURL();
|
|
1331
|
+
model = options.model || result.model || await loadModel();
|
|
1332
|
+
recovered = Boolean(apiKey);
|
|
1333
|
+
}
|
|
1334
|
+
catch (err) {
|
|
1335
|
+
logger.error('Guided setup did not complete', err instanceof Error ? err : { error: String(err) });
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
if (!recovered) {
|
|
1340
|
+
logger.error([
|
|
1341
|
+
"❌ No AI provider configured. Pick one to get started:",
|
|
1342
|
+
" • Guided setup (recommended) — interactive wizard: buddy onboard",
|
|
1343
|
+
" • Free, no API key — sign in with your ChatGPT plan: buddy login",
|
|
1344
|
+
" • Local & free — run a model with Ollama, then: export OLLAMA_HOST=http://localhost:11434",
|
|
1345
|
+
" • API key — set GROK_API_KEY / OPENAI_API_KEY / ANTHROPIC_API_KEY / GOOGLE_API_KEY (or pass --api-key)",
|
|
1346
|
+
" Run buddy doctor to check your setup.",
|
|
1347
|
+
].join("\n"));
|
|
1348
|
+
process.exit(1);
|
|
1349
|
+
}
|
|
1153
1350
|
}
|
|
1154
1351
|
// Save API key and base URL to user settings if provided via command line
|
|
1155
1352
|
if (options.apiKey || options.baseUrl) {
|
|
@@ -1333,6 +1530,7 @@ program
|
|
|
1333
1530
|
recordStartupPhase('agent-create-start');
|
|
1334
1531
|
const agent = new CodeBuddyAgent(apiKey, baseURL, model, maxToolRounds, true, systemPromptId);
|
|
1335
1532
|
recordStartupPhase('agent-create-done');
|
|
1533
|
+
await applyActiveLlmFailover(agent);
|
|
1336
1534
|
// Apply custom agent system prompt if configured
|
|
1337
1535
|
if (customAgentConfig?.systemPrompt) {
|
|
1338
1536
|
await agent.systemPromptReady;
|
|
@@ -1550,7 +1748,7 @@ program
|
|
|
1550
1748
|
try {
|
|
1551
1749
|
sessionEndFlush.writeHandoffSync(agent.getChatHistory());
|
|
1552
1750
|
}
|
|
1553
|
-
catch { /* handoff is best-effort on hard exit */ }
|
|
1751
|
+
catch (_err) { /* handoff is best-effort on hard exit */ }
|
|
1554
1752
|
};
|
|
1555
1753
|
const flushThenExit = () => {
|
|
1556
1754
|
cleanup();
|
|
@@ -1561,7 +1759,7 @@ program
|
|
|
1561
1759
|
new Promise((resolve) => setTimeout(resolve, 8000).unref()),
|
|
1562
1760
|
]);
|
|
1563
1761
|
}
|
|
1564
|
-
catch { /* never block exit on the flush */ }
|
|
1762
|
+
catch (_err) { /* never block exit on the flush */ }
|
|
1565
1763
|
process.exit(0);
|
|
1566
1764
|
})();
|
|
1567
1765
|
};
|
|
@@ -1661,7 +1859,14 @@ gitCommand
|
|
|
1661
1859
|
const model = options.model || await loadModel();
|
|
1662
1860
|
const maxToolRounds = parseInt(options.maxToolRounds) || 400;
|
|
1663
1861
|
if (!apiKey) {
|
|
1664
|
-
logger.error(
|
|
1862
|
+
logger.error([
|
|
1863
|
+
"❌ No AI provider configured. Pick one to get started:",
|
|
1864
|
+
" • Guided setup (recommended) — interactive wizard: buddy onboard",
|
|
1865
|
+
" • Free, no API key — sign in with your ChatGPT plan: buddy login",
|
|
1866
|
+
" • Local & free — run a model with Ollama, then: export OLLAMA_HOST=http://localhost:11434",
|
|
1867
|
+
" • API key — set GROK_API_KEY / OPENAI_API_KEY / ANTHROPIC_API_KEY / GOOGLE_API_KEY (or pass --api-key)",
|
|
1868
|
+
" Run buddy doctor to check your setup.",
|
|
1869
|
+
].join("\n"));
|
|
1665
1870
|
process.exit(1);
|
|
1666
1871
|
}
|
|
1667
1872
|
// Save API key and base URL to user settings if provided via command line
|
|
@@ -1701,7 +1906,7 @@ function removeCommands(parent, names) {
|
|
|
1701
1906
|
* subcommands, we register a thin wrapper that delegates to the real
|
|
1702
1907
|
* command tree on first use.
|
|
1703
1908
|
*/
|
|
1704
|
-
function addLazyCommand(parent, name, description, loader) {
|
|
1909
|
+
function addLazyCommand(parent, name, description, loader, prevalidateArgv) {
|
|
1705
1910
|
// Create a pass-through command that accepts arbitrary args
|
|
1706
1911
|
const stub = parent
|
|
1707
1912
|
.command(name)
|
|
@@ -1711,6 +1916,14 @@ function addLazyCommand(parent, name, description, loader) {
|
|
|
1711
1916
|
.helpOption(false);
|
|
1712
1917
|
// Override the parse to delegate to the real command
|
|
1713
1918
|
stub.action(async () => {
|
|
1919
|
+
try {
|
|
1920
|
+
await prevalidateArgv?.(process.argv);
|
|
1921
|
+
}
|
|
1922
|
+
catch (error) {
|
|
1923
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1924
|
+
process.stderr.write(`error: ${message}\n`);
|
|
1925
|
+
process.exit(1);
|
|
1926
|
+
}
|
|
1714
1927
|
const realCommand = await loader();
|
|
1715
1928
|
// Replace the stub with the real command and re-parse
|
|
1716
1929
|
removeCommands(parent, name);
|
|
@@ -1764,6 +1977,19 @@ program
|
|
|
1764
1977
|
process.exit(1);
|
|
1765
1978
|
}
|
|
1766
1979
|
});
|
|
1980
|
+
// Cowork (the Electron desktop GUI) needs Node >= 22; the terminal CLI runs on
|
|
1981
|
+
// Node >= 18. Without this guard a Node 18/20 user gets a cryptic Electron/Vite
|
|
1982
|
+
// crash mid-launch instead of a clear message — a classic first-run star-killer.
|
|
1983
|
+
const COWORK_MIN_NODE_MAJOR = 22;
|
|
1984
|
+
function assertNodeForCowork() {
|
|
1985
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
1986
|
+
if (Number.isFinite(major) && major < COWORK_MIN_NODE_MAJOR) {
|
|
1987
|
+
cli.stdout(`❌ Cowork (the desktop GUI) requires Node.js >= ${COWORK_MIN_NODE_MAJOR} — you're on v${process.versions.node}.`);
|
|
1988
|
+
cli.stdout(` The terminal CLI works on Node >= 18; only the Electron app needs >= ${COWORK_MIN_NODE_MAJOR}.`);
|
|
1989
|
+
cli.stdout(` Upgrade Node (e.g. \`nvm install 22 && nvm use 22\`) and retry. Linux build notes: cowork/DEV-LINUX.md`);
|
|
1990
|
+
process.exit(1);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1767
1993
|
// Desktop GUI commands
|
|
1768
1994
|
program
|
|
1769
1995
|
.command("gui")
|
|
@@ -1771,6 +1997,7 @@ program
|
|
|
1771
1997
|
.option("--dev", "start with Vite dev server (hot reload)")
|
|
1772
1998
|
.option("--detach", "run in background")
|
|
1773
1999
|
.action(async (options) => {
|
|
2000
|
+
assertNodeForCowork();
|
|
1774
2001
|
const { launchDesktop } = await import("./desktop/launcher.js");
|
|
1775
2002
|
const code = await launchDesktop({
|
|
1776
2003
|
dev: options.dev,
|
|
@@ -1784,6 +2011,7 @@ program
|
|
|
1784
2011
|
.option("--dev", "start with Vite dev server")
|
|
1785
2012
|
.option("--detach", "run in background")
|
|
1786
2013
|
.action(async (options) => {
|
|
2014
|
+
assertNodeForCowork();
|
|
1787
2015
|
const { launchDesktop } = await import("./desktop/launcher.js");
|
|
1788
2016
|
const code = await launchDesktop({
|
|
1789
2017
|
dev: options.dev,
|
|
@@ -1795,21 +2023,146 @@ program
|
|
|
1795
2023
|
.command("install-gui")
|
|
1796
2024
|
.description("Install Electron and build the desktop GUI")
|
|
1797
2025
|
.action(async () => {
|
|
2026
|
+
assertNodeForCowork();
|
|
1798
2027
|
const { installGUI } = await import("./desktop/installer.js");
|
|
1799
2028
|
await installGUI();
|
|
1800
2029
|
});
|
|
2030
|
+
/**
|
|
2031
|
+
* After a successful xAI login, probe api.x.ai with one tiny real call so the
|
|
2032
|
+
* user learns immediately whether their subscription includes API inference
|
|
2033
|
+
* (a SuperGrok plan does not always — the OAuth `api:access` scope is
|
|
2034
|
+
* requested, not guaranteed granted).
|
|
2035
|
+
*/
|
|
2036
|
+
async function probeXaiInference() {
|
|
2037
|
+
const { getValidXaiAccessToken, XAI_OAUTH_BASE_URL } = await import("./providers/xai-oauth.js");
|
|
2038
|
+
cli.stdout("\nVerifying inference access (api.x.ai) ...");
|
|
2039
|
+
const token = await getValidXaiAccessToken();
|
|
2040
|
+
if (!token) {
|
|
2041
|
+
cli.error("❌ Could not load a valid token after login.");
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
const probeModel = process.env.GROK_MODEL || "grok-3";
|
|
2045
|
+
try {
|
|
2046
|
+
const res = await fetch(`${XAI_OAUTH_BASE_URL}/chat/completions`, {
|
|
2047
|
+
method: "POST",
|
|
2048
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
2049
|
+
body: JSON.stringify({
|
|
2050
|
+
model: probeModel,
|
|
2051
|
+
max_tokens: 1,
|
|
2052
|
+
messages: [{ role: "user", content: "hi" }],
|
|
2053
|
+
}),
|
|
2054
|
+
signal: AbortSignal.timeout(30_000),
|
|
2055
|
+
});
|
|
2056
|
+
if (res.ok) {
|
|
2057
|
+
cli.stdout("✅ Inference access confirmed — your subscription works in Code Buddy.");
|
|
2058
|
+
cli.stdout(` Try: buddy --model ${probeModel} -p "hello" (or just \`buddy\`)`);
|
|
2059
|
+
}
|
|
2060
|
+
else if (res.status === 401 || res.status === 403) {
|
|
2061
|
+
const body = await res.text().catch(() => "");
|
|
2062
|
+
cli.error(`⚠️ Login succeeded, but api.x.ai returned ${res.status} — your subscription may not include API inference.`);
|
|
2063
|
+
if (body)
|
|
2064
|
+
cli.error(` Detail: ${body.slice(0, 200)}`);
|
|
2065
|
+
cli.error(" You can still use Grok with a metered API key: set XAI_API_KEY.");
|
|
2066
|
+
}
|
|
2067
|
+
else {
|
|
2068
|
+
const body = await res.text().catch(() => "");
|
|
2069
|
+
cli.error(`⚠️ Probe returned ${res.status} (model "${probeModel}"?): ${body.slice(0, 200)}`);
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
catch (err) {
|
|
2073
|
+
cli.error(`⚠️ Probe call failed (network?): ${err instanceof Error ? err.message : String(err)}`);
|
|
2074
|
+
cli.stdout(' Tokens are stored; try `buddy -p "hello"`.');
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* xAI / Grok subscription login (`buddy login xai`). Two-step so it works in
|
|
2079
|
+
* backgrounded / non-interactive / remote shells where stdin can't be pasted
|
|
2080
|
+
* into: step 1 opens the browser + persists the PKCE material, step 2
|
|
2081
|
+
* (`--code <code>`) exchanges the code xAI shows in-page. In an interactive
|
|
2082
|
+
* TTY, step 1 also accepts the code inline for a one-shot experience.
|
|
2083
|
+
*/
|
|
2084
|
+
async function loginXaiCli(codeArg) {
|
|
2085
|
+
const xai = await import("./providers/xai-oauth.js");
|
|
2086
|
+
// Step 2 — complete a pending login with the copied code.
|
|
2087
|
+
if (codeArg) {
|
|
2088
|
+
try {
|
|
2089
|
+
const auth = await xai.completeLogin(codeArg);
|
|
2090
|
+
cli.stdout("✅ Authenticated with xAI");
|
|
2091
|
+
if (auth.email)
|
|
2092
|
+
cli.stdout(` Account: ${auth.email}`);
|
|
2093
|
+
if (typeof auth.expires_in_seconds === "number") {
|
|
2094
|
+
cli.stdout(` Token TTL: ~${Math.round(auth.expires_in_seconds / 60)} min`);
|
|
2095
|
+
}
|
|
2096
|
+
cli.stdout(` Tokens stored at: ${xai.getXaiAuthFilePath()}`);
|
|
2097
|
+
await probeXaiInference();
|
|
2098
|
+
}
|
|
2099
|
+
catch (err) {
|
|
2100
|
+
cli.error(`❌ Login failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2101
|
+
process.exit(1);
|
|
2102
|
+
}
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
// Step 1 — open the browser and persist the pending PKCE material.
|
|
2106
|
+
cli.stdout("🔐 xAI / Grok login");
|
|
2107
|
+
let authorizeUrl;
|
|
2108
|
+
try {
|
|
2109
|
+
({ authorizeUrl } = await xai.beginLogin());
|
|
2110
|
+
}
|
|
2111
|
+
catch (err) {
|
|
2112
|
+
cli.error(`❌ Could not start login: ${err instanceof Error ? err.message : String(err)}`);
|
|
2113
|
+
process.exit(1);
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
try {
|
|
2117
|
+
const openModule = await import("open");
|
|
2118
|
+
await openModule.default(authorizeUrl);
|
|
2119
|
+
cli.stdout("Opening your browser to authorize Code Buddy with your xAI / SuperGrok plan ...");
|
|
2120
|
+
}
|
|
2121
|
+
catch (_err) {
|
|
2122
|
+
/* browser open is best-effort — fall back to the printed URL below */
|
|
2123
|
+
}
|
|
2124
|
+
cli.stdout(`\nIf your browser didn't open, visit this URL:\n${authorizeUrl}\n`);
|
|
2125
|
+
cli.stdout("xAI shows an authorization CODE in the page. Copy it, then run:");
|
|
2126
|
+
cli.stdout(" buddy login xai --code <PASTE_THE_CODE>\n");
|
|
2127
|
+
// Interactive convenience: if we have a real TTY, accept the code inline.
|
|
2128
|
+
if (process.stdin.isTTY) {
|
|
2129
|
+
const readline = await import("readline");
|
|
2130
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2131
|
+
const answer = await new Promise((resolve) => rl.question("Paste the code here (or Ctrl-C to finish later): ", resolve));
|
|
2132
|
+
rl.close();
|
|
2133
|
+
if (answer.trim()) {
|
|
2134
|
+
try {
|
|
2135
|
+
const auth = await xai.completeLogin(answer);
|
|
2136
|
+
cli.stdout("✅ Authenticated with xAI");
|
|
2137
|
+
if (auth.email)
|
|
2138
|
+
cli.stdout(` Account: ${auth.email}`);
|
|
2139
|
+
cli.stdout(` Tokens stored at: ${xai.getXaiAuthFilePath()}`);
|
|
2140
|
+
await probeXaiInference();
|
|
2141
|
+
}
|
|
2142
|
+
catch (err) {
|
|
2143
|
+
cli.error(`❌ Login failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2144
|
+
process.exit(1);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
1801
2149
|
// Phase d.23+ — Authentication commands.
|
|
1802
2150
|
// Run the OAuth flow directly from the CLI so users don't have to enter
|
|
1803
2151
|
// the interactive TUI just to log in. Mirrors the `/login` slash command
|
|
1804
2152
|
// that's available inside a session.
|
|
1805
2153
|
program
|
|
1806
2154
|
.command("login [provider]")
|
|
1807
|
-
.description("Authenticate with a provider (
|
|
1808
|
-
.
|
|
2155
|
+
.description("Authenticate with a provider (chatgpt | xai — uses your subscription, no API key)")
|
|
2156
|
+
.option("--code <code>", "Complete an xAI login with the code shown in the browser")
|
|
2157
|
+
.action(async (provider, options) => {
|
|
1809
2158
|
const target = (provider ?? "chatgpt").toLowerCase();
|
|
2159
|
+
if (target === "xai" || target === "grok" || target === "xai-oauth") {
|
|
2160
|
+
await loginXaiCli(options.code);
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
1810
2163
|
if (target !== "chatgpt" && target !== "codex" && target !== "openai") {
|
|
1811
|
-
cli.stdout(`Unknown provider: "${provider}".
|
|
1812
|
-
cli.stdout("Other providers (Gemini, Anthropic
|
|
2164
|
+
cli.stdout(`Unknown provider: "${provider}". Supported: \`chatgpt\`, \`xai\`.`);
|
|
2165
|
+
cli.stdout("Other providers (Gemini, Anthropic) authenticate via API key env vars.");
|
|
1813
2166
|
process.exit(1);
|
|
1814
2167
|
}
|
|
1815
2168
|
const { loginInteractive, getCodexAuthFilePath } = await import("./providers/codex-oauth.js");
|
|
@@ -1837,11 +2190,21 @@ program
|
|
|
1837
2190
|
});
|
|
1838
2191
|
program
|
|
1839
2192
|
.command("logout [provider]")
|
|
1840
|
-
.description("Clear stored credentials for a provider (
|
|
2193
|
+
.description("Clear stored credentials for a provider (chatgpt | xai)")
|
|
1841
2194
|
.action(async (provider) => {
|
|
1842
2195
|
const target = (provider ?? "chatgpt").toLowerCase();
|
|
2196
|
+
if (target === "xai" || target === "grok" || target === "xai-oauth") {
|
|
2197
|
+
const { hasXaiCredentials, clearXaiCredentials, getXaiAuthFilePath } = await import("./providers/xai-oauth.js");
|
|
2198
|
+
if (!hasXaiCredentials()) {
|
|
2199
|
+
cli.stdout("No xAI credentials on disk — already logged out.");
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
clearXaiCredentials();
|
|
2203
|
+
cli.stdout(`✅ xAI credentials cleared (${getXaiAuthFilePath()})`);
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
1843
2206
|
if (target !== "chatgpt" && target !== "codex" && target !== "openai") {
|
|
1844
|
-
cli.stdout(`Unknown provider: "${provider}".
|
|
2207
|
+
cli.stdout(`Unknown provider: "${provider}". Supported: \`chatgpt\`, \`xai\`.`);
|
|
1845
2208
|
process.exit(1);
|
|
1846
2209
|
}
|
|
1847
2210
|
const { hasCodexCredentials, clearCodexCredentials, getCodexAuthFilePath } = await import("./providers/codex-oauth.js");
|
|
@@ -1882,6 +2245,89 @@ program
|
|
|
1882
2245
|
process.exit(1);
|
|
1883
2246
|
}
|
|
1884
2247
|
});
|
|
2248
|
+
// Active-LLM registry — list the LLMs you're logged into + the failover order.
|
|
2249
|
+
program
|
|
2250
|
+
.command("llm [action] [prompt...]")
|
|
2251
|
+
.description("List active LLMs, or run several together: llm ensemble|consensus|race <prompt>")
|
|
2252
|
+
.option("--order <policy>", "Ordering: resilience | free-first | manual")
|
|
2253
|
+
.action(async (action, promptParts = [], opts) => {
|
|
2254
|
+
const primary = await getDetectedProvider();
|
|
2255
|
+
const { buildActiveLlmRegistry } = await import("./providers/active-llm-registry.js");
|
|
2256
|
+
const registry = await buildActiveLlmRegistry({
|
|
2257
|
+
primary: primary
|
|
2258
|
+
? {
|
|
2259
|
+
provider: primary.provider,
|
|
2260
|
+
apiKey: primary.apiKey,
|
|
2261
|
+
baseURL: primary.baseURL,
|
|
2262
|
+
model: primary.defaultModel,
|
|
2263
|
+
}
|
|
2264
|
+
: undefined,
|
|
2265
|
+
policy: opts.order,
|
|
2266
|
+
});
|
|
2267
|
+
if (registry.all.length === 0) {
|
|
2268
|
+
cli.stdout("No active LLMs detected. Run `buddy login`, set an API key, or start Ollama.");
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
// "Together" — run several active LLMs at once and aggregate the result.
|
|
2272
|
+
const strategyByAction = {
|
|
2273
|
+
ensemble: "ensemble",
|
|
2274
|
+
consensus: "consensus",
|
|
2275
|
+
race: "fastest",
|
|
2276
|
+
together: "all",
|
|
2277
|
+
};
|
|
2278
|
+
const strategy = action ? strategyByAction[action.toLowerCase()] : undefined;
|
|
2279
|
+
if (strategy) {
|
|
2280
|
+
const promptText = promptParts.join(" ").trim();
|
|
2281
|
+
if (!promptText) {
|
|
2282
|
+
cli.error(`Usage: buddy llm ${action} "<prompt>"`);
|
|
2283
|
+
process.exit(1);
|
|
2284
|
+
}
|
|
2285
|
+
const models = registry.all.map((p) => ({
|
|
2286
|
+
id: p.provider,
|
|
2287
|
+
name: p.provider,
|
|
2288
|
+
provider: "codebuddy",
|
|
2289
|
+
model: p.model,
|
|
2290
|
+
apiKey: p.apiKey,
|
|
2291
|
+
baseURL: p.baseURL,
|
|
2292
|
+
enabled: true,
|
|
2293
|
+
costPerToken: p.costInputUsdPerMtok / 1_000_000,
|
|
2294
|
+
}));
|
|
2295
|
+
cli.stdout(`Running ${action} across ${models.map((m) => m.id).join(", ")} …`);
|
|
2296
|
+
const { createParallelExecutor } = await import("./agent/parallel/parallel-executor.js");
|
|
2297
|
+
const executor = createParallelExecutor({ models, strategy });
|
|
2298
|
+
const result = await executor.execute(promptText);
|
|
2299
|
+
// Show each LLM's own answer — the point of running them together.
|
|
2300
|
+
for (const r of result.responses) {
|
|
2301
|
+
if (r.error) {
|
|
2302
|
+
cli.stdout(`\n── ${r.modelName} ❌ ${r.error.slice(0, 140)}`);
|
|
2303
|
+
}
|
|
2304
|
+
else {
|
|
2305
|
+
cli.stdout(`\n── ${r.modelName} (${r.latency}ms, ${r.tokensUsed} tok)\n${r.content.trim()}`);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
cli.stdout(`\n${executor.formatResult(result)}`);
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
cli.stdout("Active LLMs (logged in + reachable):");
|
|
2312
|
+
for (const p of registry.all) {
|
|
2313
|
+
const isPrimary = registry.primaryProvider === p.provider ? " ← primary" : "";
|
|
2314
|
+
const tag = p.isLocal ? "local" : "cloud";
|
|
2315
|
+
const cost = p.costInputUsdPerMtok === 0 ? "$0" : `$${p.costInputUsdPerMtok}/Mtok`;
|
|
2316
|
+
cli.stdout(` • ${p.provider.padEnd(10)} [${tag}] ${p.model} (${cost})${isPrimary}`);
|
|
2317
|
+
}
|
|
2318
|
+
if (registry.fallbacks.length > 0) {
|
|
2319
|
+
cli.stdout(`\nFailover order: ${primary?.provider ?? "?"} → ${registry.fallbacks
|
|
2320
|
+
.map((f) => f.provider)
|
|
2321
|
+
.join(" → ")}`);
|
|
2322
|
+
}
|
|
2323
|
+
else {
|
|
2324
|
+
cli.stdout("\nNo additional active LLMs available for failover.");
|
|
2325
|
+
}
|
|
2326
|
+
const { getConfigManager } = await import("./config/toml-config.js");
|
|
2327
|
+
const on = Boolean(getConfigManager().getConfig().llm?.enabled) ||
|
|
2328
|
+
process.env.CODEBUDDY_LLM_FAILOVER === "1";
|
|
2329
|
+
cli.stdout(`Auto-failover: ${on ? "ON" : "OFF (enable with [llm].enabled = true, or CODEBUDDY_LLM_FAILOVER=1)"}`);
|
|
2330
|
+
});
|
|
1885
2331
|
// MCP Server command - run Code Buddy as an MCP tool provider over stdio
|
|
1886
2332
|
program
|
|
1887
2333
|
.command("mcp-server")
|
|
@@ -1946,7 +2392,7 @@ addLazyCommandGroup(program, 'speak', 'Synthesize speech using AudioReader TTS',
|
|
|
1946
2392
|
// Utility commands (doctor, security-audit, onboard, webhook) are all registered
|
|
1947
2393
|
// by a single registerUtilityCommands() call, so we must remove all stubs before
|
|
1948
2394
|
// re-registering to avoid Commander duplicate command errors.
|
|
1949
|
-
const utilityCommandNames = ['doctor', 'security-audit', 'onboard', 'webhook'];
|
|
2395
|
+
const utilityCommandNames = ['doctor', 'security-audit', 'onboard', 'webhook', 'ollama'];
|
|
1950
2396
|
const loadUtilityCommands = async () => {
|
|
1951
2397
|
// Remove all utility stubs at once
|
|
1952
2398
|
removeCommands(program, utilityCommandNames);
|
|
@@ -1958,7 +2404,8 @@ for (const cmdName of utilityCommandNames) {
|
|
|
1958
2404
|
const desc = cmdName === 'doctor' ? 'Diagnose Code Buddy environment, dependencies, and configuration'
|
|
1959
2405
|
: cmdName === 'security-audit' ? 'Run a security audit of your Code Buddy environment'
|
|
1960
2406
|
: cmdName === 'onboard' ? 'Interactive setup wizard for Code Buddy'
|
|
1961
|
-
: '
|
|
2407
|
+
: cmdName === 'ollama' ? 'Inspect or update the local Ollama runtime'
|
|
2408
|
+
: 'Manage webhook triggers';
|
|
1962
2409
|
const stub = program
|
|
1963
2410
|
.command(cmdName)
|
|
1964
2411
|
.description(desc)
|
|
@@ -2086,6 +2533,9 @@ addLazyCommand(program, 'flow', 'Execute a multi-agent planning flow (OpenManus-
|
|
|
2086
2533
|
addLazyCommand(program, 'goal', 'Run the agent toward a standing goal until a judge model confirms it is done (Ralph loop)', async () => {
|
|
2087
2534
|
const { createGoalCommand } = await import('./commands/goal-cli.js');
|
|
2088
2535
|
return createGoalCommand();
|
|
2536
|
+
}, async (argv) => {
|
|
2537
|
+
const { validateGoalCommandNumericOptions } = await import('./commands/goal-cli.js');
|
|
2538
|
+
validateGoalCommandNumericOptions(argv);
|
|
2089
2539
|
});
|
|
2090
2540
|
// Todo attention bias — Manus AI-inspired persistent task list
|
|
2091
2541
|
addLazyCommand(program, 'todo', 'Manage persistent task list (todo.md) — injected at end of every agent turn for focus', async () => {
|
|
@@ -2117,6 +2567,11 @@ addLazyCommand(program, 'update', 'Update Code Buddy (switch channels: stable, b
|
|
|
2117
2567
|
const { createUpdateCommand } = await import('./commands/update.js');
|
|
2118
2568
|
return createUpdateCommand();
|
|
2119
2569
|
});
|
|
2570
|
+
// Tunnel — manage ngrok tunnels
|
|
2571
|
+
addLazyCommand(program, 'tunnel', 'Manage ngrok tunnels for the Code Buddy remote gateway', async () => {
|
|
2572
|
+
const { createTunnelCommand } = await import('./commands/tunnel.js');
|
|
2573
|
+
return createTunnelCommand();
|
|
2574
|
+
});
|
|
2120
2575
|
// Nodes — companion app node management (macOS, iOS, Android)
|
|
2121
2576
|
addLazyCommandGroup(program, 'nodes', 'Manage companion app nodes (macOS, iOS, Android)', async () => {
|
|
2122
2577
|
const { registerNodeCommands } = await import('./commands/cli/node-commands.js');
|