codeply-cli 0.5.5 → 0.6.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 -21
- package/THIRD_PARTY_NOTICES.md +65 -65
- package/bin/codeply.js +8 -3
- package/lib/agent.mjs +2596 -2268
- package/lib/ai.js +135 -27
- package/lib/ai.mjs +6 -6
- package/lib/apply-patch.js +217 -217
- package/lib/arity-table.json +138 -138
- package/lib/arity.js +70 -70
- package/lib/auth.mjs +6 -6
- package/lib/autopilot.js +84 -0
- package/lib/bot-mail.js +46 -0
- package/lib/bots.js +142 -15
- package/lib/brain.js +914 -0
- package/lib/call-assist.js +120 -0
- package/lib/chatgpt.js +282 -282
- package/lib/cloud-browser.mjs +143 -143
- package/lib/cloud.mjs +1396 -1312
- package/lib/commands.js +103 -103
- package/lib/computer.js +136 -0
- package/lib/design-library/assemble-artifact.mjs +21 -21
- package/lib/design-library/cleanup.mjs +45 -45
- package/lib/design-library/index.json +22379 -22379
- package/lib/design-library/make-table-data.mjs +39 -39
- package/lib/design-tools.mjs +173 -0
- package/lib/diagnostics.js +192 -192
- package/lib/dsh-acp.mjs +444 -0
- package/lib/edit-engine.js +364 -364
- package/lib/github-agent.mjs +326 -326
- package/lib/handoff.mjs +245 -0
- package/lib/history.js +78 -75
- package/lib/life-sources.js +319 -0
- package/lib/life-store.js +356 -0
- package/lib/life-triggers.js +286 -0
- package/lib/life.js +487 -0
- package/lib/mail-watch.js +3 -3
- package/lib/mcp.js +258 -258
- package/lib/native-tools.mjs +17 -5
- package/lib/oauth-connectors.js +76 -21
- package/lib/permissions.js +109 -109
- package/lib/plugins.js +356 -353
- package/lib/reach.js +67 -67
- package/lib/render-md.mjs +647 -617
- package/lib/screen.js +77 -0
- package/lib/server.mjs +458 -457
- package/lib/session-db.js +220 -220
- package/lib/share.js +208 -168
- package/lib/skills.js +4 -1
- package/lib/snapshot.js +146 -146
- package/lib/subagents.js +4 -2
- package/lib/telemetry.js +105 -105
- package/lib/terminal-hints.js +154 -154
- package/lib/textarea.mjs +264 -258
- package/lib/tools.mjs +326 -40
- package/lib/tui.mjs +1459 -1086
- package/lib/typecheck-worker.js +147 -147
- package/lib/web-tools.js +209 -209
- package/package.json +1 -1
- package/skills/SOURCE.md +58 -58
- package/skills/accessibility/SKILL.md +147 -147
- package/skills/agent-architecture-audit/SKILL.md +257 -257
- package/skills/agent-eval/SKILL.md +147 -147
- package/skills/agent-harness-construction/SKILL.md +74 -74
- package/skills/agent-introspection-debugging/SKILL.md +154 -154
- package/skills/agent-payment-x402/SKILL.md +225 -225
- package/skills/agent-self-evaluation/SKILL.md +182 -182
- package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -71
- package/skills/agent-self-evaluation/references/hook-integration.md +64 -64
- package/skills/agent-sort/SKILL.md +216 -216
- package/skills/agentic-engineering/SKILL.md +64 -64
- package/skills/agentic-os/SKILL.md +388 -388
- package/skills/ai-first-engineering/SKILL.md +52 -52
- package/skills/ai-regression-testing/SKILL.md +386 -386
- package/skills/android-clean-architecture/SKILL.md +340 -340
- package/skills/angular-developer/SKILL.md +155 -155
- package/skills/angular-developer/references/angular-animations.md +160 -160
- package/skills/angular-developer/references/angular-aria.md +410 -410
- package/skills/angular-developer/references/cli.md +86 -86
- package/skills/angular-developer/references/component-harnesses.md +59 -59
- package/skills/angular-developer/references/component-styling.md +91 -91
- package/skills/angular-developer/references/components.md +117 -117
- package/skills/angular-developer/references/creating-services.md +97 -97
- package/skills/angular-developer/references/data-resolvers.md +69 -69
- package/skills/angular-developer/references/define-routes.md +67 -67
- package/skills/angular-developer/references/defining-providers.md +72 -72
- package/skills/angular-developer/references/di-fundamentals.md +120 -120
- package/skills/angular-developer/references/e2e-testing.md +56 -56
- package/skills/angular-developer/references/effects.md +83 -83
- package/skills/angular-developer/references/hierarchical-injectors.md +43 -43
- package/skills/angular-developer/references/host-elements.md +80 -80
- package/skills/angular-developer/references/injection-context.md +63 -63
- package/skills/angular-developer/references/inputs.md +101 -101
- package/skills/angular-developer/references/linked-signal.md +59 -59
- package/skills/angular-developer/references/loading-strategies.md +61 -61
- package/skills/angular-developer/references/mcp.md +108 -108
- package/skills/angular-developer/references/navigate-to-routes.md +69 -69
- package/skills/angular-developer/references/outputs.md +86 -86
- package/skills/angular-developer/references/reactive-forms.md +122 -122
- package/skills/angular-developer/references/rendering-strategies.md +44 -44
- package/skills/angular-developer/references/resource.md +77 -77
- package/skills/angular-developer/references/route-animations.md +56 -56
- package/skills/angular-developer/references/route-guards.md +52 -52
- package/skills/angular-developer/references/router-lifecycle.md +45 -45
- package/skills/angular-developer/references/router-testing.md +87 -87
- package/skills/angular-developer/references/show-routes-with-outlets.md +68 -68
- package/skills/angular-developer/references/signal-forms.md +795 -795
- package/skills/angular-developer/references/signals-overview.md +94 -94
- package/skills/angular-developer/references/tailwind-css.md +69 -69
- package/skills/angular-developer/references/template-driven-forms.md +114 -114
- package/skills/angular-developer/references/testing-fundamentals.md +65 -65
- package/skills/api-connector-builder/SKILL.md +121 -121
- package/skills/api-design/SKILL.md +524 -524
- package/skills/architecture-decision-records/SKILL.md +180 -180
- package/skills/article-writing/SKILL.md +80 -80
- package/skills/automation-audit-ops/SKILL.md +143 -143
- package/skills/autonomous-agent-harness/SKILL.md +274 -274
- package/skills/autonomous-loops/SKILL.md +611 -611
- package/skills/backend-patterns/SKILL.md +562 -562
- package/skills/benchmark/SKILL.md +95 -95
- package/skills/benchmark-methodology/SKILL.md +191 -191
- package/skills/benchmark-optimization-loop/SKILL.md +71 -71
- package/skills/blender-motion-state-inspection/SKILL.md +165 -165
- package/skills/blueprint/SKILL.md +106 -106
- package/skills/brand-discovery/SKILL.md +145 -145
- package/skills/brand-discovery/references/10_purpose-why.md +40 -40
- package/skills/brand-discovery/references/20_positioning.md +44 -44
- package/skills/brand-discovery/references/30_audience-niche.md +52 -52
- package/skills/brand-discovery/references/40_personality-archetype.md +57 -57
- package/skills/brand-discovery/references/50_voice-tone.md +59 -59
- package/skills/brand-discovery/references/60_narrative-story.md +50 -50
- package/skills/brand-discovery/references/70_founder-tension.md +49 -49
- package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -133
- package/skills/brand-voice/SKILL.md +98 -98
- package/skills/brand-voice/references/voice-profile-schema.md +55 -55
- package/skills/browser-qa/SKILL.md +105 -105
- package/skills/bun-runtime/SKILL.md +85 -85
- package/skills/canary-watch/SKILL.md +108 -108
- package/skills/carrier-relationship-management/SKILL.md +212 -212
- package/skills/cisco-ios-patterns/SKILL.md +164 -164
- package/skills/ck/SKILL.md +148 -148
- package/skills/claude-devfleet/SKILL.md +112 -112
- package/skills/click-path-audit/SKILL.md +245 -245
- package/skills/clickhouse-io/SKILL.md +445 -445
- package/skills/code-tour/SKILL.md +254 -254
- package/skills/codebase-onboarding/SKILL.md +234 -234
- package/skills/codehealth-mcp/SKILL.md +167 -167
- package/skills/codeply-taste/LICENSE +21 -0
- package/skills/codeply-taste/SKILL.md +100 -0
- package/skills/codeply-taste/SOURCE.md +10 -0
- package/skills/codeply-taste/assets/base.css +100 -0
- package/skills/codeply-taste/assets/base.js +37 -0
- package/skills/codeply-taste/library/directions.json +1078 -0
- package/skills/codeply-taste/library/heroes.json +894 -0
- package/skills/codeply-taste/library/motions.json +57 -0
- package/skills/codeply-taste/library/palettes.json +1485 -0
- package/skills/codeply-taste/library/photos.json +7240 -0
- package/skills/codeply-taste/library/sections.json +4136 -0
- package/skills/codeply-taste/library/sites.json +7274 -0
- package/skills/codeply-taste/library/type.json +2024 -0
- package/skills/codeply-taste/references/colour.md +141 -0
- package/skills/codeply-taste/references/copy.md +177 -0
- package/skills/codeply-taste/references/critique.md +171 -0
- package/skills/codeply-taste/references/imagery.md +377 -0
- package/skills/codeply-taste/references/layout.md +178 -0
- package/skills/codeply-taste/references/motion.md +292 -0
- package/skills/codeply-taste/references/research.md +306 -0
- package/skills/codeply-taste/references/tells.md +115 -0
- package/skills/codeply-taste/references/type.md +170 -0
- package/skills/codeply-taste/scripts/assets.mjs +865 -0
- package/skills/codeply-taste/scripts/draw.mjs +525 -0
- package/skills/codeply-taste/scripts/inspect.mjs +205 -0
- package/skills/codeply-taste/scripts/lib/cdp.mjs +205 -0
- package/skills/codeply-taste/scripts/lib/probe.mjs +569 -0
- package/skills/codeply-taste/scripts/lib/rules.mjs +137 -0
- package/skills/codeply-taste/scripts/lint.mjs +108 -0
- package/skills/coding-standards/SKILL.md +551 -551
- package/skills/competitive-platform-analysis/SKILL.md +214 -214
- package/skills/competitive-report-structure/SKILL.md +162 -162
- package/skills/compose-multiplatform-patterns/SKILL.md +300 -300
- package/skills/config-gc/SKILL.md +120 -120
- package/skills/configure-ecc/SKILL.md +385 -385
- package/skills/connections-optimizer/SKILL.md +190 -190
- package/skills/content-engine/SKILL.md +132 -132
- package/skills/content-hash-cache-pattern/SKILL.md +162 -162
- package/skills/context-budget/SKILL.md +136 -136
- package/skills/continuous-agent-loop/SKILL.md +46 -46
- package/skills/continuous-learning/SKILL.md +132 -132
- package/skills/continuous-learning-v2/SKILL.md +361 -361
- package/skills/contract-first/SKILL.md +287 -287
- package/skills/cost-aware-llm-pipeline/SKILL.md +184 -184
- package/skills/cost-tracking/SKILL.md +97 -97
- package/skills/council/SKILL.md +204 -204
- package/skills/cpp-coding-standards/SKILL.md +724 -724
- package/skills/cpp-testing/SKILL.md +325 -325
- package/skills/crosspost/SKILL.md +112 -112
- package/skills/csharp-testing/SKILL.md +322 -322
- package/skills/customer-billing-ops/SKILL.md +141 -141
- package/skills/customs-trade-compliance/SKILL.md +263 -263
- package/skills/dart-flutter-patterns/SKILL.md +564 -564
- package/skills/dashboard-builder/SKILL.md +109 -109
- package/skills/data-scraper-agent/SKILL.md +765 -765
- package/skills/data-throughput-accelerator/SKILL.md +74 -74
- package/skills/database-migrations/SKILL.md +430 -430
- package/skills/deep-research/SKILL.md +160 -160
- package/skills/defi-amm-security/SKILL.md +167 -167
- package/skills/delivery-gate/SKILL.md +126 -126
- package/skills/deployment-patterns/SKILL.md +428 -428
- package/skills/design-system/SKILL.md +83 -83
- package/skills/django-celery/SKILL.md +458 -458
- package/skills/django-patterns/SKILL.md +735 -735
- package/skills/django-security/SKILL.md +644 -644
- package/skills/django-tdd/SKILL.md +730 -730
- package/skills/django-verification/SKILL.md +470 -470
- package/skills/dmux-workflows/SKILL.md +192 -192
- package/skills/docker-patterns/SKILL.md +365 -365
- package/skills/documentation-lookup/SKILL.md +91 -91
- package/skills/dotnet-patterns/SKILL.md +322 -322
- package/skills/dynamic-workflow-mode/SKILL.md +124 -124
- package/skills/e2e-testing/SKILL.md +327 -327
- package/skills/ecc-guide/SKILL.md +190 -190
- package/skills/ecc-recipes/SKILL.md +149 -149
- package/skills/ecc-tools-cost-audit/SKILL.md +161 -161
- package/skills/email-ops/SKILL.md +122 -122
- package/skills/energy-procurement/SKILL.md +228 -228
- package/skills/enterprise-agent-ops/SKILL.md +51 -51
- package/skills/error-handling/SKILL.md +377 -377
- package/skills/eval-harness/SKILL.md +271 -271
- package/skills/evm-token-decimals/SKILL.md +131 -131
- package/skills/exa-search/SKILL.md +108 -108
- package/skills/fal-ai-media/SKILL.md +289 -289
- package/skills/fastapi-patterns/SKILL.md +514 -514
- package/skills/finance-billing-ops/SKILL.md +128 -128
- package/skills/flox-environments/SKILL.md +497 -497
- package/skills/flutter-dart-code-review/SKILL.md +436 -436
- package/skills/foundation-models-on-device/SKILL.md +243 -243
- package/skills/frontend-a11y/SKILL.md +446 -446
- package/skills/frontend-design-direction/SKILL.md +93 -93
- package/skills/frontend-patterns/SKILL.md +657 -657
- package/skills/frontend-slides/SKILL.md +185 -185
- package/skills/fsharp-testing/SKILL.md +281 -281
- package/skills/gan-style-harness/SKILL.md +279 -279
- package/skills/gateguard/SKILL.md +133 -133
- package/skills/generating-python-installer/SKILL.md +820 -820
- package/skills/git-workflow/SKILL.md +716 -716
- package/skills/github-ops/SKILL.md +145 -145
- package/skills/golang-patterns/SKILL.md +676 -676
- package/skills/golang-testing/SKILL.md +721 -721
- package/skills/google-workspace-ops/SKILL.md +96 -96
- package/skills/growth-log/SKILL.md +128 -128
- package/skills/healthcare-cdss-patterns/SKILL.md +246 -246
- package/skills/healthcare-emr-patterns/SKILL.md +160 -160
- package/skills/healthcare-eval-harness/SKILL.md +208 -208
- package/skills/healthcare-phi-compliance/SKILL.md +146 -146
- package/skills/hermes-imports/SKILL.md +89 -89
- package/skills/hexagonal-architecture/SKILL.md +277 -277
- package/skills/hipaa-compliance/SKILL.md +79 -79
- package/skills/homelab-network-readiness/SKILL.md +170 -170
- package/skills/homelab-network-setup/SKILL.md +130 -130
- package/skills/homelab-pihole-dns/SKILL.md +275 -275
- package/skills/homelab-vlan-segmentation/SKILL.md +312 -312
- package/skills/homelab-wireguard-vpn/SKILL.md +306 -306
- package/skills/hookify-rules/SKILL.md +128 -128
- package/skills/inherit-legacy-style/SKILL.md +157 -157
- package/skills/intent-driven-development/SKILL.md +360 -360
- package/skills/inventory-demand-planning/SKILL.md +247 -247
- package/skills/investor-materials/SKILL.md +97 -97
- package/skills/investor-outreach/SKILL.md +92 -92
- package/skills/ios-icon-gen/SKILL.md +158 -158
- package/skills/iterative-retrieval/SKILL.md +212 -212
- package/skills/ito-basket-compare/SKILL.md +64 -64
- package/skills/ito-compute/SKILL.md +129 -129
- package/skills/ito-data-atlas-agent/SKILL.md +64 -64
- package/skills/ito-market-intelligence/SKILL.md +61 -61
- package/skills/ito-trade-planner/SKILL.md +68 -68
- package/skills/java-coding-standards/SKILL.md +384 -384
- package/skills/jira-integration/SKILL.md +303 -303
- package/skills/jpa-patterns/SKILL.md +152 -152
- package/skills/knowledge-ops/SKILL.md +155 -155
- package/skills/kotlin-coroutines-flows/SKILL.md +285 -285
- package/skills/kotlin-exposed-patterns/SKILL.md +720 -720
- package/skills/kotlin-ktor-patterns/SKILL.md +690 -690
- package/skills/kotlin-patterns/SKILL.md +712 -712
- package/skills/kotlin-testing/SKILL.md +825 -825
- package/skills/kubernetes-patterns/SKILL.md +756 -756
- package/skills/laravel-patterns/SKILL.md +416 -416
- package/skills/laravel-plugin-discovery/SKILL.md +230 -230
- package/skills/laravel-security/SKILL.md +948 -948
- package/skills/laravel-tdd/SKILL.md +675 -675
- package/skills/laravel-verification/SKILL.md +180 -180
- package/skills/latency-critical-systems/SKILL.md +75 -75
- package/skills/lead-intelligence/SKILL.md +322 -322
- package/skills/liquid-glass-design/SKILL.md +279 -279
- package/skills/llm-trading-agent-security/SKILL.md +147 -147
- package/skills/logistics-exception-management/SKILL.md +222 -222
- package/skills/loop-design-check/SKILL.md +143 -143
- package/skills/mailtrap-email-integration/SKILL.md +77 -77
- package/skills/make-interfaces-feel-better/SKILL.md +152 -152
- package/skills/manim-video/SKILL.md +90 -90
- package/skills/market-research/SKILL.md +76 -76
- package/skills/marketing-campaign/SKILL.md +114 -114
- package/skills/mcp-server-patterns/SKILL.md +70 -70
- package/skills/messages-ops/SKILL.md +105 -105
- package/skills/ml-adoption-playbook/SKILL.md +57 -57
- package/skills/mle-workflow/SKILL.md +348 -348
- package/skills/motion-advanced/SKILL.md +596 -596
- package/skills/motion-foundations/SKILL.md +299 -299
- package/skills/motion-patterns/SKILL.md +434 -434
- package/skills/motion-ui/SKILL.md +576 -576
- package/skills/mysql-patterns/SKILL.md +413 -413
- package/skills/nanoclaw-repl/SKILL.md +34 -34
- package/skills/nestjs-patterns/SKILL.md +231 -231
- package/skills/netmiko-ssh-automation/SKILL.md +174 -174
- package/skills/network-bgp-diagnostics/SKILL.md +168 -168
- package/skills/network-config-validation/SKILL.md +211 -211
- package/skills/network-interface-health/SKILL.md +153 -153
- package/skills/nextjs-turbopack/SKILL.md +58 -58
- package/skills/nodejs-keccak256/SKILL.md +103 -103
- package/skills/nutrient-document-processing/SKILL.md +168 -168
- package/skills/nuxt4-patterns/SKILL.md +101 -101
- package/skills/openclaw-persona-forge/SKILL.md +289 -289
- package/skills/openclaw-persona-forge/references/avatar-style.md +124 -124
- package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -53
- package/skills/openclaw-persona-forge/references/error-handling.md +53 -53
- package/skills/openclaw-persona-forge/references/identity-tension.md +48 -48
- package/skills/openclaw-persona-forge/references/naming-system.md +39 -39
- package/skills/openclaw-persona-forge/references/output-template.md +166 -166
- package/skills/opensource-pipeline/SKILL.md +256 -256
- package/skills/orch-add-feature/SKILL.md +45 -45
- package/skills/orch-build-mvp/SKILL.md +49 -49
- package/skills/orch-change-feature/SKILL.md +43 -43
- package/skills/orch-fix-defect/SKILL.md +43 -43
- package/skills/orch-pipeline/SKILL.md +121 -121
- package/skills/orch-refine-code/SKILL.md +44 -44
- package/skills/parallel-execution-optimizer/SKILL.md +74 -74
- package/skills/perl-patterns/SKILL.md +505 -505
- package/skills/perl-security/SKILL.md +504 -504
- package/skills/perl-testing/SKILL.md +476 -476
- package/skills/plan-canvas/SKILL.md +153 -153
- package/skills/plan-orchestrate/SKILL.md +263 -263
- package/skills/plankton-code-quality/SKILL.md +237 -237
- package/skills/postgres-patterns/SKILL.md +148 -148
- package/skills/prediction-market-oracle-research/SKILL.md +64 -64
- package/skills/prediction-market-risk-review/SKILL.md +61 -61
- package/skills/premium-web-design/SKILL.md +154 -154
- package/skills/prisma-patterns/SKILL.md +401 -401
- package/skills/product-capability/SKILL.md +142 -142
- package/skills/product-lens/SKILL.md +93 -93
- package/skills/production-audit/SKILL.md +207 -207
- package/skills/production-scheduling/SKILL.md +238 -238
- package/skills/project-flow-ops/SKILL.md +112 -112
- package/skills/prompt-optimizer/SKILL.md +398 -398
- package/skills/python-patterns/SKILL.md +751 -751
- package/skills/python-testing/SKILL.md +817 -817
- package/skills/pytorch-patterns/SKILL.md +397 -397
- package/skills/quality-nonconformance/SKILL.md +260 -260
- package/skills/quarkus-patterns/SKILL.md +723 -723
- package/skills/quarkus-security/SKILL.md +468 -468
- package/skills/quarkus-tdd/SKILL.md +812 -812
- package/skills/quarkus-verification/SKILL.md +480 -480
- package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -68
- package/skills/react-native-patterns/SKILL.md +326 -326
- package/skills/react-patterns/SKILL.md +342 -342
- package/skills/react-performance/SKILL.md +575 -575
- package/skills/react-testing/SKILL.md +424 -424
- package/skills/recsys-pipeline-architect/SKILL.md +115 -115
- package/skills/recursive-decision-ledger/SKILL.md +81 -81
- package/skills/redis-patterns/SKILL.md +404 -404
- package/skills/regex-vs-llm-structured-text/SKILL.md +221 -221
- package/skills/remotion-video-creation/SKILL.md +43 -43
- package/skills/repo-scan/SKILL.md +79 -79
- package/skills/research-ops/SKILL.md +113 -113
- package/skills/returns-reverse-logistics/SKILL.md +240 -240
- package/skills/rules-distill/SKILL.md +265 -265
- package/skills/rust-patterns/SKILL.md +500 -500
- package/skills/rust-testing/SKILL.md +501 -501
- package/skills/safety-guard/SKILL.md +76 -76
- package/skills/santa-method/SKILL.md +307 -307
- package/skills/scientific-db-pubmed-database/SKILL.md +176 -176
- package/skills/scientific-db-uspto-database/SKILL.md +178 -178
- package/skills/scientific-pkg-gget/SKILL.md +167 -167
- package/skills/scientific-thinking-literature-review/SKILL.md +193 -193
- package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -161
- package/skills/search-first/SKILL.md +183 -183
- package/skills/security-bounty-hunter/SKILL.md +100 -100
- package/skills/security-review/SKILL.md +504 -504
- package/skills/security-scan/SKILL.md +166 -166
- package/skills/seo/SKILL.md +155 -155
- package/skills/skill-comply/SKILL.md +59 -59
- package/skills/skill-scout/SKILL.md +141 -141
- package/skills/skill-stocktake/SKILL.md +195 -195
- package/skills/social-graph-ranker/SKILL.md +155 -155
- package/skills/social-publisher/SKILL.md +130 -130
- package/skills/springboot-patterns/SKILL.md +315 -315
- package/skills/springboot-security/SKILL.md +273 -273
- package/skills/springboot-tdd/SKILL.md +159 -159
- package/skills/springboot-verification/SKILL.md +232 -232
- package/skills/strategic-compact/SKILL.md +142 -142
- package/skills/swift-actor-persistence/SKILL.md +144 -144
- package/skills/swift-concurrency-6-2/SKILL.md +216 -216
- package/skills/swift-protocol-di-testing/SKILL.md +191 -191
- package/skills/swiftui-patterns/SKILL.md +259 -259
- package/skills/taste/SKILL.md +264 -264
- package/skills/taste/references/genre-taxonomy.md +87 -87
- package/skills/tdd-workflow/SKILL.md +583 -583
- package/skills/team-agent-orchestration/SKILL.md +111 -111
- package/skills/team-builder/SKILL.md +169 -169
- package/skills/terminal-ops/SKILL.md +110 -110
- package/skills/tinystruct-patterns/SKILL.md +279 -279
- package/skills/tinystruct-patterns/references/architecture.md +90 -90
- package/skills/tinystruct-patterns/references/data-handling.md +60 -60
- package/skills/tinystruct-patterns/references/database.md +99 -99
- package/skills/tinystruct-patterns/references/routing.md +64 -64
- package/skills/tinystruct-patterns/references/system-usage.md +97 -97
- package/skills/tinystruct-patterns/references/testing.md +72 -72
- package/skills/token-budget-advisor/SKILL.md +134 -134
- package/skills/ui-demo/SKILL.md +466 -466
- package/skills/ui-to-vue/SKILL.md +135 -135
- package/skills/uncloud/SKILL.md +344 -344
- package/skills/unified-memory/SKILL.md +170 -170
- package/skills/unified-notifications-ops/SKILL.md +188 -188
- package/skills/verification-loop/SKILL.md +129 -129
- package/skills/video-editing/SKILL.md +311 -311
- package/skills/videodb/SKILL.md +375 -375
- package/skills/visa-doc-translate/SKILL.md +117 -117
- package/skills/vite-patterns/SKILL.md +450 -450
- package/skills/vue-patterns/SKILL.md +471 -471
- package/skills/windows-desktop-e2e/SKILL.md +888 -888
- package/skills/workspace-surface-audit/SKILL.md +126 -126
- package/skills/x-api/SKILL.md +235 -235
package/lib/tui.mjs
CHANGED
|
@@ -1,1086 +1,1459 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Codeply TUI -
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
import
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
];
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
];
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
return
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
{
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
null,
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
);
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
)
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
const [
|
|
473
|
-
const
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
React.
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
}
|
|
873
|
-
};
|
|
874
|
-
|
|
875
|
-
//
|
|
876
|
-
//
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
//
|
|
896
|
-
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
)
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
const
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
);
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
{
|
|
1023
|
-
|
|
1024
|
-
{
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
)
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Codeply TUI - interactive shell.
|
|
3
|
+
*
|
|
4
|
+
* Layout follows the shape every good coding CLI has settled on (Claude Code,
|
|
5
|
+
* Codex, Gemini CLI, OpenCode, Grok Build): a scrolling transcript, one quiet
|
|
6
|
+
* input box at the bottom, one footer line under it.
|
|
7
|
+
*
|
|
8
|
+
* ● Read src/config.js a step: bullet, tool, target
|
|
9
|
+
* ⎿ Read 84 lines its result, indented under it
|
|
10
|
+
* ● Update src/config.js
|
|
11
|
+
* ⎿ Updated src/config.js with 2 additions and 1 removal
|
|
12
|
+
* 12 - const port = Number(raw); inline diff, line numbers from the file
|
|
13
|
+
* 12 + const port = parseInt(raw, 10);
|
|
14
|
+
* ╭──────────────────────────────────╮
|
|
15
|
+
* │ › _ │
|
|
16
|
+
* ╰──────────────────────────────────╯
|
|
17
|
+
* Build mode (tab to switch) Haze 2
|
|
18
|
+
*
|
|
19
|
+
* Rendering model (important):
|
|
20
|
+
* Everything that is *finished* (banner + past messages + tool activity) is
|
|
21
|
+
* emitted through Ink's <Static>, which writes each item to stdout exactly
|
|
22
|
+
* once and never touches it again. Only the live area - spinner, approval
|
|
23
|
+
* prompt, command palette, composer, footer - lives in the re-rendered frame.
|
|
24
|
+
*
|
|
25
|
+
* Ink redraws a frame by erasing the previous one, but it can only erase
|
|
26
|
+
* lines still on screen. Once the frame grows taller than the terminal, the
|
|
27
|
+
* erase is incomplete and the frame gets appended instead of replaced - you
|
|
28
|
+
* see the conversation twice. Keeping the live frame short makes the redraw
|
|
29
|
+
* exact. Rule for future edits: nothing tall goes outside <Static>.
|
|
30
|
+
*
|
|
31
|
+
* Resizing changes where every line wraps, and <Static> cannot reflow what it
|
|
32
|
+
* already printed, so a width change clears the screen and reprints the
|
|
33
|
+
* transcript at the new width (the same thing Claude Code does).
|
|
34
|
+
*/
|
|
35
|
+
import React from 'react';
|
|
36
|
+
import { Box, Text, Static, useInput, useApp, useStdout } from 'ink';
|
|
37
|
+
import { createRequire } from 'module';
|
|
38
|
+
import path from 'path';
|
|
39
|
+
import os from 'os';
|
|
40
|
+
import fs from 'fs';
|
|
41
|
+
|
|
42
|
+
const require = createRequire(import.meta.url);
|
|
43
|
+
const auth = require('../lib/auth.js');
|
|
44
|
+
const appConfig = require('../lib/config.js');
|
|
45
|
+
const skills = require('../lib/skills.js');
|
|
46
|
+
const VERSION = require('../package.json').version;
|
|
47
|
+
import {
|
|
48
|
+
renderMarkdown, Panel, hexToAnsi, hexToAnsiBg, visibleLength, SURFACE,
|
|
49
|
+
ANSI_RESET, ANSI_BOLD, ANSI_ITALIC, ANSI_STRIKE,
|
|
50
|
+
} from '../lib/render-md.mjs';
|
|
51
|
+
import { runAgent } from '../lib/agent.mjs';
|
|
52
|
+
import { TOOL_DISPLAY } from '../lib/tools.mjs';
|
|
53
|
+
import TextArea, { enableModifiedEnter, isTerminalReport } from '../lib/textarea.mjs';
|
|
54
|
+
import { DshEngine, summarizeArgs } from '../lib/dsh-acp.mjs';
|
|
55
|
+
|
|
56
|
+
// ─── Glyphs ─────────────────────────────────────────────────────────────────
|
|
57
|
+
//
|
|
58
|
+
// Windows Terminal, VS Code and every macOS/Linux terminal have fonts with
|
|
59
|
+
// these symbols. The old console host (powershell.exe opened on its own) uses
|
|
60
|
+
// Consolas with no fallback, where most of them turn into boxes, so it gets a
|
|
61
|
+
// plain set. CODEPLY_ASCII=1 forces the plain set anywhere.
|
|
62
|
+
|
|
63
|
+
const FANCY = !process.env.CODEPLY_ASCII && (
|
|
64
|
+
process.platform !== 'win32' ||
|
|
65
|
+
!!process.env.WT_SESSION ||
|
|
66
|
+
!!process.env.TERM_PROGRAM ||
|
|
67
|
+
/xterm|kitty|alacritty|wezterm|ghostty/i.test(process.env.TERM || '')
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const GL = FANCY
|
|
71
|
+
? {
|
|
72
|
+
dot: '●', branch: '⎿', prompt: '›', pointer: '❯', star: '✻', warn: '⚠', cross: '✗',
|
|
73
|
+
open: '☐', done: '☒', bypass: '⏵⏵', rule: '─', border: 'round',
|
|
74
|
+
corners: ['╭', '╮', '╰', '╯'], spin: ['·', '✢', '✳', '✶', '✻', '✽', '✻', '✶', '✳', '✢'],
|
|
75
|
+
}
|
|
76
|
+
: {
|
|
77
|
+
dot: '•', branch: '└', prompt: '>', pointer: '>', star: '*', warn: '!', cross: 'x',
|
|
78
|
+
open: '[ ]', done: '[x]', bypass: '>>', rule: '─', border: 'single',
|
|
79
|
+
corners: ['┌', '┐', '└', '┘'], spin: ['-', '\\', '|', '/'],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// ─── Theme ──────────────────────────────────────────────────────────────────
|
|
83
|
+
//
|
|
84
|
+
// Codeply blue (the app icon's #1f6bff) for the mascot and the welcome box, a
|
|
85
|
+
// lighter step of it for text so it stays readable on a dark background, greys
|
|
86
|
+
// for everything structural, and red/green/yellow kept for what they mean
|
|
87
|
+
// (failed, succeeded, needs attention).
|
|
88
|
+
|
|
89
|
+
const T = {
|
|
90
|
+
brand: '#1f6bff',
|
|
91
|
+
accent: '#5b9bff',
|
|
92
|
+
text: '#e3e3e3',
|
|
93
|
+
secondary: '#a8adb3',
|
|
94
|
+
dim: '#80868b',
|
|
95
|
+
faint: '#5f6368',
|
|
96
|
+
border: '#4a4f55',
|
|
97
|
+
ok: '#5bb974',
|
|
98
|
+
err: '#f28b82',
|
|
99
|
+
warn: '#fdd663',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const paint = (hex, s, extra = '') => hexToAnsi(hex) + extra + s + ANSI_RESET;
|
|
103
|
+
const bold = (hex, s) => paint(hex, s, ANSI_BOLD);
|
|
104
|
+
const plural = (n, one, many = one + 's') => `${n} ${n === 1 ? one : many}`;
|
|
105
|
+
const stripAnsi = (s) => String(s || '').replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '');
|
|
106
|
+
|
|
107
|
+
function fmtSecs(s) {
|
|
108
|
+
if (s < 60) return `${s}s`;
|
|
109
|
+
return `${Math.floor(s / 60)}m ${String(s % 60).padStart(2, '0')}s`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const MODES = ['Build', 'Plan', 'Ask'];
|
|
113
|
+
const MODE_COLOR = { Build: T.accent, Plan: T.warn, Ask: '#81c995' };
|
|
114
|
+
const MODE_HINT = {
|
|
115
|
+
Build: 'reads, edits and verifies files',
|
|
116
|
+
Plan: 'investigates, then writes a plan',
|
|
117
|
+
Ask: 'answers from the codebase, read-only',
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// The Codeply agent: a solid blue ball with two eyes, in the spirit of Kimi
|
|
121
|
+
// CLI's blue square. Drawn as pixels, two per character cell with half blocks
|
|
122
|
+
// (▀: top pixel is the foreground colour, bottom pixel the background), which
|
|
123
|
+
// comes out square in a normal terminal font. The eyes are holes, so they take
|
|
124
|
+
// the terminal's own background colour. A slight top-to-bottom shade keeps it
|
|
125
|
+
// reading as a ball rather than a flat disc. Mirror-symmetric by construction.
|
|
126
|
+
const BALL_PIXELS = [
|
|
127
|
+
'...XXXX...',
|
|
128
|
+
'.XXXXXXXX.',
|
|
129
|
+
'.XXXXXXXX.',
|
|
130
|
+
'XXX.XX.XXX',
|
|
131
|
+
'XXX.XX.XXX',
|
|
132
|
+
'XXXXXXXXXX',
|
|
133
|
+
'XXXXXXXXXX',
|
|
134
|
+
'.XXXXXXXX.',
|
|
135
|
+
'.XXXXXXXX.',
|
|
136
|
+
'...XXXX...',
|
|
137
|
+
];
|
|
138
|
+
const BALL_TOP = '#6fa3ff';
|
|
139
|
+
const BALL_BOTTOM = '#1f56e0';
|
|
140
|
+
|
|
141
|
+
function shadeAt(y, h) {
|
|
142
|
+
const t = h > 1 ? y / (h - 1) : 0;
|
|
143
|
+
const a = [1, 3, 5].map((i) => parseInt(BALL_TOP.slice(i, i + 2), 16));
|
|
144
|
+
const b = [1, 3, 5].map((i) => parseInt(BALL_BOTTOM.slice(i, i + 2), 16));
|
|
145
|
+
return '#' + a.map((v, i) => Math.round(v + (b[i] - v) * t).toString(16).padStart(2, '0')).join('');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function drawBall(pixels) {
|
|
149
|
+
const h = pixels.length;
|
|
150
|
+
const rows = [];
|
|
151
|
+
for (let y = 0; y < h; y += 2) {
|
|
152
|
+
let line = '';
|
|
153
|
+
for (let x = 0; x < pixels[y].length; x++) {
|
|
154
|
+
const top = pixels[y][x] === 'X' ? shadeAt(y, h) : null;
|
|
155
|
+
const bottom = pixels[y + 1] && pixels[y + 1][x] === 'X' ? shadeAt(y + 1, h) : null;
|
|
156
|
+
if (!top && !bottom) line += ANSI_RESET + ' ';
|
|
157
|
+
else if (top && !bottom) line += ANSI_RESET + hexToAnsi(top) + '▀';
|
|
158
|
+
else if (!top) line += ANSI_RESET + hexToAnsi(bottom) + '▄';
|
|
159
|
+
else line += hexToAnsi(top) + hexToAnsiBg(bottom) + '▀';
|
|
160
|
+
}
|
|
161
|
+
rows.push(line + ANSI_RESET);
|
|
162
|
+
}
|
|
163
|
+
return { rows, width: pixels[0].length };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const MASCOT = drawBall(BALL_PIXELS);
|
|
167
|
+
|
|
168
|
+
const COMMANDS = [
|
|
169
|
+
{ name: '/help', description: 'Show commands and keyboard shortcuts' },
|
|
170
|
+
{ name: '/clear', description: 'Clear the conversation and start fresh' },
|
|
171
|
+
{ name: '/mode', description: 'Cycle Build → Plan → Ask' },
|
|
172
|
+
{ name: '/provider', description: 'Show or switch the model provider (Haze 2 or your own key)' },
|
|
173
|
+
{ name: '/skill', description: 'List, install, or remove agent skills' },
|
|
174
|
+
{ name: '/login', description: 'Sign in with your Codeply account' },
|
|
175
|
+
{ name: '/logout', description: 'Sign out of this computer' },
|
|
176
|
+
{ name: '/whoami', description: 'Show the signed-in account' },
|
|
177
|
+
{ name: '/engine', description: 'Show or switch the engine: classic or dsh' },
|
|
178
|
+
{ name: '/keys', description: 'Show what your terminal sends for a key' },
|
|
179
|
+
{ name: '/quit', description: 'Exit Codeply' },
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
const WHATS_NEW = [
|
|
183
|
+
'New terminal UI with inline diffs and step results',
|
|
184
|
+
'↑ ↓ brings back earlier prompts',
|
|
185
|
+
'Type while it works to queue a follow-up',
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
// ─── Tool presentation ──────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
const TOOL_TITLE = {
|
|
191
|
+
read_file: ['Read', 'Reading'],
|
|
192
|
+
write_file: ['Write', 'Writing'],
|
|
193
|
+
edit_file: ['Update', 'Editing'],
|
|
194
|
+
apply_patch: ['Patch', 'Patching'],
|
|
195
|
+
search: ['Search', 'Searching'],
|
|
196
|
+
list_dir: ['List', 'Listing'],
|
|
197
|
+
run: ['Run', 'Running'],
|
|
198
|
+
job: ['Job', 'Checking job'],
|
|
199
|
+
web_fetch: ['Fetch', 'Fetching'],
|
|
200
|
+
web_search: ['Web search', 'Searching the web'],
|
|
201
|
+
image_search: ['Image search', 'Searching images'],
|
|
202
|
+
todo: ['Update todos', 'Updating todos'],
|
|
203
|
+
use_skill: ['Skill', 'Loading skill'],
|
|
204
|
+
list_skills: ['Skills', 'Listing skills'],
|
|
205
|
+
browser_check: ['Browser check', 'Checking in the browser'],
|
|
206
|
+
remember: ['Remember', 'Remembering'],
|
|
207
|
+
recall: ['Recall', 'Recalling'],
|
|
208
|
+
lsp: ['Code check', 'Checking code'],
|
|
209
|
+
// The DSH engine's own tool names.
|
|
210
|
+
read: ['Read', 'Reading'],
|
|
211
|
+
edit: ['Update', 'Editing'],
|
|
212
|
+
write: ['Write', 'Writing'],
|
|
213
|
+
glob: ['Search', 'Searching'],
|
|
214
|
+
grep: ['Search', 'Searching'],
|
|
215
|
+
pwsh: ['Run', 'Running'],
|
|
216
|
+
bash: ['Run', 'Running'],
|
|
217
|
+
shell: ['Run', 'Running'],
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
function toolTitle(name) {
|
|
221
|
+
if (TOOL_TITLE[name]) return TOOL_TITLE[name];
|
|
222
|
+
const verb = (TOOL_DISPLAY[name] && TOOL_DISPLAY[name].verb) || String(name || 'tool').replace(/_/g, ' ');
|
|
223
|
+
const cap = verb[0].toUpperCase() + verb.slice(1);
|
|
224
|
+
return [cap, cap];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const argTarget = (args = {}) =>
|
|
228
|
+
args.path || args.file_path || args.command || args.pattern || args.glob || args.url || args.query || args.name || '';
|
|
229
|
+
|
|
230
|
+
/** Line-level diff (LCS), so unchanged lines show as context, not -/+ pairs. */
|
|
231
|
+
function lineDiff(before, after) {
|
|
232
|
+
const A = String(before ?? '').split('\n');
|
|
233
|
+
const B = String(after ?? '').split('\n');
|
|
234
|
+
const n = A.length;
|
|
235
|
+
const m = B.length;
|
|
236
|
+
if (n * m > 90000) return [...A.map((s) => ({ t: '-', s })), ...B.map((s) => ({ t: '+', s }))];
|
|
237
|
+
const dp = Array.from({ length: n + 1 }, () => new Uint16Array(m + 1));
|
|
238
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
239
|
+
for (let j = m - 1; j >= 0; j--) dp[i][j] = A[i] === B[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
240
|
+
}
|
|
241
|
+
const out = [];
|
|
242
|
+
let i = 0;
|
|
243
|
+
let j = 0;
|
|
244
|
+
while (i < n && j < m) {
|
|
245
|
+
if (A[i] === B[j]) { out.push({ t: ' ', s: A[i] }); i++; j++; }
|
|
246
|
+
else if (dp[i + 1][j] >= dp[i][j + 1]) out.push({ t: '-', s: A[i++] });
|
|
247
|
+
else out.push({ t: '+', s: B[j++] });
|
|
248
|
+
}
|
|
249
|
+
while (i < n) out.push({ t: '-', s: A[i++] });
|
|
250
|
+
while (j < m) out.push({ t: '+', s: B[j++] });
|
|
251
|
+
return out;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** 1-based line where `needle` starts in the file, or null. */
|
|
255
|
+
function lineOf(cwd, rel, needle) {
|
|
256
|
+
if (!rel || !needle) return null;
|
|
257
|
+
try {
|
|
258
|
+
const content = fs.readFileSync(path.resolve(cwd, rel), 'utf8');
|
|
259
|
+
const idx = content.indexOf(needle);
|
|
260
|
+
return idx < 0 ? null : content.slice(0, idx).split('\n').length;
|
|
261
|
+
} catch { return null; }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Diff rows as finished ANSI strings, filled to `width` like Claude Code's. */
|
|
265
|
+
function diffRows(diff, start, width, max = 24) {
|
|
266
|
+
const w = Math.max(20, width);
|
|
267
|
+
const numW = String((start || 1) + diff.length).length;
|
|
268
|
+
let oldN = start || 1;
|
|
269
|
+
let newN = start || 1;
|
|
270
|
+
const rows = [];
|
|
271
|
+
for (const d of diff.slice(0, max)) {
|
|
272
|
+
const num = start ? String(d.t === '+' ? newN : oldN).padStart(numW) : ' '.repeat(numW);
|
|
273
|
+
if (d.t !== '+') oldN++;
|
|
274
|
+
if (d.t !== '-') newN++;
|
|
275
|
+
let body = `${num} ${d.t} ${d.s.replace(/\t/g, ' ')}`;
|
|
276
|
+
if (body.length > w) body = body.slice(0, w - 1) + '…';
|
|
277
|
+
if (d.t === ' ') { rows.push(paint(T.dim, body)); continue; }
|
|
278
|
+
const bg = d.t === '+' ? SURFACE.diffAdd : SURFACE.diffDel;
|
|
279
|
+
const fg = d.t === '+' ? '#c8f2d2' : '#ffd4d4';
|
|
280
|
+
rows.push(hexToAnsiBg(bg) + hexToAnsi(fg) + body + ' '.repeat(Math.max(0, w - body.length)) + ANSI_RESET);
|
|
281
|
+
}
|
|
282
|
+
if (diff.length > max) rows.push(paint(T.faint, `… +${diff.length - max} more lines`));
|
|
283
|
+
return rows;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** A finished tool call → a transcript entry with its result lines worked out. */
|
|
287
|
+
function toolMessage(e, cwd) {
|
|
288
|
+
const meta = e.meta || {};
|
|
289
|
+
const args = e.args || {};
|
|
290
|
+
const [title] = toolTitle(e.name);
|
|
291
|
+
const rejected = !!meta.rejected;
|
|
292
|
+
const exitBad = meta.exitCode != null && meta.exitCode !== 0;
|
|
293
|
+
const failed = !e.ok && !rejected;
|
|
294
|
+
const msg = {
|
|
295
|
+
role: 'tool',
|
|
296
|
+
name: e.name,
|
|
297
|
+
title,
|
|
298
|
+
label: e.name === 'todo' ? '' : (meta.label || argTarget(args) || (e.ok ? e.summary : '') || ''),
|
|
299
|
+
status: rejected ? 'declined' : failed || exitBad ? 'error' : 'ok',
|
|
300
|
+
result: [],
|
|
301
|
+
};
|
|
302
|
+
const r = (s, color = T.dim) => msg.result.push(paint(color, s));
|
|
303
|
+
|
|
304
|
+
if (rejected) { r('Declined', T.warn); return msg; }
|
|
305
|
+
// A command that ran and failed still has output worth seeing: let it fall
|
|
306
|
+
// through to the run/job rendering, which leads with the exit code.
|
|
307
|
+
const ranAndFailed = (e.name === 'run' || e.name === 'job') && (meta.output != null || meta.exitCode != null);
|
|
308
|
+
if (failed && !ranAndFailed) {
|
|
309
|
+
msg.wrapResult = true;
|
|
310
|
+
const why = String(e.error || e.summary || 'Failed').trim().split('\n').filter(Boolean).slice(0, 4);
|
|
311
|
+
for (const line of why) r(stripAnsi(line), T.err);
|
|
312
|
+
return msg;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
switch (e.name) {
|
|
316
|
+
case 'read_file':
|
|
317
|
+
if (meta.linesShown && meta.count && meta.linesShown < meta.count) {
|
|
318
|
+
const from = (meta.offset || 0) + 1;
|
|
319
|
+
r(`Read lines ${from}-${from + meta.linesShown - 1} of ${meta.count}`);
|
|
320
|
+
} else if (meta.count != null) r(`Read ${plural(meta.count, 'line')}`);
|
|
321
|
+
break;
|
|
322
|
+
case 'search':
|
|
323
|
+
r(meta.count ? `Found ${plural(meta.count, 'match', 'matches')}` : 'No matches');
|
|
324
|
+
break;
|
|
325
|
+
case 'glob':
|
|
326
|
+
r(meta.count ? `Found ${plural(meta.count, 'file')}` : 'No files found');
|
|
327
|
+
break;
|
|
328
|
+
case 'grep':
|
|
329
|
+
r(meta.count ? `Found ${plural(meta.count, 'match', 'matches')}` : 'No matches');
|
|
330
|
+
break;
|
|
331
|
+
case 'list_dir':
|
|
332
|
+
if (meta.count != null) r(plural(meta.count, 'entry', 'entries'));
|
|
333
|
+
break;
|
|
334
|
+
case 'edit_file': {
|
|
335
|
+
if (meta.noop) { r('No changes needed'); break; }
|
|
336
|
+
const search = args.search ?? args.old_string ?? args.old;
|
|
337
|
+
const replace = args.replace ?? args.new_string ?? args.new;
|
|
338
|
+
if (search != null && replace != null) {
|
|
339
|
+
msg.diff = lineDiff(search, replace);
|
|
340
|
+
msg.start = lineOf(cwd, args.path, replace);
|
|
341
|
+
const adds = msg.diff.filter((d) => d.t === '+').length;
|
|
342
|
+
const dels = msg.diff.filter((d) => d.t === '-').length;
|
|
343
|
+
r(`Updated ${msg.label} with ${plural(adds, 'addition')} and ${plural(dels, 'removal')}`);
|
|
344
|
+
} else {
|
|
345
|
+
r(`Updated ${msg.label} (-${meta.removed || 0} +${meta.added || 0} lines)`);
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
case 'write_file':
|
|
350
|
+
if (meta.noop) { r('Already up to date, nothing written'); break; }
|
|
351
|
+
if (!meta.removed) {
|
|
352
|
+
r(`Wrote ${plural(meta.added || 0, 'line')} to ${msg.label}`);
|
|
353
|
+
if (typeof args.content === 'string') {
|
|
354
|
+
msg.diff = args.content.replace(/\n$/, '').split('\n').map((s) => ({ t: '+', s }));
|
|
355
|
+
msg.start = 1;
|
|
356
|
+
msg.maxDiff = 10;
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
r(`Rewrote ${msg.label} (${meta.removed} → ${plural(meta.added || 0, 'line')})`);
|
|
360
|
+
}
|
|
361
|
+
break;
|
|
362
|
+
case 'run':
|
|
363
|
+
case 'job': {
|
|
364
|
+
if (meta.background) { r(`Running in the background as job ${meta.jobId}`); break; }
|
|
365
|
+
if (exitBad) r(`Exit code ${meta.exitCode}`, T.err);
|
|
366
|
+
const out = stripAnsi(meta.output || '').replace(/\s+$/, '');
|
|
367
|
+
const lines = out ? out.split('\n') : [];
|
|
368
|
+
if (!lines.length && !exitBad) r('(no output)', T.faint);
|
|
369
|
+
const SHOW = 5;
|
|
370
|
+
if (lines.length > SHOW) r(`… ${plural(lines.length - SHOW, 'earlier line')}`, T.faint);
|
|
371
|
+
for (const line of lines.slice(-SHOW)) r(line, T.secondary);
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
case 'todo':
|
|
375
|
+
for (const t of meta.todos || []) {
|
|
376
|
+
if (t.status === 'done') msg.result.push(paint(T.faint, `${GL.done} `) + paint(T.faint, t.text, ANSI_STRIKE));
|
|
377
|
+
else if (t.status === 'dropped') msg.result.push(paint(T.faint, `${GL.open} ${t.text}`, ANSI_STRIKE));
|
|
378
|
+
else if (t.status === 'in_progress') msg.result.push(paint(T.accent, `${GL.open} `) + bold(T.text, t.text));
|
|
379
|
+
else msg.result.push(paint(T.secondary, `${GL.open} ${t.text}`));
|
|
380
|
+
}
|
|
381
|
+
break;
|
|
382
|
+
default:
|
|
383
|
+
if (meta.deduped) r('Unchanged, reused the earlier result');
|
|
384
|
+
else if (meta.count != null) r(plural(meta.count, 'result'));
|
|
385
|
+
else if (e.summary && e.summary !== msg.label) r(stripAnsi(e.summary));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (Array.isArray(meta.problems) && meta.problems.length) {
|
|
389
|
+
r(`${GL.warn} ${plural(meta.problems.length, 'problem')}: ${stripAnsi(meta.problems[0])}`, T.warn);
|
|
390
|
+
}
|
|
391
|
+
return msg;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* A DSH tool result (name, args, raw output text) in the shape the Classic
|
|
396
|
+
* engine reports, so both engines get the same step rows and diffs.
|
|
397
|
+
*/
|
|
398
|
+
function fromDsh(e) {
|
|
399
|
+
const a = e.args || {};
|
|
400
|
+
const out = stripAnsi(e.output || '');
|
|
401
|
+
const file = a.file_path || a.path || a.filePath;
|
|
402
|
+
const lines = out.split('\n').filter((l) => l.trim());
|
|
403
|
+
switch (e.name) {
|
|
404
|
+
case 'read':
|
|
405
|
+
return { ...e, name: 'read_file', args: { path: file }, meta: { label: file, count: (out.match(/^\d+: /gm) || []).length || undefined } };
|
|
406
|
+
case 'edit':
|
|
407
|
+
return { ...e, name: 'edit_file', args: { path: file, search: a.old_string, replace: a.new_string }, meta: { label: file } };
|
|
408
|
+
case 'write':
|
|
409
|
+
return { ...e, name: 'write_file', args: { path: file, content: a.content }, meta: { label: file, added: String(a.content || '').split('\n').length, removed: 0 } };
|
|
410
|
+
case 'glob':
|
|
411
|
+
case 'grep':
|
|
412
|
+
return { ...e, meta: { label: a.pattern || a.query || '', count: e.ok ? lines.length : 0 } };
|
|
413
|
+
case 'pwsh':
|
|
414
|
+
case 'bash':
|
|
415
|
+
case 'shell': {
|
|
416
|
+
const code = /exit(?:\s+code)?:?\s*(-?\d+)/i.exec(out);
|
|
417
|
+
return {
|
|
418
|
+
...e, name: 'run', args: { command: a.command },
|
|
419
|
+
meta: { label: a.command, output: out.replace(/\n?\[stderr\]\n?/g, '\n'), exitCode: code ? Number(code[1]) : e.ok ? 0 : 1 },
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
default:
|
|
423
|
+
return { ...e, summary: e.ok ? lines[0] : undefined, meta: { label: summarizeArgs(a) } };
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function homePath(cwd) {
|
|
428
|
+
const home = os.homedir();
|
|
429
|
+
if (cwd.startsWith(home)) return '~' + cwd.slice(home.length);
|
|
430
|
+
return cwd;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** Shorten a path from the left so it never blows out a line. */
|
|
434
|
+
function fitPath(p, max) {
|
|
435
|
+
if (p.length <= max) return p;
|
|
436
|
+
return '…' + p.slice(p.length - max + 1);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const fit = (s, max) => (s.length > max ? s.slice(0, Math.max(0, max - 1)) + '…' : s);
|
|
440
|
+
|
|
441
|
+
// ─── Banner (printed once into the scrollback) ──────────────────────────────
|
|
442
|
+
//
|
|
443
|
+
// Laid out like Kimi CLI's: one blue box sized to its content, the mascot on
|
|
444
|
+
// the left with the welcome text beside it, then where you are and what is
|
|
445
|
+
// answering. Everything is pre-flattened to one ANSI string per row.
|
|
446
|
+
|
|
447
|
+
// Picked once per launch, so a resize reprints the same line.
|
|
448
|
+
const GREETINGS = [
|
|
449
|
+
(n) => `Hey ${n}! What are we building?`,
|
|
450
|
+
(n) => `Welcome back, ${n}!`,
|
|
451
|
+
(n) => `Good to see you, ${n}!`,
|
|
452
|
+
(n) => `Hi ${n}, ready when you are`,
|
|
453
|
+
];
|
|
454
|
+
const GREETING_PICK = Math.floor(Math.random() * GREETINGS.length);
|
|
455
|
+
const GREETING = (name) => (name ? GREETINGS[GREETING_PICK](name) : 'Welcome to Codeply CLI!');
|
|
456
|
+
|
|
457
|
+
/** Styled pieces → { ansi, len } so rows can be padded by visible width. */
|
|
458
|
+
function piece(parts, max) {
|
|
459
|
+
let ansi = '';
|
|
460
|
+
let len = 0;
|
|
461
|
+
for (const [text, color, isBold] of parts) {
|
|
462
|
+
if (len >= max) break;
|
|
463
|
+
const t = fit(String(text), max - len);
|
|
464
|
+
ansi += paint(color, t, isBold ? ANSI_BOLD : '');
|
|
465
|
+
len += t.length;
|
|
466
|
+
}
|
|
467
|
+
return { ansi, len };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function Banner({ cols, account, needsLogin, model, cwd, name, mode }) {
|
|
471
|
+
const edge = (s) => paint(T.brand, s);
|
|
472
|
+
const [tl, tr, bl, br] = GL.corners;
|
|
473
|
+
const PAD = 3; // spaces between the border and the content, each side
|
|
474
|
+
const GAP = 3; // between the mascot and the text beside it
|
|
475
|
+
|
|
476
|
+
const side = [
|
|
477
|
+
null,
|
|
478
|
+
[[GREETING(name), T.text, true]],
|
|
479
|
+
[['Send ', T.secondary], ['/help', T.text], [' for help, ', T.secondary], ['?', T.text], [' for shortcuts', T.secondary]],
|
|
480
|
+
[[`${mode}`, T.accent], [mode === 'DSH engine' ? ' · /engine to switch' : ' · tab to switch', T.dim]],
|
|
481
|
+
null,
|
|
482
|
+
];
|
|
483
|
+
const info = [
|
|
484
|
+
['Directory:', [[homePath(cwd), T.secondary]], true],
|
|
485
|
+
['Account:', needsLogin ? [['not signed in, send /login to sign in', T.warn]] : [[account, T.secondary]]],
|
|
486
|
+
['Model:', [[model, T.secondary]]],
|
|
487
|
+
];
|
|
488
|
+
const labelW = Math.max(...info.map(([l]) => l.length)) + 1;
|
|
489
|
+
|
|
490
|
+
const plainLen = (parts) => parts.reduce((n, [t]) => n + String(t).length, 0);
|
|
491
|
+
const wanted = Math.max(
|
|
492
|
+
MASCOT.width + GAP + Math.max(...side.filter(Boolean).map(plainLen)),
|
|
493
|
+
// A long path is shortened from the left rather than stretching the box.
|
|
494
|
+
...info.map(([, parts, isPath]) => labelW + (isPath ? Math.min(plainLen(parts), 48) : plainLen(parts)))
|
|
495
|
+
);
|
|
496
|
+
const inner = Math.max(20, Math.min(cols - 2 - 2 - PAD * 2, wanted));
|
|
497
|
+
const width = inner + PAD * 2 + 2;
|
|
498
|
+
|
|
499
|
+
const line = (p) => edge('│') + ' '.repeat(PAD) + p.ansi + ' '.repeat(Math.max(0, inner - p.len)) + ' '.repeat(PAD) + edge('│');
|
|
500
|
+
const blank = line({ ansi: '', len: 0 });
|
|
501
|
+
|
|
502
|
+
const rows = [edge(tl + GL.rule.repeat(width - 2) + tr), blank];
|
|
503
|
+
const textRoom = inner - MASCOT.width - GAP;
|
|
504
|
+
MASCOT.rows.forEach((ball, i) => {
|
|
505
|
+
const text = side[i] && textRoom > 4 ? piece(side[i], textRoom) : { ansi: '', len: 0 };
|
|
506
|
+
rows.push(line({ ansi: ball + ' '.repeat(GAP) + text.ansi, len: MASCOT.width + GAP + text.len }));
|
|
507
|
+
});
|
|
508
|
+
rows.push(blank);
|
|
509
|
+
for (const [label, parts, isPath] of info) {
|
|
510
|
+
const room = inner - labelW;
|
|
511
|
+
const value = isPath ? [[fitPath(parts[0][0], room), parts[0][1]]] : parts;
|
|
512
|
+
const p = piece(value, room);
|
|
513
|
+
rows.push(line({ ansi: paint(T.dim, label.padEnd(labelW)) + p.ansi, len: labelW + p.len }));
|
|
514
|
+
}
|
|
515
|
+
rows.push(blank, edge(bl + GL.rule.repeat(width - 2) + br));
|
|
516
|
+
|
|
517
|
+
return React.createElement(
|
|
518
|
+
Box,
|
|
519
|
+
{ flexDirection: 'column', marginBottom: 1 },
|
|
520
|
+
...rows.map((row, i) => React.createElement(Text, { key: i }, row))
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// ─── Transcript entries ─────────────────────────────────────────────────────
|
|
525
|
+
|
|
526
|
+
/** Bullet in a 2-column gutter, content beside it, one blank line after. */
|
|
527
|
+
function Bulleted({ bullet, children, gap = 1 }) {
|
|
528
|
+
return React.createElement(
|
|
529
|
+
Box,
|
|
530
|
+
{ flexDirection: 'row', marginBottom: gap },
|
|
531
|
+
React.createElement(Box, { width: 2, flexShrink: 0 }, React.createElement(Text, null, bullet)),
|
|
532
|
+
React.createElement(Box, { flexDirection: 'column', flexGrow: 1, flexShrink: 1 }, ...[].concat(children).filter(Boolean))
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** The ⎿ result block under a step. */
|
|
537
|
+
function Result({ lines, wrap = 'truncate-end', extra = [] }) {
|
|
538
|
+
if (!lines.length && !extra.length) return null;
|
|
539
|
+
return React.createElement(
|
|
540
|
+
Box,
|
|
541
|
+
{ flexDirection: 'row' },
|
|
542
|
+
React.createElement(Box, { width: 3, flexShrink: 0 }, React.createElement(Text, null, paint(T.faint, GL.branch))),
|
|
543
|
+
React.createElement(
|
|
544
|
+
Box,
|
|
545
|
+
{ flexDirection: 'column', flexGrow: 1, flexShrink: 1 },
|
|
546
|
+
...lines.map((l, i) => React.createElement(Text, { key: `l${i}`, wrap }, l)),
|
|
547
|
+
...extra.map((l, i) => React.createElement(Text, { key: `x${i}` }, l))
|
|
548
|
+
)
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function MessageBlock({ msg, cols, rows, banner }) {
|
|
553
|
+
const width = cols - 2;
|
|
554
|
+
|
|
555
|
+
if (msg.role === 'banner') return React.createElement(Banner, { cols, rows, ...banner });
|
|
556
|
+
|
|
557
|
+
if (msg.role === 'user') {
|
|
558
|
+
return React.createElement(
|
|
559
|
+
Bulleted,
|
|
560
|
+
{ bullet: paint(T.dim, GL.prompt) },
|
|
561
|
+
React.createElement(Text, { wrap: 'wrap' }, paint(T.secondary, msg.content))
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (msg.role === 'assistant') {
|
|
566
|
+
if (msg.plan) {
|
|
567
|
+
return React.createElement(
|
|
568
|
+
Bulleted,
|
|
569
|
+
{ bullet: paint(T.warn, GL.dot) },
|
|
570
|
+
React.createElement(Panel, { title: 'Plan', lines: msg.content.split('\n'), width: width - 2, accent: T.warn })
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
return React.createElement(Bulleted, { bullet: paint(T.text, GL.dot) }, ...renderMarkdown(msg.content, width - 2));
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (msg.role === 'tool') {
|
|
577
|
+
const color = msg.status === 'error' ? T.err : msg.status === 'declined' ? T.warn : T.ok;
|
|
578
|
+
const extra = msg.diff ? diffRows(msg.diff, msg.start, width - 5, msg.maxDiff || 24) : [];
|
|
579
|
+
return React.createElement(
|
|
580
|
+
Bulleted,
|
|
581
|
+
{ bullet: paint(color, GL.dot) },
|
|
582
|
+
React.createElement(
|
|
583
|
+
Text,
|
|
584
|
+
{ wrap: 'truncate-end' },
|
|
585
|
+
bold(T.text, msg.title) + (msg.label ? ' ' + paint(T.secondary, msg.label.replace(/\s*\n\s*/g, ' ')) : '')
|
|
586
|
+
),
|
|
587
|
+
React.createElement(Result, { lines: msg.result, wrap: msg.wrapResult ? 'wrap' : 'truncate-end', extra })
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (msg.role === 'reasoning') {
|
|
592
|
+
const preview = String(msg.text || '').replace(/\s+/g, ' ').trim();
|
|
593
|
+
return React.createElement(
|
|
594
|
+
Bulleted,
|
|
595
|
+
{ bullet: paint(T.faint, GL.star) },
|
|
596
|
+
React.createElement(Text, null, paint(T.dim, `Thought for ${fmtSecs(Math.max(1, Math.round((msg.ms || 0) / 1000)))}`, ANSI_ITALIC)),
|
|
597
|
+
preview && React.createElement(Text, { wrap: 'truncate-end' }, paint(T.faint, fit(preview, width * 2), ANSI_ITALIC))
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (msg.role === 'notice') {
|
|
602
|
+
const loud = msg.level === 'warn' || msg.level === 'limit';
|
|
603
|
+
return React.createElement(
|
|
604
|
+
Bulleted,
|
|
605
|
+
{ bullet: paint(loud ? T.warn : T.dim, loud ? GL.warn : GL.dot) },
|
|
606
|
+
React.createElement(Text, { wrap: 'wrap' }, paint(loud ? T.warn : T.dim, msg.content))
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (msg.role === 'system') {
|
|
611
|
+
return React.createElement(
|
|
612
|
+
Box,
|
|
613
|
+
{ flexDirection: 'row', marginBottom: 1, paddingLeft: 2 },
|
|
614
|
+
React.createElement(Result, { lines: [paint(T.secondary, msg.content)], wrap: 'wrap' })
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (msg.role === 'error') {
|
|
619
|
+
return React.createElement(
|
|
620
|
+
Bulleted,
|
|
621
|
+
{ bullet: paint(T.err, GL.dot) },
|
|
622
|
+
React.createElement(Text, { wrap: 'wrap' }, paint(T.err, msg.content))
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (msg.role === 'done') {
|
|
627
|
+
const parts = [`Worked for ${fmtSecs(msg.secs)}`];
|
|
628
|
+
if (msg.steps) parts.push(plural(msg.steps, 'step'));
|
|
629
|
+
if (msg.files && msg.files.length) {
|
|
630
|
+
parts.push(msg.files.length <= 2 ? `changed ${msg.files.join(', ')}` : `changed ${plural(msg.files.length, 'file')}`);
|
|
631
|
+
}
|
|
632
|
+
const label = ` ${parts.join(' · ')} `;
|
|
633
|
+
const right = Math.max(0, width - label.length - 2);
|
|
634
|
+
return React.createElement(
|
|
635
|
+
Box,
|
|
636
|
+
{ marginBottom: 1 },
|
|
637
|
+
React.createElement(Text, { wrap: 'truncate-end' }, paint(T.faint, GL.rule.repeat(2) + label + GL.rule.repeat(right)))
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// ─── Live area ──────────────────────────────────────────────────────────────
|
|
645
|
+
|
|
646
|
+
// While the model is thinking (no tool running), the spinner word rotates
|
|
647
|
+
// every few seconds instead of sitting on one word for a minute.
|
|
648
|
+
const THINKING_WORDS = ['Thinking', 'Pondering', 'Tinkering', 'Brewing', 'Noodling', 'Cooking', 'Scheming', 'Untangling', 'Doodling'];
|
|
649
|
+
|
|
650
|
+
/** Spinner line with a soft shimmer running across the label. */
|
|
651
|
+
function Working({ label, startedAt, steps, queued, width }) {
|
|
652
|
+
const wordOffset = React.useMemo(() => Math.floor(Math.random() * THINKING_WORDS.length), []);
|
|
653
|
+
const [tick, setTick] = React.useState(0);
|
|
654
|
+
React.useEffect(() => {
|
|
655
|
+
const t = setInterval(() => setTick((n) => n + 1), 120);
|
|
656
|
+
return () => clearInterval(t);
|
|
657
|
+
}, []);
|
|
658
|
+
const secs = Math.floor((Date.now() - startedAt) / 1000);
|
|
659
|
+
const frame = GL.spin[tick % GL.spin.length];
|
|
660
|
+
const word = label === 'Thinking' ? THINKING_WORDS[(wordOffset + Math.floor(secs / 4)) % THINKING_WORDS.length] : label;
|
|
661
|
+
const text = `${word}…`;
|
|
662
|
+
const head = tick % (text.length + 12);
|
|
663
|
+
let shimmer = '';
|
|
664
|
+
for (let i = 0; i < text.length; i++) {
|
|
665
|
+
const d = Math.abs(i - head);
|
|
666
|
+
shimmer += hexToAnsi(d === 0 ? '#ffffff' : d <= 2 ? T.text : T.secondary) + text[i];
|
|
667
|
+
}
|
|
668
|
+
const meta = [fmtSecs(secs), steps > 0 ? `step ${steps}` : null, 'esc to interrupt'].filter(Boolean).join(' · ');
|
|
669
|
+
return React.createElement(
|
|
670
|
+
Box,
|
|
671
|
+
{ flexDirection: 'column', marginBottom: 1, width },
|
|
672
|
+
React.createElement(Text, { wrap: 'truncate-end' }, paint(T.accent, frame + ' ') + shimmer + ANSI_RESET + paint(T.faint, ` (${meta})`)),
|
|
673
|
+
queued && React.createElement(
|
|
674
|
+
Text,
|
|
675
|
+
{ wrap: 'truncate-end' },
|
|
676
|
+
paint(T.faint, ` ${GL.branch} queued: `) + paint(T.secondary, queued.replace(/\s*\n\s*/g, ' '))
|
|
677
|
+
)
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function approvalOptions(req) {
|
|
682
|
+
const edits = ['edit_file', 'write_file', 'apply_patch'].includes(req.tool);
|
|
683
|
+
const what = req.tool === 'run' ? 'commands' : edits ? 'file edits' : toolTitle(req.tool)[0].toLowerCase();
|
|
684
|
+
return [
|
|
685
|
+
{ verdict: 'once', label: 'Yes' },
|
|
686
|
+
{ verdict: 'always', label: `Yes, and don't ask again for ${what} this session` },
|
|
687
|
+
{ verdict: 'reject', label: 'No (esc)' },
|
|
688
|
+
];
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function approvalQuestion(req) {
|
|
692
|
+
const base = req.path ? path.basename(req.path) : '';
|
|
693
|
+
if (req.tool === 'edit_file') return `Do you want to make this edit to ${base}?`;
|
|
694
|
+
if (req.tool === 'write_file') return /^Create/.test(req.title || '') ? `Do you want to create ${base}?` : `Do you want to overwrite ${base}?`;
|
|
695
|
+
if (req.tool === 'run') return 'Do you want to run this command?';
|
|
696
|
+
return 'Do you want to allow this?';
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function ApprovalPrompt({ request, selected, cols, rows, cwd }) {
|
|
700
|
+
const accent = request.danger ? T.err : T.accent;
|
|
701
|
+
const width = cols - 2;
|
|
702
|
+
const body = [];
|
|
703
|
+
|
|
704
|
+
if (request.tool === 'run') {
|
|
705
|
+
body.push(React.createElement(Text, { key: 'cmd', wrap: 'wrap' }, paint(T.dim, '$ ') + paint(T.text, request.detail || '')));
|
|
706
|
+
} else if (request.detail && !request.diff) {
|
|
707
|
+
body.push(React.createElement(Text, { key: 'det', wrap: 'wrap' }, paint(T.secondary, request.detail)));
|
|
708
|
+
}
|
|
709
|
+
if (request.diff) {
|
|
710
|
+
const diff = lineDiff(request.diff.search, request.diff.replace);
|
|
711
|
+
const start = lineOf(cwd, request.path, request.diff.search);
|
|
712
|
+
const max = Math.max(3, rows - 16);
|
|
713
|
+
diffRows(diff, start, width - 4, max).forEach((l, i) => body.push(React.createElement(Text, { key: `d${i}` }, l)));
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const options = approvalOptions(request);
|
|
717
|
+
return React.createElement(
|
|
718
|
+
Box,
|
|
719
|
+
{ flexDirection: 'column', borderStyle: GL.border, borderColor: accent, paddingX: 1, marginBottom: 1, width },
|
|
720
|
+
React.createElement(
|
|
721
|
+
Text,
|
|
722
|
+
{ wrap: 'truncate-end' },
|
|
723
|
+
bold(accent, request.title || 'Permission needed') +
|
|
724
|
+
(request.path && !String(request.title || '').includes(request.path) ? paint(T.dim, ' ' + request.path) : '') +
|
|
725
|
+
(request.danger ? paint(T.err, ` ${GL.warn} looks destructive`) : '')
|
|
726
|
+
),
|
|
727
|
+
...body,
|
|
728
|
+
React.createElement(Text, { key: 'q' }, ' '),
|
|
729
|
+
React.createElement(Text, { key: 'qq', wrap: 'wrap' }, paint(T.text, approvalQuestion(request))),
|
|
730
|
+
...options.map((o, i) => React.createElement(
|
|
731
|
+
Text,
|
|
732
|
+
{ key: `o${i}`, wrap: 'truncate-end' },
|
|
733
|
+
i === selected
|
|
734
|
+
? paint(accent, `${GL.pointer} ${i + 1}. ${o.label}`)
|
|
735
|
+
: paint(T.secondary, ` ${i + 1}. ${o.label}`)
|
|
736
|
+
))
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const PALETTE_ROWS = 8;
|
|
741
|
+
|
|
742
|
+
function CommandsMenu({ commands, selectedIndex, width }) {
|
|
743
|
+
const start = Math.max(0, Math.min(selectedIndex - PALETTE_ROWS + 1, commands.length - PALETTE_ROWS));
|
|
744
|
+
const shown = commands.slice(start, start + PALETTE_ROWS);
|
|
745
|
+
const nameW = Math.max(...COMMANDS.map((c) => c.name.length)) + 3;
|
|
746
|
+
return React.createElement(
|
|
747
|
+
Box,
|
|
748
|
+
{ flexDirection: 'column', paddingLeft: 2, width },
|
|
749
|
+
...shown.map((cmd, k) => {
|
|
750
|
+
const selected = start + k === selectedIndex;
|
|
751
|
+
return React.createElement(
|
|
752
|
+
Text,
|
|
753
|
+
{ key: cmd.name, wrap: 'truncate-end' },
|
|
754
|
+
selected
|
|
755
|
+
? bold(T.accent, cmd.name.padEnd(nameW)) + paint(T.text, cmd.description)
|
|
756
|
+
: paint(T.secondary, cmd.name.padEnd(nameW)) + paint(T.dim, cmd.description)
|
|
757
|
+
);
|
|
758
|
+
}),
|
|
759
|
+
commands.length > PALETTE_ROWS && React.createElement(
|
|
760
|
+
Text,
|
|
761
|
+
{ key: 'more' },
|
|
762
|
+
paint(T.faint, `${commands.length - PALETTE_ROWS} more, keep typing to filter`)
|
|
763
|
+
)
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function Shortcuts({ width, newlineKey }) {
|
|
768
|
+
const items = [
|
|
769
|
+
['/', 'for commands'], ['tab', 'to switch mode'], ['shift+tab', 'to toggle bypass'],
|
|
770
|
+
[newlineKey, 'for a new line'], ['\\ enter', 'for a new line'], ['↑ ↓', 'for prompt history'],
|
|
771
|
+
['esc', 'to interrupt'], ['esc esc', 'to clear input'], ['ctrl+c', 'twice to quit'],
|
|
772
|
+
];
|
|
773
|
+
const per = width >= 96 ? 3 : width >= 60 ? 2 : 1;
|
|
774
|
+
const colW = Math.floor((width - 2) / per);
|
|
775
|
+
const lines = [];
|
|
776
|
+
for (let i = 0; i < items.length; i += per) {
|
|
777
|
+
lines.push(items.slice(i, i + per).map(([k, d]) => {
|
|
778
|
+
const cell = paint(T.text, k) + ' ' + paint(T.dim, d);
|
|
779
|
+
return cell + ' '.repeat(Math.max(1, colW - k.length - d.length - 1));
|
|
780
|
+
}).join(''));
|
|
781
|
+
}
|
|
782
|
+
return React.createElement(
|
|
783
|
+
Box,
|
|
784
|
+
{ flexDirection: 'column', paddingLeft: 2, width },
|
|
785
|
+
...lines.map((l, i) => React.createElement(Text, { key: i, wrap: 'truncate-end' }, l))
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// ─── App ────────────────────────────────────────────────────────────────────
|
|
790
|
+
|
|
791
|
+
const BANNER_ITEM = { id: 'banner', role: 'banner' };
|
|
792
|
+
|
|
793
|
+
const App = () => {
|
|
794
|
+
const { exit } = useApp();
|
|
795
|
+
const { stdout } = useStdout();
|
|
796
|
+
const [cols, setCols] = React.useState(stdout?.columns || 80);
|
|
797
|
+
const [rows, setRows] = React.useState(stdout?.rows || 24);
|
|
798
|
+
|
|
799
|
+
const [input, setInput] = React.useState('');
|
|
800
|
+
const [modeIndex, setModeIndex] = React.useState(0);
|
|
801
|
+
const [session, setSession] = React.useState(null);
|
|
802
|
+
const [booted, setBooted] = React.useState(false);
|
|
803
|
+
const [running, setRunning] = React.useState(false);
|
|
804
|
+
const [runStarted, setRunStarted] = React.useState(0);
|
|
805
|
+
const [statusLine, setStatusLine] = React.useState('');
|
|
806
|
+
const [steps, setSteps] = React.useState(0);
|
|
807
|
+
const [chatHistory, setChatHistory] = React.useState([]);
|
|
808
|
+
const [staticKey, setStaticKey] = React.useState(0);
|
|
809
|
+
const [showCommands, setShowCommands] = React.useState(false);
|
|
810
|
+
const [selectedCommand, setSelectedCommand] = React.useState(0);
|
|
811
|
+
const [filteredCommands, setFilteredCommands] = React.useState(COMMANDS);
|
|
812
|
+
const [showShortcuts, setShowShortcuts] = React.useState(false);
|
|
813
|
+
const [escArmed, setEscArmed] = React.useState(false);
|
|
814
|
+
const [ctrlCArmed, setCtrlCArmed] = React.useState(false);
|
|
815
|
+
const [approval, setApproval] = React.useState(null);
|
|
816
|
+
const [approvalIndex, setApprovalIndex] = React.useState(0);
|
|
817
|
+
const [bypassMode, setBypassMode] = React.useState(false);
|
|
818
|
+
const [activeAgents, setActiveAgents] = React.useState([]); // [{id,label}] - running subagents, this session's own run excluded
|
|
819
|
+
const [queued, setQueued] = React.useState(null);
|
|
820
|
+
const [exiting, setExiting] = React.useState(false);
|
|
821
|
+
// Only true once the terminal answers our capability query. Windows Terminal
|
|
822
|
+
// never does, so there we advertise ctrl+j instead of lying about shift+enter.
|
|
823
|
+
const [modifiedEnterWorks, setModifiedEnterWorks] = React.useState(false);
|
|
824
|
+
const [keyProbe, setKeyProbe] = React.useState(false);
|
|
825
|
+
// 'classic' is codeply-cli's own agent; 'dsh' is Craft's DeepSeek Harness
|
|
826
|
+
// fork, driven over ACP (see dsh-acp.mjs). `codeply dsh` starts in DSH.
|
|
827
|
+
const [engine, setEngine] = React.useState(process.env.CODEPLY_ENGINE === 'dsh' ? 'dsh' : 'classic');
|
|
828
|
+
const dshRef = React.useRef(null);
|
|
829
|
+
const getDsh = React.useCallback(() => {
|
|
830
|
+
if (!dshRef.current) dshRef.current = new DshEngine({ cwd: process.cwd() });
|
|
831
|
+
return dshRef.current;
|
|
832
|
+
}, []);
|
|
833
|
+
const closeDsh = React.useCallback(() => {
|
|
834
|
+
if (dshRef.current) { dshRef.current.close(); dshRef.current = null; }
|
|
835
|
+
}, []);
|
|
836
|
+
React.useEffect(() => {
|
|
837
|
+
process.once('exit', closeDsh);
|
|
838
|
+
return () => { process.off('exit', closeDsh); closeDsh(); };
|
|
839
|
+
}, [closeDsh]);
|
|
840
|
+
// Boot the engine in the background as soon as DSH is picked: its first
|
|
841
|
+
// start takes a few seconds, and this hides most of it behind typing.
|
|
842
|
+
React.useEffect(() => {
|
|
843
|
+
if (engine !== 'dsh') return;
|
|
844
|
+
getDsh().start().catch((e) => push({ role: 'error', content: e.message }));
|
|
845
|
+
}, [engine]);
|
|
846
|
+
|
|
847
|
+
const nextId = React.useRef(1);
|
|
848
|
+
// A real AbortController, not a plain {aborted} object: its .signal ends up
|
|
849
|
+
// handed straight to fetch() (see ai.js), which requires an actual
|
|
850
|
+
// AbortSignal instance - a look-alike object throws a TypeError there
|
|
851
|
+
// ("Expected signal ... to be an instance of AbortSignal"), and that
|
|
852
|
+
// message contains the word "AbortSignal", which the fetch error classifier
|
|
853
|
+
// then mistakes for a real abort. Net effect: every single turn "aborted"
|
|
854
|
+
// immediately, before the model was ever called.
|
|
855
|
+
const abortRef = React.useRef(new AbortController());
|
|
856
|
+
const alwaysAllow = React.useRef(new Set());
|
|
857
|
+
const convo = React.useRef([]); // {role,content} pairs handed to the agent
|
|
858
|
+
const runningRef = React.useRef(false);
|
|
859
|
+
const queuedRef = React.useRef(null);
|
|
860
|
+
const submitRef = React.useRef(null);
|
|
861
|
+
// Prompt history for up/down: past prompts, where we are in them, and the
|
|
862
|
+
// unsent draft to come back to.
|
|
863
|
+
const promptHistory = React.useRef([]);
|
|
864
|
+
const historyPos = React.useRef(-1);
|
|
865
|
+
const draft = React.useRef('');
|
|
866
|
+
|
|
867
|
+
const cwd = process.cwd();
|
|
868
|
+
const mode = MODES[modeIndex];
|
|
869
|
+
const modeColor = MODE_COLOR[mode];
|
|
870
|
+
|
|
871
|
+
const push = React.useCallback((msg) => {
|
|
872
|
+
setChatHistory((prev) => [...prev, { id: `m-${nextId.current++}`, ...msg }]);
|
|
873
|
+
}, []);
|
|
874
|
+
|
|
875
|
+
// The banner names the account, so hold it back until the session lookup
|
|
876
|
+
// settles (or briefly times out) instead of printing "not signed in" first.
|
|
877
|
+
React.useEffect(() => {
|
|
878
|
+
let done = false;
|
|
879
|
+
const finish = () => { if (!done) { done = true; setBooted(true); } };
|
|
880
|
+
auth.getSession().then((s) => setSession(s)).catch(() => {}).finally(finish);
|
|
881
|
+
const t = setTimeout(finish, 1200);
|
|
882
|
+
return () => clearTimeout(t);
|
|
883
|
+
}, []);
|
|
884
|
+
|
|
885
|
+
// Ask the terminal to disambiguate shift+enter from enter (see textarea.mjs).
|
|
886
|
+
React.useEffect(() => enableModifiedEnter(stdout), [stdout]);
|
|
887
|
+
|
|
888
|
+
React.useEffect(() => {
|
|
889
|
+
if (!stdout) return;
|
|
890
|
+
const onResize = () => { setCols(stdout.columns || 80); setRows(stdout.rows || 24); };
|
|
891
|
+
stdout.on('resize', onResize);
|
|
892
|
+
return () => stdout.off('resize', onResize);
|
|
893
|
+
}, [stdout]);
|
|
894
|
+
|
|
895
|
+
// A width change reflows every line, and <Static> cannot reprint what it
|
|
896
|
+
// already wrote: clear and print the whole transcript again at the new width.
|
|
897
|
+
const firstWidth = React.useRef(true);
|
|
898
|
+
React.useEffect(() => {
|
|
899
|
+
if (firstWidth.current) { firstWidth.current = false; return; }
|
|
900
|
+
const t = setTimeout(() => {
|
|
901
|
+
if (stdout) stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
902
|
+
setStaticKey((k) => k + 1);
|
|
903
|
+
}, 150);
|
|
904
|
+
return () => clearTimeout(t);
|
|
905
|
+
}, [cols]);
|
|
906
|
+
|
|
907
|
+
React.useEffect(() => {
|
|
908
|
+
if (input.startsWith('/') && !input.includes('\n')) {
|
|
909
|
+
const query = input.toLowerCase().split(/\s/)[0];
|
|
910
|
+
const filtered = COMMANDS.filter((cmd) => cmd.name.startsWith(query));
|
|
911
|
+
setFilteredCommands(filtered);
|
|
912
|
+
setShowCommands(filtered.length > 0 && !input.includes(' '));
|
|
913
|
+
setSelectedCommand(0);
|
|
914
|
+
} else {
|
|
915
|
+
setShowCommands(false);
|
|
916
|
+
}
|
|
917
|
+
}, [input]);
|
|
918
|
+
|
|
919
|
+
React.useEffect(() => {
|
|
920
|
+
if (!escArmed) return;
|
|
921
|
+
const t = setTimeout(() => setEscArmed(false), 1500);
|
|
922
|
+
return () => clearTimeout(t);
|
|
923
|
+
}, [escArmed]);
|
|
924
|
+
|
|
925
|
+
React.useEffect(() => {
|
|
926
|
+
if (!ctrlCArmed) return;
|
|
927
|
+
const t = setTimeout(() => setCtrlCArmed(false), 1500);
|
|
928
|
+
return () => clearTimeout(t);
|
|
929
|
+
}, [ctrlCArmed]);
|
|
930
|
+
|
|
931
|
+
// Leave nothing behind: blank the live frame first, then exit, so the shell
|
|
932
|
+
// prompt lands right under the transcript instead of under a dead input box.
|
|
933
|
+
const quit = React.useCallback(() => {
|
|
934
|
+
closeDsh();
|
|
935
|
+
setExiting(true);
|
|
936
|
+
setTimeout(() => exit(), 30);
|
|
937
|
+
}, [exit]);
|
|
938
|
+
|
|
939
|
+
// ── approval bridge: tools await this promise, the keypress handler settles it ──
|
|
940
|
+
const requestApproval = React.useCallback((request) => {
|
|
941
|
+
if (bypassModeRef.current) return Promise.resolve('always');
|
|
942
|
+
if (alwaysAllow.current.has(request.tool)) return Promise.resolve('always');
|
|
943
|
+
return new Promise((resolve) => {
|
|
944
|
+
setApprovalIndex(0);
|
|
945
|
+
setApproval({ request, resolve });
|
|
946
|
+
});
|
|
947
|
+
}, []);
|
|
948
|
+
|
|
949
|
+
// Read inside the approval callback without re-creating it on every toggle -
|
|
950
|
+
// requestApproval is captured once per run and handed down through several
|
|
951
|
+
// layers of nested subagent calls, so it must see the current value, not a
|
|
952
|
+
// stale one closed over when the run started.
|
|
953
|
+
const bypassModeRef = React.useRef(false);
|
|
954
|
+
React.useEffect(() => { bypassModeRef.current = bypassMode; }, [bypassMode]);
|
|
955
|
+
|
|
956
|
+
// ── subagent bridge: nested runs report in here so the footer can show them ──
|
|
957
|
+
const onSubagentEvent = React.useCallback((evt) => {
|
|
958
|
+
if (evt.type === 'start') {
|
|
959
|
+
setActiveAgents((prev) => [...prev, { id: evt.id, label: evt.label }]);
|
|
960
|
+
} else if (evt.type === 'end') {
|
|
961
|
+
setActiveAgents((prev) => prev.filter((a) => a.id !== evt.id));
|
|
962
|
+
}
|
|
963
|
+
}, []);
|
|
964
|
+
|
|
965
|
+
const settleApproval = React.useCallback((verdict) => {
|
|
966
|
+
setApproval((current) => {
|
|
967
|
+
if (!current) return null;
|
|
968
|
+
if (verdict === 'always') alwaysAllow.current.add(current.request.tool);
|
|
969
|
+
current.resolve(verdict);
|
|
970
|
+
return null;
|
|
971
|
+
});
|
|
972
|
+
}, []);
|
|
973
|
+
|
|
974
|
+
// Up/down on the first/last line walks through earlier prompts.
|
|
975
|
+
const onHistory = React.useCallback((dir) => {
|
|
976
|
+
const list = promptHistory.current;
|
|
977
|
+
if (!list.length) return false;
|
|
978
|
+
let pos = historyPos.current;
|
|
979
|
+
if (pos === -1) {
|
|
980
|
+
if (dir > 0) return false;
|
|
981
|
+
draft.current = input;
|
|
982
|
+
pos = list.length - 1;
|
|
983
|
+
} else {
|
|
984
|
+
pos += dir;
|
|
985
|
+
}
|
|
986
|
+
if (pos < 0) pos = 0;
|
|
987
|
+
if (pos >= list.length) {
|
|
988
|
+
historyPos.current = -1;
|
|
989
|
+
setInput(draft.current);
|
|
990
|
+
return true;
|
|
991
|
+
}
|
|
992
|
+
historyPos.current = pos;
|
|
993
|
+
setInput(list[pos]);
|
|
994
|
+
return true;
|
|
995
|
+
}, [input]);
|
|
996
|
+
|
|
997
|
+
// "?" on an empty input opens the shortcuts list instead of typing a "?".
|
|
998
|
+
const onChange = React.useCallback((next) => {
|
|
999
|
+
if (input === '' && next === '?') { setShowShortcuts((s) => !s); return; }
|
|
1000
|
+
if (showShortcuts) setShowShortcuts(false);
|
|
1001
|
+
setInput(next);
|
|
1002
|
+
}, [input, showShortcuts]);
|
|
1003
|
+
|
|
1004
|
+
useInput((char, key) => {
|
|
1005
|
+
// The terminal's answer to our kitty query - proof that modified enter is
|
|
1006
|
+
// really distinguishable here. Swallow it so it never reaches the draft.
|
|
1007
|
+
if (isTerminalReport(char)) { setModifiedEnterWorks(true); return; }
|
|
1008
|
+
|
|
1009
|
+
// /keys diagnostic: report exactly what the terminal sent for one keypress.
|
|
1010
|
+
if (keyProbe) {
|
|
1011
|
+
setKeyProbe(false);
|
|
1012
|
+
const seq = JSON.stringify(char);
|
|
1013
|
+
const flags = ['ctrl', 'shift', 'meta', 'return', 'tab', 'escape', 'backspace', 'delete']
|
|
1014
|
+
.filter((f) => key[f]).join(', ') || 'none';
|
|
1015
|
+
push({ role: 'system', content: `Key sent: ${seq}\nFlags: ${flags}` });
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// The approval prompt owns the keyboard while it is up.
|
|
1020
|
+
if (approval) {
|
|
1021
|
+
const options = approvalOptions(approval.request);
|
|
1022
|
+
if (key.upArrow) { setApprovalIndex((i) => (i > 0 ? i - 1 : options.length - 1)); return; }
|
|
1023
|
+
if (key.downArrow || key.tab) { setApprovalIndex((i) => (i + 1) % options.length); return; }
|
|
1024
|
+
if (key.return) { settleApproval(options[approvalIndex].verdict); return; }
|
|
1025
|
+
if (char === '1' || char === 'y') { settleApproval('once'); return; }
|
|
1026
|
+
if (char === '2' || char === 'a') { settleApproval('always'); return; }
|
|
1027
|
+
if (char === '3' || char === 'n' || key.escape) { settleApproval('reject'); return; }
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (key.escape) {
|
|
1032
|
+
if (running) { abortRef.current.abort(); setStatusLine('Interrupting'); return; }
|
|
1033
|
+
if (showCommands) { setShowCommands(false); return; }
|
|
1034
|
+
if (showShortcuts) { setShowShortcuts(false); return; }
|
|
1035
|
+
if (input.length > 0) {
|
|
1036
|
+
if (escArmed) { setInput(''); setEscArmed(false); }
|
|
1037
|
+
else { setEscArmed(true); }
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
if (key.ctrl && char === 'c') {
|
|
1044
|
+
if (running) { abortRef.current.abort(); return; }
|
|
1045
|
+
if (input.length > 0) { setInput(''); return; }
|
|
1046
|
+
if (ctrlCArmed) { quit(); } else { setCtrlCArmed(true); }
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (key.tab && key.shift) {
|
|
1051
|
+
setBypassMode((b) => !b);
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
if (key.tab) {
|
|
1056
|
+
if (showCommands && filteredCommands.length > 0) {
|
|
1057
|
+
const sel = filteredCommands[selectedCommand];
|
|
1058
|
+
if (sel) { setInput(sel.name + ' '); setShowCommands(false); }
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
if (engine === 'dsh') return; // modes belong to the Classic engine
|
|
1062
|
+
setModeIndex((i) => (i + 1) % MODES.length);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if (showCommands) {
|
|
1067
|
+
if (key.upArrow) { setSelectedCommand((p) => (p > 0 ? p - 1 : filteredCommands.length - 1)); return; }
|
|
1068
|
+
if (key.downArrow) { setSelectedCommand((p) => (p < filteredCommands.length - 1 ? p + 1 : 0)); return; }
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
// ── slash commands ──
|
|
1073
|
+
const runCommand = async (cmd, args = '') => {
|
|
1074
|
+
if (cmd === '/login') {
|
|
1075
|
+
push({ role: 'system', content: 'Opening sign-in…' });
|
|
1076
|
+
try {
|
|
1077
|
+
await auth.login();
|
|
1078
|
+
const s = await auth.getSession();
|
|
1079
|
+
setSession(s);
|
|
1080
|
+
push({ role: 'system', content: s ? `Signed in as ${s.user.email}` : 'Login cancelled or failed' });
|
|
1081
|
+
} catch (e) {
|
|
1082
|
+
push({ role: 'error', content: e.message || 'Login failed' });
|
|
1083
|
+
}
|
|
1084
|
+
} else if (cmd === '/logout') {
|
|
1085
|
+
await auth.logout();
|
|
1086
|
+
setSession(null);
|
|
1087
|
+
push({ role: 'system', content: 'Signed out' });
|
|
1088
|
+
} else if (cmd === '/whoami') {
|
|
1089
|
+
const s = await auth.getSession();
|
|
1090
|
+
push({ role: 'system', content: s ? s.user.email : 'Not signed in' });
|
|
1091
|
+
} else if (cmd === '/help') {
|
|
1092
|
+
const w = Math.max(...COMMANDS.map((c) => c.name.length)) + 3;
|
|
1093
|
+
push({
|
|
1094
|
+
role: 'system',
|
|
1095
|
+
content: 'Commands\n' + COMMANDS.map((c) => ` ${c.name.padEnd(w)}${c.description}`).join('\n') +
|
|
1096
|
+
'\n\nKeys\n' +
|
|
1097
|
+
` ${(newlineKey + ' or \\ enter').padEnd(22)}new line\n` +
|
|
1098
|
+
` ${'tab'.padEnd(22)}switch mode (Build, Plan, Ask)\n` +
|
|
1099
|
+
` ${'shift+tab'.padEnd(22)}toggle bypass permissions\n` +
|
|
1100
|
+
` ${'↑ ↓'.padEnd(22)}prompt history\n` +
|
|
1101
|
+
` ${'esc'.padEnd(22)}interrupt a run, or twice to clear the input\n` +
|
|
1102
|
+
` ${'ctrl+c'.padEnd(22)}twice to quit\n` +
|
|
1103
|
+
`\nModel\n ${appConfig.describeProvider()} (change with /provider)\n` +
|
|
1104
|
+
`\nWhat's new\n${WHATS_NEW.map((l) => ` ${l}`).join('\n')}`,
|
|
1105
|
+
});
|
|
1106
|
+
} else if (cmd === '/clear') {
|
|
1107
|
+
// <Static> never re-renders what it already printed, so a clear means
|
|
1108
|
+
// wiping the terminal and remounting it with a fresh key. The banner
|
|
1109
|
+
// comes back with it.
|
|
1110
|
+
convo.current = [];
|
|
1111
|
+
// A fresh DSH session too: the engine keeps its own history per session.
|
|
1112
|
+
if (engine === 'dsh') closeDsh();
|
|
1113
|
+
if (stdout) stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
1114
|
+
setChatHistory([]);
|
|
1115
|
+
setStaticKey((k) => k + 1);
|
|
1116
|
+
} else if (cmd === '/mode') {
|
|
1117
|
+
const next = (modeIndex + 1) % MODES.length;
|
|
1118
|
+
setModeIndex(next);
|
|
1119
|
+
push({ role: 'system', content: `${MODES[next]} mode: ${MODE_HINT[MODES[next]]}` });
|
|
1120
|
+
} else if (cmd === '/provider') {
|
|
1121
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
1122
|
+
const name = parts[0];
|
|
1123
|
+
|
|
1124
|
+
if (!name) {
|
|
1125
|
+
const cfg = appConfig.getConfig();
|
|
1126
|
+
let msg = `Provider: ${appConfig.describeProvider(cfg)}\n` +
|
|
1127
|
+
`Providers: ${appConfig.PROVIDERS.join(', ')}\n` +
|
|
1128
|
+
'Set with /provider <name> [key], for example /provider openrouter sk-or-v1-...';
|
|
1129
|
+
if (cfg.provider === 'codeply') msg += `\n\n${appConfig.byokHint()}`;
|
|
1130
|
+
push({ role: 'system', content: msg });
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (!appConfig.PROVIDERS.includes(name)) {
|
|
1135
|
+
push({ role: 'error', content: `Unknown provider "${name}". Use one of: ${appConfig.PROVIDERS.join(', ')}.` });
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const key = parts[1];
|
|
1140
|
+
const patch = { provider: name };
|
|
1141
|
+
if (key && (appConfig.BYOK_PROVIDERS.includes(name) || name === 'ollama')) {
|
|
1142
|
+
patch[name] = { apiKey: key };
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
const saved = appConfig.saveConfig(patch);
|
|
1146
|
+
if (!saved.ok) {
|
|
1147
|
+
push({ role: 'error', content: `Could not save config: ${saved.error}` });
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
setProvider(appConfig.getConfig());
|
|
1151
|
+
push({ role: 'system', content: `Provider set to ${appConfig.describeProvider(appConfig.getConfig())}` });
|
|
1152
|
+
} else if (cmd === '/engine') {
|
|
1153
|
+
const want = args.trim().toLowerCase();
|
|
1154
|
+
if (!want) {
|
|
1155
|
+
push({ role: 'system', content: `Engine: ${engine === 'dsh' ? "DSH (Craft's DeepSeek Harness fork, over ACP)" : 'Classic (codeply-cli)'}\nSwitch with /engine classic or /engine dsh` });
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
if (want !== 'dsh' && want !== 'classic') {
|
|
1159
|
+
push({ role: 'error', content: `Unknown engine "${want}". Use classic or dsh.` });
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (want === engine) { push({ role: 'system', content: `Already on the ${want === 'dsh' ? 'DSH' : 'Classic'} engine.` }); return; }
|
|
1163
|
+
if (want === 'classic') closeDsh();
|
|
1164
|
+
setEngine(want);
|
|
1165
|
+
push({ role: 'system', content: want === 'dsh' ? 'Switched to the DSH engine. Starting it…' : 'Switched to the Classic engine.' });
|
|
1166
|
+
} else if (cmd === '/keys') {
|
|
1167
|
+
push({ role: 'system', content: 'Press any key (or combination)…' });
|
|
1168
|
+
setKeyProbe(true);
|
|
1169
|
+
} else if (cmd === '/skill') {
|
|
1170
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
1171
|
+
const sub = parts[0];
|
|
1172
|
+
|
|
1173
|
+
if (!sub || sub === 'list') {
|
|
1174
|
+
const list = skills.listSkills();
|
|
1175
|
+
if (list.length === 0) {
|
|
1176
|
+
push({ role: 'system', content: 'No skills available.' });
|
|
1177
|
+
} else {
|
|
1178
|
+
const lines = list.map((s) => `${s.name.padEnd(28)} [${s.source}] ${s.description}`);
|
|
1179
|
+
push({ role: 'system', content: `Skills (${list.length})\n` + lines.join('\n') +
|
|
1180
|
+
'\n\n/skill install <path-or-github-url> add one\n/skill remove <name> remove a user-installed one' });
|
|
1181
|
+
}
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
if (sub === 'install') {
|
|
1186
|
+
const source = parts.slice(1).join(' ');
|
|
1187
|
+
if (!source) { push({ role: 'error', content: 'Usage: /skill install <local-path-or-github-url>' }); return; }
|
|
1188
|
+
push({ role: 'system', content: `Installing from ${source}…` });
|
|
1189
|
+
try {
|
|
1190
|
+
const isLocal = fs.existsSync(source);
|
|
1191
|
+
const result = isLocal
|
|
1192
|
+
? skills.installFromLocalDir(path.resolve(cwd, source))
|
|
1193
|
+
: await skills.installFromGitHub(source);
|
|
1194
|
+
if (isLocal) {
|
|
1195
|
+
if (!result.ok) push({ role: 'error', content: result.error });
|
|
1196
|
+
else push({ role: 'system', content: `Installed "${result.name}" to ${result.dest}` });
|
|
1197
|
+
} else if (!result.ok) {
|
|
1198
|
+
push({ role: 'error', content: result.error || `No skills installed from ${source}.` });
|
|
1199
|
+
} else {
|
|
1200
|
+
const names = result.installed.map((i) => i.name).join(', ');
|
|
1201
|
+
push({ role: 'system', content: `Installed ${result.installed.length} skill(s) from ${result.source}: ${names}` });
|
|
1202
|
+
if (result.failed.length) {
|
|
1203
|
+
push({ role: 'error', content: `${result.failed.length} failed: ${result.failed.map((f) => f.dir).join(', ')}` });
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
} catch (e) {
|
|
1207
|
+
push({ role: 'error', content: `Install failed: ${e.message}` });
|
|
1208
|
+
}
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
if (sub === 'remove') {
|
|
1213
|
+
const name = parts[1];
|
|
1214
|
+
if (!name) { push({ role: 'error', content: 'Usage: /skill remove <name>' }); return; }
|
|
1215
|
+
const result = skills.removeSkill(name);
|
|
1216
|
+
push(result.ok
|
|
1217
|
+
? { role: 'system', content: `Removed "${name}".` }
|
|
1218
|
+
: { role: 'error', content: result.error });
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
push({ role: 'error', content: `Unknown /skill subcommand "${sub}". Use list, install, or remove.` });
|
|
1223
|
+
} else if (cmd === '/quit' || cmd === '/exit') {
|
|
1224
|
+
quit();
|
|
1225
|
+
} else {
|
|
1226
|
+
push({ role: 'error', content: `Unknown command: ${cmd}. Type /help for available commands.` });
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
|
|
1230
|
+
// ── main turn ──
|
|
1231
|
+
const handleSubmit = async (value) => {
|
|
1232
|
+
const text = value.trim();
|
|
1233
|
+
if (!text) return;
|
|
1234
|
+
|
|
1235
|
+
let submitValue = text;
|
|
1236
|
+
if (showCommands && filteredCommands[selectedCommand]) {
|
|
1237
|
+
const raw = text.split(/\s+/)[0].toLowerCase();
|
|
1238
|
+
if (!COMMANDS.some((c) => c.name === raw)) {
|
|
1239
|
+
submitValue = filteredCommands[selectedCommand].name;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
setShowCommands(false);
|
|
1243
|
+
setShowShortcuts(false);
|
|
1244
|
+
setInput('');
|
|
1245
|
+
historyPos.current = -1;
|
|
1246
|
+
draft.current = '';
|
|
1247
|
+
const list = promptHistory.current;
|
|
1248
|
+
if (list[list.length - 1] !== submitValue) list.push(submitValue);
|
|
1249
|
+
|
|
1250
|
+
// Typed while a run is going: hold it and send it when the run ends.
|
|
1251
|
+
if (runningRef.current) {
|
|
1252
|
+
queuedRef.current = submitValue;
|
|
1253
|
+
setQueued(submitValue);
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
if (submitValue.startsWith('/')) {
|
|
1258
|
+
const [first, ...rest] = submitValue.split(/\s+/);
|
|
1259
|
+
const cmd = first.toLowerCase();
|
|
1260
|
+
if (cmd !== '/clear' && cmd !== '/quit' && cmd !== '/exit') push({ role: 'user', content: submitValue });
|
|
1261
|
+
await runCommand(cmd, rest.join(' '));
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
push({ role: 'user', content: submitValue });
|
|
1266
|
+
runningRef.current = true;
|
|
1267
|
+
setRunning(true);
|
|
1268
|
+
const startedAt = Date.now();
|
|
1269
|
+
setRunStarted(startedAt);
|
|
1270
|
+
setStatusLine('Thinking');
|
|
1271
|
+
setSteps(0);
|
|
1272
|
+
abortRef.current = new AbortController();
|
|
1273
|
+
let runMode = mode;
|
|
1274
|
+
|
|
1275
|
+
const turn = [];
|
|
1276
|
+
try {
|
|
1277
|
+
if (engine === 'dsh' && !(dshRef.current && dshRef.current.sessionId)) setStatusLine('Starting the DSH engine');
|
|
1278
|
+
const stream = engine === 'dsh'
|
|
1279
|
+
? getDsh().prompt(submitValue, { approve: requestApproval, signal: abortRef.current.signal })
|
|
1280
|
+
: runAgent({
|
|
1281
|
+
userMessage: submitValue,
|
|
1282
|
+
history: convo.current.slice(-20),
|
|
1283
|
+
mode,
|
|
1284
|
+
cwd,
|
|
1285
|
+
approve: requestApproval,
|
|
1286
|
+
signal: abortRef.current.signal,
|
|
1287
|
+
onSubagentEvent,
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
for await (const event of stream) {
|
|
1291
|
+
if (event.type === 'text') {
|
|
1292
|
+
// In Plan mode the substantial reply is the plan itself; short
|
|
1293
|
+
// interstitial notes ("let me check X") stay as ordinary prose.
|
|
1294
|
+
const isPlan = runMode === 'Plan' && !event.interim && event.text.split('\n').length >= 3;
|
|
1295
|
+
push({ role: 'assistant', content: event.text, plan: isPlan });
|
|
1296
|
+
turn.push({ role: 'assistant', content: event.text });
|
|
1297
|
+
setStatusLine('Thinking');
|
|
1298
|
+
} else if (event.type === 'reasoning') {
|
|
1299
|
+
push({ role: 'reasoning', text: event.text, ms: event.ms });
|
|
1300
|
+
} else if (event.type === 'notice') {
|
|
1301
|
+
push({ role: 'notice', level: event.level, content: event.text });
|
|
1302
|
+
} else if (event.type === 'tool_start') {
|
|
1303
|
+
const [, verb] = toolTitle(event.name);
|
|
1304
|
+
const target = argTarget(event.args).split('\n')[0];
|
|
1305
|
+
setSteps((n) => n + 1);
|
|
1306
|
+
setStatusLine(target ? `${verb} ${target}` : verb);
|
|
1307
|
+
} else if (event.type === 'tool_end') {
|
|
1308
|
+
push(toolMessage(event.engine === 'dsh' ? fromDsh(event) : event, cwd));
|
|
1309
|
+
setStatusLine('Thinking');
|
|
1310
|
+
} else if (event.type === 'verifying') {
|
|
1311
|
+
setStatusLine(`Verifying ${plural((event.files || []).length, 'file')}`);
|
|
1312
|
+
} else if (event.type === 'mode_switch') {
|
|
1313
|
+
const idx = MODES.indexOf(event.mode);
|
|
1314
|
+
if (idx >= 0) { setModeIndex(idx); runMode = event.mode; }
|
|
1315
|
+
push({ role: 'system', content: `Switched to ${event.mode} mode${event.planPath ? `. Plan saved to ${event.planPath}` : ''}` });
|
|
1316
|
+
} else if (event.type === 'done') {
|
|
1317
|
+
push({
|
|
1318
|
+
role: 'done',
|
|
1319
|
+
secs: Math.max(1, Math.round((Date.now() - startedAt) / 1000)),
|
|
1320
|
+
steps: event.steps,
|
|
1321
|
+
files: event.writtenFiles || [],
|
|
1322
|
+
});
|
|
1323
|
+
} else if (event.type === 'aborted') {
|
|
1324
|
+
push({ role: 'notice', level: 'info', content: 'Interrupted. Tell Codeply what to do instead, or press ↑ to edit your last message.' });
|
|
1325
|
+
} else if (event.type === 'error') {
|
|
1326
|
+
let content = event.error;
|
|
1327
|
+
if (event.limited && event.resetsAt) {
|
|
1328
|
+
const at = new Date(event.resetsAt);
|
|
1329
|
+
if (!Number.isNaN(at.getTime())) content += `\nResets at ${at.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })}.`;
|
|
1330
|
+
}
|
|
1331
|
+
if (event.limited) content += `\n${appConfig.byokHint()}`;
|
|
1332
|
+
push({ role: 'error', content });
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
} catch (e) {
|
|
1336
|
+
push({ role: 'error', content: e.message });
|
|
1337
|
+
} finally {
|
|
1338
|
+
convo.current = [...convo.current, { role: 'user', content: submitValue }, ...turn];
|
|
1339
|
+
runningRef.current = false;
|
|
1340
|
+
setRunning(false);
|
|
1341
|
+
setStatusLine('');
|
|
1342
|
+
// A run that ended while a prompt was up must not leave a dangling promise.
|
|
1343
|
+
setApproval((current) => { if (current) current.resolve('reject'); return null; });
|
|
1344
|
+
const next = queuedRef.current;
|
|
1345
|
+
if (next) {
|
|
1346
|
+
queuedRef.current = null;
|
|
1347
|
+
setQueued(null);
|
|
1348
|
+
setTimeout(() => submitRef.current && submitRef.current(next), 0);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1352
|
+
submitRef.current = handleSubmit;
|
|
1353
|
+
|
|
1354
|
+
// Ollama needs no Codeply sign-in, so showing "not signed in" there would be
|
|
1355
|
+
// a false alarm - surface the model that is actually answering instead.
|
|
1356
|
+
const [provider, setProvider] = React.useState(() => appConfig.getConfig());
|
|
1357
|
+
// The DSH engine always answers through Haze 2, whatever /provider says.
|
|
1358
|
+
const usingHaze = engine === 'dsh' || provider.provider === 'codeply';
|
|
1359
|
+
const needsLogin = usingHaze && !session;
|
|
1360
|
+
const modelLabel = engine === 'dsh' ? 'Haze 2 (auto) on the DSH engine' : usingHaze ? 'Haze 2 (auto)' : appConfig.describeProvider(provider);
|
|
1361
|
+
const modelShort = engine === 'dsh' ? 'DSH · Haze 2' : usingHaze ? 'Haze 2' : appConfig.describeProviderShort(provider);
|
|
1362
|
+
const account = session ? session.user.email : usingHaze ? 'not signed in' : 'using your own key';
|
|
1363
|
+
|
|
1364
|
+
// Advertise the key that actually inserts a newline in THIS terminal.
|
|
1365
|
+
const newlineKey = modifiedEnterWorks ? 'shift+enter' : 'ctrl+j';
|
|
1366
|
+
|
|
1367
|
+
if (exiting) return null;
|
|
1368
|
+
|
|
1369
|
+
const width = Math.max(20, cols - 2);
|
|
1370
|
+
const items = booted ? [BANNER_ITEM, ...chatHistory] : [];
|
|
1371
|
+
const bannerProps = {
|
|
1372
|
+
account, needsLogin, model: modelLabel, cwd, mode: engine === 'dsh' ? 'DSH engine' : `${mode} mode`,
|
|
1373
|
+
name: (session?.user?.email || '').split('@')[0] || null,
|
|
1374
|
+
};
|
|
1375
|
+
|
|
1376
|
+
// ── footer: one line, left = state, right = model ──
|
|
1377
|
+
let left;
|
|
1378
|
+
if (escArmed) left = paint(T.warn, `Press esc again to clear`);
|
|
1379
|
+
else if (ctrlCArmed) left = paint(T.warn, 'Press ctrl+c again to exit');
|
|
1380
|
+
else {
|
|
1381
|
+
left = (bypassMode ? paint(T.warn, `${GL.bypass} bypass permissions on`) + paint(T.faint, ' · ') : '') +
|
|
1382
|
+
(engine === 'dsh'
|
|
1383
|
+
? paint(T.accent, 'DSH engine') + paint(T.faint, ' (/engine to switch)')
|
|
1384
|
+
: paint(modeColor, `${mode} mode`) + paint(T.faint, ' (tab to switch)'));
|
|
1385
|
+
if (!bypassMode && !running && !input) left += paint(T.faint, ' · ? for shortcuts');
|
|
1386
|
+
}
|
|
1387
|
+
const subagents = activeAgents.length;
|
|
1388
|
+
const rightPlain = (subagents ? `${plural(subagents, 'subagent')} · ` : '') + (needsLogin ? 'not signed in · /login' : modelShort);
|
|
1389
|
+
const right = (subagents ? paint(T.accent, `${plural(subagents, 'subagent')}`) + paint(T.faint, ' · ') : '') +
|
|
1390
|
+
(needsLogin ? paint(T.warn, 'not signed in · /login') : paint(T.dim, modelShort));
|
|
1391
|
+
const showRight = width - 4 - rightPlain.length >= 24;
|
|
1392
|
+
|
|
1393
|
+
const footer = React.createElement(
|
|
1394
|
+
Box,
|
|
1395
|
+
{ key: 'footer', flexDirection: 'row', width, paddingX: 2, flexShrink: 0 },
|
|
1396
|
+
React.createElement(
|
|
1397
|
+
Box,
|
|
1398
|
+
{ flexGrow: 1, flexShrink: 1 },
|
|
1399
|
+
React.createElement(Text, { wrap: 'truncate-end' }, left)
|
|
1400
|
+
),
|
|
1401
|
+
showRight && React.createElement(Box, { flexShrink: 0, marginLeft: 2 }, React.createElement(Text, null, right))
|
|
1402
|
+
);
|
|
1403
|
+
|
|
1404
|
+
const borderColor = approval ? T.faint : bypassMode ? T.warn : mode === 'Build' ? T.border : modeColor;
|
|
1405
|
+
const composer = React.createElement(
|
|
1406
|
+
Box,
|
|
1407
|
+
{ key: 'composer', flexDirection: 'row', borderStyle: GL.border, borderColor, paddingX: 1, width, flexShrink: 0 },
|
|
1408
|
+
React.createElement(Text, null, paint(running ? T.faint : T.accent, GL.prompt + ' ')),
|
|
1409
|
+
React.createElement(TextArea, {
|
|
1410
|
+
value: input,
|
|
1411
|
+
onChange,
|
|
1412
|
+
onSubmit: handleSubmit,
|
|
1413
|
+
onHistory,
|
|
1414
|
+
width: width - 6,
|
|
1415
|
+
focus: !approval && !keyProbe,
|
|
1416
|
+
allowVertical: !showCommands,
|
|
1417
|
+
placeholder: running
|
|
1418
|
+
? 'Type to queue a follow-up'
|
|
1419
|
+
: 'Ask for a change, a fix, or an explanation',
|
|
1420
|
+
})
|
|
1421
|
+
);
|
|
1422
|
+
|
|
1423
|
+
return React.createElement(
|
|
1424
|
+
Box,
|
|
1425
|
+
{ flexDirection: 'column', width: cols },
|
|
1426
|
+
|
|
1427
|
+
// ── Scrollback: printed once, never redrawn. Keeps the live frame short. ──
|
|
1428
|
+
// <Static> is absolutely positioned, so a parent's right padding does not
|
|
1429
|
+
// narrow it: each item carries its own width instead, or every wrapped
|
|
1430
|
+
// line comes out one column too wide and spills onto the next row.
|
|
1431
|
+
React.createElement(
|
|
1432
|
+
Static,
|
|
1433
|
+
{ key: staticKey, items },
|
|
1434
|
+
(msg) => React.createElement(
|
|
1435
|
+
Box,
|
|
1436
|
+
{ key: msg.id, width: cols - 1, paddingLeft: 1 },
|
|
1437
|
+
React.createElement(MessageBlock, { msg, cols, rows, banner: bannerProps })
|
|
1438
|
+
)
|
|
1439
|
+
),
|
|
1440
|
+
|
|
1441
|
+
// ── Live frame below ──
|
|
1442
|
+
React.createElement(Box, { flexDirection: 'column', paddingX: 1 },
|
|
1443
|
+
running && !approval && React.createElement(Working, {
|
|
1444
|
+
label: statusLine || 'Thinking', startedAt: runStarted, steps, queued, width,
|
|
1445
|
+
}),
|
|
1446
|
+
|
|
1447
|
+
approval && React.createElement(ApprovalPrompt, {
|
|
1448
|
+
request: approval.request, selected: approvalIndex, cols, rows, cwd,
|
|
1449
|
+
}),
|
|
1450
|
+
|
|
1451
|
+
booted && composer,
|
|
1452
|
+
booted && showCommands && React.createElement(CommandsMenu, { commands: filteredCommands, selectedIndex: selectedCommand, width }),
|
|
1453
|
+
booted && showShortcuts && !showCommands && React.createElement(Shortcuts, { width, newlineKey }),
|
|
1454
|
+
booted && !showCommands && footer
|
|
1455
|
+
)
|
|
1456
|
+
);
|
|
1457
|
+
};
|
|
1458
|
+
|
|
1459
|
+
export default App;
|