@hoangsonw/forge 0.1.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/LICENSE +21 -0
- package/README.md +919 -0
- package/bin/forge.js +30 -0
- package/dist/agents/architect.d.ts +20 -0
- package/dist/agents/architect.d.ts.map +1 -0
- package/dist/agents/architect.js +75 -0
- package/dist/agents/architect.js.map +1 -0
- package/dist/agents/base.d.ts +20 -0
- package/dist/agents/base.d.ts.map +1 -0
- package/dist/agents/base.js +3 -0
- package/dist/agents/base.js.map +1 -0
- package/dist/agents/debugger.d.ts +16 -0
- package/dist/agents/debugger.d.ts.map +1 -0
- package/dist/agents/debugger.js +93 -0
- package/dist/agents/debugger.js.map +1 -0
- package/dist/agents/executor.d.ts +48 -0
- package/dist/agents/executor.d.ts.map +1 -0
- package/dist/agents/executor.js +402 -0
- package/dist/agents/executor.js.map +1 -0
- package/dist/agents/memory.d.ts +8 -0
- package/dist/agents/memory.d.ts.map +1 -0
- package/dist/agents/memory.js +84 -0
- package/dist/agents/memory.js.map +1 -0
- package/dist/agents/planner.d.ts +5 -0
- package/dist/agents/planner.d.ts.map +1 -0
- package/dist/agents/planner.js +185 -0
- package/dist/agents/planner.js.map +1 -0
- package/dist/agents/registry.d.ts +6 -0
- package/dist/agents/registry.d.ts.map +1 -0
- package/dist/agents/registry.js +32 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/agents/reviewer.d.ts +18 -0
- package/dist/agents/reviewer.d.ts.map +1 -0
- package/dist/agents/reviewer.js +87 -0
- package/dist/agents/reviewer.js.map +1 -0
- package/dist/classifier/classifier.d.ts +9 -0
- package/dist/classifier/classifier.d.ts.map +1 -0
- package/dist/classifier/classifier.js +83 -0
- package/dist/classifier/classifier.js.map +1 -0
- package/dist/classifier/heuristics.d.ts +11 -0
- package/dist/classifier/heuristics.d.ts.map +1 -0
- package/dist/classifier/heuristics.js +112 -0
- package/dist/classifier/heuristics.js.map +1 -0
- package/dist/cli/animations.d.ts +27 -0
- package/dist/cli/animations.d.ts.map +1 -0
- package/dist/cli/animations.js +186 -0
- package/dist/cli/animations.js.map +1 -0
- package/dist/cli/banners.d.ts +47 -0
- package/dist/cli/banners.d.ts.map +1 -0
- package/dist/cli/banners.js +211 -0
- package/dist/cli/banners.js.map +1 -0
- package/dist/cli/bootstrap.d.ts +2 -0
- package/dist/cli/bootstrap.d.ts.map +1 -0
- package/dist/cli/bootstrap.js +21 -0
- package/dist/cli/bootstrap.js.map +1 -0
- package/dist/cli/commands/bundle.d.ts +3 -0
- package/dist/cli/commands/bundle.d.ts.map +1 -0
- package/dist/cli/commands/bundle.js +80 -0
- package/dist/cli/commands/bundle.js.map +1 -0
- package/dist/cli/commands/changelog.d.ts +3 -0
- package/dist/cli/commands/changelog.d.ts.map +1 -0
- package/dist/cli/commands/changelog.js +60 -0
- package/dist/cli/commands/changelog.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 +91 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/container.d.ts +3 -0
- package/dist/cli/commands/container.d.ts.map +1 -0
- package/dist/cli/commands/container.js +149 -0
- package/dist/cli/commands/container.js.map +1 -0
- package/dist/cli/commands/cost.d.ts +3 -0
- package/dist/cli/commands/cost.d.ts.map +1 -0
- package/dist/cli/commands/cost.js +38 -0
- package/dist/cli/commands/cost.js.map +1 -0
- package/dist/cli/commands/daemon.d.ts +3 -0
- package/dist/cli/commands/daemon.d.ts.map +1 -0
- package/dist/cli/commands/daemon.js +39 -0
- package/dist/cli/commands/daemon.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +3 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +73 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +214 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +148 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +3 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +227 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/memory.d.ts +3 -0
- package/dist/cli/commands/memory.d.ts.map +1 -0
- package/dist/cli/commands/memory.js +101 -0
- package/dist/cli/commands/memory.js.map +1 -0
- package/dist/cli/commands/migrate.d.ts +3 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -0
- package/dist/cli/commands/migrate.js +18 -0
- package/dist/cli/commands/migrate.js.map +1 -0
- package/dist/cli/commands/model.d.ts +3 -0
- package/dist/cli/commands/model.d.ts.map +1 -0
- package/dist/cli/commands/model.js +37 -0
- package/dist/cli/commands/model.js.map +1 -0
- package/dist/cli/commands/permissions.d.ts +3 -0
- package/dist/cli/commands/permissions.d.ts.map +1 -0
- package/dist/cli/commands/permissions.js +32 -0
- package/dist/cli/commands/permissions.js.map +1 -0
- package/dist/cli/commands/resume.d.ts +3 -0
- package/dist/cli/commands/resume.d.ts.map +1 -0
- package/dist/cli/commands/resume.js +90 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/run.d.ts +5 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +164 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/session.d.ts +3 -0
- package/dist/cli/commands/session.d.ts.map +1 -0
- package/dist/cli/commands/session.js +94 -0
- package/dist/cli/commands/session.js.map +1 -0
- package/dist/cli/commands/skills.d.ts +4 -0
- package/dist/cli/commands/skills.d.ts.map +1 -0
- package/dist/cli/commands/skills.js +176 -0
- package/dist/cli/commands/skills.js.map +1 -0
- package/dist/cli/commands/spec.d.ts +3 -0
- package/dist/cli/commands/spec.d.ts.map +1 -0
- package/dist/cli/commands/spec.js +58 -0
- package/dist/cli/commands/spec.js.map +1 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +65 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/task.d.ts +3 -0
- package/dist/cli/commands/task.d.ts.map +1 -0
- package/dist/cli/commands/task.js +42 -0
- package/dist/cli/commands/task.js.map +1 -0
- package/dist/cli/commands/ui.d.ts +3 -0
- package/dist/cli/commands/ui.d.ts.map +1 -0
- package/dist/cli/commands/ui.js +28 -0
- package/dist/cli/commands/ui.js.map +1 -0
- package/dist/cli/commands/update.d.ts +3 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.js +53 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/commands/web.d.ts +3 -0
- package/dist/cli/commands/web.d.ts.map +1 -0
- package/dist/cli/commands/web.js +42 -0
- package/dist/cli/commands/web.js.map +1 -0
- package/dist/cli/help.d.ts +21 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +216 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +154 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/repl-commands.d.ts +47 -0
- package/dist/cli/repl-commands.d.ts.map +1 -0
- package/dist/cli/repl-commands.js +508 -0
- package/dist/cli/repl-commands.js.map +1 -0
- package/dist/cli/repl-input.d.ts +87 -0
- package/dist/cli/repl-input.d.ts.map +1 -0
- package/dist/cli/repl-input.js +764 -0
- package/dist/cli/repl-input.js.map +1 -0
- package/dist/cli/repl.d.ts +5 -0
- package/dist/cli/repl.d.ts.map +1 -0
- package/dist/cli/repl.js +1046 -0
- package/dist/cli/repl.js.map +1 -0
- package/dist/cli/ui.d.ts +19 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +106 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/config/loader.d.ts +11 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +132 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/paths.d.ts +35 -0
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +114 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/schema.d.ts +372 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +161 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/xdg.d.ts +2 -0
- package/dist/config/xdg.d.ts.map +1 -0
- package/dist/config/xdg.js +55 -0
- package/dist/config/xdg.js.map +1 -0
- package/dist/core/continuity.d.ts +8 -0
- package/dist/core/continuity.d.ts.map +1 -0
- package/dist/core/continuity.js +36 -0
- package/dist/core/continuity.js.map +1 -0
- package/dist/core/conversation.d.ts +152 -0
- package/dist/core/conversation.d.ts.map +1 -0
- package/dist/core/conversation.js +435 -0
- package/dist/core/conversation.js.map +1 -0
- package/dist/core/estimation.d.ts +19 -0
- package/dist/core/estimation.d.ts.map +1 -0
- package/dist/core/estimation.js +53 -0
- package/dist/core/estimation.js.map +1 -0
- package/dist/core/fork.d.ts +7 -0
- package/dist/core/fork.d.ts.map +1 -0
- package/dist/core/fork.js +93 -0
- package/dist/core/fork.js.map +1 -0
- package/dist/core/interactive-host.d.ts +28 -0
- package/dist/core/interactive-host.d.ts.map +1 -0
- package/dist/core/interactive-host.js +19 -0
- package/dist/core/interactive-host.js.map +1 -0
- package/dist/core/loop-detection.d.ts +25 -0
- package/dist/core/loop-detection.d.ts.map +1 -0
- package/dist/core/loop-detection.js +37 -0
- package/dist/core/loop-detection.js.map +1 -0
- package/dist/core/loop.d.ts +15 -0
- package/dist/core/loop.d.ts.map +1 -0
- package/dist/core/loop.js +417 -0
- package/dist/core/loop.js.map +1 -0
- package/dist/core/mode-policy.d.ts +33 -0
- package/dist/core/mode-policy.d.ts.map +1 -0
- package/dist/core/mode-policy.js +62 -0
- package/dist/core/mode-policy.js.map +1 -0
- package/dist/core/orchestrator.d.ts +14 -0
- package/dist/core/orchestrator.d.ts.map +1 -0
- package/dist/core/orchestrator.js +69 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/plan-fixer.d.ts +16 -0
- package/dist/core/plan-fixer.d.ts.map +1 -0
- package/dist/core/plan-fixer.js +55 -0
- package/dist/core/plan-fixer.js.map +1 -0
- package/dist/core/signals.d.ts +5 -0
- package/dist/core/signals.d.ts.map +1 -0
- package/dist/core/signals.js +44 -0
- package/dist/core/signals.js.map +1 -0
- package/dist/core/spec.d.ts +8 -0
- package/dist/core/spec.d.ts.map +1 -0
- package/dist/core/spec.js +75 -0
- package/dist/core/spec.js.map +1 -0
- package/dist/core/validation.d.ts +21 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +126 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/daemon/control.d.ts +9 -0
- package/dist/daemon/control.d.ts.map +1 -0
- package/dist/daemon/control.js +88 -0
- package/dist/daemon/control.js.map +1 -0
- package/dist/daemon/server.d.ts +8 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +129 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/updater.d.ts +21 -0
- package/dist/daemon/updater.d.ts.map +1 -0
- package/dist/daemon/updater.js +159 -0
- package/dist/daemon/updater.js.map +1 -0
- package/dist/keychain/index.d.ts +8 -0
- package/dist/keychain/index.d.ts.map +1 -0
- package/dist/keychain/index.js +243 -0
- package/dist/keychain/index.js.map +1 -0
- package/dist/keychain/windows.d.ts +5 -0
- package/dist/keychain/windows.d.ts.map +1 -0
- package/dist/keychain/windows.js +65 -0
- package/dist/keychain/windows.js.map +1 -0
- package/dist/logging/logger.d.ts +12 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +127 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/rotation.d.ts +9 -0
- package/dist/logging/rotation.d.ts.map +1 -0
- package/dist/logging/rotation.js +85 -0
- package/dist/logging/rotation.js.map +1 -0
- package/dist/logging/trace.d.ts +7 -0
- package/dist/logging/trace.d.ts.map +1 -0
- package/dist/logging/trace.js +50 -0
- package/dist/logging/trace.js.map +1 -0
- package/dist/mcp/client.d.ts +37 -0
- package/dist/mcp/client.d.ts.map +1 -0
- package/dist/mcp/client.js +111 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/http-transport.d.ts +30 -0
- package/dist/mcp/http-transport.d.ts.map +1 -0
- package/dist/mcp/http-transport.js +109 -0
- package/dist/mcp/http-transport.js.map +1 -0
- package/dist/mcp/oauth.d.ts +23 -0
- package/dist/mcp/oauth.d.ts.map +1 -0
- package/dist/mcp/oauth.js +235 -0
- package/dist/mcp/oauth.js.map +1 -0
- package/dist/mcp/registry.d.ts +5 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +35 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/memory/cold.d.ts +16 -0
- package/dist/memory/cold.d.ts.map +1 -0
- package/dist/memory/cold.js +244 -0
- package/dist/memory/cold.js.map +1 -0
- package/dist/memory/graph.d.ts +19 -0
- package/dist/memory/graph.d.ts.map +1 -0
- package/dist/memory/graph.js +102 -0
- package/dist/memory/graph.js.map +1 -0
- package/dist/memory/hot.d.ts +26 -0
- package/dist/memory/hot.d.ts.map +1 -0
- package/dist/memory/hot.js +58 -0
- package/dist/memory/hot.js.map +1 -0
- package/dist/memory/index.d.ts +7 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +26 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/learning.d.ts +18 -0
- package/dist/memory/learning.d.ts.map +1 -0
- package/dist/memory/learning.js +83 -0
- package/dist/memory/learning.js.map +1 -0
- package/dist/memory/retrieval.d.ts +21 -0
- package/dist/memory/retrieval.d.ts.map +1 -0
- package/dist/memory/retrieval.js +114 -0
- package/dist/memory/retrieval.js.map +1 -0
- package/dist/memory/warm.d.ts +9 -0
- package/dist/memory/warm.d.ts.map +1 -0
- package/dist/memory/warm.js +150 -0
- package/dist/memory/warm.js.map +1 -0
- package/dist/migrations/runner.d.ts +18 -0
- package/dist/migrations/runner.d.ts.map +1 -0
- package/dist/migrations/runner.js +62 -0
- package/dist/migrations/runner.js.map +1 -0
- package/dist/models/adapter.d.ts +46 -0
- package/dist/models/adapter.d.ts.map +1 -0
- package/dist/models/adapter.js +85 -0
- package/dist/models/adapter.js.map +1 -0
- package/dist/models/anthropic.d.ts +17 -0
- package/dist/models/anthropic.d.ts.map +1 -0
- package/dist/models/anthropic.js +128 -0
- package/dist/models/anthropic.js.map +1 -0
- package/dist/models/cache.d.ts +5 -0
- package/dist/models/cache.d.ts.map +1 -0
- package/dist/models/cache.js +135 -0
- package/dist/models/cache.js.map +1 -0
- package/dist/models/circuit-breaker.d.ts +18 -0
- package/dist/models/circuit-breaker.d.ts.map +1 -0
- package/dist/models/circuit-breaker.js +63 -0
- package/dist/models/circuit-breaker.js.map +1 -0
- package/dist/models/cost.d.ts +13 -0
- package/dist/models/cost.d.ts.map +1 -0
- package/dist/models/cost.js +92 -0
- package/dist/models/cost.js.map +1 -0
- package/dist/models/llamacpp.d.ts +9 -0
- package/dist/models/llamacpp.d.ts.map +1 -0
- package/dist/models/llamacpp.js +15 -0
- package/dist/models/llamacpp.js.map +1 -0
- package/dist/models/lmstudio.d.ts +11 -0
- package/dist/models/lmstudio.d.ts.map +1 -0
- package/dist/models/lmstudio.js +18 -0
- package/dist/models/lmstudio.js.map +1 -0
- package/dist/models/local-catalog.d.ts +45 -0
- package/dist/models/local-catalog.d.ts.map +1 -0
- package/dist/models/local-catalog.js +314 -0
- package/dist/models/local-catalog.js.map +1 -0
- package/dist/models/ollama.d.ts +10 -0
- package/dist/models/ollama.d.ts.map +1 -0
- package/dist/models/ollama.js +98 -0
- package/dist/models/ollama.js.map +1 -0
- package/dist/models/openai.d.ts +16 -0
- package/dist/models/openai.d.ts.map +1 -0
- package/dist/models/openai.js +139 -0
- package/dist/models/openai.js.map +1 -0
- package/dist/models/provider.d.ts +7 -0
- package/dist/models/provider.d.ts.map +1 -0
- package/dist/models/provider.js +39 -0
- package/dist/models/provider.js.map +1 -0
- package/dist/models/rate-limit.d.ts +13 -0
- package/dist/models/rate-limit.d.ts.map +1 -0
- package/dist/models/rate-limit.js +37 -0
- package/dist/models/rate-limit.js.map +1 -0
- package/dist/models/registry.d.ts +2 -0
- package/dist/models/registry.d.ts.map +1 -0
- package/dist/models/registry.js +69 -0
- package/dist/models/registry.js.map +1 -0
- package/dist/models/router.d.ts +26 -0
- package/dist/models/router.d.ts.map +1 -0
- package/dist/models/router.js +185 -0
- package/dist/models/router.js.map +1 -0
- package/dist/models/vllm.d.ts +13 -0
- package/dist/models/vllm.d.ts.map +1 -0
- package/dist/models/vllm.js +19 -0
- package/dist/models/vllm.js.map +1 -0
- package/dist/notifications/manager.d.ts +5 -0
- package/dist/notifications/manager.d.ts.map +1 -0
- package/dist/notifications/manager.js +65 -0
- package/dist/notifications/manager.js.map +1 -0
- package/dist/permissions/manager.d.ts +15 -0
- package/dist/permissions/manager.d.ts.map +1 -0
- package/dist/permissions/manager.js +159 -0
- package/dist/permissions/manager.js.map +1 -0
- package/dist/permissions/risk.d.ts +13 -0
- package/dist/permissions/risk.d.ts.map +1 -0
- package/dist/permissions/risk.js +43 -0
- package/dist/permissions/risk.js.map +1 -0
- package/dist/persistence/compression.d.ts +9 -0
- package/dist/persistence/compression.d.ts.map +1 -0
- package/dist/persistence/compression.js +126 -0
- package/dist/persistence/compression.js.map +1 -0
- package/dist/persistence/conversation-store.d.ts +67 -0
- package/dist/persistence/conversation-store.d.ts.map +1 -0
- package/dist/persistence/conversation-store.js +370 -0
- package/dist/persistence/conversation-store.js.map +1 -0
- package/dist/persistence/events.d.ts +4 -0
- package/dist/persistence/events.d.ts.map +1 -0
- package/dist/persistence/events.js +50 -0
- package/dist/persistence/events.js.map +1 -0
- package/dist/persistence/index-db.d.ts +65 -0
- package/dist/persistence/index-db.d.ts.map +1 -0
- package/dist/persistence/index-db.js +280 -0
- package/dist/persistence/index-db.js.map +1 -0
- package/dist/persistence/jsonl.d.ts +8 -0
- package/dist/persistence/jsonl.d.ts.map +1 -0
- package/dist/persistence/jsonl.js +90 -0
- package/dist/persistence/jsonl.js.map +1 -0
- package/dist/persistence/sessions.d.ts +5 -0
- package/dist/persistence/sessions.d.ts.map +1 -0
- package/dist/persistence/sessions.js +54 -0
- package/dist/persistence/sessions.js.map +1 -0
- package/dist/persistence/tasks.d.ts +7 -0
- package/dist/persistence/tasks.d.ts.map +1 -0
- package/dist/persistence/tasks.js +162 -0
- package/dist/persistence/tasks.js.map +1 -0
- package/dist/prompts/assembler.d.ts +29 -0
- package/dist/prompts/assembler.d.ts.map +1 -0
- package/dist/prompts/assembler.js +136 -0
- package/dist/prompts/assembler.js.map +1 -0
- package/dist/prompts/layers.d.ts +6 -0
- package/dist/prompts/layers.d.ts.map +1 -0
- package/dist/prompts/layers.js +60 -0
- package/dist/prompts/layers.js.map +1 -0
- package/dist/release/download.d.ts +19 -0
- package/dist/release/download.d.ts.map +1 -0
- package/dist/release/download.js +187 -0
- package/dist/release/download.js.map +1 -0
- package/dist/release/verify.d.ts +34 -0
- package/dist/release/verify.d.ts.map +1 -0
- package/dist/release/verify.js +127 -0
- package/dist/release/verify.js.map +1 -0
- package/dist/sandbox/fs.d.ts +10 -0
- package/dist/sandbox/fs.d.ts.map +1 -0
- package/dist/sandbox/fs.js +114 -0
- package/dist/sandbox/fs.js.map +1 -0
- package/dist/sandbox/shell.d.ts +20 -0
- package/dist/sandbox/shell.d.ts.map +1 -0
- package/dist/sandbox/shell.js +131 -0
- package/dist/sandbox/shell.js.map +1 -0
- package/dist/scheduler/dag.d.ts +7 -0
- package/dist/scheduler/dag.d.ts.map +1 -0
- package/dist/scheduler/dag.js +72 -0
- package/dist/scheduler/dag.js.map +1 -0
- package/dist/scheduler/resource-manager.d.ts +25 -0
- package/dist/scheduler/resource-manager.d.ts.map +1 -0
- package/dist/scheduler/resource-manager.js +101 -0
- package/dist/scheduler/resource-manager.js.map +1 -0
- package/dist/security/injection.d.ts +14 -0
- package/dist/security/injection.d.ts.map +1 -0
- package/dist/security/injection.js +46 -0
- package/dist/security/injection.js.map +1 -0
- package/dist/security/redact.d.ts +10 -0
- package/dist/security/redact.d.ts.map +1 -0
- package/dist/security/redact.js +89 -0
- package/dist/security/redact.js.map +1 -0
- package/dist/skills/loader.d.ts +4 -0
- package/dist/skills/loader.d.ts.map +1 -0
- package/dist/skills/loader.js +142 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/skills/marketplace.d.ts +15 -0
- package/dist/skills/marketplace.d.ts.map +1 -0
- package/dist/skills/marketplace.js +132 -0
- package/dist/skills/marketplace.js.map +1 -0
- package/dist/tools/apply-patch.d.ts +20 -0
- package/dist/tools/apply-patch.d.ts.map +1 -0
- package/dist/tools/apply-patch.js +195 -0
- package/dist/tools/apply-patch.js.map +1 -0
- package/dist/tools/ask-user.d.ts +12 -0
- package/dist/tools/ask-user.d.ts.map +1 -0
- package/dist/tools/ask-user.js +86 -0
- package/dist/tools/ask-user.js.map +1 -0
- package/dist/tools/delete-file.d.ts +10 -0
- package/dist/tools/delete-file.d.ts.map +1 -0
- package/dist/tools/delete-file.js +94 -0
- package/dist/tools/delete-file.js.map +1 -0
- package/dist/tools/edit-file.d.ts +20 -0
- package/dist/tools/edit-file.d.ts.map +1 -0
- package/dist/tools/edit-file.js +128 -0
- package/dist/tools/edit-file.js.map +1 -0
- package/dist/tools/format.d.ts +5 -0
- package/dist/tools/format.d.ts.map +1 -0
- package/dist/tools/format.js +131 -0
- package/dist/tools/format.js.map +1 -0
- package/dist/tools/git.d.ts +24 -0
- package/dist/tools/git.d.ts.map +1 -0
- package/dist/tools/git.js +122 -0
- package/dist/tools/git.js.map +1 -0
- package/dist/tools/glob.d.ts +12 -0
- package/dist/tools/glob.d.ts.map +1 -0
- package/dist/tools/glob.js +55 -0
- package/dist/tools/glob.js.map +1 -0
- package/dist/tools/grep.d.ts +19 -0
- package/dist/tools/grep.d.ts.map +1 -0
- package/dist/tools/grep.js +97 -0
- package/dist/tools/grep.js.map +1 -0
- package/dist/tools/init.d.ts +3 -0
- package/dist/tools/init.d.ts.map +1 -0
- package/dist/tools/init.js +66 -0
- package/dist/tools/init.js.map +1 -0
- package/dist/tools/list-dir.d.ts +16 -0
- package/dist/tools/list-dir.d.ts.map +1 -0
- package/dist/tools/list-dir.js +107 -0
- package/dist/tools/list-dir.js.map +1 -0
- package/dist/tools/move-file.d.ts +13 -0
- package/dist/tools/move-file.d.ts.map +1 -0
- package/dist/tools/move-file.js +100 -0
- package/dist/tools/move-file.js.map +1 -0
- package/dist/tools/read-file.d.ts +14 -0
- package/dist/tools/read-file.d.ts.map +1 -0
- package/dist/tools/read-file.js +99 -0
- package/dist/tools/read-file.js.map +1 -0
- package/dist/tools/registry.d.ts +10 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +30 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/run-command.d.ts +17 -0
- package/dist/tools/run-command.d.ts.map +1 -0
- package/dist/tools/run-command.js +73 -0
- package/dist/tools/run-command.js.map +1 -0
- package/dist/tools/run-tests.d.ts +16 -0
- package/dist/tools/run-tests.d.ts.map +1 -0
- package/dist/tools/run-tests.js +140 -0
- package/dist/tools/run-tests.js.map +1 -0
- package/dist/tools/web-browse.d.ts +10 -0
- package/dist/tools/web-browse.d.ts.map +1 -0
- package/dist/tools/web-browse.js +45 -0
- package/dist/tools/web-browse.js.map +1 -0
- package/dist/tools/web-fetch.d.ts +11 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +43 -0
- package/dist/tools/web-fetch.js.map +1 -0
- package/dist/tools/web-search.d.ts +12 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +52 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/tools/write-file.d.ts +13 -0
- package/dist/tools/write-file.d.ts.map +1 -0
- package/dist/tools/write-file.js +100 -0
- package/dist/tools/write-file.js.map +1 -0
- package/dist/types/errors.d.ts +14 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +55 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/index.d.ts +267 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +38 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/chat.d.ts +89 -0
- package/dist/ui/chat.d.ts.map +1 -0
- package/dist/ui/chat.js +311 -0
- package/dist/ui/chat.js.map +1 -0
- package/dist/ui/public/app.js +2113 -0
- package/dist/ui/public/index.html +78 -0
- package/dist/ui/public/styles.css +1703 -0
- package/dist/ui/server-errors.d.ts +24 -0
- package/dist/ui/server-errors.d.ts.map +1 -0
- package/dist/ui/server-errors.js +31 -0
- package/dist/ui/server-errors.js.map +1 -0
- package/dist/ui/server.d.ts +10 -0
- package/dist/ui/server.d.ts.map +1 -0
- package/dist/ui/server.js +815 -0
- package/dist/ui/server.js.map +1 -0
- package/dist/ui/task-runner.d.ts +71 -0
- package/dist/ui/task-runner.d.ts.map +1 -0
- package/dist/ui/task-runner.js +334 -0
- package/dist/ui/task-runner.js.map +1 -0
- package/dist/web/browse.d.ts +35 -0
- package/dist/web/browse.d.ts.map +1 -0
- package/dist/web/browse.js +166 -0
- package/dist/web/browse.js.map +1 -0
- package/dist/web/fetch.d.ts +18 -0
- package/dist/web/fetch.d.ts.map +1 -0
- package/dist/web/fetch.js +107 -0
- package/dist/web/fetch.js.map +1 -0
- package/dist/web/sanitize.d.ts +8 -0
- package/dist/web/sanitize.d.ts.map +1 -0
- package/dist/web/sanitize.js +58 -0
- package/dist/web/sanitize.js.map +1 -0
- package/dist/web/search.d.ts +12 -0
- package/dist/web/search.d.ts.map +1 -0
- package/dist/web/search.js +124 -0
- package/dist/web/search.js.map +1 -0
- package/install/install.ps1 +46 -0
- package/install/install.sh +72 -0
- package/package.json +89 -0
- package/scripts/bundle.js +26 -0
- package/scripts/copy-assets.js +33 -0
- package/scripts/link.sh +79 -0
- package/scripts/metrics.sh +33 -0
- package/scripts/postinstall.js +36 -0
package/dist/cli/repl.js
ADDED
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.startRepl = void 0;
|
|
40
|
+
/**
|
|
41
|
+
* Forge REPL — production-grade interactive shell.
|
|
42
|
+
*
|
|
43
|
+
* Composed of three collaborators:
|
|
44
|
+
* • repl-input.ts raw-mode line editor with ghost text + dropdown +
|
|
45
|
+
* status line
|
|
46
|
+
* • repl-commands.ts slash-command catalog + fuzzy ranker + semantic
|
|
47
|
+
* prompt shortcuts (/ask, /explain, /fix, …)
|
|
48
|
+
* • this file session state, routing, task execution, persistence
|
|
49
|
+
*
|
|
50
|
+
* Each turn flows:
|
|
51
|
+
* input → slash dispatch OR semantic expansion OR bare text
|
|
52
|
+
* → orchestrateRun({ input, description = prior-turns + new })
|
|
53
|
+
* → result recorded in state.turns + persisted to JSONL
|
|
54
|
+
* → next prompt
|
|
55
|
+
*
|
|
56
|
+
* Multi-turn context lives in state.turns and is woven into the planner's
|
|
57
|
+
* `description` field so the agent sees the conversation so far.
|
|
58
|
+
*/
|
|
59
|
+
const fs = __importStar(require("fs"));
|
|
60
|
+
const os = __importStar(require("os"));
|
|
61
|
+
const path = __importStar(require("path"));
|
|
62
|
+
const child_process_1 = require("child_process");
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- JSON import via require avoids assert syntax churn
|
|
64
|
+
const pkg = require('../../package.json');
|
|
65
|
+
const commander_1 = require("commander");
|
|
66
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
67
|
+
const banners_1 = require("./banners");
|
|
68
|
+
const ui_1 = require("./ui");
|
|
69
|
+
const bootstrap_1 = require("./bootstrap");
|
|
70
|
+
const logger_1 = require("../logging/logger");
|
|
71
|
+
const orchestrator_1 = require("../core/orchestrator");
|
|
72
|
+
const loader_1 = require("../config/loader");
|
|
73
|
+
const paths_1 = require("../config/paths");
|
|
74
|
+
const repl_input_1 = require("./repl-input");
|
|
75
|
+
const repl_commands_1 = require("./repl-commands");
|
|
76
|
+
const provider_1 = require("../models/provider");
|
|
77
|
+
const conversation_1 = require("../core/conversation");
|
|
78
|
+
/** Backwards-compat helpers: map between ConversationTurn (new source of
|
|
79
|
+
* truth) and the older ReplTurn shape used throughout the REPL. We keep the
|
|
80
|
+
* abstraction thin — the conversation stores everything, the REPL reads
|
|
81
|
+
* from it. */
|
|
82
|
+
const turnsOf = (state) => state.conversation.turns;
|
|
83
|
+
// ---------- History persistence ----------
|
|
84
|
+
const HISTORY_FILE = path.join(paths_1.FORGE_HOME, 'history');
|
|
85
|
+
const HISTORY_MAX = 1000;
|
|
86
|
+
const loadHistory = () => {
|
|
87
|
+
try {
|
|
88
|
+
if (!fs.existsSync(HISTORY_FILE))
|
|
89
|
+
return [];
|
|
90
|
+
const raw = fs.readFileSync(HISTORY_FILE, 'utf8');
|
|
91
|
+
return raw
|
|
92
|
+
.split('\n')
|
|
93
|
+
.map((s) => s.trim())
|
|
94
|
+
.filter(Boolean)
|
|
95
|
+
.slice(-HISTORY_MAX);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const appendHistory = (line) => {
|
|
102
|
+
try {
|
|
103
|
+
fs.mkdirSync(path.dirname(HISTORY_FILE), { recursive: true });
|
|
104
|
+
fs.appendFileSync(HISTORY_FILE, line + '\n', 'utf8');
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* best-effort */
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
// ---------- Rendering helpers ----------
|
|
111
|
+
const chip = (label, tone = 'dim') => {
|
|
112
|
+
const map = {
|
|
113
|
+
ok: chalk_1.default.bgRgb(...banners_1.PALETTE.teal).black,
|
|
114
|
+
warn: chalk_1.default.bgRgb(...banners_1.PALETTE.amber).black,
|
|
115
|
+
info: chalk_1.default.bgRgb(...banners_1.PALETTE.violet).white,
|
|
116
|
+
dim: chalk_1.default.bgRgb(...banners_1.PALETTE.dim).white,
|
|
117
|
+
};
|
|
118
|
+
return map[tone](` ${label} `);
|
|
119
|
+
};
|
|
120
|
+
const modeColor = (m) => {
|
|
121
|
+
const toneOf = {
|
|
122
|
+
fast: 'ok',
|
|
123
|
+
balanced: 'ok',
|
|
124
|
+
heavy: 'warn',
|
|
125
|
+
plan: 'info',
|
|
126
|
+
execute: 'warn',
|
|
127
|
+
audit: 'info',
|
|
128
|
+
debug: 'warn',
|
|
129
|
+
architect: 'info',
|
|
130
|
+
'offline-safe': 'ok',
|
|
131
|
+
};
|
|
132
|
+
return chip(m, toneOf[m] ?? 'dim');
|
|
133
|
+
};
|
|
134
|
+
const shortCwd = (root) => {
|
|
135
|
+
const home = os.homedir();
|
|
136
|
+
if (root === home)
|
|
137
|
+
return '~';
|
|
138
|
+
if (root.startsWith(home + path.sep))
|
|
139
|
+
return '~/' + path.relative(home, root);
|
|
140
|
+
const parts = root.split(path.sep).filter(Boolean);
|
|
141
|
+
return parts.length <= 3 ? root : '…/' + parts.slice(-3).join('/');
|
|
142
|
+
};
|
|
143
|
+
// Rough token estimate: ~4 chars per token for English + code.
|
|
144
|
+
const approxTokens = (s) => Math.ceil(s.length / 4);
|
|
145
|
+
const contextEstimate = (state) => {
|
|
146
|
+
const used = turnsOf(state).reduce((acc, t) => {
|
|
147
|
+
const body = `${t.input}\n${t.result?.summary ?? ''}\n${(t.result?.filesChanged ?? []).join(' ')}`;
|
|
148
|
+
return acc + approxTokens(body);
|
|
149
|
+
}, 0);
|
|
150
|
+
// rough ceiling by provider default; real value comes from model descriptor
|
|
151
|
+
const modelCtx = {
|
|
152
|
+
ollama: 32_000,
|
|
153
|
+
anthropic: 200_000,
|
|
154
|
+
openai: 128_000,
|
|
155
|
+
llamacpp: 8_192,
|
|
156
|
+
};
|
|
157
|
+
return { used, max: modelCtx[state.provider] ?? 32_000 };
|
|
158
|
+
};
|
|
159
|
+
const fmtTokens = (n) => {
|
|
160
|
+
if (n < 1000)
|
|
161
|
+
return String(n);
|
|
162
|
+
if (n < 1000000)
|
|
163
|
+
return (n / 1000).toFixed(n < 10000 ? 1 : 0) + 'k';
|
|
164
|
+
return (n / 1000000).toFixed(1) + 'M';
|
|
165
|
+
};
|
|
166
|
+
const sessionCost = (state) => turnsOf(state).reduce((acc, t) => acc + (t.result?.costUsd ?? 0), 0);
|
|
167
|
+
const prompt = (state) => {
|
|
168
|
+
const n = chalk_1.default.dim(`[${turnsOf(state).length + 1}]`);
|
|
169
|
+
const arrow = chalk_1.default.bold.rgb(...banners_1.PALETTE.teal)('❯');
|
|
170
|
+
return `${n} ${chalk_1.default.bold('forge')} ${arrow} `;
|
|
171
|
+
};
|
|
172
|
+
const statusLine = (state) => {
|
|
173
|
+
const ctx = contextEstimate(state);
|
|
174
|
+
const pctUsed = ctx.used / ctx.max;
|
|
175
|
+
const ctxStr = `${fmtTokens(ctx.used)}/${fmtTokens(ctx.max)}`;
|
|
176
|
+
const ctxColor = pctUsed > 0.85
|
|
177
|
+
? chalk_1.default.rgb(...banners_1.PALETTE.red)
|
|
178
|
+
: pctUsed > 0.6
|
|
179
|
+
? chalk_1.default.rgb(...banners_1.PALETTE.amber)
|
|
180
|
+
: chalk_1.default.rgb(...banners_1.PALETTE.teal);
|
|
181
|
+
const bits = [];
|
|
182
|
+
bits.push(chalk_1.default.rgb(...banners_1.PALETTE.teal)('◆') + ' ' + modeColor(state.mode));
|
|
183
|
+
bits.push(chalk_1.default.dim(state.provider) + chalk_1.default.rgb(...banners_1.PALETTE.muted)(':' + state.modelId));
|
|
184
|
+
bits.push(chalk_1.default.rgb(...banners_1.PALETTE.violet)(shortCwd(state.projectRoot)));
|
|
185
|
+
bits.push(chalk_1.default.dim('ctx ') + ctxColor(ctxStr));
|
|
186
|
+
bits.push(chalk_1.default.dim(`turn ${turnsOf(state).length + 1}`));
|
|
187
|
+
const cost = sessionCost(state);
|
|
188
|
+
if (cost > 0)
|
|
189
|
+
bits.push(chalk_1.default.rgb(...banners_1.PALETTE.pink)('$' + cost.toFixed(4)));
|
|
190
|
+
if (state.autoApprove)
|
|
191
|
+
bits.push(chip('auto', 'warn'));
|
|
192
|
+
if (state.flags.strict)
|
|
193
|
+
bits.push(chip('strict', 'warn'));
|
|
194
|
+
if (state.flags.allowFiles)
|
|
195
|
+
bits.push(chip('+files', 'warn'));
|
|
196
|
+
if (state.flags.allowShell)
|
|
197
|
+
bits.push(chip('+shell', 'warn'));
|
|
198
|
+
if (state.flags.allowNetwork)
|
|
199
|
+
bits.push(chip('+net', 'warn'));
|
|
200
|
+
if (state.flags.allowWeb)
|
|
201
|
+
bits.push(chip('+web', 'warn'));
|
|
202
|
+
if (state.flags.allowMcp)
|
|
203
|
+
bits.push(chip('+mcp', 'warn'));
|
|
204
|
+
// Remote writers (UI, another terminal) indicator — click-for-details via /turns.
|
|
205
|
+
if (state.remoteUpdates > 0) {
|
|
206
|
+
bits.push(chalk_1.default.bgRgb(...banners_1.PALETTE.violet).white(` ⇣${state.remoteUpdates} new `));
|
|
207
|
+
}
|
|
208
|
+
// Conversation source badge so the user knows where this lives.
|
|
209
|
+
bits.push(state.conversation.meta.source === 'chat'
|
|
210
|
+
? chalk_1.default.dim('chat:') + chalk_1.default.rgb(...banners_1.PALETTE.violet)(state.conversation.meta.id.slice(0, 18))
|
|
211
|
+
: chalk_1.default.dim('repl:') + chalk_1.default.rgb(...banners_1.PALETTE.teal)(state.conversation.meta.id.slice(0, 18)));
|
|
212
|
+
return ' ' + bits.join(chalk_1.default.dim(' · '));
|
|
213
|
+
};
|
|
214
|
+
const hero = (_state, version) => {
|
|
215
|
+
const lines = [
|
|
216
|
+
'',
|
|
217
|
+
' ' +
|
|
218
|
+
chalk_1.default.bold.rgb(...banners_1.PALETTE.teal)('forge') +
|
|
219
|
+
chalk_1.default.dim(` v${version} · interactive session`),
|
|
220
|
+
' ' +
|
|
221
|
+
chalk_1.default.rgb(...banners_1.PALETTE.muted)('Type a prompt to run. Start with / for a command. Tab to accept. ↑/↓ to browse.'),
|
|
222
|
+
' ' +
|
|
223
|
+
chalk_1.default.rgb(...banners_1.PALETTE.muted)('Try: ') +
|
|
224
|
+
chalk_1.default.white('"add a /health endpoint"') +
|
|
225
|
+
chalk_1.default.dim(' · ') +
|
|
226
|
+
chalk_1.default.white('/ask how does the scheduler work?') +
|
|
227
|
+
chalk_1.default.dim(' · ') +
|
|
228
|
+
chalk_1.default.white('/help'),
|
|
229
|
+
'',
|
|
230
|
+
];
|
|
231
|
+
return lines.join('\n');
|
|
232
|
+
};
|
|
233
|
+
const helpCard = () => {
|
|
234
|
+
const col = (k, v) => ' ' + chalk_1.default.bold.rgb(...banners_1.PALETTE.teal)(k.padEnd(28)) + chalk_1.default.dim(v);
|
|
235
|
+
const header = (s) => '\n ' + chalk_1.default.bold.rgb(...banners_1.PALETTE.violet)(s);
|
|
236
|
+
const byCat = new Map();
|
|
237
|
+
for (const c of repl_commands_1.SLASH_COMMANDS) {
|
|
238
|
+
const arr = byCat.get(c.category) ?? [];
|
|
239
|
+
arr.push(c);
|
|
240
|
+
byCat.set(c.category, arr);
|
|
241
|
+
}
|
|
242
|
+
const ordered = [
|
|
243
|
+
'Agentic',
|
|
244
|
+
'Shortcut',
|
|
245
|
+
'Session',
|
|
246
|
+
'Modes',
|
|
247
|
+
'Knowledge',
|
|
248
|
+
'Models',
|
|
249
|
+
'Infrastructure',
|
|
250
|
+
];
|
|
251
|
+
const out = [''];
|
|
252
|
+
out.push(header('Input'));
|
|
253
|
+
out.push(col('<text>', 'run as a task; prior turns thread into the planner'));
|
|
254
|
+
out.push(col('/<command> [args]', 'commander passthrough or REPL action'));
|
|
255
|
+
out.push(col('Tab / Shift+Tab', 'accept suggestion · cycle backward'));
|
|
256
|
+
out.push(col('↑ / ↓', 'navigate suggestions (when dropdown open) or history'));
|
|
257
|
+
out.push(col('→ at end', 'accept ghost-text suggestion'));
|
|
258
|
+
out.push(col('Ctrl+R', 'reverse-i-search through history (Ctrl+S newer · Esc cancel)'));
|
|
259
|
+
out.push(col('Alt+Enter · Ctrl+J', 'insert newline (multi-line compose)'));
|
|
260
|
+
out.push(col('Ctrl+A/E · Home/End', 'line start / line end'));
|
|
261
|
+
out.push(col('Alt+B · Alt+F', 'word back / word forward'));
|
|
262
|
+
out.push(col('Ctrl+U · Ctrl+K', 'kill to start / kill to end'));
|
|
263
|
+
out.push(col('Ctrl+W · Alt+Bksp', 'kill word backward'));
|
|
264
|
+
out.push(col('Ctrl+Y', 'yank last killed text'));
|
|
265
|
+
out.push(col('Ctrl+T', 'transpose characters around cursor'));
|
|
266
|
+
out.push(col('Ctrl+L', 'clear screen'));
|
|
267
|
+
out.push(col('F1 / F2 / F3', 'help / sessions / new conversation'));
|
|
268
|
+
out.push(col('Esc · Esc Esc', 'dismiss dropdown · clear buffer'));
|
|
269
|
+
out.push(col('Ctrl+C', 'non-empty: clear line · empty: press twice to exit · during task: cancel'));
|
|
270
|
+
out.push(col('Ctrl+D', 'exit'));
|
|
271
|
+
for (const cat of ordered) {
|
|
272
|
+
const group = byCat.get(cat);
|
|
273
|
+
if (!group)
|
|
274
|
+
continue;
|
|
275
|
+
out.push(header(cat));
|
|
276
|
+
for (const c of group) {
|
|
277
|
+
const aliases = c.aliases?.length
|
|
278
|
+
? chalk_1.default.dim(` (${c.aliases.map((a) => '/' + a).join(' ')})`)
|
|
279
|
+
: '';
|
|
280
|
+
// eslint-disable-next-line no-control-regex -- stripping ANSI CSI SGR sequences
|
|
281
|
+
out.push(col('/' + c.name + aliases.replace(/\x1b\[[0-9;]*m/g, ''), c.description));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
out.push('');
|
|
285
|
+
return out.join('\n');
|
|
286
|
+
};
|
|
287
|
+
// ---------- Turn persistence + threading ----------
|
|
288
|
+
// ---------- Session summary rendering ----------
|
|
289
|
+
const printResumedSummary = (state) => {
|
|
290
|
+
const turns = turnsOf(state);
|
|
291
|
+
if (!turns.length) {
|
|
292
|
+
(0, ui_1.info)(`Resumed ${state.conversation.meta.id} · no prior turns on disk.`);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const n = turns.length;
|
|
296
|
+
process.stdout.write('\n ' +
|
|
297
|
+
chalk_1.default.bold.rgb(...banners_1.PALETTE.teal)('Resumed ') +
|
|
298
|
+
chalk_1.default.white(state.conversation.meta.id) +
|
|
299
|
+
chalk_1.default.dim(` · ${n} prior turn${n === 1 ? '' : 's'}\n\n`));
|
|
300
|
+
const show = turns.slice(-5);
|
|
301
|
+
const offset = turns.length - show.length;
|
|
302
|
+
show.forEach((t, idx) => {
|
|
303
|
+
const num = chalk_1.default.dim(` ${String(offset + idx + 1).padStart(2, '0')}.`);
|
|
304
|
+
const head = chalk_1.default.white(t.input.replace(/\s+/g, ' ').slice(0, 100));
|
|
305
|
+
process.stdout.write(`${num} ${head}\n`);
|
|
306
|
+
if (t.result) {
|
|
307
|
+
const ok = t.result.success
|
|
308
|
+
? chalk_1.default.rgb(...banners_1.PALETTE.green)('✓')
|
|
309
|
+
: chalk_1.default.rgb(...banners_1.PALETTE.red)('✗');
|
|
310
|
+
const meta = `${(t.result.durationMs / 1000).toFixed(1)}s · ${t.result.filesChanged.length} files` +
|
|
311
|
+
(t.result.costUsd ? ` · $${t.result.costUsd.toFixed(4)}` : '');
|
|
312
|
+
process.stdout.write(' ' +
|
|
313
|
+
ok +
|
|
314
|
+
' ' +
|
|
315
|
+
chalk_1.default.dim((t.result.summary ?? '').replace(/\s+/g, ' ').slice(0, 100)) +
|
|
316
|
+
chalk_1.default.dim(` (${meta})`) +
|
|
317
|
+
'\n');
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
if (turns.length > show.length) {
|
|
321
|
+
process.stdout.write(chalk_1.default.dim(` …+${turns.length - show.length} earlier\n`));
|
|
322
|
+
}
|
|
323
|
+
process.stdout.write(chalk_1.default.dim(' Continue the conversation below. Forge will thread the prior turns into the next plan.\n\n'));
|
|
324
|
+
};
|
|
325
|
+
const relativeTime = (iso) => {
|
|
326
|
+
const age = Date.now() - new Date(iso).getTime();
|
|
327
|
+
if (age < 60_000)
|
|
328
|
+
return 'just now';
|
|
329
|
+
if (age < 3_600_000)
|
|
330
|
+
return `${Math.floor(age / 60_000)}m ago`;
|
|
331
|
+
if (age < 86_400_000)
|
|
332
|
+
return `${Math.floor(age / 3_600_000)}h ago`;
|
|
333
|
+
return `${Math.floor(age / 86_400_000)}d ago`;
|
|
334
|
+
};
|
|
335
|
+
const printSessionsList = (state) => {
|
|
336
|
+
const list = (0, conversation_1.listConversations)(state.projectRoot);
|
|
337
|
+
if (!list.length) {
|
|
338
|
+
(0, ui_1.info)('No prior conversations in this project.');
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
process.stdout.write('\n');
|
|
342
|
+
for (const s of list.slice(0, 20)) {
|
|
343
|
+
const sourceTag = s.source === 'repl'
|
|
344
|
+
? chalk_1.default.bgRgb(...banners_1.PALETTE.cyan).black(' REPL ')
|
|
345
|
+
: chalk_1.default.bgRgb(...banners_1.PALETTE.violet).white(' CHAT ');
|
|
346
|
+
const id = chalk_1.default.rgb(...banners_1.PALETTE.teal)(s.id);
|
|
347
|
+
const turnsLabel = chalk_1.default.dim(`${s.turns} turn${s.turns === 1 ? '' : 's'}`);
|
|
348
|
+
const active = s.id === state.conversation.meta.id ? chalk_1.default.rgb(...banners_1.PALETTE.amber)(' ← current') : '';
|
|
349
|
+
process.stdout.write(` ${sourceTag} ${id} ${turnsLabel} ${chalk_1.default.dim(relativeTime(s.lastAt))}${active}\n`);
|
|
350
|
+
if (s.title && s.title !== 'New conversation' && s.title !== 'Untitled') {
|
|
351
|
+
process.stdout.write(' ' + chalk_1.default.dim(s.title.slice(0, 100)) + '\n');
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (list.length > 20)
|
|
355
|
+
process.stdout.write(chalk_1.default.dim(` …+${list.length - 20} older\n`));
|
|
356
|
+
process.stdout.write(chalk_1.default.dim('\n load one with: /load <sessionId>\n\n'));
|
|
357
|
+
};
|
|
358
|
+
const renderTurnSummaryLine = (idx, t) => {
|
|
359
|
+
const head = chalk_1.default.bold.rgb(...banners_1.PALETTE.teal)(` ${idx + 1}.`);
|
|
360
|
+
process.stdout.write(`${head} ${chalk_1.default.white(t.input.slice(0, 200))}\n`);
|
|
361
|
+
if (t.result) {
|
|
362
|
+
const tag = t.result.success
|
|
363
|
+
? chalk_1.default.rgb(...banners_1.PALETTE.green)(' ✓')
|
|
364
|
+
: chalk_1.default.rgb(...banners_1.PALETTE.red)(' ✗');
|
|
365
|
+
const meta = chalk_1.default.dim(`(${(t.result.durationMs / 1000).toFixed(1)}s · ${t.result.filesChanged.length} files${t.result.costUsd ? ` · $${t.result.costUsd.toFixed(4)}` : ''})`);
|
|
366
|
+
process.stdout.write(`${tag} ${chalk_1.default.dim((t.result.summary ?? '').slice(0, 200))} ${meta}\n`);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
// ---------- Internal slash handlers ----------
|
|
370
|
+
const parseArgv = (input) => {
|
|
371
|
+
const out = [];
|
|
372
|
+
let buf = '';
|
|
373
|
+
let quote = null;
|
|
374
|
+
for (let i = 0; i < input.length; i++) {
|
|
375
|
+
const c = input[i];
|
|
376
|
+
if (quote) {
|
|
377
|
+
if (c === quote)
|
|
378
|
+
quote = null;
|
|
379
|
+
else if (c === '\\' && input[i + 1])
|
|
380
|
+
buf += input[++i];
|
|
381
|
+
else
|
|
382
|
+
buf += c;
|
|
383
|
+
}
|
|
384
|
+
else if (c === '"' || c === "'")
|
|
385
|
+
quote = c;
|
|
386
|
+
else if (/\s/.test(c)) {
|
|
387
|
+
if (buf) {
|
|
388
|
+
out.push(buf);
|
|
389
|
+
buf = '';
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
else
|
|
393
|
+
buf += c;
|
|
394
|
+
}
|
|
395
|
+
if (buf)
|
|
396
|
+
out.push(buf);
|
|
397
|
+
return out;
|
|
398
|
+
};
|
|
399
|
+
const togglePermission = (state, flag, enable) => {
|
|
400
|
+
const key = flag.toLowerCase();
|
|
401
|
+
if (key === 'all') {
|
|
402
|
+
state.flags.allowFiles = enable;
|
|
403
|
+
state.flags.allowShell = enable;
|
|
404
|
+
state.flags.allowNetwork = enable;
|
|
405
|
+
state.flags.allowWeb = enable;
|
|
406
|
+
state.flags.allowMcp = enable;
|
|
407
|
+
return `all → ${enable ? 'on' : 'off'}`;
|
|
408
|
+
}
|
|
409
|
+
const map = {
|
|
410
|
+
files: 'allowFiles',
|
|
411
|
+
shell: 'allowShell',
|
|
412
|
+
net: 'allowNetwork',
|
|
413
|
+
network: 'allowNetwork',
|
|
414
|
+
web: 'allowWeb',
|
|
415
|
+
mcp: 'allowMcp',
|
|
416
|
+
strict: 'strict',
|
|
417
|
+
};
|
|
418
|
+
const field = map[key];
|
|
419
|
+
if (!field)
|
|
420
|
+
return null;
|
|
421
|
+
state.flags[field] = enable;
|
|
422
|
+
return `${String(field)} → ${enable ? 'on' : 'off'}`;
|
|
423
|
+
};
|
|
424
|
+
const printTurns = (state) => {
|
|
425
|
+
const turns = turnsOf(state);
|
|
426
|
+
if (!turns.length) {
|
|
427
|
+
(0, ui_1.info)('No turns yet in this session.');
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
process.stdout.write('\n');
|
|
431
|
+
turns.forEach((t, i) => renderTurnSummaryLine(i, t));
|
|
432
|
+
process.stdout.write('\n');
|
|
433
|
+
};
|
|
434
|
+
const handleInternalSlash = async (state, cmd, rest, closeRepl) => {
|
|
435
|
+
switch (cmd.name) {
|
|
436
|
+
case 'help':
|
|
437
|
+
process.stdout.write(helpCard());
|
|
438
|
+
return true;
|
|
439
|
+
case 'exit':
|
|
440
|
+
closeRepl();
|
|
441
|
+
return true;
|
|
442
|
+
case 'clear':
|
|
443
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
444
|
+
return true;
|
|
445
|
+
case 'new': {
|
|
446
|
+
// Create a fresh conversation atomically (await the disk write) so the
|
|
447
|
+
// next frame renders against the new state, not the stale old one.
|
|
448
|
+
state.watcher?.close();
|
|
449
|
+
state.conversation = await (0, conversation_1.createConversation)({
|
|
450
|
+
projectPath: state.projectRoot,
|
|
451
|
+
source: 'repl',
|
|
452
|
+
mode: state.mode,
|
|
453
|
+
});
|
|
454
|
+
state.remoteUpdates = 0;
|
|
455
|
+
attachWatcher(state);
|
|
456
|
+
(0, ui_1.ok)(`Started a fresh conversation: ${state.conversation.meta.id}`);
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
case 'turns':
|
|
460
|
+
// Reviewing the turn list counts as acknowledging remote updates.
|
|
461
|
+
state.remoteUpdates = 0;
|
|
462
|
+
printTurns(state);
|
|
463
|
+
return true;
|
|
464
|
+
case 'pwd':
|
|
465
|
+
(0, ui_1.info)(state.projectRoot);
|
|
466
|
+
return true;
|
|
467
|
+
case 'cd': {
|
|
468
|
+
const target = rest.join(' ') || os.homedir();
|
|
469
|
+
try {
|
|
470
|
+
const resolved = path.resolve(state.projectRoot, target);
|
|
471
|
+
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
|
|
472
|
+
(0, ui_1.err)(`not a directory: ${resolved}`);
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
process.chdir(resolved);
|
|
476
|
+
state.projectRoot = (0, loader_1.findProjectRoot)(resolved) ?? resolved;
|
|
477
|
+
(0, paths_1.ensureProjectDir)(state.projectRoot);
|
|
478
|
+
(0, ui_1.ok)(`cwd → ${state.projectRoot}`);
|
|
479
|
+
}
|
|
480
|
+
catch (e) {
|
|
481
|
+
(0, ui_1.err)(`cd failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
482
|
+
}
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
case 'mode': {
|
|
486
|
+
const name = rest[0];
|
|
487
|
+
const valid = [
|
|
488
|
+
'fast',
|
|
489
|
+
'balanced',
|
|
490
|
+
'heavy',
|
|
491
|
+
'plan',
|
|
492
|
+
'execute',
|
|
493
|
+
'audit',
|
|
494
|
+
'debug',
|
|
495
|
+
'architect',
|
|
496
|
+
'offline-safe',
|
|
497
|
+
];
|
|
498
|
+
if (!name) {
|
|
499
|
+
(0, ui_1.info)(`current mode: ${(0, ui_1.accent)(state.mode)} (one of: ${valid.join(', ')})`);
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
if (!valid.includes(name)) {
|
|
503
|
+
(0, ui_1.err)(`unknown mode: ${name}. one of: ${valid.join(', ')}`);
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
state.mode = name;
|
|
507
|
+
(0, ui_1.ok)(`mode → ${(0, ui_1.accent)(state.mode)}`);
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
case 'yes':
|
|
511
|
+
state.autoApprove = !state.autoApprove;
|
|
512
|
+
(0, ui_1.ok)(`auto-approve → ${state.autoApprove ? 'on' : 'off'}`);
|
|
513
|
+
return true;
|
|
514
|
+
case 'strict':
|
|
515
|
+
state.flags.strict = !state.flags.strict;
|
|
516
|
+
(0, ui_1.ok)(`strict → ${state.flags.strict ? 'on' : 'off'}`);
|
|
517
|
+
return true;
|
|
518
|
+
case 'allow': {
|
|
519
|
+
const msg = togglePermission(state, rest[0] ?? '', true);
|
|
520
|
+
if (!msg)
|
|
521
|
+
(0, ui_1.err)('usage: /allow files|shell|network|web|mcp|all');
|
|
522
|
+
else
|
|
523
|
+
(0, ui_1.ok)(msg);
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
case 'deny': {
|
|
527
|
+
const msg = togglePermission(state, rest[0] ?? '', false);
|
|
528
|
+
if (!msg)
|
|
529
|
+
(0, ui_1.err)('usage: /deny files|shell|network|web|mcp|all');
|
|
530
|
+
else
|
|
531
|
+
(0, ui_1.ok)(msg);
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
case 'retry': {
|
|
535
|
+
const turns = turnsOf(state);
|
|
536
|
+
const last = turns[turns.length - 1];
|
|
537
|
+
if (!last) {
|
|
538
|
+
(0, ui_1.err)('No previous turn to retry.');
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
(0, ui_1.info)(`Retrying: ${last.input.slice(0, 120)}`);
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
case 'undo': {
|
|
545
|
+
try {
|
|
546
|
+
(0, child_process_1.execSync)('git stash push -u -m forge-repl-undo', {
|
|
547
|
+
cwd: state.projectRoot,
|
|
548
|
+
stdio: 'ignore',
|
|
549
|
+
});
|
|
550
|
+
(0, ui_1.ok)('Stashed local changes (git stash pop to restore).');
|
|
551
|
+
}
|
|
552
|
+
catch (e) {
|
|
553
|
+
(0, ui_1.err)(`/undo requires git; ${e instanceof Error ? e.message : String(e)}`);
|
|
554
|
+
}
|
|
555
|
+
return true;
|
|
556
|
+
}
|
|
557
|
+
case 'sessions':
|
|
558
|
+
printSessionsList(state);
|
|
559
|
+
return true;
|
|
560
|
+
case 'load': {
|
|
561
|
+
const id = rest[0];
|
|
562
|
+
if (!id) {
|
|
563
|
+
(0, ui_1.err)('usage: /load <sessionId>');
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
// Normalise: accept bare id, repl-<id>, chat-<id>, or conv-<id>.
|
|
567
|
+
const sessionId = id.startsWith('repl-') || id.startsWith('chat-') || id.startsWith('conv-')
|
|
568
|
+
? id
|
|
569
|
+
: `repl-${id}`;
|
|
570
|
+
const loaded = (0, conversation_1.loadConversation)(state.projectRoot, sessionId);
|
|
571
|
+
if (!loaded) {
|
|
572
|
+
(0, ui_1.err)(`No conversation found for ${sessionId}`);
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
state.watcher?.close();
|
|
576
|
+
state.conversation = loaded;
|
|
577
|
+
state.remoteUpdates = 0;
|
|
578
|
+
attachWatcher(state);
|
|
579
|
+
(0, ui_1.ok)(`Loaded ${loaded.turns.length} turn${loaded.turns.length === 1 ? '' : 's'} from ${sessionId} (${loaded.meta.source.toUpperCase()})`);
|
|
580
|
+
printResumedSummary(state);
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
case 'continue': {
|
|
584
|
+
const list = (0, conversation_1.listConversations)(state.projectRoot).filter((s) => s.id !== state.conversation.meta.id);
|
|
585
|
+
if (!list.length) {
|
|
586
|
+
(0, ui_1.info)('No other conversations to continue.');
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
const target = list[0]; // newest first
|
|
590
|
+
const loaded = (0, conversation_1.loadConversation)(state.projectRoot, target.id);
|
|
591
|
+
if (!loaded) {
|
|
592
|
+
(0, ui_1.err)(`Could not load ${target.id}`);
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
state.watcher?.close();
|
|
596
|
+
state.conversation = loaded;
|
|
597
|
+
state.remoteUpdates = 0;
|
|
598
|
+
attachWatcher(state);
|
|
599
|
+
(0, ui_1.ok)(`Continuing ${loaded.meta.id} (${loaded.meta.source.toUpperCase()}) with ${loaded.turns.length} prior turn${loaded.turns.length === 1 ? '' : 's'}.`);
|
|
600
|
+
printResumedSummary(state);
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
case 'rename': {
|
|
604
|
+
const title = rest.join(' ').trim();
|
|
605
|
+
if (!title) {
|
|
606
|
+
(0, ui_1.info)(`current title: ${(0, ui_1.accent)(state.conversation.meta.title)}`);
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
await (0, conversation_1.renameConversation)(state.projectRoot, state.conversation.meta.id, title);
|
|
610
|
+
// Refresh meta from disk.
|
|
611
|
+
const reloaded = (0, conversation_1.loadConversation)(state.projectRoot, state.conversation.meta.id);
|
|
612
|
+
if (reloaded)
|
|
613
|
+
state.conversation = reloaded;
|
|
614
|
+
(0, ui_1.ok)(`Renamed to: ${(0, ui_1.accent)(title)}`);
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
case 'delete': {
|
|
618
|
+
const raw = rest[0];
|
|
619
|
+
const targetId = raw
|
|
620
|
+
? raw.startsWith('repl-') || raw.startsWith('chat-') || raw.startsWith('conv-')
|
|
621
|
+
? raw
|
|
622
|
+
: `repl-${raw}`
|
|
623
|
+
: state.conversation.meta.id;
|
|
624
|
+
const isCurrent = targetId === state.conversation.meta.id;
|
|
625
|
+
const target = (0, conversation_1.loadConversation)(state.projectRoot, targetId);
|
|
626
|
+
if (!target) {
|
|
627
|
+
(0, ui_1.err)(`No conversation ${targetId}`);
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
630
|
+
const gone = (0, conversation_1.deleteConversation)(state.projectRoot, targetId);
|
|
631
|
+
if (!gone) {
|
|
632
|
+
(0, ui_1.err)(`Delete failed for ${targetId}`);
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
if (isCurrent) {
|
|
636
|
+
// Roll over to a fresh conversation so the user has somewhere to type.
|
|
637
|
+
state.watcher?.close();
|
|
638
|
+
state.conversation = await (0, conversation_1.createConversation)({
|
|
639
|
+
projectPath: state.projectRoot,
|
|
640
|
+
source: 'repl',
|
|
641
|
+
mode: state.mode,
|
|
642
|
+
});
|
|
643
|
+
state.remoteUpdates = 0;
|
|
644
|
+
attachWatcher(state);
|
|
645
|
+
}
|
|
646
|
+
(0, ui_1.ok)(`Deleted ${targetId}.`);
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
case 'export': {
|
|
650
|
+
const destRaw = rest.join(' ').trim();
|
|
651
|
+
const dest = destRaw
|
|
652
|
+
? path.resolve(state.projectRoot, destRaw)
|
|
653
|
+
: path.join(state.projectRoot, `${state.conversation.meta.id}.json`);
|
|
654
|
+
try {
|
|
655
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
656
|
+
const payload = JSON.stringify(state.conversation, null, 2);
|
|
657
|
+
fs.writeFileSync(dest, payload, 'utf8');
|
|
658
|
+
(0, ui_1.ok)(`Exported ${state.conversation.turns.length} turn${state.conversation.turns.length === 1 ? '' : 's'} → ${dest}`);
|
|
659
|
+
}
|
|
660
|
+
catch (e) {
|
|
661
|
+
(0, ui_1.err)(`export failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
662
|
+
}
|
|
663
|
+
return true;
|
|
664
|
+
}
|
|
665
|
+
default:
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
// ---------- Commander passthrough ----------
|
|
670
|
+
/** Recursively install exitOverride on every command so neither the root nor
|
|
671
|
+
* any subcommand ever calls process.exit — even on help/version/parse errors. */
|
|
672
|
+
const installExitOverride = (cmd) => {
|
|
673
|
+
cmd.exitOverride();
|
|
674
|
+
for (const sub of cmd.commands)
|
|
675
|
+
installExitOverride(sub);
|
|
676
|
+
};
|
|
677
|
+
const runCommanderPassthrough = async (program, argv, _state) => {
|
|
678
|
+
installExitOverride(program);
|
|
679
|
+
process.exitCode = 0;
|
|
680
|
+
// Safety net: if anything in the passthrough tries to call process.exit
|
|
681
|
+
// directly (e.g. a subcommand action with a legacy exit), intercept it so
|
|
682
|
+
// the REPL stays alive.
|
|
683
|
+
const originalExit = process.exit;
|
|
684
|
+
let interceptedCode;
|
|
685
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
686
|
+
process.exit = ((code) => {
|
|
687
|
+
interceptedCode = code;
|
|
688
|
+
throw Object.assign(new Error('[REPL intercepted process.exit]'), {
|
|
689
|
+
__replInterceptedExit: true,
|
|
690
|
+
__code: code,
|
|
691
|
+
});
|
|
692
|
+
// never reached
|
|
693
|
+
});
|
|
694
|
+
try {
|
|
695
|
+
await program.parseAsync(['node', 'forge', ...argv]);
|
|
696
|
+
}
|
|
697
|
+
catch (e) {
|
|
698
|
+
const isIntercept = e && typeof e === 'object' && '__replInterceptedExit' in e;
|
|
699
|
+
if (isIntercept) {
|
|
700
|
+
// Give the user visible context: a command tried to exit the process.
|
|
701
|
+
const code = e.__code ?? 0;
|
|
702
|
+
if (code !== 0) {
|
|
703
|
+
(0, ui_1.err)(`/${argv[0]} exited with code ${code} (REPL stayed open).`);
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
// exit code 0 is usually an "OK, I'm done" signal — stay silent.
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
else if (e instanceof commander_1.CommanderError) {
|
|
710
|
+
const quiet = new Set([
|
|
711
|
+
'commander.help',
|
|
712
|
+
'commander.version',
|
|
713
|
+
'commander.helpDisplayed',
|
|
714
|
+
'commander.missingMandatoryOptionValue',
|
|
715
|
+
]);
|
|
716
|
+
if (!quiet.has(e.code)) {
|
|
717
|
+
(0, ui_1.err)(`/${argv[0]}: ${e.message}`);
|
|
718
|
+
}
|
|
719
|
+
// help-like exits are expected — the help card was already printed.
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
(0, ui_1.err)(`/${argv[0]} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
finally {
|
|
726
|
+
// Restore the real exit so the next Ctrl+D / clean shutdown still works.
|
|
727
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
728
|
+
process.exit = originalExit;
|
|
729
|
+
if (interceptedCode !== undefined && interceptedCode !== 0) {
|
|
730
|
+
process.exitCode = interceptedCode;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
// ---------- Orchestrator turn ----------
|
|
735
|
+
const runTaskTurn = async (state, rawInput, semantic) => {
|
|
736
|
+
const now = new Date().toISOString();
|
|
737
|
+
const effectiveInput = semantic?.prompt ?? rawInput;
|
|
738
|
+
const turn = {
|
|
739
|
+
id: (0, conversation_1.newTurnId)(),
|
|
740
|
+
at: now,
|
|
741
|
+
input: rawInput,
|
|
742
|
+
mode: semantic?.mode ?? state.mode,
|
|
743
|
+
status: 'running',
|
|
744
|
+
};
|
|
745
|
+
// Local-first in-memory update so status bar reflects the turn immediately,
|
|
746
|
+
// then persist. The watcher will re-deliver this event to our own process;
|
|
747
|
+
// we dedupe by id in applyRemoteUpdate.
|
|
748
|
+
state.conversation.turns.push(turn);
|
|
749
|
+
// Submitting a turn implicitly acknowledges any remote updates seen since
|
|
750
|
+
// the last prompt — the user has now participated again.
|
|
751
|
+
state.remoteUpdates = 0;
|
|
752
|
+
try {
|
|
753
|
+
await (0, conversation_1.appendUserTurn)(state.projectRoot, state.conversation.meta.id, turn);
|
|
754
|
+
}
|
|
755
|
+
catch (e) {
|
|
756
|
+
(0, ui_1.warn)(`turn persist failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
757
|
+
}
|
|
758
|
+
state.running = true;
|
|
759
|
+
state.abort = new AbortController();
|
|
760
|
+
const composed = (0, conversation_1.composeDescription)(effectiveInput, state.conversation.turns.slice(0, -1));
|
|
761
|
+
process.stdout.write('\n');
|
|
762
|
+
try {
|
|
763
|
+
const out = await (0, orchestrator_1.orchestrateRun)({
|
|
764
|
+
input: effectiveInput,
|
|
765
|
+
description: composed,
|
|
766
|
+
mode: turn.mode,
|
|
767
|
+
autoApprove: state.autoApprove || semantic?.autoApprove,
|
|
768
|
+
planOnly: turn.mode === 'plan' || Boolean(semantic?.planOnly),
|
|
769
|
+
flags: { ...state.flags },
|
|
770
|
+
});
|
|
771
|
+
const r = out.result;
|
|
772
|
+
const result = {
|
|
773
|
+
taskId: out.task.id,
|
|
774
|
+
success: r.success,
|
|
775
|
+
summary: r.summary ?? '',
|
|
776
|
+
filesChanged: r.filesChanged ?? [],
|
|
777
|
+
durationMs: r.durationMs ?? 0,
|
|
778
|
+
costUsd: r.costUsd,
|
|
779
|
+
};
|
|
780
|
+
const status = r.success ? 'done' : 'failed';
|
|
781
|
+
turn.taskId = out.task.id;
|
|
782
|
+
turn.status = status;
|
|
783
|
+
turn.result = result;
|
|
784
|
+
state.localTaskIds.add(out.task.id);
|
|
785
|
+
try {
|
|
786
|
+
await (0, conversation_1.attachTurnResult)(state.projectRoot, state.conversation.meta.id, {
|
|
787
|
+
turnId: turn.id,
|
|
788
|
+
taskId: out.task.id,
|
|
789
|
+
result,
|
|
790
|
+
status,
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
catch (e) {
|
|
794
|
+
(0, ui_1.warn)(`turn result persist failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
795
|
+
}
|
|
796
|
+
process.stdout.write('\n');
|
|
797
|
+
if (r.success) {
|
|
798
|
+
const costBit = r.costUsd && r.costUsd > 0 ? chalk_1.default.dim(` · $${r.costUsd.toFixed(4)}`) : '';
|
|
799
|
+
(0, ui_1.ok)(`turn ${turnsOf(state).length} done ${chalk_1.default.dim(`(${((r.durationMs ?? 0) / 1000).toFixed(1)}s · ${r.filesChanged?.length ?? 0} files)`)}${costBit}`);
|
|
800
|
+
if (r.filesChanged?.length) {
|
|
801
|
+
for (const f of r.filesChanged.slice(0, 8)) {
|
|
802
|
+
process.stdout.write(` ${chalk_1.default.rgb(...banners_1.PALETTE.teal)('▸')} ${chalk_1.default.white(f)}\n`);
|
|
803
|
+
}
|
|
804
|
+
if (r.filesChanged.length > 8) {
|
|
805
|
+
process.stdout.write(chalk_1.default.dim(` …+${r.filesChanged.length - 8} more\n`));
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
(0, ui_1.err)(`turn ${turnsOf(state).length} failed — ${(r.summary ?? '').slice(0, 160)}`);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
catch (e) {
|
|
814
|
+
const result = {
|
|
815
|
+
taskId: 'unknown',
|
|
816
|
+
success: false,
|
|
817
|
+
summary: e instanceof Error ? e.message : String(e),
|
|
818
|
+
filesChanged: [],
|
|
819
|
+
durationMs: 0,
|
|
820
|
+
};
|
|
821
|
+
turn.status = 'failed';
|
|
822
|
+
turn.result = result;
|
|
823
|
+
try {
|
|
824
|
+
await (0, conversation_1.attachTurnResult)(state.projectRoot, state.conversation.meta.id, {
|
|
825
|
+
turnId: turn.id,
|
|
826
|
+
taskId: turn.taskId ?? 'unknown',
|
|
827
|
+
result,
|
|
828
|
+
status: 'failed',
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
catch {
|
|
832
|
+
/* double-fault — already reported via err() below */
|
|
833
|
+
}
|
|
834
|
+
(0, ui_1.err)(`Turn crashed: ${e instanceof Error ? e.message : String(e)}`);
|
|
835
|
+
}
|
|
836
|
+
finally {
|
|
837
|
+
state.running = false;
|
|
838
|
+
state.abort = undefined;
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
// ---------- Watcher wiring ----------
|
|
842
|
+
/**
|
|
843
|
+
* Attach a watcher to the active conversation file. When another writer
|
|
844
|
+
* appends an event (another terminal, the UI, or a subagent recording a
|
|
845
|
+
* task result that we didn't originate), sync it into local state and
|
|
846
|
+
* increment the unread counter. The cursor redraws on the next keystroke
|
|
847
|
+
* via statusLine().
|
|
848
|
+
*/
|
|
849
|
+
const attachWatcher = (state) => {
|
|
850
|
+
state.watcher?.close();
|
|
851
|
+
state.watcher = (0, conversation_1.watchConversationFile)(state.projectRoot, state.conversation.meta.id, (update) => {
|
|
852
|
+
let remoteAdded = 0;
|
|
853
|
+
for (const nt of update.newTurns) {
|
|
854
|
+
// Did we author this turn? If yes, the local push already accounted
|
|
855
|
+
// for it; skip to avoid duplication.
|
|
856
|
+
const already = state.conversation.turns.some((t) => t.id === nt.id);
|
|
857
|
+
if (already)
|
|
858
|
+
continue;
|
|
859
|
+
state.conversation.turns.push(nt);
|
|
860
|
+
remoteAdded++;
|
|
861
|
+
}
|
|
862
|
+
for (const { turn } of update.completedTurns) {
|
|
863
|
+
const mine = turn.taskId ? state.localTaskIds.has(turn.taskId) : false;
|
|
864
|
+
if (mine)
|
|
865
|
+
continue;
|
|
866
|
+
const idx = state.conversation.turns.findIndex((t) => t.id === turn.id);
|
|
867
|
+
if (idx >= 0)
|
|
868
|
+
state.conversation.turns[idx] = turn;
|
|
869
|
+
// If the completed turn arrived as a pure result without the user
|
|
870
|
+
// event, the newTurns branch above already added it.
|
|
871
|
+
}
|
|
872
|
+
if (remoteAdded ||
|
|
873
|
+
update.completedTurns.some((c) => !state.localTaskIds.has(c.turn.taskId ?? ''))) {
|
|
874
|
+
state.remoteUpdates += remoteAdded;
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
};
|
|
878
|
+
// ---------- Entry point ----------
|
|
879
|
+
const startRepl = async (program, opts = {}) => {
|
|
880
|
+
(0, bootstrap_1.bootstrap)();
|
|
881
|
+
// During interactive REPL, silence logger stderr/stdout output — provider
|
|
882
|
+
// failures etc. would otherwise corrupt the rendered frame. File logging
|
|
883
|
+
// continues so the user can inspect ~/.forge/logs/forge.log after.
|
|
884
|
+
(0, logger_1.setConsoleOutput)(false);
|
|
885
|
+
const cfg = (0, loader_1.loadGlobalConfig)();
|
|
886
|
+
const projectRoot = (0, loader_1.findProjectRoot)() ?? process.cwd();
|
|
887
|
+
(0, paths_1.ensureProjectDir)(projectRoot);
|
|
888
|
+
// Pull a readable "provider:model" label for the status line.
|
|
889
|
+
const providers = (() => {
|
|
890
|
+
try {
|
|
891
|
+
return (0, provider_1.listProviders)().map((p) => p.name);
|
|
892
|
+
}
|
|
893
|
+
catch {
|
|
894
|
+
return [];
|
|
895
|
+
}
|
|
896
|
+
})();
|
|
897
|
+
const provider = cfg.provider ?? providers[0] ?? 'local';
|
|
898
|
+
const modelId = cfg.models?.[cfg.defaultMode] ?? cfg.models?.balanced ?? 'default';
|
|
899
|
+
// Resolve conversation: resume an existing one (accepting any prefix), or
|
|
900
|
+
// create a fresh REPL-sourced conversation.
|
|
901
|
+
let conversation = null;
|
|
902
|
+
if (opts.resumeSessionId) {
|
|
903
|
+
const normalised = opts.resumeSessionId.startsWith('repl-') ||
|
|
904
|
+
opts.resumeSessionId.startsWith('chat-') ||
|
|
905
|
+
opts.resumeSessionId.startsWith('conv-')
|
|
906
|
+
? opts.resumeSessionId
|
|
907
|
+
: `repl-${opts.resumeSessionId}`;
|
|
908
|
+
conversation = (0, conversation_1.loadConversation)(projectRoot, normalised);
|
|
909
|
+
}
|
|
910
|
+
if (!conversation) {
|
|
911
|
+
conversation = await (0, conversation_1.createConversation)({
|
|
912
|
+
projectPath: projectRoot,
|
|
913
|
+
source: 'repl',
|
|
914
|
+
mode: cfg.defaultMode ?? 'balanced',
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
const state = {
|
|
918
|
+
conversation,
|
|
919
|
+
projectRoot,
|
|
920
|
+
mode: cfg.defaultMode ?? 'balanced',
|
|
921
|
+
flags: {
|
|
922
|
+
skipRoutine: false,
|
|
923
|
+
allowFiles: false,
|
|
924
|
+
allowShell: false,
|
|
925
|
+
allowNetwork: false,
|
|
926
|
+
allowWeb: false,
|
|
927
|
+
allowMcp: false,
|
|
928
|
+
strict: false,
|
|
929
|
+
nonInteractive: false,
|
|
930
|
+
},
|
|
931
|
+
autoApprove: false,
|
|
932
|
+
running: false,
|
|
933
|
+
provider,
|
|
934
|
+
modelId: String(modelId),
|
|
935
|
+
localTaskIds: new Set(),
|
|
936
|
+
remoteUpdates: 0,
|
|
937
|
+
};
|
|
938
|
+
attachWatcher(state);
|
|
939
|
+
process.stdout.write(hero(state, pkg.version ?? '0.1.0'));
|
|
940
|
+
if (conversation.turns.length)
|
|
941
|
+
printResumedSummary(state);
|
|
942
|
+
const history = loadHistory();
|
|
943
|
+
let editor = null;
|
|
944
|
+
let lastSigint = 0;
|
|
945
|
+
let closed = false;
|
|
946
|
+
const hooks = {
|
|
947
|
+
prompt: () => prompt(state),
|
|
948
|
+
statusLine: () => statusLine(state),
|
|
949
|
+
suggestions: (input) => {
|
|
950
|
+
const ranked = (0, repl_commands_1.rankSlash)(input, 8);
|
|
951
|
+
return ranked.map((r) => ({
|
|
952
|
+
label: r.label,
|
|
953
|
+
value: r.value,
|
|
954
|
+
description: r.description,
|
|
955
|
+
score: r.score,
|
|
956
|
+
}));
|
|
957
|
+
},
|
|
958
|
+
history,
|
|
959
|
+
isRunning: () => state.running,
|
|
960
|
+
onCancel: () => {
|
|
961
|
+
const now = Date.now();
|
|
962
|
+
// During a running task: cancel it.
|
|
963
|
+
if (state.running) {
|
|
964
|
+
(0, ui_1.warn)('Cancelling current task …');
|
|
965
|
+
try {
|
|
966
|
+
process.kill(process.pid, 'SIGINT');
|
|
967
|
+
}
|
|
968
|
+
catch {
|
|
969
|
+
/* noop */
|
|
970
|
+
}
|
|
971
|
+
state.running = false;
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
// Idle + buffer was empty (editor handles the non-empty case itself).
|
|
975
|
+
// Two presses within 2s exits. Otherwise just print a hint.
|
|
976
|
+
if (now - lastSigint < 2000) {
|
|
977
|
+
editor?.close();
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
lastSigint = now;
|
|
981
|
+
process.stdout.write('\n' + chalk_1.default.dim(' (Ctrl+C again to exit · Ctrl+D also exits)\n'));
|
|
982
|
+
},
|
|
983
|
+
onExit: () => {
|
|
984
|
+
editor?.close();
|
|
985
|
+
},
|
|
986
|
+
onSubmit: async (line, picked) => {
|
|
987
|
+
if (!line)
|
|
988
|
+
return;
|
|
989
|
+
appendHistory(line);
|
|
990
|
+
history.push(line);
|
|
991
|
+
// Slash command?
|
|
992
|
+
if (line.startsWith('/')) {
|
|
993
|
+
const body = line.slice(1);
|
|
994
|
+
const parts = parseArgv(body);
|
|
995
|
+
const head = (parts[0] ?? '').toLowerCase();
|
|
996
|
+
const rest = parts.slice(1);
|
|
997
|
+
const cmd = (0, repl_commands_1.findSlash)(head);
|
|
998
|
+
if (!cmd) {
|
|
999
|
+
(0, ui_1.err)(`Unknown command: /${head} ${chalk_1.default.dim('(type /help)')}`);
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
if (cmd.kind === 'internal') {
|
|
1003
|
+
const handled = await handleInternalSlash(state, cmd, rest, () => {
|
|
1004
|
+
closed = true;
|
|
1005
|
+
editor?.close();
|
|
1006
|
+
});
|
|
1007
|
+
// retry falls through to run
|
|
1008
|
+
if (cmd.name === 'retry' && !handled) {
|
|
1009
|
+
const turns = turnsOf(state);
|
|
1010
|
+
const last = turns[turns.length - 1];
|
|
1011
|
+
if (last)
|
|
1012
|
+
await runTaskTurn(state, last.input);
|
|
1013
|
+
}
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
if (cmd.kind === 'passthrough') {
|
|
1017
|
+
await runCommanderPassthrough(program, [cmd.passthroughTo ?? cmd.name, ...rest]);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
if (cmd.kind === 'semantic' && cmd.template) {
|
|
1021
|
+
const args = rest.join(' ');
|
|
1022
|
+
const expansion = cmd.template(args);
|
|
1023
|
+
await runTaskTurn(state, `/${cmd.name} ${args}`.trim(), expansion);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
// Bare text → task turn
|
|
1029
|
+
await runTaskTurn(state, line);
|
|
1030
|
+
void picked; // unused for bare text
|
|
1031
|
+
},
|
|
1032
|
+
};
|
|
1033
|
+
editor = new repl_input_1.LineEditor(hooks);
|
|
1034
|
+
await editor.run();
|
|
1035
|
+
// Tear down watcher; otherwise fs watchers keep the process alive.
|
|
1036
|
+
state.watcher?.close();
|
|
1037
|
+
// Restore logger console output for subsequent CLI invocations in the
|
|
1038
|
+
// same process (unusual, but safe to do).
|
|
1039
|
+
(0, logger_1.setConsoleOutput)(true);
|
|
1040
|
+
if (!closed)
|
|
1041
|
+
process.stdout.write('\n');
|
|
1042
|
+
process.stdout.write((0, ui_1.dim)(` session ${state.conversation.meta.id} saved.`) + '\n');
|
|
1043
|
+
process.stdout.write((0, ui_1.dim)(` resume with: forge repl --resume ${state.conversation.meta.id}`) + '\n\n');
|
|
1044
|
+
};
|
|
1045
|
+
exports.startRepl = startRepl;
|
|
1046
|
+
//# sourceMappingURL=repl.js.map
|