@pellux/goodvibes-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.goodvibes/GOODVIBES.md +35 -0
- package/.goodvibes/agents/reviewer.md +48 -0
- package/.goodvibes/skills/add-provider/SKILL.md +199 -0
- package/CHANGELOG.md +25 -0
- package/README.md +74 -0
- package/bin/goodvibes-agent.ts +2 -0
- package/docs/README.md +23 -0
- package/docs/deployment-and-services.md +57 -0
- package/docs/getting-started.md +53 -0
- package/docs/release-and-publishing.md +46 -0
- package/package.json +134 -0
- package/scripts/check-bun.sh +20 -0
- package/src/audio/player.ts +156 -0
- package/src/audio/spoken-turn-controller.ts +203 -0
- package/src/audio/spoken-turn-model-routing.ts +117 -0
- package/src/audio/spoken-turn-wiring.ts +44 -0
- package/src/audio/text-chunker.ts +110 -0
- package/src/cli/bundle-command.ts +227 -0
- package/src/cli/completion.ts +90 -0
- package/src/cli/config-overrides.ts +159 -0
- package/src/cli/endpoints.ts +63 -0
- package/src/cli/entrypoint.ts +172 -0
- package/src/cli/help.ts +299 -0
- package/src/cli/index.ts +11 -0
- package/src/cli/management-commands.ts +426 -0
- package/src/cli/management.ts +744 -0
- package/src/cli/network-posture.ts +46 -0
- package/src/cli/package-verification.ts +123 -0
- package/src/cli/parser.ts +369 -0
- package/src/cli/provider-auth-routes.ts +22 -0
- package/src/cli/provider-classification.ts +107 -0
- package/src/cli/redaction.ts +105 -0
- package/src/cli/service-command.ts +26 -0
- package/src/cli/service-posture.ts +482 -0
- package/src/cli/status.ts +383 -0
- package/src/cli/surface-command.ts +247 -0
- package/src/cli/tui-startup.ts +32 -0
- package/src/cli/types.ts +69 -0
- package/src/cli-flags.ts +21 -0
- package/src/config/goodvibes-home-audit.ts +465 -0
- package/src/config/index.ts +57 -0
- package/src/config/provider-model.ts +23 -0
- package/src/config/secret-config.ts +119 -0
- package/src/config/secrets.ts +71 -0
- package/src/config/surface.ts +1 -0
- package/src/core/composer-state.ts +61 -0
- package/src/core/conversation-rendering.ts +359 -0
- package/src/core/conversation.ts +551 -0
- package/src/core/history.ts +45 -0
- package/src/core/orchestrator.ts +7 -0
- package/src/core/system-message-router.ts +171 -0
- package/src/daemon/cli.ts +55 -0
- package/src/daemon/safe-serve.ts +61 -0
- package/src/input/agent-workspace.ts +428 -0
- package/src/input/autocomplete.ts +96 -0
- package/src/input/bookmark-modal.ts +115 -0
- package/src/input/command-args-hint.ts +36 -0
- package/src/input/command-registry.ts +329 -0
- package/src/input/commands/agent-externalized-tui.ts +73 -0
- package/src/input/commands/agent-workspace-runtime.ts +17 -0
- package/src/input/commands/branch-runtime.ts +72 -0
- package/src/input/commands/cloudflare-runtime.ts +370 -0
- package/src/input/commands/config.ts +18 -0
- package/src/input/commands/control-room-runtime.ts +255 -0
- package/src/input/commands/conversation-runtime.ts +207 -0
- package/src/input/commands/discovery-runtime.ts +52 -0
- package/src/input/commands/eval.ts +204 -0
- package/src/input/commands/experience-runtime.ts +278 -0
- package/src/input/commands/guidance-runtime.ts +106 -0
- package/src/input/commands/health-runtime.ts +434 -0
- package/src/input/commands/hooks-runtime.ts +148 -0
- package/src/input/commands/incident-runtime.ts +95 -0
- package/src/input/commands/integration-runtime.ts +394 -0
- package/src/input/commands/intelligence-runtime.ts +223 -0
- package/src/input/commands/knowledge.ts +531 -0
- package/src/input/commands/local-auth-runtime.ts +105 -0
- package/src/input/commands/local-provider-runtime.ts +170 -0
- package/src/input/commands/local-runtime.ts +392 -0
- package/src/input/commands/local-setup-review.ts +199 -0
- package/src/input/commands/local-setup-transfer.ts +135 -0
- package/src/input/commands/local-setup.ts +282 -0
- package/src/input/commands/managed-runtime.ts +209 -0
- package/src/input/commands/marketplace-runtime.ts +290 -0
- package/src/input/commands/mcp-runtime.ts +432 -0
- package/src/input/commands/memory-product-runtime.ts +111 -0
- package/src/input/commands/memory.ts +151 -0
- package/src/input/commands/notify-runtime.ts +83 -0
- package/src/input/commands/onboarding-runtime.ts +14 -0
- package/src/input/commands/operator-panel-runtime.ts +146 -0
- package/src/input/commands/operator-runtime.ts +392 -0
- package/src/input/commands/planning-runtime.ts +205 -0
- package/src/input/commands/platform-access-runtime.ts +422 -0
- package/src/input/commands/platform-services-runtime.ts +246 -0
- package/src/input/commands/policy-dispatch.ts +339 -0
- package/src/input/commands/policy.ts +17 -0
- package/src/input/commands/product-runtime.ts +351 -0
- package/src/input/commands/profile-sync-runtime.ts +99 -0
- package/src/input/commands/provider-accounts-runtime.ts +113 -0
- package/src/input/commands/provider.ts +363 -0
- package/src/input/commands/qrcode-runtime.ts +20 -0
- package/src/input/commands/quit-shared.ts +162 -0
- package/src/input/commands/recall-bundle.ts +132 -0
- package/src/input/commands/recall-capture.ts +152 -0
- package/src/input/commands/recall-query.ts +229 -0
- package/src/input/commands/recall-review.ts +98 -0
- package/src/input/commands/recall-shared.ts +22 -0
- package/src/input/commands/remote-runtime-pool.ts +106 -0
- package/src/input/commands/remote-runtime-setup.ts +199 -0
- package/src/input/commands/remote-runtime.ts +431 -0
- package/src/input/commands/replay-runtime.ts +18 -0
- package/src/input/commands/runtime-services.ts +291 -0
- package/src/input/commands/schedule-runtime.ts +91 -0
- package/src/input/commands/services-runtime.ts +209 -0
- package/src/input/commands/session-content.ts +408 -0
- package/src/input/commands/session-workflow.ts +464 -0
- package/src/input/commands/session.ts +375 -0
- package/src/input/commands/settings-sync-runtime.ts +174 -0
- package/src/input/commands/share-runtime.ts +119 -0
- package/src/input/commands/shell-core.ts +307 -0
- package/src/input/commands/skills-runtime.ts +221 -0
- package/src/input/commands/subscription-runtime.ts +434 -0
- package/src/input/commands/tasks-runtime.ts +230 -0
- package/src/input/commands/teamwork-runtime.ts +339 -0
- package/src/input/commands/teleport-runtime.ts +57 -0
- package/src/input/commands/tts-runtime.ts +29 -0
- package/src/input/commands/work-plan-runtime.ts +169 -0
- package/src/input/commands.ts +131 -0
- package/src/input/feed-context-factory.ts +254 -0
- package/src/input/file-picker.ts +192 -0
- package/src/input/handler-command-route.ts +180 -0
- package/src/input/handler-content-actions.ts +497 -0
- package/src/input/handler-feed-routes.ts +648 -0
- package/src/input/handler-feed.ts +452 -0
- package/src/input/handler-interactions.ts +281 -0
- package/src/input/handler-modal-routes.ts +418 -0
- package/src/input/handler-modal-stack.ts +263 -0
- package/src/input/handler-modal-token-routes.ts +329 -0
- package/src/input/handler-onboarding-cloudflare.ts +391 -0
- package/src/input/handler-onboarding.ts +620 -0
- package/src/input/handler-picker-routes.ts +472 -0
- package/src/input/handler-prompt-buffer.ts +320 -0
- package/src/input/handler-shortcuts.ts +213 -0
- package/src/input/handler-ui-state.ts +372 -0
- package/src/input/handler.ts +729 -0
- package/src/input/input-history.ts +297 -0
- package/src/input/keybindings.ts +292 -0
- package/src/input/mcp-workspace.ts +554 -0
- package/src/input/model-picker-provider-filter.ts +28 -0
- package/src/input/model-picker-types.ts +137 -0
- package/src/input/model-picker.ts +797 -0
- package/src/input/onboarding/handler-onboarding-routes.ts +125 -0
- package/src/input/onboarding/onboarding-runtime-status.ts +87 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +277 -0
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +494 -0
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +204 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +158 -0
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +130 -0
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +762 -0
- package/src/input/onboarding/onboarding-wizard-helpers.ts +167 -0
- package/src/input/onboarding/onboarding-wizard-rules.ts +256 -0
- package/src/input/onboarding/onboarding-wizard-state.ts +365 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +798 -0
- package/src/input/onboarding/onboarding-wizard-types.ts +195 -0
- package/src/input/onboarding/onboarding-wizard.ts +711 -0
- package/src/input/panel-integration-actions.ts +78 -0
- package/src/input/profile-picker-modal.ts +222 -0
- package/src/input/search.ts +100 -0
- package/src/input/selection-modal.ts +163 -0
- package/src/input/selection.ts +135 -0
- package/src/input/session-picker-modal.ts +136 -0
- package/src/input/settings-modal-behavior.ts +37 -0
- package/src/input/settings-modal-secrets.ts +41 -0
- package/src/input/settings-modal-subscriptions.ts +95 -0
- package/src/input/settings-modal-types.ts +91 -0
- package/src/input/settings-modal.ts +793 -0
- package/src/input/submission-intent.ts +17 -0
- package/src/input/submission-router.ts +59 -0
- package/src/input/tts-settings-actions.ts +100 -0
- package/src/main.ts +792 -0
- package/src/mcp/runtime-reload.ts +81 -0
- package/src/panels/agent-inspector-panel.ts +521 -0
- package/src/panels/agent-inspector-shared.ts +94 -0
- package/src/panels/agent-logs-panel.ts +559 -0
- package/src/panels/agent-logs-shared.ts +129 -0
- package/src/panels/approval-panel.ts +150 -0
- package/src/panels/automation-control-panel.ts +212 -0
- package/src/panels/base-panel.ts +254 -0
- package/src/panels/builtin/agent.ts +117 -0
- package/src/panels/builtin/development.ts +31 -0
- package/src/panels/builtin/knowledge.ts +26 -0
- package/src/panels/builtin/operations.ts +349 -0
- package/src/panels/builtin/session.ts +129 -0
- package/src/panels/builtin/shared.ts +274 -0
- package/src/panels/builtin-panels.ts +23 -0
- package/src/panels/cockpit-panel.ts +183 -0
- package/src/panels/communication-panel.ts +153 -0
- package/src/panels/confirm-state.ts +61 -0
- package/src/panels/context-visualizer-panel.ts +204 -0
- package/src/panels/control-plane-panel.ts +211 -0
- package/src/panels/cost-tracker-panel.ts +444 -0
- package/src/panels/debug-panel.ts +432 -0
- package/src/panels/diff-panel.ts +520 -0
- package/src/panels/docs-panel.ts +283 -0
- package/src/panels/eval-panel.ts +399 -0
- package/src/panels/file-explorer-panel.ts +584 -0
- package/src/panels/file-preview-panel.ts +434 -0
- package/src/panels/forensics-panel.ts +364 -0
- package/src/panels/git-panel.ts +638 -0
- package/src/panels/hooks-panel.ts +239 -0
- package/src/panels/incident-review-panel.ts +197 -0
- package/src/panels/index.ts +46 -0
- package/src/panels/intelligence-panel.ts +176 -0
- package/src/panels/knowledge-panel.ts +345 -0
- package/src/panels/local-auth-panel.ts +130 -0
- package/src/panels/marketplace-panel.ts +212 -0
- package/src/panels/memory-panel.ts +225 -0
- package/src/panels/ops-control-panel.ts +150 -0
- package/src/panels/ops-strategy-panel.ts +235 -0
- package/src/panels/orchestration-panel.ts +273 -0
- package/src/panels/panel-list-panel.ts +509 -0
- package/src/panels/panel-manager.ts +570 -0
- package/src/panels/panel-picker.ts +106 -0
- package/src/panels/plan-dashboard-panel.ts +274 -0
- package/src/panels/plugins-panel.ts +178 -0
- package/src/panels/policy-panel.ts +308 -0
- package/src/panels/polish.ts +717 -0
- package/src/panels/project-planning-panel.ts +711 -0
- package/src/panels/provider-account-snapshot.ts +259 -0
- package/src/panels/provider-accounts-panel.ts +218 -0
- package/src/panels/provider-health-domains.ts +215 -0
- package/src/panels/provider-health-panel.ts +727 -0
- package/src/panels/provider-health-tracker.ts +115 -0
- package/src/panels/provider-stats-panel.ts +366 -0
- package/src/panels/qr-panel.ts +182 -0
- package/src/panels/remote-panel.ts +449 -0
- package/src/panels/routes-panel.ts +178 -0
- package/src/panels/sandbox-panel.ts +283 -0
- package/src/panels/schedule-panel.ts +329 -0
- package/src/panels/scrollable-list-panel.ts +491 -0
- package/src/panels/search-focus.ts +32 -0
- package/src/panels/security-panel.ts +295 -0
- package/src/panels/services-panel.ts +231 -0
- package/src/panels/session-browser-panel.ts +400 -0
- package/src/panels/session-maintenance.ts +125 -0
- package/src/panels/settings-sync-panel.ts +120 -0
- package/src/panels/skills-panel.ts +431 -0
- package/src/panels/subscription-panel.ts +263 -0
- package/src/panels/symbol-outline-panel.ts +486 -0
- package/src/panels/system-messages-panel.ts +230 -0
- package/src/panels/tasks-panel.ts +399 -0
- package/src/panels/thinking-panel.ts +304 -0
- package/src/panels/token-budget-panel.ts +475 -0
- package/src/panels/tool-inspector-panel.ts +429 -0
- package/src/panels/types.ts +54 -0
- package/src/panels/watchers-panel.ts +193 -0
- package/src/panels/work-plan-panel.ts +175 -0
- package/src/panels/worktree-panel.ts +182 -0
- package/src/panels/wrfc-panel.ts +609 -0
- package/src/permissions/prompt.ts +165 -0
- package/src/planning/project-planning-coordinator.ts +543 -0
- package/src/plugins/loader.ts +15 -0
- package/src/renderer/agent-detail-modal.ts +331 -0
- package/src/renderer/agent-workspace.ts +238 -0
- package/src/renderer/ansi-sanitize.ts +76 -0
- package/src/renderer/autocomplete-overlay.ts +154 -0
- package/src/renderer/block-actions.ts +76 -0
- package/src/renderer/bookmark-modal.ts +101 -0
- package/src/renderer/bottom-bar.ts +58 -0
- package/src/renderer/buffer.ts +113 -0
- package/src/renderer/code-block.ts +373 -0
- package/src/renderer/compositor.ts +283 -0
- package/src/renderer/context-inspector.ts +219 -0
- package/src/renderer/conversation-layout.ts +67 -0
- package/src/renderer/conversation-overlays.ts +140 -0
- package/src/renderer/conversation-surface.ts +260 -0
- package/src/renderer/diff-view.ts +132 -0
- package/src/renderer/diff.ts +130 -0
- package/src/renderer/file-picker-overlay.ts +101 -0
- package/src/renderer/file-tree.ts +153 -0
- package/src/renderer/fullscreen-primitives.ts +130 -0
- package/src/renderer/fullscreen-workspace.ts +199 -0
- package/src/renderer/git-status.ts +89 -0
- package/src/renderer/help-overlay.ts +267 -0
- package/src/renderer/history-search-overlay.ts +73 -0
- package/src/renderer/layout-engine.ts +97 -0
- package/src/renderer/layout.ts +32 -0
- package/src/renderer/live-tail-modal.ts +156 -0
- package/src/renderer/markdown.ts +635 -0
- package/src/renderer/mcp-workspace.ts +237 -0
- package/src/renderer/modal-factory.ts +467 -0
- package/src/renderer/modal-utils.ts +24 -0
- package/src/renderer/model-picker-overlay.ts +473 -0
- package/src/renderer/model-workspace.ts +488 -0
- package/src/renderer/onboarding/onboarding-wizard.ts +615 -0
- package/src/renderer/overlay-box.ts +146 -0
- package/src/renderer/overlay-viewport.ts +104 -0
- package/src/renderer/panel-composite.ts +158 -0
- package/src/renderer/panel-picker-overlay.ts +202 -0
- package/src/renderer/panel-tab-bar.ts +69 -0
- package/src/renderer/panel-workspace-bar.ts +42 -0
- package/src/renderer/process-indicator.ts +96 -0
- package/src/renderer/process-modal.ts +656 -0
- package/src/renderer/process-summary.ts +67 -0
- package/src/renderer/profile-picker-modal.ts +129 -0
- package/src/renderer/progress.ts +98 -0
- package/src/renderer/qr-renderer.ts +120 -0
- package/src/renderer/search-overlay.ts +54 -0
- package/src/renderer/selection-modal-overlay.ts +214 -0
- package/src/renderer/semantic-diff.ts +369 -0
- package/src/renderer/session-picker-modal.ts +127 -0
- package/src/renderer/settings-modal-helpers.ts +193 -0
- package/src/renderer/settings-modal.ts +537 -0
- package/src/renderer/shell-surface.ts +88 -0
- package/src/renderer/status-glyphs.ts +21 -0
- package/src/renderer/status-token.ts +67 -0
- package/src/renderer/surface-layout.ts +101 -0
- package/src/renderer/syntax-highlighter.ts +542 -0
- package/src/renderer/system-message.ts +83 -0
- package/src/renderer/tab-strip.ts +108 -0
- package/src/renderer/text-layout.ts +31 -0
- package/src/renderer/thinking.ts +17 -0
- package/src/renderer/tool-call.ts +234 -0
- package/src/renderer/ui-factory.ts +524 -0
- package/src/renderer/ui-primitives.ts +96 -0
- package/src/runtime/bootstrap-command-context.ts +278 -0
- package/src/runtime/bootstrap-command-parts.ts +386 -0
- package/src/runtime/bootstrap-core.ts +540 -0
- package/src/runtime/bootstrap-hook-bridge.ts +112 -0
- package/src/runtime/bootstrap-shell.ts +283 -0
- package/src/runtime/bootstrap.ts +575 -0
- package/src/runtime/cloudflare-control-plane.ts +349 -0
- package/src/runtime/context.ts +142 -0
- package/src/runtime/diagnostics/panels/index.ts +24 -0
- package/src/runtime/diagnostics/panels/ops.ts +156 -0
- package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
- package/src/runtime/diagnostics/panels/policy.ts +177 -0
- package/src/runtime/index.ts +662 -0
- package/src/runtime/onboarding/apply.ts +642 -0
- package/src/runtime/onboarding/derivation.ts +534 -0
- package/src/runtime/onboarding/index.ts +7 -0
- package/src/runtime/onboarding/markers.ts +148 -0
- package/src/runtime/onboarding/snapshot.ts +406 -0
- package/src/runtime/onboarding/state.ts +141 -0
- package/src/runtime/onboarding/types.ts +404 -0
- package/src/runtime/onboarding/verify.ts +171 -0
- package/src/runtime/operator-token-cleanup.ts +27 -0
- package/src/runtime/perf/panel-contracts.ts +32 -0
- package/src/runtime/perf/panel-health-monitor.ts +18 -0
- package/src/runtime/sandbox-public-gaps.ts +358 -0
- package/src/runtime/services.ts +670 -0
- package/src/runtime/store/domains/domain-read-matrix.ts +15 -0
- package/src/runtime/store/domains/index.ts +222 -0
- package/src/runtime/store/domains/panels.ts +117 -0
- package/src/runtime/store/domains/ui-perf.ts +103 -0
- package/src/runtime/store/index.ts +305 -0
- package/src/runtime/store/selectors/index.ts +359 -0
- package/src/runtime/store/state.ts +145 -0
- package/src/runtime/surface-feature-flags.ts +65 -0
- package/src/runtime/terminal-output-guard.ts +228 -0
- package/src/runtime/ui/index.ts +39 -0
- package/src/runtime/ui/model-picker/data-provider.ts +182 -0
- package/src/runtime/ui/model-picker/health-enrichment.ts +228 -0
- package/src/runtime/ui/model-picker/index.ts +59 -0
- package/src/runtime/ui/model-picker/types.ts +149 -0
- package/src/runtime/ui/provider-health/data-provider.ts +244 -0
- package/src/runtime/ui/provider-health/fallback-visualizer.ts +71 -0
- package/src/runtime/ui/provider-health/index.ts +46 -0
- package/src/runtime/ui/provider-health/types.ts +146 -0
- package/src/runtime/ui-events.ts +1 -0
- package/src/runtime/ui-read-model-helpers.ts +1 -0
- package/src/runtime/ui-read-models-observability-maintenance.ts +1 -0
- package/src/runtime/ui-read-models-observability-options.ts +1 -0
- package/src/runtime/ui-read-models-observability-remote.ts +1 -0
- package/src/runtime/ui-read-models-observability-security.ts +1 -0
- package/src/runtime/ui-read-models-observability-system.ts +1 -0
- package/src/runtime/ui-read-models-observability.ts +1 -0
- package/src/runtime/ui-read-models.ts +61 -0
- package/src/runtime/ui-service-queries.ts +1 -0
- package/src/runtime/ui-services.ts +190 -0
- package/src/scripts/process-messages.ts +42 -0
- package/src/shell/blocking-input.ts +98 -0
- package/src/shell/service-settings-sync.ts +273 -0
- package/src/shell/ui-openers.ts +352 -0
- package/src/tools/index.ts +1 -0
- package/src/tools/wrfc-agent-guard.ts +49 -0
- package/src/types/grid.ts +48 -0
- package/src/types/sql-js.d.ts +15 -0
- package/src/utils/clipboard.ts +22 -0
- package/src/utils/splash-lines.ts +46 -0
- package/src/utils/terminal-width.ts +185 -0
- package/src/verification/live-verifier.ts +430 -0
- package/src/verification/verification-ledger.ts +242 -0
- package/src/version.ts +17 -0
- package/src/widget/index.ts +2 -0
- package/src/widget/types.ts +9 -0
- package/src/widget/widget.ts +8 -0
- package/src/work-plans/work-plan-store.ts +374 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type ProcessSummaryAgent = {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly progress?: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type RuntimeProcessSummaryAgent = {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly latestProgress?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type WrfcProcessSummaryChain = {
|
|
12
|
+
readonly state?: string;
|
|
13
|
+
readonly ownerAgentId?: string;
|
|
14
|
+
readonly engineerAgentId?: string;
|
|
15
|
+
readonly reviewerAgentId?: string;
|
|
16
|
+
readonly fixerAgentId?: string;
|
|
17
|
+
readonly allAgentIds?: readonly unknown[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type RunningAgentSummary = {
|
|
21
|
+
readonly count: number;
|
|
22
|
+
readonly progress?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function summarizeRunningAgents(
|
|
26
|
+
managerAgents: readonly ProcessSummaryAgent[],
|
|
27
|
+
runtimeAgents: readonly RuntimeProcessSummaryAgent[],
|
|
28
|
+
wrfcChains: readonly WrfcProcessSummaryChain[],
|
|
29
|
+
): RunningAgentSummary {
|
|
30
|
+
const runningAgentIds = new Set<string>();
|
|
31
|
+
let progress: string | undefined;
|
|
32
|
+
|
|
33
|
+
for (const agent of managerAgents) {
|
|
34
|
+
runningAgentIds.add(agent.id);
|
|
35
|
+
if (!progress && agent.progress) progress = agent.progress;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const agent of runtimeAgents) {
|
|
39
|
+
runningAgentIds.add(agent.id);
|
|
40
|
+
if (!progress && agent.latestProgress) progress = agent.latestProgress;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const chain of wrfcChains) {
|
|
44
|
+
if (isTerminalWrfcState(chain.state)) continue;
|
|
45
|
+
const chainAgentIds = collectChainAgentIds(chain);
|
|
46
|
+
const hasVisibleChainWork = Array.from(chainAgentIds).some((id) => runningAgentIds.has(id));
|
|
47
|
+
if (!hasVisibleChainWork || !chain.ownerAgentId) continue;
|
|
48
|
+
runningAgentIds.add(chain.ownerAgentId);
|
|
49
|
+
if (!progress) progress = `WRFC chain ${chain.state ?? 'running'}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { count: runningAgentIds.size, progress };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isTerminalWrfcState(state: string | undefined): boolean {
|
|
56
|
+
return state === 'passed' || state === 'failed';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function collectChainAgentIds(chain: WrfcProcessSummaryChain): Set<string> {
|
|
60
|
+
return new Set([
|
|
61
|
+
chain.ownerAgentId,
|
|
62
|
+
chain.engineerAgentId,
|
|
63
|
+
chain.reviewerAgentId,
|
|
64
|
+
chain.fixerAgentId,
|
|
65
|
+
...(chain.allAgentIds ?? []),
|
|
66
|
+
].filter((id): id is string => typeof id === 'string' && id.length > 0));
|
|
67
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* renderProfilePickerModal — renders the /profiles picker modal as Line[]
|
|
3
|
+
* using ModalFactory.
|
|
4
|
+
*
|
|
5
|
+
* Shows a list of saved profiles with:
|
|
6
|
+
* - name, timestamp (formatted), settings preview
|
|
7
|
+
* Footer hints: [Up/Down] Navigate [Enter] Load [d] Arm/Delete [s] Save current [Esc] Close
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Line } from '../types/grid.ts';
|
|
11
|
+
import { ModalFactory } from './modal-factory.ts';
|
|
12
|
+
import type { ProfilePickerModal } from '../input/profile-picker-modal.ts';
|
|
13
|
+
import { formatTimestamp } from './modal-utils.ts';
|
|
14
|
+
import { fitDisplay } from '../utils/terminal-width.ts';
|
|
15
|
+
import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Renderer
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Render the profile picker modal as Line[] for overlay in the viewport.
|
|
23
|
+
*
|
|
24
|
+
* @param modal ProfilePickerModal state object.
|
|
25
|
+
* @param width Terminal width.
|
|
26
|
+
*/
|
|
27
|
+
export function renderProfilePickerModal(
|
|
28
|
+
modal: ProfilePickerModal,
|
|
29
|
+
width: number,
|
|
30
|
+
viewportHeight = 24,
|
|
31
|
+
): Line[] {
|
|
32
|
+
const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
|
|
33
|
+
chromeRows: 6,
|
|
34
|
+
minContentRows: 5,
|
|
35
|
+
maxContentRows: 9,
|
|
36
|
+
});
|
|
37
|
+
const boxMargin = metrics.margin;
|
|
38
|
+
const boxW = metrics.boxWidth;
|
|
39
|
+
const contentW = metrics.contentWidth;
|
|
40
|
+
const visibleRows = metrics.contentRows;
|
|
41
|
+
const targetContentRows = getStableOverlayContentRows(metrics.contentRows, 8);
|
|
42
|
+
modal.setVisibleRows(visibleRows);
|
|
43
|
+
|
|
44
|
+
const sections: import('./modal-factory.ts').ModalSection[] = [];
|
|
45
|
+
|
|
46
|
+
if (modal.profiles.length === 0) {
|
|
47
|
+
sections.push({
|
|
48
|
+
type: 'text',
|
|
49
|
+
content: 'No saved profiles.',
|
|
50
|
+
style: { fg: '244', dim: true },
|
|
51
|
+
});
|
|
52
|
+
sections.push({
|
|
53
|
+
type: 'text',
|
|
54
|
+
content: 'Press [s] to save the current settings as a profile.',
|
|
55
|
+
style: { fg: '240', dim: true },
|
|
56
|
+
});
|
|
57
|
+
} else {
|
|
58
|
+
// Column widths: name(24) | timestamp(16) | preview(remaining)
|
|
59
|
+
const nameW = 24;
|
|
60
|
+
const tsW = 16;
|
|
61
|
+
const previewW = Math.max(4, contentW - nameW - tsW - 4);
|
|
62
|
+
|
|
63
|
+
// Column header
|
|
64
|
+
const nameHdr = fitDisplay('Name', nameW);
|
|
65
|
+
const tsHdr = fitDisplay('Saved', tsW);
|
|
66
|
+
const previewHdr = fitDisplay('Settings', previewW);
|
|
67
|
+
sections.push({
|
|
68
|
+
type: 'text',
|
|
69
|
+
content: `${nameHdr} ${tsHdr} ${previewHdr}`,
|
|
70
|
+
style: { fg: '240', dim: true },
|
|
71
|
+
});
|
|
72
|
+
sections.push({ type: 'separator' });
|
|
73
|
+
|
|
74
|
+
const visibleProfiles = modal.profiles.slice(modal.scrollOffset, modal.scrollOffset + visibleRows);
|
|
75
|
+
const listItems: import('./modal-factory.ts').ModalListItem[] = visibleProfiles.map((prof, idx) => {
|
|
76
|
+
const isSelected = modal.scrollOffset + idx === modal.selectedIndex;
|
|
77
|
+
|
|
78
|
+
const nameStr = fitDisplay(prof.name, nameW);
|
|
79
|
+
|
|
80
|
+
const tsStr = fitDisplay(formatTimestamp(prof.timestamp), tsW);
|
|
81
|
+
|
|
82
|
+
// Read the profile file to get a preview of settings
|
|
83
|
+
// (We only have name/timestamp in ProfileInfo, so show a placeholder)
|
|
84
|
+
const preview = fitDisplay('(display/provider/behavior)', previewW);
|
|
85
|
+
|
|
86
|
+
const label = `${nameStr} ${tsStr} ${preview}`;
|
|
87
|
+
return { label, selected: isSelected };
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
sections.push({ type: 'list', items: listItems });
|
|
91
|
+
if (modal.profiles.length > visibleRows) {
|
|
92
|
+
sections.push({ type: 'separator' });
|
|
93
|
+
sections.push({
|
|
94
|
+
type: 'text',
|
|
95
|
+
content: `[${modal.scrollOffset + 1}-${Math.min(modal.profiles.length, modal.scrollOffset + visibleRows)} of ${modal.profiles.length}]`,
|
|
96
|
+
style: { fg: '244', dim: true },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Status message if present
|
|
102
|
+
if (modal.statusMessage) {
|
|
103
|
+
sections.push({ type: 'separator' });
|
|
104
|
+
sections.push({
|
|
105
|
+
type: 'text',
|
|
106
|
+
content: modal.statusMessage,
|
|
107
|
+
style: { fg: '#00ffcc' },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (modal.deleteConfirmationTarget) {
|
|
111
|
+
sections.push({
|
|
112
|
+
type: 'text',
|
|
113
|
+
content: `Press [d] again to permanently delete ${modal.deleteConfirmationTarget}.`,
|
|
114
|
+
style: { fg: '#f59e0b', dim: true },
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return ModalFactory.createModal(
|
|
119
|
+
{
|
|
120
|
+
title: 'Profiles',
|
|
121
|
+
width: boxW,
|
|
122
|
+
margin: boxMargin,
|
|
123
|
+
targetContentRows,
|
|
124
|
+
sections,
|
|
125
|
+
hints: ['[Up/Down] Navigate', '[Enter] Load', '[d] Arm/Delete', '[s] Save current', '[Esc] Close'],
|
|
126
|
+
},
|
|
127
|
+
width,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { type Line } from '../types/grid.ts';
|
|
2
|
+
import { UIFactory } from './ui-factory.ts';
|
|
3
|
+
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
4
|
+
|
|
5
|
+
// Rich spinner frames (used by progress indicators)
|
|
6
|
+
export const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
7
|
+
// Braille thinking spinner frames (used by the orchestrator thinking animation)
|
|
8
|
+
export const THINKING_SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* renderSpinner - Render a spinner with label as a single Line.
|
|
12
|
+
*/
|
|
13
|
+
export function renderSpinner(
|
|
14
|
+
frame: string,
|
|
15
|
+
label: string,
|
|
16
|
+
width: number,
|
|
17
|
+
fg: string = '135'
|
|
18
|
+
): Line {
|
|
19
|
+
const text = ` ${frame} ${label}`;
|
|
20
|
+
return UIFactory.stringToLine(text.padEnd(width), width, { fg, bold: true });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* renderToolProgress - Render tool execution progress.
|
|
25
|
+
* E.g. "[2/5] Editing src/config.ts..."
|
|
26
|
+
*/
|
|
27
|
+
export function renderToolProgress(
|
|
28
|
+
current: number,
|
|
29
|
+
total: number,
|
|
30
|
+
label: string,
|
|
31
|
+
width: number
|
|
32
|
+
): Line[] {
|
|
33
|
+
const counter = `[${current}/${total}]`;
|
|
34
|
+
const text = ` ${counter} ${label}`;
|
|
35
|
+
return [
|
|
36
|
+
UIFactory.stringToLine(text.padEnd(width), width, { fg: '#ffcc00', bold: true }),
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* renderTokenBar - Render a token usage bar for the footer.
|
|
42
|
+
* Shows used/max tokens as a visual bar + numbers.
|
|
43
|
+
*/
|
|
44
|
+
export function renderTokenBar(
|
|
45
|
+
used: number,
|
|
46
|
+
max: number,
|
|
47
|
+
width: number,
|
|
48
|
+
model: string,
|
|
49
|
+
toolCount: number
|
|
50
|
+
): Line[] {
|
|
51
|
+
const lines: Line[] = [];
|
|
52
|
+
|
|
53
|
+
// Stats row
|
|
54
|
+
const usedK = used >= 1000 ? `${(used / 1000).toFixed(1)}k` : String(used);
|
|
55
|
+
const maxK = max >= 1000 ? `${(max / 1000).toFixed(1)}k` : String(max);
|
|
56
|
+
const pct = max > 0 ? Math.min(100, Math.round((used / max) * 100)) : 0;
|
|
57
|
+
const toolStr = toolCount > 0 ? ` tools:${toolCount}` : '';
|
|
58
|
+
const statsText = ` ${model} in:${usedK}/${maxK} (${pct}%)${toolStr}`;
|
|
59
|
+
|
|
60
|
+
// Progress bar
|
|
61
|
+
const barLabel = ' ctx ';
|
|
62
|
+
const barLabelW = getDisplayWidth(barLabel);
|
|
63
|
+
const barW = Math.max(10, Math.floor(width * 0.3));
|
|
64
|
+
const filled = Math.round((pct / 100) * barW);
|
|
65
|
+
const empty = barW - filled;
|
|
66
|
+
|
|
67
|
+
// Color based on usage
|
|
68
|
+
const barFg = pct > 85 ? '#ef4444' : pct > 60 ? '#ffcc00' : '#22c55e';
|
|
69
|
+
|
|
70
|
+
const bar = '#'.repeat(filled) + '-'.repeat(empty);
|
|
71
|
+
const barText = barLabel + bar;
|
|
72
|
+
|
|
73
|
+
const statsW = getDisplayWidth(statsText);
|
|
74
|
+
const barTextW = getDisplayWidth(barText);
|
|
75
|
+
const spacingW = Math.max(1, width - statsW - barTextW - 2);
|
|
76
|
+
|
|
77
|
+
// Build with mixed colors
|
|
78
|
+
const line = UIFactory.stringToLine(statsText + ' '.repeat(spacingW), width, { fg: '244', dim: true });
|
|
79
|
+
|
|
80
|
+
// Overlay the bar with color
|
|
81
|
+
let barStartX = getDisplayWidth(statsText) + spacingW;
|
|
82
|
+
for (const ch of barLabel) {
|
|
83
|
+
if (barStartX >= width) break;
|
|
84
|
+
const cw = getDisplayWidth(ch);
|
|
85
|
+
line[barStartX] = { char: ch, fg: '244', bg: '', bold: false, dim: true, underline: false, italic: false, strikethrough: false };
|
|
86
|
+
if (cw === 2 && barStartX + 1 < width) line[barStartX + 1] = { ...line[barStartX], char: '' };
|
|
87
|
+
barStartX += cw;
|
|
88
|
+
}
|
|
89
|
+
for (let i = 0; i < filled && barStartX + i < width; i++) {
|
|
90
|
+
line[barStartX + i] = { char: '#', fg: barFg, bg: '', bold: false, dim: false, underline: false, italic: false, strikethrough: false };
|
|
91
|
+
}
|
|
92
|
+
for (let i = 0; i < empty && barStartX + filled + i < width; i++) {
|
|
93
|
+
line[barStartX + filled + i] = { char: '-', fg: '238', bg: '', bold: false, dim: true, underline: false, italic: false, strikethrough: false };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
lines.push(line);
|
|
97
|
+
return lines;
|
|
98
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { Line } from '../types/grid.ts';
|
|
2
|
+
import { createEmptyLine, createStyledCell } from '../types/grid.ts';
|
|
3
|
+
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
4
|
+
import { generateQrMatrix } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
5
|
+
|
|
6
|
+
export { generateQrMatrix };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Render a QR boolean matrix to terminal Lines using Unicode half-block characters.
|
|
10
|
+
*
|
|
11
|
+
* Two matrix rows map to one terminal row:
|
|
12
|
+
* top=dark, bottom=dark → '█' (FULL BLOCK)
|
|
13
|
+
* top=dark, bottom=light → '▀' (UPPER HALF BLOCK)
|
|
14
|
+
* top=light, bottom=dark → '▄' (LOWER HALF BLOCK)
|
|
15
|
+
* top=light, bottom=light → ' ' (SPACE)
|
|
16
|
+
*
|
|
17
|
+
* @param modules - 2D boolean matrix where true = dark module
|
|
18
|
+
* @param width - Terminal width available for centering
|
|
19
|
+
* @param options - Optional fg/bg overrides
|
|
20
|
+
*/
|
|
21
|
+
export function renderQrMatrix(
|
|
22
|
+
modules: readonly boolean[][],
|
|
23
|
+
width: number,
|
|
24
|
+
options?: { fg?: string; bg?: string },
|
|
25
|
+
): Line[] {
|
|
26
|
+
const fg = options?.fg ?? '#000000';
|
|
27
|
+
const bg = options?.bg ?? '#ffffff';
|
|
28
|
+
|
|
29
|
+
const rows = modules.length;
|
|
30
|
+
const cols = modules[0]?.length ?? 0;
|
|
31
|
+
if (rows === 0 || cols === 0) return [];
|
|
32
|
+
|
|
33
|
+
// Each terminal row covers two matrix rows
|
|
34
|
+
const terminalRows = Math.ceil(rows / 2);
|
|
35
|
+
// Left-align with a single-cell indent. Visually aligns with the text above
|
|
36
|
+
// the QR when rendered with half-block characters; bumping higher
|
|
37
|
+
// mis-registers the finder patterns by a visible unit.
|
|
38
|
+
const leftPad = 1;
|
|
39
|
+
|
|
40
|
+
const lines: Line[] = [];
|
|
41
|
+
|
|
42
|
+
// Prepend a half-height top quiet band: the BOTTOM half of this terminal row
|
|
43
|
+
// is white (QR bg) flush against the QR's first module row below; the TOP half
|
|
44
|
+
// is the terminal's default background (chrome). Using '▄' (LOWER HALF BLOCK)
|
|
45
|
+
// with fg = QR bg and no bg override achieves the half-height effect.
|
|
46
|
+
// Combined with the leftPad=1 on the horizontal axis, this keeps the
|
|
47
|
+
// finder-pattern square margin consistent on both axes without stealing a
|
|
48
|
+
// full row of vertical space.
|
|
49
|
+
{
|
|
50
|
+
const topBand = createEmptyLine(width);
|
|
51
|
+
const endCol = Math.min(leftPad + cols + 1, width);
|
|
52
|
+
for (let col = 0; col < endCol; col++) {
|
|
53
|
+
topBand[col] = createStyledCell('▄', { fg: bg });
|
|
54
|
+
}
|
|
55
|
+
lines.push(topBand);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (let termRow = 0; termRow < terminalRows; termRow++) {
|
|
59
|
+
const matrixRowTop = termRow * 2;
|
|
60
|
+
const matrixRowBot = termRow * 2 + 1;
|
|
61
|
+
const topRow = modules[matrixRowTop];
|
|
62
|
+
const botRow = matrixRowBot < rows ? modules[matrixRowBot] : null;
|
|
63
|
+
|
|
64
|
+
const line = createEmptyLine(width);
|
|
65
|
+
|
|
66
|
+
// Fill leading padding with bg
|
|
67
|
+
for (let col = 0; col < leftPad && col < width; col++) {
|
|
68
|
+
line[col] = createStyledCell(' ', { fg, bg });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Render QR columns
|
|
72
|
+
for (let col = 0; col < cols; col++) {
|
|
73
|
+
const termCol = leftPad + col;
|
|
74
|
+
if (termCol >= width) break;
|
|
75
|
+
|
|
76
|
+
const topDark = topRow ? (topRow[col] ?? false) : false;
|
|
77
|
+
const botDark = botRow ? (botRow[col] ?? false) : false;
|
|
78
|
+
|
|
79
|
+
let char: string;
|
|
80
|
+
let cellFg: string;
|
|
81
|
+
let cellBg: string;
|
|
82
|
+
|
|
83
|
+
if (topDark && botDark) {
|
|
84
|
+
char = '█';
|
|
85
|
+
cellFg = fg;
|
|
86
|
+
cellBg = bg;
|
|
87
|
+
} else if (topDark && !botDark) {
|
|
88
|
+
char = '▀';
|
|
89
|
+
cellFg = fg;
|
|
90
|
+
cellBg = bg;
|
|
91
|
+
} else if (!topDark && botDark) {
|
|
92
|
+
char = '▄';
|
|
93
|
+
cellFg = fg;
|
|
94
|
+
cellBg = bg;
|
|
95
|
+
} else {
|
|
96
|
+
char = ' ';
|
|
97
|
+
cellFg = fg;
|
|
98
|
+
cellBg = bg;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Some terminals may not render block chars at full width — guard
|
|
102
|
+
const charWidth = getDisplayWidth(char);
|
|
103
|
+
if (charWidth <= 0) {
|
|
104
|
+
line[termCol] = createStyledCell(' ', { fg: cellFg, bg: cellBg });
|
|
105
|
+
} else {
|
|
106
|
+
line[termCol] = createStyledCell(char, { fg: cellFg, bg: cellBg });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Fill trailing with bg up to end of QR block
|
|
111
|
+
for (let col = leftPad + cols; col < leftPad + cols + 1 && col < width; col++) {
|
|
112
|
+
line[col] = createStyledCell(' ', { fg, bg });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
lines.push(line);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return lines;
|
|
119
|
+
}
|
|
120
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Line } from '../types/grid.ts';
|
|
2
|
+
import { fitDisplay, getDisplayWidth, truncateDisplay } from '../utils/terminal-width.ts';
|
|
3
|
+
import type { SearchManager } from '../input/search.ts';
|
|
4
|
+
import { createBottomBarLine, writeBottomBarText } from './bottom-bar.ts';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Render the search bar as a single Line[] overlay at the bottom of the viewport.
|
|
8
|
+
* Format: [ Find: <query> 3/17 up/down [n] next [N] prev [Esc] close ]
|
|
9
|
+
* The match count is dim grey; the rest of the bar is teal.
|
|
10
|
+
*/
|
|
11
|
+
export function renderSearchOverlay(
|
|
12
|
+
manager: SearchManager,
|
|
13
|
+
width: number
|
|
14
|
+
): Line[] {
|
|
15
|
+
// Match count text — displayed in dim grey, right of query, left of hints
|
|
16
|
+
const matchCount = manager.matches?.length > 0
|
|
17
|
+
? `${manager.currentMatch + 1}/${manager.matches.length} up/down`
|
|
18
|
+
: manager.query.length > 0
|
|
19
|
+
? 'No matches'
|
|
20
|
+
: '';
|
|
21
|
+
|
|
22
|
+
const locked = manager.locked;
|
|
23
|
+
const cursor = locked ? '' : '█';
|
|
24
|
+
const queryDisplay = manager.query + cursor;
|
|
25
|
+
const hints = locked
|
|
26
|
+
? ' [Up/Down] or [jk] navigate [Bksp] edit [Esc] close'
|
|
27
|
+
: ' [Enter/Tab] lock [Esc] close';
|
|
28
|
+
const label = ' Find: ';
|
|
29
|
+
const matchStr = matchCount ? ` ${matchCount}` : '';
|
|
30
|
+
|
|
31
|
+
// Build left portion: label + query (no match count — that gets separate styling)
|
|
32
|
+
const leftPart = label + queryDisplay;
|
|
33
|
+
const hintsW = getDisplayWidth(hints);
|
|
34
|
+
const matchStrW = getDisplayWidth(matchStr);
|
|
35
|
+
// Available width for left content (query area)
|
|
36
|
+
const leftWidth = width - hintsW - matchStrW - 2;
|
|
37
|
+
const truncatedLeft = fitDisplay(
|
|
38
|
+
getDisplayWidth(leftPart) > leftWidth ? truncateDisplay(leftPart, leftWidth) : leftPart,
|
|
39
|
+
leftWidth,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Build the full line text (match count embedded for positional tracking)
|
|
43
|
+
const fullLine = truncatedLeft + matchStr + hints + ' ';
|
|
44
|
+
const line = createBottomBarLine(width, { fg: '#000000', bg: '#00ffcc' });
|
|
45
|
+
writeBottomBarText(line, 0, width, fitDisplay(truncateDisplay(fullLine, width), width), { fg: '#000000', bg: '#00ffcc' });
|
|
46
|
+
|
|
47
|
+
// Overwrite match count segment with dim grey styling
|
|
48
|
+
if (matchStr.length > 0) {
|
|
49
|
+
const matchStart = getDisplayWidth(truncatedLeft);
|
|
50
|
+
writeBottomBarText(line, matchStart, matchStrW, matchStr, { fg: '#888888', bg: '#00ffcc', dim: true });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return [line];
|
|
54
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { type Line } from '../types/grid.ts';
|
|
2
|
+
import { fitDisplay, getDisplayWidth, truncateDisplay } from '../utils/terminal-width.ts';
|
|
3
|
+
import type { SelectionModal } from '../input/selection-modal.ts';
|
|
4
|
+
import {
|
|
5
|
+
createOverlayBoxLayout,
|
|
6
|
+
createOverlayContentLine,
|
|
7
|
+
createOverlayFilledBorderLine,
|
|
8
|
+
DEFAULT_OVERLAY_PALETTE,
|
|
9
|
+
OVERLAY_GLYPHS,
|
|
10
|
+
putOverlayText,
|
|
11
|
+
} from './overlay-box.ts';
|
|
12
|
+
import { getOverlaySurfaceMetrics } from './overlay-viewport.ts';
|
|
13
|
+
import { fitLabelDetailColumns, wrapWithHangingIndent } from './text-layout.ts';
|
|
14
|
+
|
|
15
|
+
const BORDER_FG = DEFAULT_OVERLAY_PALETTE.borderFg;
|
|
16
|
+
const TITLE_FG = DEFAULT_OVERLAY_PALETTE.titleFg;
|
|
17
|
+
const BODY_FG = DEFAULT_OVERLAY_PALETTE.bodyFg;
|
|
18
|
+
const MUTED_FG = DEFAULT_OVERLAY_PALETTE.mutedFg;
|
|
19
|
+
const CATEGORY_FG = '#4488cc';
|
|
20
|
+
const SELECTED_BG = DEFAULT_OVERLAY_PALETTE.selectedBg;
|
|
21
|
+
|
|
22
|
+
interface CellStyle {
|
|
23
|
+
fg: string;
|
|
24
|
+
bg?: string;
|
|
25
|
+
bold?: boolean;
|
|
26
|
+
dim?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function putText(line: Line, startX: number, maxWidth: number, text: string, style: CellStyle): void {
|
|
30
|
+
putOverlayText(line, startX, maxWidth, text, style);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Render the selection modal as Line[] for overlay in the viewport.
|
|
35
|
+
*/
|
|
36
|
+
export function renderSelectionModalOverlay(
|
|
37
|
+
modal: SelectionModal,
|
|
38
|
+
width: number,
|
|
39
|
+
viewportHeight = 24,
|
|
40
|
+
): Line[] {
|
|
41
|
+
const lines: Line[] = [];
|
|
42
|
+
const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
|
|
43
|
+
margin: 4,
|
|
44
|
+
maxWidth: 72,
|
|
45
|
+
chromeRows: modal.allowSearch ? 5 : 4,
|
|
46
|
+
minContentRows: 6,
|
|
47
|
+
maxContentRows: 10,
|
|
48
|
+
});
|
|
49
|
+
const layout = createOverlayBoxLayout(width, metrics.margin, metrics.boxWidth);
|
|
50
|
+
|
|
51
|
+
lines.push(createOverlayFilledBorderLine(width, layout, OVERLAY_GLYPHS.topLeft, OVERLAY_GLYPHS.horizontal, OVERLAY_GLYPHS.topRight, BORDER_FG, DEFAULT_OVERLAY_PALETTE.titleBg));
|
|
52
|
+
|
|
53
|
+
const titleLine = createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.titleBg);
|
|
54
|
+
putText(
|
|
55
|
+
titleLine,
|
|
56
|
+
layout.margin + 2,
|
|
57
|
+
layout.innerWidth,
|
|
58
|
+
fitDisplay(truncateDisplay(modal.title, layout.innerWidth), layout.innerWidth),
|
|
59
|
+
{ fg: TITLE_FG, bold: true },
|
|
60
|
+
);
|
|
61
|
+
lines.push(titleLine);
|
|
62
|
+
|
|
63
|
+
if (modal.allowSearch) {
|
|
64
|
+
const labelLine = createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.sectionBg);
|
|
65
|
+
putText(labelLine, layout.margin + 2, layout.innerWidth, fitDisplay(' Search', layout.innerWidth), {
|
|
66
|
+
fg: CATEGORY_FG,
|
|
67
|
+
dim: true,
|
|
68
|
+
});
|
|
69
|
+
lines.push(labelLine);
|
|
70
|
+
const searchLine = createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.inputBg);
|
|
71
|
+
const prefix = '/ ';
|
|
72
|
+
const queryAreaWidth = layout.innerWidth - getDisplayWidth(prefix);
|
|
73
|
+
const queryValue = modal.query + (modal.searchFocused ? OVERLAY_GLYPHS.cursor : '');
|
|
74
|
+
const queryText = fitDisplay(
|
|
75
|
+
truncateDisplay(queryValue, queryAreaWidth),
|
|
76
|
+
queryAreaWidth,
|
|
77
|
+
);
|
|
78
|
+
putText(searchLine, layout.margin + 2, getDisplayWidth(prefix), prefix, { fg: modal.searchFocused ? BODY_FG : MUTED_FG });
|
|
79
|
+
putText(searchLine, layout.margin + 2 + getDisplayWidth(prefix), queryAreaWidth, queryText, {
|
|
80
|
+
fg: modal.query.length > 0 || modal.searchFocused ? BODY_FG : MUTED_FG,
|
|
81
|
+
});
|
|
82
|
+
lines.push(searchLine);
|
|
83
|
+
lines.push(createOverlayFilledBorderLine(width, layout, OVERLAY_GLYPHS.teeLeft, OVERLAY_GLYPHS.horizontal, OVERLAY_GLYPHS.teeRight, BORDER_FG, DEFAULT_OVERLAY_PALETTE.sectionBg));
|
|
84
|
+
} else {
|
|
85
|
+
lines.push(createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.sectionBg));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const listTitle = createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.sectionBg);
|
|
89
|
+
putText(listTitle, layout.margin + 2, layout.innerWidth, fitDisplay(' Results', layout.innerWidth), {
|
|
90
|
+
fg: CATEGORY_FG,
|
|
91
|
+
dim: true,
|
|
92
|
+
});
|
|
93
|
+
lines.push(listTitle);
|
|
94
|
+
|
|
95
|
+
const items = modal.filteredItems;
|
|
96
|
+
if (items.length === 0) {
|
|
97
|
+
const line = createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.bodyBg);
|
|
98
|
+
const message = modal.query ? 'No matching items' : 'No items';
|
|
99
|
+
putText(line, layout.margin + 2, layout.innerWidth, fitDisplay(message, layout.innerWidth), { fg: MUTED_FG, dim: true });
|
|
100
|
+
lines.push(line);
|
|
101
|
+
} else {
|
|
102
|
+
const maxVisible = metrics.contentRows;
|
|
103
|
+
let startIdx = 0;
|
|
104
|
+
if (items.length > maxVisible) {
|
|
105
|
+
startIdx = Math.max(0, Math.min(
|
|
106
|
+
modal.selectedIndex - Math.floor(maxVisible / 2),
|
|
107
|
+
items.length - maxVisible,
|
|
108
|
+
));
|
|
109
|
+
}
|
|
110
|
+
const endIdx = Math.min(startIdx + maxVisible, items.length);
|
|
111
|
+
let lastCategory: string | undefined;
|
|
112
|
+
|
|
113
|
+
for (let i = startIdx; i < endIdx; i++) {
|
|
114
|
+
const item = items[i];
|
|
115
|
+
const isSelected = i === modal.selectedIndex;
|
|
116
|
+
|
|
117
|
+
if (item.category && item.category !== lastCategory) {
|
|
118
|
+
lastCategory = item.category;
|
|
119
|
+
const categoryLine = createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.sectionBg);
|
|
120
|
+
putText(categoryLine, layout.margin + 2, layout.innerWidth, fitDisplay(` ${item.category}`, layout.innerWidth), {
|
|
121
|
+
fg: CATEGORY_FG,
|
|
122
|
+
dim: true,
|
|
123
|
+
});
|
|
124
|
+
lines.push(categoryLine);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const indicator = isSelected ? `${OVERLAY_GLYPHS.selected} ` : ' ';
|
|
128
|
+
const indicatorWidth = 2;
|
|
129
|
+
const remaining = layout.innerWidth - indicatorWidth;
|
|
130
|
+
const labelColor = isSelected ? TITLE_FG : (item.fg ?? BODY_FG);
|
|
131
|
+
const detailColor = isSelected ? BODY_FG : MUTED_FG;
|
|
132
|
+
const labelWidth = item.detail
|
|
133
|
+
? fitLabelDetailColumns(item.label, item.detail, remaining).labelWidth
|
|
134
|
+
: remaining;
|
|
135
|
+
const labelLine = createOverlayContentLine(width, layout, BORDER_FG, isSelected ? SELECTED_BG : DEFAULT_OVERLAY_PALETTE.bodyBg);
|
|
136
|
+
putText(labelLine, layout.margin + 2, indicatorWidth, indicator, {
|
|
137
|
+
fg: isSelected ? TITLE_FG : MUTED_FG,
|
|
138
|
+
bg: isSelected ? SELECTED_BG : DEFAULT_OVERLAY_PALETTE.bodyBg,
|
|
139
|
+
bold: isSelected,
|
|
140
|
+
});
|
|
141
|
+
putText(labelLine, layout.margin + 2 + indicatorWidth, labelWidth, fitDisplay(truncateDisplay(item.label, labelWidth), labelWidth), {
|
|
142
|
+
fg: labelColor,
|
|
143
|
+
bg: isSelected ? SELECTED_BG : DEFAULT_OVERLAY_PALETTE.bodyBg,
|
|
144
|
+
bold: isSelected,
|
|
145
|
+
});
|
|
146
|
+
if (item.detail) {
|
|
147
|
+
const detailWidth = fitLabelDetailColumns(item.label, item.detail, remaining).detailWidth;
|
|
148
|
+
if (detailWidth >= 12) {
|
|
149
|
+
putText(labelLine, layout.margin + 2 + indicatorWidth + labelWidth, 2, ' ', {
|
|
150
|
+
fg: BODY_FG,
|
|
151
|
+
bg: isSelected ? SELECTED_BG : DEFAULT_OVERLAY_PALETTE.bodyBg,
|
|
152
|
+
});
|
|
153
|
+
putText(labelLine, layout.margin + 2 + indicatorWidth + labelWidth + 2, detailWidth, fitDisplay(truncateDisplay(item.detail, detailWidth), detailWidth), {
|
|
154
|
+
fg: detailColor,
|
|
155
|
+
bg: isSelected ? SELECTED_BG : DEFAULT_OVERLAY_PALETTE.bodyBg,
|
|
156
|
+
});
|
|
157
|
+
lines.push(labelLine);
|
|
158
|
+
} else {
|
|
159
|
+
lines.push(labelLine);
|
|
160
|
+
const wrappedDetails = wrapWithHangingIndent(item.detail, Math.max(8, remaining), '', 2);
|
|
161
|
+
for (const detailLineText of wrappedDetails) {
|
|
162
|
+
const detailLine = createOverlayContentLine(width, layout, BORDER_FG, isSelected ? SELECTED_BG : DEFAULT_OVERLAY_PALETTE.bodyBg);
|
|
163
|
+
putText(detailLine, layout.margin + 2 + indicatorWidth, remaining, fitDisplay(truncateDisplay(detailLineText, remaining), remaining), {
|
|
164
|
+
fg: detailColor,
|
|
165
|
+
bg: isSelected ? SELECTED_BG : DEFAULT_OVERLAY_PALETTE.bodyBg,
|
|
166
|
+
dim: !isSelected,
|
|
167
|
+
});
|
|
168
|
+
lines.push(detailLine);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
lines.push(labelLine);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (items.length > maxVisible) {
|
|
177
|
+
const above = startIdx;
|
|
178
|
+
const below = items.length - endIdx;
|
|
179
|
+
const scrollHint = above > 0 && below > 0
|
|
180
|
+
? `(${above} above, ${below} below)`
|
|
181
|
+
: below > 0
|
|
182
|
+
? `(${below} below)`
|
|
183
|
+
: `(${above} above)`;
|
|
184
|
+
const hintLine = createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.sectionBg);
|
|
185
|
+
putText(hintLine, layout.margin + 2, layout.innerWidth, fitDisplay(scrollHint, layout.innerWidth), { fg: MUTED_FG, dim: true });
|
|
186
|
+
lines.push(hintLine);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const footerLine = createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.sectionBg);
|
|
191
|
+
const selectedItem = modal.getSelected();
|
|
192
|
+
const primaryVerb = selectedItem?.primaryAction === 'toggle'
|
|
193
|
+
? '[Enter] Toggle'
|
|
194
|
+
: selectedItem?.primaryAction === 'edit'
|
|
195
|
+
? '[Enter] Edit'
|
|
196
|
+
: selectedItem?.primaryAction === 'delete'
|
|
197
|
+
? '[Enter] Delete'
|
|
198
|
+
: '[Enter] Select';
|
|
199
|
+
let hints = `[Up/Down] Navigate ${primaryVerb} [Esc] Close`;
|
|
200
|
+
if (modal.allowSearch) hints += ' [/] Search';
|
|
201
|
+
if (selectedItem?.primaryAction === 'toggle' && !selectedItem.actions) hints += ' [Space] Toggle';
|
|
202
|
+
if (selectedItem?.actions) hints += ` ${selectedItem.actions}`;
|
|
203
|
+
putText(
|
|
204
|
+
footerLine,
|
|
205
|
+
layout.margin + 2,
|
|
206
|
+
layout.innerWidth,
|
|
207
|
+
fitDisplay(truncateDisplay(hints, layout.innerWidth), layout.innerWidth),
|
|
208
|
+
{ fg: MUTED_FG, dim: true },
|
|
209
|
+
);
|
|
210
|
+
lines.push(footerLine);
|
|
211
|
+
lines.push(createOverlayFilledBorderLine(width, layout, OVERLAY_GLYPHS.bottomLeft, OVERLAY_GLYPHS.horizontal, OVERLAY_GLYPHS.bottomRight, BORDER_FG, DEFAULT_OVERLAY_PALETTE.sectionBg));
|
|
212
|
+
|
|
213
|
+
return lines;
|
|
214
|
+
}
|