@pugi/cli 0.1.0-beta.98 → 1.0.0-alpha.1
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 +1 -1
- package/README.md +11 -191
- package/bin/pugi +8 -0
- package/package.json +15 -71
- package/postinstall.mjs +31 -0
- package/CHANGELOG.md +0 -132
- package/THIRD_PARTY_NOTICES.md +0 -40
- package/assets/pugi-mascot.ansi +0 -16
- package/assets/pugi-prozr2-mascot.ansi +0 -9
- package/bin/run.js +0 -34
- package/dist/commands/deploy.js +0 -439
- package/dist/commands/flatten.js +0 -191
- package/dist/commands/jobs-watch.js +0 -201
- package/dist/commands/jobs.js +0 -260
- package/dist/commands/retro.js +0 -210
- package/dist/commands/smoke.js +0 -133
- package/dist/core/agent-progress/cleanup.js +0 -134
- package/dist/core/agent-progress/schema.js +0 -144
- package/dist/core/agent-progress/writer.js +0 -101
- package/dist/core/agents/adaptive-router.js +0 -330
- package/dist/core/agents/loader.js +0 -104
- package/dist/core/agents/query-decomposer.js +0 -297
- package/dist/core/agents/registry.js +0 -69
- package/dist/core/approvals/shortcut-resolver.js +0 -98
- package/dist/core/artifact-chain/dispatcher.js +0 -148
- package/dist/core/artifact-chain/exporter.js +0 -164
- package/dist/core/artifact-chain/state.js +0 -243
- package/dist/core/artifact-chain/steps.js +0 -169
- package/dist/core/ask-user/question.js +0 -92
- package/dist/core/audit/audit-trail.js +0 -275
- package/dist/core/auth/ensure-authenticated.js +0 -129
- package/dist/core/auth/env-provider.js +0 -238
- package/dist/core/auto-open-browser.js +0 -128
- package/dist/core/auto-update/channels.js +0 -122
- package/dist/core/auto-update/checker.js +0 -241
- package/dist/core/auto-update/state.js +0 -235
- package/dist/core/bare-mode/index.js +0 -107
- package/dist/core/bash/redirect.js +0 -281
- package/dist/core/bash-classifier.js +0 -1397
- package/dist/core/checkpoint/resumer.js +0 -149
- package/dist/core/checkpoint/rewinder.js +0 -291
- package/dist/core/checkpoints/shadow-git.js +0 -670
- package/dist/core/citations/parser.js +0 -109
- package/dist/core/classifier/yolo-classifier.js +0 -88
- package/dist/core/clipboard.js +0 -70
- package/dist/core/codegraph/decision-store.js +0 -248
- package/dist/core/codegraph/detect-repo.js +0 -459
- package/dist/core/codegraph/install.js +0 -134
- package/dist/core/codegraph/offer-hook.js +0 -220
- package/dist/core/compact/auto-trigger.js +0 -96
- package/dist/core/compact/buffer-rewriter.js +0 -115
- package/dist/core/compact/summarizer.js +0 -208
- package/dist/core/compact/token-counter.js +0 -108
- package/dist/core/consensus/anvil-fanout.js +0 -276
- package/dist/core/consensus/diff-capture.js +0 -491
- package/dist/core/consensus/rubric.js +0 -233
- package/dist/core/context/builder.js +0 -114
- package/dist/core/context/compaction-events.js +0 -99
- package/dist/core/context/compaction.js +0 -602
- package/dist/core/context/index.js +0 -28
- package/dist/core/context/invariants.js +0 -250
- package/dist/core/context/markdown-loader.js +0 -288
- package/dist/core/context/markdown-traverse.js +0 -255
- package/dist/core/context/pugiignore.js +0 -316
- package/dist/core/context/repo-skeleton.js +0 -533
- package/dist/core/context/tool-eviction.js +0 -55
- package/dist/core/context/watcher.js +0 -342
- package/dist/core/context/working-set.js +0 -165
- package/dist/core/coordinator/agent-tools.js +0 -77
- package/dist/core/coordinator/agent-toolset.js +0 -65
- package/dist/core/coordinator/fsm.js +0 -73
- package/dist/core/coordinator/mode-fsm.js +0 -70
- package/dist/core/cost/rate-card.js +0 -129
- package/dist/core/cost/tracker.js +0 -221
- package/dist/core/credentials.js +0 -355
- package/dist/core/cron/scheduler.js +0 -138
- package/dist/core/denial-tracking/index.js +0 -8
- package/dist/core/denial-tracking/state.js +0 -264
- package/dist/core/diagnostics/probe-runner.js +0 -93
- package/dist/core/diagnostics/probes/api.js +0 -46
- package/dist/core/diagnostics/probes/auth.js +0 -93
- package/dist/core/diagnostics/probes/bare-mode.js +0 -42
- package/dist/core/diagnostics/probes/cli-version.js +0 -127
- package/dist/core/diagnostics/probes/config.js +0 -72
- package/dist/core/diagnostics/probes/denial-tracking.js +0 -57
- package/dist/core/diagnostics/probes/disk.js +0 -81
- package/dist/core/diagnostics/probes/engine-live.js +0 -46
- package/dist/core/diagnostics/probes/git.js +0 -65
- package/dist/core/diagnostics/probes/hooks.js +0 -118
- package/dist/core/diagnostics/probes/mcp.js +0 -75
- package/dist/core/diagnostics/probes/node.js +0 -59
- package/dist/core/diagnostics/probes/pnpm.js +0 -36
- package/dist/core/diagnostics/probes/pugi-md.js +0 -89
- package/dist/core/diagnostics/probes/sandbox.js +0 -72
- package/dist/core/diagnostics/probes/session.js +0 -74
- package/dist/core/diagnostics/probes/status-snapshot.js +0 -488
- package/dist/core/diagnostics/probes/workspace.js +0 -63
- package/dist/core/diagnostics/types.js +0 -70
- package/dist/core/dispatch/cache-cleanup.js +0 -197
- package/dist/core/dispatch/cache-handoff.js +0 -295
- package/dist/core/edits/apply-patch-layer-e.js +0 -189
- package/dist/core/edits/dispatch.js +0 -511
- package/dist/core/edits/format-detector.js +0 -260
- package/dist/core/edits/format-matrix.js +0 -26
- package/dist/core/edits/fuzzy-ladder.js +0 -650
- package/dist/core/edits/index.js +0 -19
- package/dist/core/edits/journal.js +0 -199
- package/dist/core/edits/layer-a-apply.js +0 -217
- package/dist/core/edits/layer-a-fuzzy-apply.js +0 -198
- package/dist/core/edits/layer-b-apply.js +0 -211
- package/dist/core/edits/layer-c-apply.js +0 -160
- package/dist/core/edits/layer-d-ast.js +0 -572
- package/dist/core/edits/marker-parser.js +0 -401
- package/dist/core/edits/security-gate.js +0 -223
- package/dist/core/edits/verify-hook.js +0 -273
- package/dist/core/edits/worktree.js +0 -322
- package/dist/core/engine/adapter-runner.js +0 -8
- package/dist/core/engine/anvil-client.js +0 -344
- package/dist/core/engine/auto-compact.js +0 -179
- package/dist/core/engine/budgets.js +0 -192
- package/dist/core/engine/context-prefix.js +0 -155
- package/dist/core/engine/index.js +0 -12
- package/dist/core/engine/intensity.js +0 -163
- package/dist/core/engine/intent.js +0 -260
- package/dist/core/engine/native-pugi.js +0 -1616
- package/dist/core/engine/noop.js +0 -27
- package/dist/core/engine/prompts.js +0 -236
- package/dist/core/engine/strip-internal-fields.js +0 -124
- package/dist/core/engine/tool-bridge.js +0 -2173
- package/dist/core/engine/verification-patterns.js +0 -195
- package/dist/core/evaluation/golden-dataset.js +0 -293
- package/dist/core/feedback/queue.js +0 -177
- package/dist/core/feedback/submitter.js +0 -145
- package/dist/core/file-cache.js +0 -141
- package/dist/core/flatten/flatten-repo.js +0 -439
- package/dist/core/format/osc8-link.js +0 -28
- package/dist/core/hook-chains.js +0 -392
- package/dist/core/hooks/citation-verify-hook.js +0 -138
- package/dist/core/hooks/citation-verify.js +0 -112
- package/dist/core/hooks/events.js +0 -46
- package/dist/core/hooks/index.js +0 -15
- package/dist/core/hooks/registry.js +0 -216
- package/dist/core/hooks/runner.js +0 -236
- package/dist/core/hooks/v2/event-emitter.js +0 -115
- package/dist/core/hooks/v2/executor.js +0 -282
- package/dist/core/hooks/v2/index.js +0 -25
- package/dist/core/hooks/v2/lifecycle.js +0 -104
- package/dist/core/hooks/v2/loader.js +0 -216
- package/dist/core/hooks/v2/matcher.js +0 -125
- package/dist/core/hooks/v2/trust.js +0 -143
- package/dist/core/hooks/v2/types.js +0 -86
- package/dist/core/hooks/worktree-events.js +0 -158
- package/dist/core/hooks.js +0 -415
- package/dist/core/image/renderer.js +0 -71
- package/dist/core/index-store.js +0 -260
- package/dist/core/init/detector.js +0 -582
- package/dist/core/init/template-renderer.js +0 -242
- package/dist/core/jobs/registry.js +0 -462
- package/dist/core/ledger/results-tsv.js +0 -142
- package/dist/core/log-discipline/stdout-redirect.js +0 -51
- package/dist/core/lsp/cache.js +0 -105
- package/dist/core/lsp/client.js +0 -1229
- package/dist/core/lsp/language-detect.js +0 -66
- package/dist/core/lsp/post-edit-diagnostics.js +0 -171
- package/dist/core/lsp/server-detect.js +0 -173
- package/dist/core/lsp/symbol-cache.js +0 -162
- package/dist/core/lsp/symbol-tools.js +0 -664
- package/dist/core/mcp/client.js +0 -385
- package/dist/core/mcp/http-server.js +0 -553
- package/dist/core/mcp/orchestrator-config.js +0 -192
- package/dist/core/mcp/orchestrator-tools.js +0 -806
- package/dist/core/mcp/permission.js +0 -190
- package/dist/core/mcp/registry.js +0 -193
- package/dist/core/mcp/server-tools.js +0 -219
- package/dist/core/mcp/server.js +0 -397
- package/dist/core/mcp/trust.js +0 -91
- package/dist/core/memory/dual-write.js +0 -416
- package/dist/core/memory/passive-extract.js +0 -130
- package/dist/core/memory/phase1-kinds.js +0 -20
- package/dist/core/memory/secret-scanner.js +0 -304
- package/dist/core/memory-sync/queue.js +0 -170
- package/dist/core/metrics/extract.js +0 -113
- package/dist/core/modes/roo-modes.js +0 -68
- package/dist/core/onboarding/ensure-initialized.js +0 -133
- package/dist/core/onboarding/marker.js +0 -111
- package/dist/core/onboarding/telemetry-state.js +0 -108
- package/dist/core/output-style/presets.js +0 -176
- package/dist/core/output-style/state.js +0 -185
- package/dist/core/path-security.js +0 -345
- package/dist/core/permission.js +0 -369
- package/dist/core/permissions/auto-classifier.js +0 -124
- package/dist/core/permissions/bash-parser.js +0 -371
- package/dist/core/permissions/circuit-breaker.js +0 -83
- package/dist/core/permissions/constrained-edit.js +0 -91
- package/dist/core/permissions/gate.js +0 -278
- package/dist/core/permissions/index.js +0 -20
- package/dist/core/permissions/mode.js +0 -174
- package/dist/core/permissions/network-egress.js +0 -137
- package/dist/core/permissions/state.js +0 -241
- package/dist/core/permissions/tool-class.js +0 -107
- package/dist/core/plan-mode/ui-state.js +0 -51
- package/dist/core/plans/plan-artifact.js +0 -721
- package/dist/core/policy-limits/etag-store.js +0 -122
- package/dist/core/prd-check/parser.js +0 -215
- package/dist/core/prd-check/reporter.js +0 -127
- package/dist/core/prd-check/session-review.js +0 -557
- package/dist/core/prd-check/verifiers.js +0 -223
- package/dist/core/prompt-cache/client-cache.js +0 -99
- package/dist/core/prompts/assembly.js +0 -29
- package/dist/core/prompts/registry.js +0 -364
- package/dist/core/pugi-gitignore.js +0 -52
- package/dist/core/pugi-md/cc-compat-rules.js +0 -735
- package/dist/core/pugi-md/context-injector.js +0 -76
- package/dist/core/pugi-md/walk-up.js +0 -207
- package/dist/core/python/uv-installer.js +0 -270
- package/dist/core/python/uv-resolver.js +0 -83
- package/dist/core/rate-limit/narrator.js +0 -146
- package/dist/core/recipes/cli-types.js +0 -20
- package/dist/core/recipes/loader.js +0 -103
- package/dist/core/recipes/runner.js +0 -345
- package/dist/core/recipes/schema.js +0 -587
- package/dist/core/release-notes/parser.js +0 -241
- package/dist/core/release-notes/state.js +0 -116
- package/dist/core/repl/ask.js +0 -512
- package/dist/core/repl/cancellation.js +0 -98
- package/dist/core/repl/cap-warning.js +0 -91
- package/dist/core/repl/clipboard-read.js +0 -174
- package/dist/core/repl/dispatch-fsm.js +0 -220
- package/dist/core/repl/engine-bridge.js +0 -303
- package/dist/core/repl/history-search.js +0 -175
- package/dist/core/repl/history.js +0 -182
- package/dist/core/repl/kill-ring.js +0 -138
- package/dist/core/repl/model-pricing.js +0 -135
- package/dist/core/repl/privacy-banner.js +0 -71
- package/dist/core/repl/session.js +0 -4962
- package/dist/core/repl/slash-commands.js +0 -747
- package/dist/core/repl/store/index.js +0 -12
- package/dist/core/repl/store/jsonl-log.js +0 -321
- package/dist/core/repl/store/lockfile.js +0 -155
- package/dist/core/repl/store/session-store.js +0 -821
- package/dist/core/repl/store/types.js +0 -44
- package/dist/core/repl/store/uuid-v7.js +0 -68
- package/dist/core/repl/tool-route.js +0 -382
- package/dist/core/repl/workspace-context.js +0 -206
- package/dist/core/repo-map/build.js +0 -125
- package/dist/core/repo-map/cache.js +0 -185
- package/dist/core/repo-map/extractor.js +0 -254
- package/dist/core/repo-map/formatter.js +0 -145
- package/dist/core/repo-map/page-rank.js +0 -105
- package/dist/core/repo-map/scanner.js +0 -211
- package/dist/core/retro/git-collector.js +0 -251
- package/dist/core/retro/health-card.js +0 -25
- package/dist/core/retro/metrics.js +0 -342
- package/dist/core/retro/narrative.js +0 -249
- package/dist/core/retro/plane-collector.js +0 -274
- package/dist/core/retro/pr-issue-link.js +0 -65
- package/dist/core/retro/types.js +0 -16
- package/dist/core/retry-budget/budget.js +0 -284
- package/dist/core/retry-budget/index.js +0 -5
- package/dist/core/retry-budget/retry-cap.js +0 -74
- package/dist/core/routing/lead-worker.js +0 -43
- package/dist/core/routing/pre-flight-estimator.js +0 -108
- package/dist/core/runs/run-tree.js +0 -103
- package/dist/core/sandboxing/adapter.js +0 -29
- package/dist/core/sandboxing/index.js +0 -49
- package/dist/core/sandboxing/none.js +0 -19
- package/dist/core/sandboxing/seatbelt.js +0 -183
- package/dist/core/security/injection-scanner.js +0 -367
- package/dist/core/security/output-filter.js +0 -418
- package/dist/core/session/env-file.js +0 -105
- package/dist/core/session/section-budgets.js +0 -140
- package/dist/core/session.js +0 -377
- package/dist/core/settings.js +0 -400
- package/dist/core/share/formatter.js +0 -271
- package/dist/core/share/redactor.js +0 -221
- package/dist/core/share/uploader.js +0 -267
- package/dist/core/skills/defaults.js +0 -457
- package/dist/core/skills/loader.js +0 -454
- package/dist/core/skills/sources.js +0 -480
- package/dist/core/skills/trust.js +0 -172
- package/dist/core/smoke/headless-driver.js +0 -174
- package/dist/core/smoke/orchestrator.js +0 -194
- package/dist/core/smoke/runner.js +0 -238
- package/dist/core/smoke/scenario-parser.js +0 -316
- package/dist/core/statusline.js +0 -99
- package/dist/core/subagents/dispatcher-real.js +0 -600
- package/dist/core/subagents/dispatcher.js +0 -352
- package/dist/core/subagents/index.js +0 -39
- package/dist/core/subagents/isolation-matrix.js +0 -213
- package/dist/core/subagents/spawn.js +0 -101
- package/dist/core/telemetry/emitter.js +0 -229
- package/dist/core/telemetry/queue.js +0 -251
- package/dist/core/theme/context.js +0 -91
- package/dist/core/theme/presets.js +0 -228
- package/dist/core/theme/state.js +0 -181
- package/dist/core/todos/invariant.js +0 -10
- package/dist/core/todos/state.js +0 -177
- package/dist/core/tool-schema/compressor.js +0 -89
- package/dist/core/transport/version-interceptor.js +0 -166
- package/dist/core/trust.js +0 -109
- package/dist/core/tui/thinking-block.js +0 -64
- package/dist/core/vim/keymap.js +0 -288
- package/dist/core/vim/state.js +0 -92
- package/dist/core/watch-markers/marker-watcher.js +0 -133
- package/dist/core/worktree/include-parser.js +0 -249
- package/dist/core/worktree-manager/cleanup.js +0 -123
- package/dist/core/worktree-manager/manager.js +0 -303
- package/dist/index.js +0 -44
- package/dist/runtime/bootstrap.js +0 -190
- package/dist/runtime/cli.js +0 -8121
- package/dist/runtime/commands/agents.js +0 -385
- package/dist/runtime/commands/budget.js +0 -192
- package/dist/runtime/commands/cancel.js +0 -231
- package/dist/runtime/commands/chain.js +0 -489
- package/dist/runtime/commands/codegraph-status.js +0 -227
- package/dist/runtime/commands/compact.js +0 -297
- package/dist/runtime/commands/config.js +0 -595
- package/dist/runtime/commands/cost.js +0 -199
- package/dist/runtime/commands/delegate.js +0 -312
- package/dist/runtime/commands/dispatch.js +0 -126
- package/dist/runtime/commands/doctor.js +0 -579
- package/dist/runtime/commands/feedback.js +0 -184
- package/dist/runtime/commands/hooks.js +0 -187
- package/dist/runtime/commands/init.js +0 -254
- package/dist/runtime/commands/lsp.js +0 -368
- package/dist/runtime/commands/mcp.js +0 -935
- package/dist/runtime/commands/memory.js +0 -582
- package/dist/runtime/commands/model.js +0 -237
- package/dist/runtime/commands/onboarding.js +0 -275
- package/dist/runtime/commands/patch.js +0 -128
- package/dist/runtime/commands/permissions.js +0 -112
- package/dist/runtime/commands/plan.js +0 -143
- package/dist/runtime/commands/prd-check.js +0 -285
- package/dist/runtime/commands/privacy.js +0 -107
- package/dist/runtime/commands/recipe.js +0 -325
- package/dist/runtime/commands/redo-blob-store.js +0 -92
- package/dist/runtime/commands/redo.js +0 -361
- package/dist/runtime/commands/release-notes.js +0 -229
- package/dist/runtime/commands/repo-map.js +0 -95
- package/dist/runtime/commands/report.js +0 -299
- package/dist/runtime/commands/resume.js +0 -118
- package/dist/runtime/commands/review-consensus.js +0 -414
- package/dist/runtime/commands/rewind.js +0 -333
- package/dist/runtime/commands/roster.js +0 -117
- package/dist/runtime/commands/sessions.js +0 -163
- package/dist/runtime/commands/share.js +0 -316
- package/dist/runtime/commands/skills.js +0 -401
- package/dist/runtime/commands/status.js +0 -186
- package/dist/runtime/commands/stickers.js +0 -82
- package/dist/runtime/commands/style.js +0 -194
- package/dist/runtime/commands/theme.js +0 -196
- package/dist/runtime/commands/undo.js +0 -361
- package/dist/runtime/commands/update.js +0 -289
- package/dist/runtime/commands/vim.js +0 -140
- package/dist/runtime/commands/worktree.js +0 -177
- package/dist/runtime/commands/worktrees.js +0 -155
- package/dist/runtime/deprecation-warning.js +0 -69
- package/dist/runtime/engine-exit-code.js +0 -50
- package/dist/runtime/headless-repl.js +0 -195
- package/dist/runtime/headless.js +0 -548
- package/dist/runtime/load-hooks-or-exit.js +0 -71
- package/dist/runtime/plan-decompose.js +0 -531
- package/dist/runtime/sigint-guard.js +0 -272
- package/dist/runtime/stream-renderer.js +0 -195
- package/dist/runtime/update-check.js +0 -294
- package/dist/runtime/version.js +0 -65
- package/dist/runtime/worktree-bootstrap.js +0 -579
- package/dist/skills/bundled/batch.js +0 -617
- package/dist/skills/bundled/index.js +0 -45
- package/dist/skills/bundled/loop.js +0 -358
- package/dist/skills/bundled/remember.js +0 -383
- package/dist/skills/bundled/simplify.js +0 -289
- package/dist/skills/bundled/skillify.js +0 -373
- package/dist/skills/bundled/stuck.js +0 -558
- package/dist/skills/bundled/verify.js +0 -439
- package/dist/testing/vcr.js +0 -486
- package/dist/tools/agent-tool.js +0 -229
- package/dist/tools/apply-patch.js +0 -556
- package/dist/tools/ask-user-question.js +0 -337
- package/dist/tools/ask-user.js +0 -115
- package/dist/tools/bash.js +0 -1238
- package/dist/tools/brief.js +0 -224
- package/dist/tools/cron.js +0 -433
- package/dist/tools/enter-worktree.js +0 -250
- package/dist/tools/exit-worktree.js +0 -147
- package/dist/tools/file-tools.js +0 -553
- package/dist/tools/http-request.js +0 -336
- package/dist/tools/lsp-tools.js +0 -565
- package/dist/tools/mcp-tool.js +0 -260
- package/dist/tools/multi-edit.js +0 -361
- package/dist/tools/powershell.js +0 -268
- package/dist/tools/registry.js +0 -166
- package/dist/tools/server-tools.js +0 -892
- package/dist/tools/skill-tool.js +0 -96
- package/dist/tools/sleep.js +0 -99
- package/dist/tools/synthetic-output.js +0 -133
- package/dist/tools/tasks.js +0 -208
- package/dist/tools/todo-write.js +0 -184
- package/dist/tools/verify-plan-execution.js +0 -295
- package/dist/tools/web-fetch-injection-scanner.js +0 -207
- package/dist/tools/web-fetch.js +0 -720
- package/dist/tools/web-search.js +0 -458
- package/dist/tui/agent-progress-card.js +0 -111
- package/dist/tui/agent-tree-pane.js +0 -9
- package/dist/tui/agent-tree.js +0 -87
- package/dist/tui/ask-cli.js +0 -52
- package/dist/tui/ask-modal.js +0 -211
- package/dist/tui/ask-user-question-chips.js +0 -315
- package/dist/tui/ask-user-question-prompt.js +0 -203
- package/dist/tui/compact-banner.js +0 -81
- package/dist/tui/conversation-pane.js +0 -164
- package/dist/tui/cost-table.js +0 -111
- package/dist/tui/device-flow.js +0 -142
- package/dist/tui/doctor-table.js +0 -46
- package/dist/tui/feedback-prompt.js +0 -156
- package/dist/tui/input-box.js +0 -732
- package/dist/tui/login-picker.js +0 -69
- package/dist/tui/markdown-render.js +0 -266
- package/dist/tui/multi-file-diff-approval.js +0 -375
- package/dist/tui/onboarding-wizard.js +0 -240
- package/dist/tui/permissions-picker.js +0 -86
- package/dist/tui/render.js +0 -160
- package/dist/tui/repl-render.js +0 -770
- package/dist/tui/repl-splash-art.js +0 -64
- package/dist/tui/repl-splash-mascot.js +0 -154
- package/dist/tui/repl-splash.js +0 -117
- package/dist/tui/repl.js +0 -378
- package/dist/tui/slash-palette.js +0 -106
- package/dist/tui/splash-data.js +0 -61
- package/dist/tui/splash.js +0 -31
- package/dist/tui/status-bar.js +0 -209
- package/dist/tui/status-table.js +0 -7
- package/dist/tui/stickers-art.js +0 -136
- package/dist/tui/style-table.js +0 -28
- package/dist/tui/theme-table.js +0 -29
- package/dist/tui/thinking-spinner.js +0 -123
- package/dist/tui/tool-stream-pane.js +0 -140
- package/dist/tui/update-banner.js +0 -33
- package/dist/tui/vim-input.js +0 -267
- package/dist/tui/welcome-banner.js +0 -107
- package/dist/tui/welcome-data.js +0 -293
- package/dist/tui/workspace-context.js +0 -105
- package/docs/examples/codegraph.mcp.json +0 -10
- package/test/scenarios/codegen-create-file.scenario.txt +0 -13
- package/test/scenarios/compact-force.scenario.txt +0 -12
- package/test/scenarios/identity.scenario.txt +0 -11
- package/test/scenarios/persona-handoff.scenario.txt +0 -12
- package/test/scenarios/walkback.scenario.txt +0 -12
package/dist/tui/input-box.js
DELETED
|
@@ -1,732 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* REPL input box - Sprint (REPL UX P0 wave 1).
|
|
4
|
-
*
|
|
5
|
-
* Bordered, cursor-aware input box matching the upstream tool / peer CLI
|
|
6
|
-
* aesthetics. Layered upgrade over :
|
|
7
|
-
*
|
|
8
|
-
* - Top divider (full-width cyan rule) anchors the input below the
|
|
9
|
-
* main pane and gives the operator a clear visual seam.
|
|
10
|
-
* - Rounded cyan border wraps the prompt + line + cursor.
|
|
11
|
-
* - Cursor follows the caret position (left/right arrows + Home/End
|
|
12
|
-
* reposition without losing the trailing text).
|
|
13
|
-
* - Single-arrow `›` prompt in brand cyan; dim continuation prompt
|
|
14
|
-
* `┊` when the input wraps past the available width so the operator
|
|
15
|
-
* sees that they are still inside one logical line.
|
|
16
|
-
* - History navigation (↑/↓), slash palette inline suggestion, Esc
|
|
17
|
-
* cancel, Ctrl+C ×2 exit remain wired identically to .
|
|
18
|
-
*
|
|
19
|
-
* State that belongs in this component:
|
|
20
|
-
* - `line` (string) - current buffer
|
|
21
|
-
* - `cursor` (number) - caret offset into `line`
|
|
22
|
-
* - `history` (string[]) - session-local; persistence lands in
|
|
23
|
-
* commit 2 (per-workspace JSONL).
|
|
24
|
-
* - `historyIndex` (number) - -1 when not navigating
|
|
25
|
-
*
|
|
26
|
-
* Brand voice gate: no forbidden words. ASCII-only glyphs.
|
|
27
|
-
*/
|
|
28
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
29
|
-
import { Box, Text, useInput, useStdout } from 'ink';
|
|
30
|
-
import { append as appendHistory, read as readHistory, } from '../core/repl/history.js';
|
|
31
|
-
import { applyQuery, currentBrief, cycle, initialSearchState, } from '../core/repl/history-search.js';
|
|
32
|
-
import { SlashPalette, completePalette, filterPalette, } from './slash-palette.js';
|
|
33
|
-
import { EMPTY_KILL_RING, killToLineEnd, killToLineStart, killWordBackward, yankAtCursor, } from '../core/repl/kill-ring.js';
|
|
34
|
-
import { readClipboard } from '../core/repl/clipboard-read.js';
|
|
35
|
-
const CTRL_C_DOUBLE_TAP_MS = 1_000;
|
|
36
|
-
/**
|
|
37
|
-
* BT 8 (the upstream tool parity): Esc-Esc walks the conversation back
|
|
38
|
-
* one turn. 500ms is tight enough that an operator clearing the buffer +
|
|
39
|
-
* later changing their mind does NOT accidentally pop a turn, while
|
|
40
|
-
* still feeling like one motion. Matches the upstream tool's documented
|
|
41
|
-
* double-Esc window.
|
|
42
|
-
*/
|
|
43
|
-
const ESCAPE_DOUBLE_TAP_MS = 500;
|
|
44
|
-
/** Width subtracted from the terminal width so the border + padding fit. */
|
|
45
|
-
const FRAME_OVERHEAD_COLUMNS = 4;
|
|
46
|
-
/** Fallback width when ink cannot read stdout (e.g. test harness). */
|
|
47
|
-
const FALLBACK_COLUMNS = 80;
|
|
48
|
-
/** Cursor blink interval (ms). 530ms matches the GNOME / iTerm2 default. */
|
|
49
|
-
const CURSOR_BLINK_MS = 530;
|
|
50
|
-
/**
|
|
51
|
-
* Strip ANSI escape sequences, bracketed-paste markers, and C0 control
|
|
52
|
-
* bytes (except `\n` and `\t`) from clipboard text before splicing it
|
|
53
|
-
* into the buffer. Keystroke input never reaches the input box with
|
|
54
|
-
* escapes (ink filters them), so the buffer assumes printable text +
|
|
55
|
-
* the two whitespace forms we preserve. Clipboard text bypasses ink's
|
|
56
|
-
* filter, so we re-apply the same constraint here to neutralise any
|
|
57
|
-
* escape sequence a hostile or accidental copy might carry into the
|
|
58
|
-
* operator's terminal.
|
|
59
|
-
*/
|
|
60
|
-
export function sanitiseClipboardText(raw) {
|
|
61
|
-
return raw
|
|
62
|
-
.replace(/\x1b\[200~/g, '')
|
|
63
|
-
.replace(/\x1b\[201~/g, '')
|
|
64
|
-
.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '')
|
|
65
|
-
.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '')
|
|
66
|
-
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
|
|
67
|
-
}
|
|
68
|
-
export function InputBox(props) {
|
|
69
|
-
// Seed history from disk on mount so the operator can ↑ to last
|
|
70
|
-
// session's brief immediately. Memoised so we read the file once per
|
|
71
|
-
// workspace, not on every render.
|
|
72
|
-
const seededHistory = useMemo(() => {
|
|
73
|
-
if (!props.workspaceSlug)
|
|
74
|
-
return [];
|
|
75
|
-
const entries = readHistory({
|
|
76
|
-
home: props.historyHome,
|
|
77
|
-
workspaceSlug: props.workspaceSlug,
|
|
78
|
-
});
|
|
79
|
-
return entries.map((e) => e.brief);
|
|
80
|
-
}, [props.workspaceSlug, props.historyHome]);
|
|
81
|
-
const [line, setLine] = useState(props.initial ?? '');
|
|
82
|
-
const [cursor, setCursor] = useState(props.initial?.length ?? 0);
|
|
83
|
-
const [history, setHistory] = useState(seededHistory);
|
|
84
|
-
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
85
|
-
const [lastCtrlCAt, setLastCtrlCAt] = useState(undefined);
|
|
86
|
-
// CEO P0 #2 : the upstream tool parity — surface а visible
|
|
87
|
-
// "Press Ctrl+C again to exit" toast on the first Ctrl+C press so
|
|
88
|
-
// the operator knows the second press will terminate the REPL.
|
|
89
|
-
// Auto-clears after CTRL_C_DOUBLE_TAP_MS so it never lingers past
|
|
90
|
-
// the double-tap window.
|
|
91
|
-
const [ctrlCToast, setCtrlCToast] = useState(null);
|
|
92
|
-
const ctrlCToastTimerRef = useRef(null);
|
|
93
|
-
// BT 8: Esc-Esc walkback double-tap window. Tracks the epoch
|
|
94
|
-
// ms of the most recent Esc press so the next Esc within
|
|
95
|
-
// ESCAPE_DOUBLE_TAP_MS triggers the walkback handler instead of
|
|
96
|
-
// re-clearing the buffer.
|
|
97
|
-
const [lastEscapeAt, setLastEscapeAt] = useState(undefined);
|
|
98
|
-
const [cursorVisible, setCursorVisible] = useState(true);
|
|
99
|
-
// Ctrl+R / Ctrl+S reverse-search mode. Undefined when idle, a
|
|
100
|
-
// HistorySearchState while the operator is searching.
|
|
101
|
-
const [search, setSearch] = useState(undefined);
|
|
102
|
-
// Draft preserved while the operator searches so Esc returns the
|
|
103
|
-
// pre-search buffer instead of dropping it on the floor.
|
|
104
|
-
const [draftBeforeSearch, setDraftBeforeSearch] = useState('');
|
|
105
|
-
// Slash palette state. When the buffer starts with `/` the palette
|
|
106
|
-
// renders, ↑/↓ selects a row, Tab autocompletes, Enter runs.
|
|
107
|
-
const [paletteIndex, setPaletteIndex] = useState(0);
|
|
108
|
-
// Operator-toggled palette suppression. Esc closes the palette
|
|
109
|
-
// without clearing the buffer (useful for `/help` text dispatch).
|
|
110
|
-
const [paletteSuppressed, setPaletteSuppressed] = useState(false);
|
|
111
|
-
// Readline-style kill ring backing Ctrl+U / Ctrl+K / Ctrl+W / Ctrl+Y.
|
|
112
|
-
const [killRing, setKillRing] = useState(EMPTY_KILL_RING);
|
|
113
|
-
// Soft counter; bumping forces Ink to re-render the surrounding
|
|
114
|
-
// panes when Ctrl+L wipes the terminal (the parent React tree is
|
|
115
|
-
// otherwise stable and would not redraw on a stdout.write alone).
|
|
116
|
-
const [, setRedrawTick] = useState(0);
|
|
117
|
-
// Shift+Tab toast — flashed for 2s after a mode cycle so the
|
|
118
|
-
// operator sees `Mode → acceptEdits` под the input divider. Cleared
|
|
119
|
-
// by a setTimeout so a quick second Shift+Tab refreshes the toast.
|
|
120
|
-
const [modeCycleToast, setModeCycleToast] = useState(null);
|
|
121
|
-
const modeCycleTimerRef = useRef(null);
|
|
122
|
-
const now = props.now ?? Date.now;
|
|
123
|
-
const { stdout } = useStdout();
|
|
124
|
-
const columns = stdout?.columns ?? FALLBACK_COLUMNS;
|
|
125
|
-
const innerWidth = Math.max(20, columns - FRAME_OVERHEAD_COLUMNS);
|
|
126
|
-
// Refs mirror the latest committed line + cursor so the async clipboard
|
|
127
|
-
// paste handler can splice against current values without re-entering
|
|
128
|
-
// React's setState updater (which strict mode invokes twice and would
|
|
129
|
-
// double-insert the pasted text). The setState updater pattern used to
|
|
130
|
-
// capture latest line/cursor (see Codex P2 below) is correct under
|
|
131
|
-
// single-render mode but broken under React 18 strict mode + the
|
|
132
|
-
// setLine-inside-setCursor nesting we ended up with. Refs are the
|
|
133
|
-
// canonical escape hatch for "use the most recently committed value
|
|
134
|
-
// inside a one-shot async callback".
|
|
135
|
-
const lineRef = useRef(line);
|
|
136
|
-
const cursorRef = useRef(cursor);
|
|
137
|
-
useEffect(() => {
|
|
138
|
-
lineRef.current = line;
|
|
139
|
-
}, [line]);
|
|
140
|
-
useEffect(() => {
|
|
141
|
-
cursorRef.current = cursor;
|
|
142
|
-
}, [cursor]);
|
|
143
|
-
// Soft blink so the operator can spot where typing will land. Ink
|
|
144
|
-
// re-renders only when state changes, so a 530ms toggle is the cheapest
|
|
145
|
-
// honest cursor we can show without a custom raw-mode dance. Tests
|
|
146
|
-
// disable the interval so node:test does not see a dangling timer.
|
|
147
|
-
const blinkEnabled = props.blinkCursor ?? true;
|
|
148
|
-
useEffect(() => {
|
|
149
|
-
if (!blinkEnabled)
|
|
150
|
-
return undefined;
|
|
151
|
-
const interval = setInterval(() => {
|
|
152
|
-
setCursorVisible((prev) => !prev);
|
|
153
|
-
}, CURSOR_BLINK_MS);
|
|
154
|
-
return () => clearInterval(interval);
|
|
155
|
-
}, [blinkEnabled]);
|
|
156
|
-
useInput((input, key) => {
|
|
157
|
-
if (key.ctrl && input === 'c') {
|
|
158
|
-
const t = now();
|
|
159
|
-
// : the upstream tool-style double-press semantics. First Ctrl+C
|
|
160
|
-
// ALWAYS attempts to cancel an in-flight dispatch (when the
|
|
161
|
-
// session reports non-idle); second Ctrl+C within 1s exits the
|
|
162
|
-
// process. If onCancel is omitted (legacy callers, tests), the
|
|
163
|
-
// old behaviour is preserved: first Ctrl+C clears the buffer +
|
|
164
|
-
// arms the exit timer, second Ctrl+C exits.
|
|
165
|
-
const withinDoubleTapWindow = typeof lastCtrlCAt === 'number' && t - lastCtrlCAt <= CTRL_C_DOUBLE_TAP_MS;
|
|
166
|
-
if (withinDoubleTapWindow) {
|
|
167
|
-
// Second press inside the window — always exit. This matches
|
|
168
|
-
// the upstream tool: even mid-dispatch, the second Ctrl+C wins so
|
|
169
|
-
// the operator can always escape a stuck REPL.
|
|
170
|
-
props.onExit();
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
// First press in a fresh window. If the host wired a cancel
|
|
174
|
-
// surface and there is something to cancel, abort the dispatch.
|
|
175
|
-
// The buffer is left untouched on a cancel (the operator's
|
|
176
|
-
// current input is NOT trashed by an accidental Ctrl+C while a
|
|
177
|
-
// tool is running).
|
|
178
|
-
//
|
|
179
|
-
// Three-valued onCancel return (see prop docstring):
|
|
180
|
-
// - true → dispatch cancelled, keep buffer, arm exit timer
|
|
181
|
-
// - false → idle, clear buffer (legacy), arm exit timer
|
|
182
|
-
// - undefined → handler bypassed (modal owns input); NO state
|
|
183
|
-
// change at all. Buffer stays, exit timer NOT
|
|
184
|
-
// armed (otherwise the modal would silently
|
|
185
|
-
// promote a Ctrl+C to "press again to exit",
|
|
186
|
-
// which is wrong context for a modal cancel).
|
|
187
|
-
let cancelResult;
|
|
188
|
-
if (props.onCancel) {
|
|
189
|
-
cancelResult = props.onCancel();
|
|
190
|
-
}
|
|
191
|
-
if (cancelResult === undefined && props.onCancel) {
|
|
192
|
-
// Bypass path - modal owns the input. Drop the press silently
|
|
193
|
-
// so the modal's own cancel surface (Esc / its own Ctrl+C
|
|
194
|
-
// binding inside the modal component) takes effect on its own
|
|
195
|
-
// terms. P2 fix: previously this fell through to the
|
|
196
|
-
// legacy buffer-clear + setLastCtrlCAt path and wiped modal
|
|
197
|
-
// draft text on first Ctrl+C.
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
setLastCtrlCAt(t);
|
|
201
|
-
// CEO P0 #2 : surface the "Press Ctrl+C again to
|
|
202
|
-
// exit" toast on the first press so the operator sees the
|
|
203
|
-
// double-tap semantics in the UI, not just в the bottom hint
|
|
204
|
-
// line. Mirrors the standard tool's exit affordance verbatim. The
|
|
205
|
-
// toast string varies by which branch fired (cancel vs idle
|
|
206
|
-
// clear) so the operator learns what the press just did:
|
|
207
|
-
//
|
|
208
|
-
// - cancelResult === true → "Aborted. Press Ctrl+C again to exit."
|
|
209
|
-
// - cancelResult === false → "Press Ctrl+C again to exit."
|
|
210
|
-
//
|
|
211
|
-
// (The undefined branch already returned above — а modal owns
|
|
212
|
-
// input и the toast is suppressed.)
|
|
213
|
-
const toastCopy = cancelResult === true
|
|
214
|
-
? 'Aborted. Press Ctrl+C again to exit.'
|
|
215
|
-
: 'Press Ctrl+C again to exit.';
|
|
216
|
-
setCtrlCToast(toastCopy);
|
|
217
|
-
if (ctrlCToastTimerRef.current)
|
|
218
|
-
clearTimeout(ctrlCToastTimerRef.current);
|
|
219
|
-
ctrlCToastTimerRef.current = setTimeout(() => {
|
|
220
|
-
setCtrlCToast(null);
|
|
221
|
-
ctrlCToastTimerRef.current = null;
|
|
222
|
-
}, CTRL_C_DOUBLE_TAP_MS);
|
|
223
|
-
// Legacy behaviour: on idle (or no onCancel wired), clear the
|
|
224
|
-
// buffer + reset search so the operator's screen is calm before
|
|
225
|
-
// they confirm exit. When we DID cancel a live dispatch, keep
|
|
226
|
-
// the buffer so a half-typed brief is not lost.
|
|
227
|
-
if (cancelResult !== true) {
|
|
228
|
-
setLine('');
|
|
229
|
-
setCursor(0);
|
|
230
|
-
setSearch(undefined);
|
|
231
|
-
}
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
// — the upstream tool parity: Shift+Tab cycles permission mode.
|
|
235
|
-
// The host owns the cycle logic + persistence; we just intercept
|
|
236
|
-
// the chord and surface a one-line toast on success. Place this
|
|
237
|
-
// BEFORE the search-mode and palette branches so a Shift+Tab fires
|
|
238
|
-
// even while reverse-search is active (operator habit-driven).
|
|
239
|
-
if (key.shift && key.tab && props.onCyclePermissionMode) {
|
|
240
|
-
const nextMode = props.onCyclePermissionMode();
|
|
241
|
-
if (nextMode) {
|
|
242
|
-
setModeCycleToast(`Mode → ${nextMode}`);
|
|
243
|
-
if (modeCycleTimerRef.current)
|
|
244
|
-
clearTimeout(modeCycleTimerRef.current);
|
|
245
|
-
modeCycleTimerRef.current = setTimeout(() => {
|
|
246
|
-
setModeCycleToast(null);
|
|
247
|
-
modeCycleTimerRef.current = null;
|
|
248
|
-
}, 2_000);
|
|
249
|
-
}
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
// Search-mode key handling. Ctrl+R / Ctrl+S cycle, Enter accepts,
|
|
253
|
-
// Esc cancels (restoring the pre-search draft), backspace shortens
|
|
254
|
-
// the query, typed characters extend it.
|
|
255
|
-
if (search) {
|
|
256
|
-
if (key.ctrl && input === 'r') {
|
|
257
|
-
setSearch((s) => (s ? cycle(s, 1) : s));
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
if (key.ctrl && input === 's') {
|
|
261
|
-
setSearch((s) => (s ? cycle(s, -1) : s));
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
if (key.escape) {
|
|
265
|
-
setSearch(undefined);
|
|
266
|
-
setLine(draftBeforeSearch);
|
|
267
|
-
setCursor(draftBeforeSearch.length);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
// Bare LF accepts the focused match, same as CR (`key.return`).
|
|
271
|
-
// See the post-search block below for the rationale.
|
|
272
|
-
if (key.return || (input === '\n' && !key.meta && !key.ctrl && !key.shift)) {
|
|
273
|
-
const picked = currentBrief(search);
|
|
274
|
-
setSearch(undefined);
|
|
275
|
-
if (picked !== null) {
|
|
276
|
-
setLine(picked);
|
|
277
|
-
setCursor(picked.length);
|
|
278
|
-
}
|
|
279
|
-
else {
|
|
280
|
-
setLine(draftBeforeSearch);
|
|
281
|
-
setCursor(draftBeforeSearch.length);
|
|
282
|
-
}
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
if (key.backspace || key.delete) {
|
|
286
|
-
const nextQuery = search.query.slice(0, -1);
|
|
287
|
-
setSearch(applyQuery(search, nextQuery, history));
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
if (input && !key.meta && !key.ctrl) {
|
|
291
|
-
// Drop a bare LF from the search query — the Enter-accept
|
|
292
|
-
// branch above already handled it; falling through here would
|
|
293
|
-
// splice a newline into the search string.
|
|
294
|
-
if (input === '\n')
|
|
295
|
-
return;
|
|
296
|
-
const nextQuery = search.query + input;
|
|
297
|
-
setSearch(applyQuery(search, nextQuery, history));
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
// Any other key inside search mode is ignored - the operator can
|
|
301
|
-
// still escape with Esc or cancel via Ctrl+C handled above.
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
if (key.ctrl && input === 'r') {
|
|
305
|
-
// Enter reverse-search mode. Stash the current buffer so Esc
|
|
306
|
-
// restores it untouched.
|
|
307
|
-
setDraftBeforeSearch(line);
|
|
308
|
-
setSearch(initialSearchState(history));
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
if (key.ctrl && input === 's') {
|
|
312
|
-
// Symmetric forward search entry. Cycle direction differs once
|
|
313
|
-
// active; entry seeds the same browse state.
|
|
314
|
-
setDraftBeforeSearch(line);
|
|
315
|
-
setSearch(initialSearchState(history));
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
// P0 fix (CEO dogfood, second iteration): bare LF (`\n`)
|
|
319
|
-
// MUST submit the brief, same as bare CR (`\r`). Ink's parseKeypress
|
|
320
|
-
// maps `\r` to `key.return` and `\n` to `key.name === 'enter'`
|
|
321
|
-
// WITHOUT setting `key.return`. Most real terminals deliver CR for
|
|
322
|
-
// Enter (ICRNL on by default), so the `key.return` branch below
|
|
323
|
-
// catches them. But when stdin is a PTY whose parent writes raw
|
|
324
|
-
// `\n` (Python's `pty.fork` + `os.write(fd, b"\n")`, automation
|
|
325
|
-
// harnesses, certain SSH multiplexers), the LF arrives as a
|
|
326
|
-
// printable char.
|
|
327
|
-
//
|
|
328
|
-
// PR (beta.45) fixed the case where `input === '\n'` exactly.
|
|
329
|
-
// CEO PTY smoke surfaced the REAL shape: when the parent
|
|
330
|
-
// writes the brief AND the Enter as separate `os.write` calls (or
|
|
331
|
-
// even when it doesn't), Node's stdin buffer COALESCES them into
|
|
332
|
-
// ONE chunk before Ink delivers the `useInput` event. The repro
|
|
333
|
-
// confirmed via stderr instrumentation: typing `hi\n` arrives in
|
|
334
|
-
// input-box as `bytes=[68 69 0a] len=3 flags=-` — a SINGLE 3-char
|
|
335
|
-
// chunk "hi\n" with no key flags. The PR branch (`input ===
|
|
336
|
-
// '\n'`) does not match, so `hi\n` falls through to the printable-
|
|
337
|
-
// char branch and the literal newline lands in the buffer as
|
|
338
|
-
// `› hi\n █` (multi-line composer, brief never dispatches, status
|
|
339
|
-
// stays `idle` forever).
|
|
340
|
-
//
|
|
341
|
-
// Fix: detect a TRAILING `\n` in a printable chunk with no
|
|
342
|
-
// modifiers — type the prefix into the buffer, then submit. The
|
|
343
|
-
// discriminator that keeps multi-line paste working: the chunk
|
|
344
|
-
// must contain EXACTLY ONE `\n` (the trailing one) and no other
|
|
345
|
-
// newlines. Multi-line pastes have ≥2 `\n` characters (or arrive
|
|
346
|
-
// wrapped in bracketed-paste markers handled below), so they
|
|
347
|
-
// still preserve interior newlines via the printable-char branch.
|
|
348
|
-
//
|
|
349
|
-
// Detection contract:
|
|
350
|
-
// - `input` ends with `\n`
|
|
351
|
-
// - no Ctrl / Meta / Shift modifiers
|
|
352
|
-
// - exactly ONE `\n` in the chunk (the trailing one)
|
|
353
|
-
// - chunk is not bracketed-paste wrapped (markers stripped below)
|
|
354
|
-
//
|
|
355
|
-
// Edge cases covered by `test/input-box-lf-submit.spec.tsx`:
|
|
356
|
-
// - bare `\n` → submit empty (no-op on empty buf)
|
|
357
|
-
// - `hi\n` → splice `hi` + submit
|
|
358
|
-
// - `hi\nthere\n` (multi-line) → printable branch, preserves \n
|
|
359
|
-
// - `\r` (CR) → key.return branch unchanged
|
|
360
|
-
// - `hi\r\n` (CRLF) → key.return branch (CR wins first)
|
|
361
|
-
const endsWithLf = input.length > 0 && input.charCodeAt(input.length - 1) === 0x0a;
|
|
362
|
-
const newlineCount = (input.match(/\n/g) || []).length;
|
|
363
|
-
if (endsWithLf
|
|
364
|
-
&& newlineCount === 1
|
|
365
|
-
&& !key.meta
|
|
366
|
-
&& !key.ctrl
|
|
367
|
-
&& !key.shift) {
|
|
368
|
-
// Splice the prefix (everything before the trailing `\n`) into
|
|
369
|
-
// the buffer at the cursor, then run the canonical submit path.
|
|
370
|
-
// Refs (cursorRef / lineRef) hold the latest committed values so
|
|
371
|
-
// the splice runs against the operator's most recent edits even
|
|
372
|
-
// if a previous async paste / setState is still mid-flight.
|
|
373
|
-
const prefix = input.slice(0, -1);
|
|
374
|
-
let mergedLine = lineRef.current;
|
|
375
|
-
let mergedCursor = cursorRef.current;
|
|
376
|
-
if (prefix.length > 0) {
|
|
377
|
-
// Same sanitisation as the printable-char branch below — strip
|
|
378
|
-
// bracketed-paste markers so a stray escape sequence never
|
|
379
|
-
// lands in the submitted brief.
|
|
380
|
-
const stripped = prefix
|
|
381
|
-
.replace(/\x1b\[200~/g, '')
|
|
382
|
-
.replace(/\x1b\[201~/g, '')
|
|
383
|
-
.replace(/\[200~/g, '')
|
|
384
|
-
.replace(/\[201~/g, '');
|
|
385
|
-
if (stripped.length > 0) {
|
|
386
|
-
mergedLine =
|
|
387
|
-
mergedLine.slice(0, mergedCursor) + stripped + mergedLine.slice(mergedCursor);
|
|
388
|
-
mergedCursor = mergedCursor + stripped.length;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
// Synthesise the same payload-shape the `key.return` branch
|
|
392
|
-
// below uses so palette completion + history dedup + onSubmit
|
|
393
|
-
// dispatch all run identically.
|
|
394
|
-
const paletteHere = !paletteSuppressed
|
|
395
|
-
? filterPalette(mergedLine)
|
|
396
|
-
: { rows: [], totalBeforeLimit: 0 };
|
|
397
|
-
const paletteOpenHere = paletteHere.rows.length > 0;
|
|
398
|
-
const paletteFocusedIndexHere = paletteHere.rows.length === 0
|
|
399
|
-
? 0
|
|
400
|
-
: Math.min(paletteIndex, paletteHere.rows.length - 1);
|
|
401
|
-
let payload = mergedLine;
|
|
402
|
-
if (paletteOpenHere) {
|
|
403
|
-
const completed = completePalette(mergedLine, paletteHere.rows, paletteFocusedIndexHere);
|
|
404
|
-
if (completed !== null)
|
|
405
|
-
payload = completed;
|
|
406
|
-
}
|
|
407
|
-
const trimmed = payload.trim();
|
|
408
|
-
if (trimmed.length > 0) {
|
|
409
|
-
setHistory((prev) => {
|
|
410
|
-
if (prev[prev.length - 1] === trimmed)
|
|
411
|
-
return prev;
|
|
412
|
-
return [...prev, trimmed];
|
|
413
|
-
});
|
|
414
|
-
setHistoryIndex(-1);
|
|
415
|
-
if (props.workspaceSlug) {
|
|
416
|
-
appendHistory({
|
|
417
|
-
home: props.historyHome,
|
|
418
|
-
workspaceSlug: props.workspaceSlug,
|
|
419
|
-
brief: trimmed,
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
props.onSubmit(trimmed);
|
|
423
|
-
}
|
|
424
|
-
setLine('');
|
|
425
|
-
setCursor(0);
|
|
426
|
-
setPaletteSuppressed(false);
|
|
427
|
-
setPaletteIndex(0);
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
// Readline-style kill ring shortcuts. All four kills push the
|
|
431
|
-
// removed slice onto the ring; Ctrl+Y yanks the most recent.
|
|
432
|
-
if (key.ctrl && input === 'u') {
|
|
433
|
-
const next = killToLineStart(line, cursor, killRing);
|
|
434
|
-
setLine(next.line);
|
|
435
|
-
setCursor(next.cursor);
|
|
436
|
-
setKillRing(next.ring);
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
if (key.ctrl && input === 'k') {
|
|
440
|
-
const next = killToLineEnd(line, cursor, killRing);
|
|
441
|
-
setLine(next.line);
|
|
442
|
-
setCursor(next.cursor);
|
|
443
|
-
setKillRing(next.ring);
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
if (key.ctrl && input === 'w') {
|
|
447
|
-
const next = killWordBackward(line, cursor, killRing);
|
|
448
|
-
setLine(next.line);
|
|
449
|
-
setCursor(next.cursor);
|
|
450
|
-
setKillRing(next.ring);
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
if (key.ctrl && input === 'y') {
|
|
454
|
-
const next = yankAtCursor(line, cursor, killRing);
|
|
455
|
-
setLine(next.line);
|
|
456
|
-
setCursor(next.cursor);
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
if (key.ctrl && input === 'l') {
|
|
460
|
-
// Clear the terminal viewport. ANSI 2J = erase entire screen,
|
|
461
|
-
// 0;0H = home cursor. We bump the redraw tick so Ink repaints
|
|
462
|
-
// the surrounding REPL panes on the next frame; without this
|
|
463
|
-
// ink keeps its diff buffer and we get a half-wiped screen.
|
|
464
|
-
if (stdout && typeof stdout.write === 'function') {
|
|
465
|
-
stdout.write('\x1b[2J\x1b[0;0H');
|
|
466
|
-
}
|
|
467
|
-
setRedrawTick((t) => t + 1);
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
if (key.ctrl && input === 'v') {
|
|
471
|
-
// Ctrl+V: read the OS clipboard and insert at cursor. The
|
|
472
|
-
// platform helper (pbpaste / wl-paste / xclip -o / Get-Clipboard)
|
|
473
|
-
// is async + best-effort; on failure the buffer is left
|
|
474
|
-
// untouched and the operator falls back to terminal paste
|
|
475
|
-
// (Cmd+V on macOS, right-click on Linux).
|
|
476
|
-
//
|
|
477
|
-
// Multi-line pastes preserve interior newlines so the operator
|
|
478
|
-
// sees the full text in the buffer; the Enter-on-buffered-LF
|
|
479
|
-
// case is left to the operator (they press Enter when ready).
|
|
480
|
-
//
|
|
481
|
-
// Refs (lineRef / cursorRef) hold the latest committed values so
|
|
482
|
-
// the splice runs against the operator's most recent edits even
|
|
483
|
-
// if they kept typing while pbpaste was in flight. The previous
|
|
484
|
-
// setLine-inside-setCursor nesting double-inserted under React
|
|
485
|
-
// 18 strict mode because each setState updater runs twice in
|
|
486
|
-
// development. Claude P1.
|
|
487
|
-
//
|
|
488
|
-
// sanitiseClipboardText strips ANSI escapes, bracketed-paste
|
|
489
|
-
// markers, and C0 control bytes so a hostile or accidental copy
|
|
490
|
-
// cannot inject terminal-control sequences. Claude P2.
|
|
491
|
-
readClipboard()
|
|
492
|
-
.then((res) => {
|
|
493
|
-
if (res.text === null)
|
|
494
|
-
return;
|
|
495
|
-
const sanitised = sanitiseClipboardText(res.text);
|
|
496
|
-
if (sanitised.length === 0)
|
|
497
|
-
return;
|
|
498
|
-
const currentLine = lineRef.current;
|
|
499
|
-
const currentCursor = cursorRef.current;
|
|
500
|
-
const newLine = currentLine.slice(0, currentCursor) +
|
|
501
|
-
sanitised +
|
|
502
|
-
currentLine.slice(currentCursor);
|
|
503
|
-
const newCursor = currentCursor + sanitised.length;
|
|
504
|
-
setLine(newLine);
|
|
505
|
-
setCursor(newCursor);
|
|
506
|
-
})
|
|
507
|
-
.catch(() => {
|
|
508
|
-
// Helper already swallows errors; the catch here is belt +
|
|
509
|
-
// suspenders so a TypeError never bubbles into the render.
|
|
510
|
-
});
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
// Compute palette visibility FIRST so Enter can run the focused
|
|
514
|
-
// palette row instead of submitting a partial `/he` as unknown.
|
|
515
|
-
// Palette is hidden when the operator pressed Esc on it.
|
|
516
|
-
const palette = !paletteSuppressed ? filterPalette(line) : { rows: [], totalBeforeLimit: 0 };
|
|
517
|
-
const paletteOpen = palette.rows.length > 0;
|
|
518
|
-
const paletteFocusedIndex = palette.rows.length === 0
|
|
519
|
-
? 0
|
|
520
|
-
: Math.min(paletteIndex, palette.rows.length - 1);
|
|
521
|
-
if (key.return) {
|
|
522
|
-
// When the palette is open, Enter expands the buffer to the
|
|
523
|
-
// complete command name (preserving any args past the head)
|
|
524
|
-
// before submission. Codex review P2.
|
|
525
|
-
let payload = line;
|
|
526
|
-
if (paletteOpen) {
|
|
527
|
-
const completed = completePalette(line, palette.rows, paletteFocusedIndex);
|
|
528
|
-
if (completed !== null)
|
|
529
|
-
payload = completed;
|
|
530
|
-
}
|
|
531
|
-
const trimmed = payload.trim();
|
|
532
|
-
if (trimmed.length > 0) {
|
|
533
|
-
setHistory((prev) => {
|
|
534
|
-
// In-session dedup of consecutive identical entries mirrors
|
|
535
|
-
// the on-disk dedup so ↑ never shows duplicates.
|
|
536
|
-
if (prev[prev.length - 1] === trimmed)
|
|
537
|
-
return prev;
|
|
538
|
-
return [...prev, trimmed];
|
|
539
|
-
});
|
|
540
|
-
setHistoryIndex(-1);
|
|
541
|
-
if (props.workspaceSlug) {
|
|
542
|
-
// Fire-and-forget. Helper never throws - it returns null
|
|
543
|
-
// when storage is unavailable and history degrades to
|
|
544
|
-
// session-local for this turn.
|
|
545
|
-
appendHistory({
|
|
546
|
-
home: props.historyHome,
|
|
547
|
-
workspaceSlug: props.workspaceSlug,
|
|
548
|
-
brief: trimmed,
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
props.onSubmit(trimmed);
|
|
552
|
-
}
|
|
553
|
-
setLine('');
|
|
554
|
-
setCursor(0);
|
|
555
|
-
setPaletteSuppressed(false);
|
|
556
|
-
setPaletteIndex(0);
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
if (key.escape) {
|
|
560
|
-
if (paletteOpen) {
|
|
561
|
-
// Close the palette without clearing the buffer so the operator
|
|
562
|
-
// can still send `/help` as plain text if they want. Palette
|
|
563
|
-
// takes precedence over walkback because the operator's mental
|
|
564
|
-
// model is "Esc closes the visible overlay first".
|
|
565
|
-
setPaletteSuppressed(true);
|
|
566
|
-
setLastEscapeAt(undefined);
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
// BT 8: Esc-Esc walkback. Two presses within
|
|
570
|
-
// ESCAPE_DOUBLE_TAP_MS step the conversation back by one turn.
|
|
571
|
-
// First press still clears the buffer (legacy behaviour for the
|
|
572
|
-
// single-Esc cancel UX); the second press calls the host's
|
|
573
|
-
// walkback handler. Buffer-clear on the first press is what makes
|
|
574
|
-
// the double-tap feel "free" - the operator did not have to
|
|
575
|
-
// memorise a new chord; they just have to keep pressing.
|
|
576
|
-
const tEsc = now();
|
|
577
|
-
const withinEscapeWindow = typeof lastEscapeAt === 'number'
|
|
578
|
-
&& tEsc - lastEscapeAt <= ESCAPE_DOUBLE_TAP_MS;
|
|
579
|
-
if (withinEscapeWindow && props.onWalkback) {
|
|
580
|
-
// Second tap inside the window. Buffer was already cleared on
|
|
581
|
-
// the first press, so the host sees a clean input box AND the
|
|
582
|
-
// walkback result. We clear the window so a third tap restarts
|
|
583
|
-
// the cycle (no run-on walkbacks from a stuck Esc key).
|
|
584
|
-
const verdict = props.onWalkback();
|
|
585
|
-
setLastEscapeAt(undefined);
|
|
586
|
-
if (verdict !== 'walked-back') {
|
|
587
|
-
// Host refused (dispatch in flight, no turns to pop). The
|
|
588
|
-
// host owns the refusal copy via its own writeOutput path;
|
|
589
|
-
// we do not double-message here.
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
// First Esc (or no walkback wired). Arm the window + clear the
|
|
595
|
-
// buffer per the long-standing single-Esc cancel contract.
|
|
596
|
-
setLastEscapeAt(tEsc);
|
|
597
|
-
setLine('');
|
|
598
|
-
setCursor(0);
|
|
599
|
-
setHistoryIndex(-1);
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
if (key.tab && paletteOpen) {
|
|
603
|
-
const completed = completePalette(line, palette.rows, paletteFocusedIndex);
|
|
604
|
-
if (completed !== null) {
|
|
605
|
-
setLine(completed);
|
|
606
|
-
setCursor(completed.length);
|
|
607
|
-
}
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
if (key.upArrow) {
|
|
611
|
-
if (paletteOpen) {
|
|
612
|
-
setPaletteIndex((i) => (palette.rows.length === 0 ? 0 : (i - 1 + palette.rows.length) % palette.rows.length));
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
if (history.length === 0)
|
|
616
|
-
return;
|
|
617
|
-
const nextIndex = historyIndex === -1 ? history.length - 1 : Math.max(0, historyIndex - 1);
|
|
618
|
-
const entry = history[nextIndex] ?? '';
|
|
619
|
-
setHistoryIndex(nextIndex);
|
|
620
|
-
setLine(entry);
|
|
621
|
-
setCursor(entry.length);
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
if (key.downArrow) {
|
|
625
|
-
if (paletteOpen) {
|
|
626
|
-
setPaletteIndex((i) => (palette.rows.length === 0 ? 0 : (i + 1) % palette.rows.length));
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
|
-
if (history.length === 0)
|
|
630
|
-
return;
|
|
631
|
-
if (historyIndex === -1)
|
|
632
|
-
return;
|
|
633
|
-
const nextIndex = historyIndex + 1;
|
|
634
|
-
if (nextIndex >= history.length) {
|
|
635
|
-
setHistoryIndex(-1);
|
|
636
|
-
setLine('');
|
|
637
|
-
setCursor(0);
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
const entry = history[nextIndex] ?? '';
|
|
641
|
-
setHistoryIndex(nextIndex);
|
|
642
|
-
setLine(entry);
|
|
643
|
-
setCursor(entry.length);
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
if (key.leftArrow) {
|
|
647
|
-
setCursor((c) => Math.max(0, c - 1));
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
if (key.rightArrow) {
|
|
651
|
-
setCursor((c) => Math.min(line.length, c + 1));
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
if (key.backspace || key.delete) {
|
|
655
|
-
if (cursor === 0)
|
|
656
|
-
return;
|
|
657
|
-
// Read cursor via ref inside the updater so the slice indices come
|
|
658
|
-
// from the latest committed value, not the closure-captured one.
|
|
659
|
-
// Same React-18 strict-mode race that bit the Ctrl+V paste path
|
|
660
|
-
// (Claude P1 on PR wave 1): updaters run twice under strict
|
|
661
|
-
// mode, and a stale closure `cursor` produces a double-edit on the
|
|
662
|
-
// second invocation. cursorRef is the canonical fix — do NOT
|
|
663
|
-
// re-introduce closure `cursor` inside setLine updaters.
|
|
664
|
-
const cursorAtPress = cursorRef.current;
|
|
665
|
-
setLine((prev) => prev.slice(0, cursorAtPress - 1) + prev.slice(cursorAtPress));
|
|
666
|
-
setCursor((c) => Math.max(0, c - 1));
|
|
667
|
-
// Backspacing past `/` re-opens the palette next render.
|
|
668
|
-
setPaletteSuppressed(false);
|
|
669
|
-
setPaletteIndex(0);
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
if (input && !key.meta && !key.ctrl) {
|
|
673
|
-
// Ink delivers one or more characters per event; concatenate at
|
|
674
|
-
// the cursor without filtering so non-Latin + emoji sequences
|
|
675
|
-
// (the operator's own brief copy) survive paste.
|
|
676
|
-
//
|
|
677
|
-
// Bracketed-paste mode markers (`ESC[200~ ... ESC[201~`) may
|
|
678
|
-
// arrive when a modern terminal pastes multi-line text. Strip
|
|
679
|
-
// them inline so the buffer stays clean; the spec calls out
|
|
680
|
-
// disabling Enter-as-submit during the burst, but Ink's
|
|
681
|
-
// useInput delivers the whole paste as one event so the burst
|
|
682
|
-
// is atomic and Enter cannot fire mid-paste here.
|
|
683
|
-
//
|
|
684
|
-
// Ink's escape-sequence parser may consume the leading ESC for
|
|
685
|
-
// unknown CSI sequences, so we match both forms (with + without
|
|
686
|
-
// the leading ESC byte) to be safe across terminal emulators.
|
|
687
|
-
const stripped = input
|
|
688
|
-
.replace(/\x1b\[200~/g, '')
|
|
689
|
-
.replace(/\x1b\[201~/g, '')
|
|
690
|
-
.replace(/\[200~/g, '')
|
|
691
|
-
.replace(/\[201~/g, '');
|
|
692
|
-
if (stripped.length === 0)
|
|
693
|
-
return;
|
|
694
|
-
// Read cursor via ref inside the updater so the splice indices come
|
|
695
|
-
// from the latest committed value, not the closure-captured one.
|
|
696
|
-
// Same React-18 strict-mode race that bit the Ctrl+V paste path
|
|
697
|
-
// (Claude P1 on PR wave 1): updaters run twice under strict
|
|
698
|
-
// mode, and a stale closure `cursor` produces a duplicated insert
|
|
699
|
-
// on the second invocation. cursorRef is the canonical fix — do
|
|
700
|
-
// NOT re-introduce closure `cursor` inside setLine updaters.
|
|
701
|
-
const cursorAtPress = cursorRef.current;
|
|
702
|
-
setLine((prev) => prev.slice(0, cursorAtPress) + stripped + prev.slice(cursorAtPress));
|
|
703
|
-
setCursor((c) => c + stripped.length);
|
|
704
|
-
// Typing a new char un-suppresses the palette and resets focus.
|
|
705
|
-
setPaletteSuppressed(false);
|
|
706
|
-
setPaletteIndex(0);
|
|
707
|
-
}
|
|
708
|
-
});
|
|
709
|
-
const paletteView = !search && !paletteSuppressed ? filterPalette(line) : { rows: [], totalBeforeLimit: 0 };
|
|
710
|
-
// Clamp focus to current row count so a shrink (e.g. operator typed
|
|
711
|
-
// a char that narrowed the list) does not point off-the-end.
|
|
712
|
-
const clampedPaletteIndex = paletteView.rows.length === 0
|
|
713
|
-
? 0
|
|
714
|
-
: Math.min(paletteIndex, paletteView.rows.length - 1);
|
|
715
|
-
const divider = '─'.repeat(innerWidth);
|
|
716
|
-
const focusedMatch = search ? search.matches[search.focusedIndex] : undefined;
|
|
717
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#3da9fc", dimColor: true, children: divider }), _jsx(Box, { paddingX: 1, flexDirection: "column", children: search ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "#3da9fc", children: '(reverse-i-search) ' }), _jsx(Text, { children: `\`${search.query}\`: ` }), _jsx(Text, { color: "yellow", children: focusedMatch ? focusedMatch.brief : '(no match)' })] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: `Ctrl+R next · Ctrl+S prev · Enter accept · Esc cancel · ${search.matches.length} match${search.matches.length === 1 ? '' : 'es'}` }) })] })) : (_jsxs(Box, { children: [_jsx(Text, { color: "#3da9fc", children: '› ' }), _jsx(Text, { children: renderLineWithCursor(line, cursor, cursorVisible) })] })) }), _jsx(Text, { color: "#3da9fc", dimColor: true, children: divider }), modeCycleToast ? (_jsx(Box, { children: _jsx(Text, { color: "#3da9fc", bold: true, children: ` ${modeCycleToast}` }) })) : null, ctrlCToast ? (_jsx(Box, { children: _jsx(Text, { color: "yellow", bold: true, children: ` ${ctrlCToast}` }) })) : null, line.length > innerWidth - 4 ? (_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: '┊ ' }), _jsx(Text, { dimColor: true, children: 'line wraps - Enter still submits' })] })) : null, _jsx(SlashPalette, { rows: paletteView.rows, focusedIndex: clampedPaletteIndex, totalBeforeLimit: paletteView.totalBeforeLimit }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: '↑/↓ history · Ctrl+R search · / commands · Shift+Tab mode · Enter brief · Esc cancel · Ctrl+C abort / ×2 exit' }) })] }));
|
|
718
|
-
}
|
|
719
|
-
/**
|
|
720
|
-
* Render the line with the cursor glyph inserted at `cursor`. The cursor
|
|
721
|
-
* glyph is an inverted block when visible, a single space when blinked
|
|
722
|
-
* off - keeping the rendered width stable so the surrounding border
|
|
723
|
-
* does not jitter.
|
|
724
|
-
*/
|
|
725
|
-
function renderLineWithCursor(line, cursor, visible) {
|
|
726
|
-
const safeCursor = Math.max(0, Math.min(line.length, cursor));
|
|
727
|
-
const before = line.slice(0, safeCursor);
|
|
728
|
-
const after = line.slice(safeCursor);
|
|
729
|
-
const caret = visible ? '█' : ' ';
|
|
730
|
-
return `${before}${caret}${after}`;
|
|
731
|
-
}
|
|
732
|
-
//# sourceMappingURL=input-box.js.map
|