@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,383 @@
|
|
|
1
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
|
+
import type { OnboardingCheckMarkersState } from '../runtime/onboarding/index.ts';
|
|
3
|
+
import { resolveRuntimeEndpointBinding } from './endpoints.ts';
|
|
4
|
+
import { isNetworkFacing } from './network-posture.ts';
|
|
5
|
+
import type { GoodVibesCliOutputFormat } from './types.ts';
|
|
6
|
+
import type { CliServicePosture } from './service-posture.ts';
|
|
7
|
+
import { getProviderIdFromModel } from '../config/provider-model.ts';
|
|
8
|
+
|
|
9
|
+
export interface CliStatusOptions {
|
|
10
|
+
readonly configManager: Pick<ConfigManager, 'get'>;
|
|
11
|
+
readonly workingDirectory: string;
|
|
12
|
+
readonly homeDirectory: string;
|
|
13
|
+
readonly onboardingMarkers?: OnboardingCheckMarkersState;
|
|
14
|
+
readonly auth?: CliAuthStatus;
|
|
15
|
+
readonly service?: CliServicePosture;
|
|
16
|
+
readonly doctor?: boolean;
|
|
17
|
+
readonly outputFormat?: GoodVibesCliOutputFormat;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CliAuthStatus {
|
|
21
|
+
readonly userStorePath: string;
|
|
22
|
+
readonly userStorePresent: boolean;
|
|
23
|
+
readonly bootstrapCredentialPath: string;
|
|
24
|
+
readonly bootstrapCredentialPresent: boolean;
|
|
25
|
+
readonly operatorTokenPath: string;
|
|
26
|
+
readonly operatorTokenPresent: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CliDoctorFinding {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly area: 'auth' | 'network' | 'onboarding' | 'security' | 'service' | 'secrets';
|
|
32
|
+
readonly severity: 'warning' | 'risk';
|
|
33
|
+
readonly summary: string;
|
|
34
|
+
readonly cause: string;
|
|
35
|
+
readonly impact: string;
|
|
36
|
+
readonly action: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CliStatusSnapshot {
|
|
40
|
+
readonly title: 'GoodVibes Agent status' | 'GoodVibes Agent doctor';
|
|
41
|
+
readonly workingDirectory: string;
|
|
42
|
+
readonly homeDirectory: string;
|
|
43
|
+
readonly provider: {
|
|
44
|
+
readonly provider: string;
|
|
45
|
+
readonly model: string;
|
|
46
|
+
readonly reasoning: string;
|
|
47
|
+
};
|
|
48
|
+
readonly auth: {
|
|
49
|
+
readonly permissionMode: unknown;
|
|
50
|
+
readonly permissionLabel: string;
|
|
51
|
+
readonly secretPolicy: unknown;
|
|
52
|
+
readonly secretPolicyLabel: string;
|
|
53
|
+
readonly localUsers: CliAuthStatus | null;
|
|
54
|
+
};
|
|
55
|
+
readonly service: {
|
|
56
|
+
readonly enabled: unknown;
|
|
57
|
+
readonly autostart: unknown;
|
|
58
|
+
readonly restartOnFailure: unknown;
|
|
59
|
+
readonly lifecycle?: CliServicePosture;
|
|
60
|
+
};
|
|
61
|
+
readonly surfaces: {
|
|
62
|
+
readonly controlPlane: ReturnType<typeof resolveRuntimeEndpointBinding> & { readonly enabled: unknown };
|
|
63
|
+
readonly httpListener: ReturnType<typeof resolveRuntimeEndpointBinding> & { readonly enabled: unknown };
|
|
64
|
+
readonly web: ReturnType<typeof resolveRuntimeEndpointBinding> & { readonly enabled: unknown };
|
|
65
|
+
};
|
|
66
|
+
readonly onboarding: {
|
|
67
|
+
readonly checked: boolean;
|
|
68
|
+
readonly scope: string;
|
|
69
|
+
readonly updatedAt: number | null;
|
|
70
|
+
};
|
|
71
|
+
readonly findings: readonly CliDoctorFinding[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function yesNo(value: unknown): string {
|
|
75
|
+
return value === true ? 'yes' : 'no';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function permissionModeLabel(mode: unknown): string {
|
|
79
|
+
if (mode === 'prompt') return 'Ask before powerful actions';
|
|
80
|
+
if (mode === 'allow-all') return 'Allow everything';
|
|
81
|
+
if (mode === 'custom') return 'Custom rules';
|
|
82
|
+
return String(mode ?? 'unknown');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function secretPolicyLabel(policy: unknown): string {
|
|
86
|
+
if (policy === 'preferred_secure') return 'Use secure storage when available';
|
|
87
|
+
if (policy === 'require_secure') return 'Require secure storage';
|
|
88
|
+
if (policy === 'plaintext_allowed') return 'Allow plaintext storage';
|
|
89
|
+
return String(policy ?? 'unknown');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function bindLine(label: string, enabled: unknown, binding: { readonly hostMode: string; readonly host: string; readonly port: number }): string {
|
|
93
|
+
return ` ${label}: ${yesNo(enabled)} (${binding.hostMode} ${binding.host}:${binding.port})`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function buildCliDoctorFindings(options: CliStatusOptions): readonly CliDoctorFinding[] {
|
|
97
|
+
const config = options.configManager;
|
|
98
|
+
const serviceEnabled = config.get('service.enabled') === true;
|
|
99
|
+
const serviceAutostart = config.get('service.autostart') === true;
|
|
100
|
+
const restartOnFailure = config.get('service.restartOnFailure') === true;
|
|
101
|
+
const daemonEnabled = config.get('danger.daemon') === true;
|
|
102
|
+
const listenerEnabled = config.get('danger.httpListener') === true;
|
|
103
|
+
const webEnabled = config.get('web.enabled') === true;
|
|
104
|
+
const controlPlaneEnabled = config.get('controlPlane.enabled') === true;
|
|
105
|
+
const controlPlaneBinding = resolveRuntimeEndpointBinding(config, 'controlPlane');
|
|
106
|
+
const httpListenerBinding = resolveRuntimeEndpointBinding(config, 'httpListener');
|
|
107
|
+
const webBinding = resolveRuntimeEndpointBinding(config, 'web');
|
|
108
|
+
const permissionMode = config.get('permissions.mode');
|
|
109
|
+
const secretPolicy = config.get('storage.secretPolicy');
|
|
110
|
+
const marker = options.onboardingMarkers?.effective;
|
|
111
|
+
const serverBackedEnabled = daemonEnabled || controlPlaneEnabled || listenerEnabled || webEnabled;
|
|
112
|
+
const networkFacingSurfaces = [
|
|
113
|
+
['control plane', controlPlaneEnabled, controlPlaneBinding],
|
|
114
|
+
['HTTP listener', listenerEnabled, httpListenerBinding],
|
|
115
|
+
['web surface', webEnabled, webBinding],
|
|
116
|
+
].filter(([, enabled, binding]) => isNetworkFacing(enabled, binding as typeof controlPlaneBinding));
|
|
117
|
+
|
|
118
|
+
const findings: CliDoctorFinding[] = [];
|
|
119
|
+
|
|
120
|
+
if (serverBackedEnabled && !serviceEnabled) {
|
|
121
|
+
findings.push({
|
|
122
|
+
id: 'service-disabled-for-server-surfaces',
|
|
123
|
+
area: 'service',
|
|
124
|
+
severity: 'warning',
|
|
125
|
+
summary: 'Server-backed surfaces are enabled but service mode is off.',
|
|
126
|
+
cause: 'One or more daemon, control-plane, listener, or web settings are enabled while service.enabled is false.',
|
|
127
|
+
impact: 'The configured surfaces may not start automatically or survive restarts.',
|
|
128
|
+
action: 'Enable service mode or disable the server-backed surfaces you do not want.',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (serviceEnabled && !serviceAutostart) {
|
|
133
|
+
findings.push({
|
|
134
|
+
id: 'service-autostart-disabled',
|
|
135
|
+
area: 'service',
|
|
136
|
+
severity: 'warning',
|
|
137
|
+
summary: 'Service mode is enabled but autostart is off.',
|
|
138
|
+
cause: 'service.enabled is true and service.autostart is false.',
|
|
139
|
+
impact: 'The external GoodVibes daemon may not be available after login or reboot even though service mode is selected.',
|
|
140
|
+
action: 'Enable service.autostart if the daemon/listener/web surfaces should stay available.',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (serviceEnabled && !restartOnFailure) {
|
|
145
|
+
findings.push({
|
|
146
|
+
id: 'service-restart-disabled',
|
|
147
|
+
area: 'service',
|
|
148
|
+
severity: 'warning',
|
|
149
|
+
summary: 'Service restart-on-failure is off.',
|
|
150
|
+
cause: 'service.enabled is true and service.restartOnFailure is false.',
|
|
151
|
+
impact: 'A crashed daemon or listener may stay down until manually restarted.',
|
|
152
|
+
action: 'Enable service.restartOnFailure for durable daemon/listener operation.',
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (options.service) {
|
|
157
|
+
for (const issue of options.service.issues) {
|
|
158
|
+
if (findings.some((finding) => finding.summary === issue)) continue;
|
|
159
|
+
findings.push({
|
|
160
|
+
id: `service-lifecycle-${findings.length}`,
|
|
161
|
+
area: 'service',
|
|
162
|
+
severity: 'warning',
|
|
163
|
+
summary: issue,
|
|
164
|
+
cause: 'The service lifecycle inspection found a mismatch between configured service/surface state and observed host state.',
|
|
165
|
+
impact: 'Daemon, control-plane, listener, or web availability may not match the configuration.',
|
|
166
|
+
action: 'Run goodvibes-agent service check, then manage the daemon from GoodVibes TUI or your daemon host tooling.',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!marker?.exists) {
|
|
172
|
+
findings.push({
|
|
173
|
+
id: 'onboarding-incomplete',
|
|
174
|
+
area: 'onboarding',
|
|
175
|
+
severity: 'warning',
|
|
176
|
+
summary: 'Onboarding has not been shown for this user.',
|
|
177
|
+
cause: 'No global user onboarding check marker was found.',
|
|
178
|
+
impact: 'Important service, network, provider, auth, or permission choices may still be implicit defaults.',
|
|
179
|
+
action: 'Run /onboarding in GoodVibes Agent or goodvibes-agent onboarding status to review setup state.',
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (networkFacingSurfaces.length > 0 && options.auth?.userStorePresent !== true) {
|
|
184
|
+
findings.push({
|
|
185
|
+
id: 'network-surface-without-local-users',
|
|
186
|
+
area: 'auth',
|
|
187
|
+
severity: 'risk',
|
|
188
|
+
summary: 'Network-facing surfaces are enabled before local users are configured.',
|
|
189
|
+
cause: `${networkFacingSurfaces.map(([name]) => name).join(', ')} are LAN/custom-bound, but no local auth user store was found.`,
|
|
190
|
+
impact: 'Remote access paths may be unusable or unsafe until local admin auth is configured.',
|
|
191
|
+
action: 'Create/verify a local admin user before exposing GoodVibes on the network.',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (networkFacingSurfaces.length > 0 && options.auth?.bootstrapCredentialPresent === true) {
|
|
196
|
+
findings.push({
|
|
197
|
+
id: 'network-surface-with-bootstrap-credential',
|
|
198
|
+
area: 'auth',
|
|
199
|
+
severity: 'risk',
|
|
200
|
+
summary: 'A bootstrap credential is still present while network-facing surfaces are enabled.',
|
|
201
|
+
cause: `${networkFacingSurfaces.map(([name]) => name).join(', ')} are LAN/custom-bound and auth-bootstrap.txt exists.`,
|
|
202
|
+
impact: 'Bootstrap credentials should be treated as temporary setup material, not long-lived network access credentials.',
|
|
203
|
+
action: 'Replace bootstrap auth with a named admin user and retire the bootstrap credential.',
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (permissionMode === 'allow-all') {
|
|
208
|
+
findings.push({
|
|
209
|
+
id: 'allow-all-permissions',
|
|
210
|
+
area: 'security',
|
|
211
|
+
severity: 'risk',
|
|
212
|
+
summary: 'Allow everything permission mode is active.',
|
|
213
|
+
cause: 'permissions.mode is allow-all.',
|
|
214
|
+
impact: 'Powerful write, edit, network, and execution tools can run without a Human-in-the-Loop (HITL) approval prompt.',
|
|
215
|
+
action: 'Use Ask before powerful actions or Custom rules unless this is an intentionally trusted environment.',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (secretPolicy === 'plaintext_allowed') {
|
|
220
|
+
findings.push({
|
|
221
|
+
id: 'plaintext-secrets-allowed',
|
|
222
|
+
area: 'secrets',
|
|
223
|
+
severity: 'risk',
|
|
224
|
+
summary: 'Plaintext secret storage is allowed.',
|
|
225
|
+
cause: 'storage.secretPolicy is plaintext_allowed.',
|
|
226
|
+
impact: 'Provider keys and surface tokens may be stored without secure backend protection.',
|
|
227
|
+
action: 'Use Require secure storage or Use secure storage when available for normal operation.',
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (listenerEnabled && isNetworkFacing(listenerEnabled, httpListenerBinding)) {
|
|
232
|
+
findings.push({
|
|
233
|
+
id: 'network-http-listener-enabled',
|
|
234
|
+
area: 'network',
|
|
235
|
+
severity: 'warning',
|
|
236
|
+
summary: 'The HTTP listener is reachable beyond loopback.',
|
|
237
|
+
cause: `HTTP listener is enabled on ${httpListenerBinding.host}:${httpListenerBinding.port} with ${httpListenerBinding.hostMode} binding.`,
|
|
238
|
+
impact: 'External tools and devices may be able to reach incoming event endpoints.',
|
|
239
|
+
action: 'Keep listener secrets/signature checks configured for every enabled webhook surface.',
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return findings;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function buildCliStatusSnapshot(options: CliStatusOptions): CliStatusSnapshot {
|
|
247
|
+
const config = options.configManager;
|
|
248
|
+
const controlPlaneBinding = resolveRuntimeEndpointBinding(config, 'controlPlane');
|
|
249
|
+
const httpListenerBinding = resolveRuntimeEndpointBinding(config, 'httpListener');
|
|
250
|
+
const webBinding = resolveRuntimeEndpointBinding(config, 'web');
|
|
251
|
+
const marker = options.onboardingMarkers?.effective;
|
|
252
|
+
const findings = buildCliDoctorFindings(options);
|
|
253
|
+
return {
|
|
254
|
+
title: options.doctor ? 'GoodVibes Agent doctor' : 'GoodVibes Agent status',
|
|
255
|
+
workingDirectory: options.workingDirectory,
|
|
256
|
+
homeDirectory: options.homeDirectory,
|
|
257
|
+
provider: {
|
|
258
|
+
provider: getProviderIdFromModel(config.get('provider.model')),
|
|
259
|
+
model: String(config.get('provider.model')),
|
|
260
|
+
reasoning: String(config.get('provider.reasoningEffort')),
|
|
261
|
+
},
|
|
262
|
+
auth: {
|
|
263
|
+
permissionMode: config.get('permissions.mode'),
|
|
264
|
+
permissionLabel: permissionModeLabel(config.get('permissions.mode')),
|
|
265
|
+
secretPolicy: config.get('storage.secretPolicy'),
|
|
266
|
+
secretPolicyLabel: secretPolicyLabel(config.get('storage.secretPolicy')),
|
|
267
|
+
localUsers: options.auth ?? null,
|
|
268
|
+
},
|
|
269
|
+
service: {
|
|
270
|
+
enabled: config.get('service.enabled'),
|
|
271
|
+
autostart: config.get('service.autostart'),
|
|
272
|
+
restartOnFailure: config.get('service.restartOnFailure'),
|
|
273
|
+
...(options.service ? { lifecycle: options.service } : {}),
|
|
274
|
+
},
|
|
275
|
+
surfaces: {
|
|
276
|
+
controlPlane: { enabled: config.get('controlPlane.enabled'), ...controlPlaneBinding },
|
|
277
|
+
httpListener: { enabled: config.get('danger.httpListener'), ...httpListenerBinding },
|
|
278
|
+
web: { enabled: config.get('web.enabled'), ...webBinding },
|
|
279
|
+
},
|
|
280
|
+
onboarding: {
|
|
281
|
+
checked: Boolean(marker?.exists),
|
|
282
|
+
scope: marker?.scope ?? 'none',
|
|
283
|
+
updatedAt: marker?.payload?.updatedAt ?? null,
|
|
284
|
+
},
|
|
285
|
+
findings,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function renderCliStatus(options: CliStatusOptions): string {
|
|
290
|
+
const config = options.configManager;
|
|
291
|
+
const snapshot = buildCliStatusSnapshot(options);
|
|
292
|
+
const serviceEnabled = snapshot.service.enabled;
|
|
293
|
+
const serviceAutostart = snapshot.service.autostart;
|
|
294
|
+
const restartOnFailure = snapshot.service.restartOnFailure;
|
|
295
|
+
const controlPlaneEnabled = snapshot.surfaces.controlPlane.enabled;
|
|
296
|
+
const listenerEnabled = snapshot.surfaces.httpListener.enabled;
|
|
297
|
+
const webEnabled = snapshot.surfaces.web.enabled;
|
|
298
|
+
const controlPlaneBinding = snapshot.surfaces.controlPlane;
|
|
299
|
+
const httpListenerBinding = snapshot.surfaces.httpListener;
|
|
300
|
+
const webBinding = snapshot.surfaces.web;
|
|
301
|
+
const marker = options.onboardingMarkers?.effective;
|
|
302
|
+
const findings = snapshot.findings;
|
|
303
|
+
|
|
304
|
+
if (options.outputFormat === 'json') return JSON.stringify(snapshot, null, 2);
|
|
305
|
+
|
|
306
|
+
const lines = [
|
|
307
|
+
snapshot.title,
|
|
308
|
+
` workingDir: ${options.workingDirectory}`,
|
|
309
|
+
` homeDir: ${options.homeDirectory}`,
|
|
310
|
+
'',
|
|
311
|
+
'Provider:',
|
|
312
|
+
` provider: ${getProviderIdFromModel(config.get('provider.model'))}`,
|
|
313
|
+
` model: ${String(config.get('provider.model'))}`,
|
|
314
|
+
` reasoning: ${String(config.get('provider.reasoningEffort'))}`,
|
|
315
|
+
'',
|
|
316
|
+
'Auth:',
|
|
317
|
+
` permissions: ${permissionModeLabel(config.get('permissions.mode'))} (${String(config.get('permissions.mode'))})`,
|
|
318
|
+
` secretPolicy: ${secretPolicyLabel(config.get('storage.secretPolicy'))} (${String(config.get('storage.secretPolicy'))})`,
|
|
319
|
+
options.auth
|
|
320
|
+
? ` localUsers: ${options.auth.userStorePresent ? 'present' : 'missing'} (${options.auth.userStorePath})`
|
|
321
|
+
: ' localUsers: unknown',
|
|
322
|
+
options.auth
|
|
323
|
+
? ` bootstrapCredential: ${options.auth.bootstrapCredentialPresent ? 'present' : 'missing'} (${options.auth.bootstrapCredentialPath})`
|
|
324
|
+
: ' bootstrapCredential: unknown',
|
|
325
|
+
options.auth
|
|
326
|
+
? ` operatorTokens: ${options.auth.operatorTokenPresent ? 'present' : 'missing'} (${options.auth.operatorTokenPath})`
|
|
327
|
+
: ' operatorTokens: unknown',
|
|
328
|
+
'',
|
|
329
|
+
'Service:',
|
|
330
|
+
` enabled: ${yesNo(serviceEnabled)}`,
|
|
331
|
+
` autostart: ${yesNo(serviceAutostart)}`,
|
|
332
|
+
` restartOnFailure: ${yesNo(restartOnFailure)}`,
|
|
333
|
+
...(options.service ? [
|
|
334
|
+
` platform: ${options.service.managed.platform}`,
|
|
335
|
+
` installed: ${yesNo(options.service.managed.installed)}`,
|
|
336
|
+
` running: ${yesNo(options.service.managed.running)}`,
|
|
337
|
+
` pid: ${options.service.managed.pid ?? 'n/a'}`,
|
|
338
|
+
` definition: ${options.service.managed.path}`,
|
|
339
|
+
` log: ${options.service.log.path ?? 'n/a'} (${options.service.log.exists ? 'present' : 'missing'})`,
|
|
340
|
+
] : []),
|
|
341
|
+
'',
|
|
342
|
+
'Surfaces:',
|
|
343
|
+
bindLine('controlPlane', controlPlaneEnabled, controlPlaneBinding),
|
|
344
|
+
bindLine('httpListener', listenerEnabled, httpListenerBinding),
|
|
345
|
+
bindLine('web', webEnabled, webBinding),
|
|
346
|
+
'',
|
|
347
|
+
'Onboarding:',
|
|
348
|
+
` checked: ${marker?.exists ? 'yes' : 'no'}`,
|
|
349
|
+
` scope: ${marker?.scope ?? 'none'}`,
|
|
350
|
+
` updatedAt: ${marker?.payload ? new Date(marker.payload.updatedAt).toISOString() : 'n/a'}`,
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
if (options.doctor) {
|
|
354
|
+
lines.push('', 'Warnings:');
|
|
355
|
+
if (findings.length === 0) {
|
|
356
|
+
lines.push(' none');
|
|
357
|
+
} else {
|
|
358
|
+
for (const finding of findings) {
|
|
359
|
+
lines.push(
|
|
360
|
+
` - [${finding.severity}:${finding.area}:${finding.id}] ${finding.summary}`,
|
|
361
|
+
` cause: ${finding.cause}`,
|
|
362
|
+
` impact: ${finding.impact}`,
|
|
363
|
+
` action: ${finding.action}`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return lines.join('\n');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function renderOnboardingCliStatus(options: CliStatusOptions): string {
|
|
373
|
+
const marker = options.onboardingMarkers?.effective;
|
|
374
|
+
return [
|
|
375
|
+
'GoodVibes onboarding status',
|
|
376
|
+
` checked: ${marker?.exists ? 'yes' : 'no'}`,
|
|
377
|
+
` scope: ${marker?.scope ?? 'none'}`,
|
|
378
|
+
` source: ${marker?.payload?.source ?? 'n/a'}`,
|
|
379
|
+
` mode: ${marker?.payload?.mode ?? 'n/a'}`,
|
|
380
|
+
` updatedAt: ${marker?.payload ? new Date(marker.payload.updatedAt).toISOString() : 'n/a'}`,
|
|
381
|
+
` workingDir: ${options.workingDirectory}`,
|
|
382
|
+
].join('\n');
|
|
383
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import type { ConfigKey } from '../config/index.ts';
|
|
2
|
+
import {
|
|
3
|
+
GOODVIBES_NTFY_AGENT_TOPIC,
|
|
4
|
+
GOODVIBES_NTFY_CHAT_TOPIC,
|
|
5
|
+
GOODVIBES_NTFY_REMOTE_TOPIC,
|
|
6
|
+
resolveGoodVibesNtfyTopics,
|
|
7
|
+
} from '@pellux/goodvibes-sdk/platform/integrations';
|
|
8
|
+
import { getMissingSurfaceFeatureFlags } from '../runtime/surface-feature-flags.ts';
|
|
9
|
+
import { resolveRuntimeEndpointBinding } from './endpoints.ts';
|
|
10
|
+
import { classifyBindPosture, isNetworkFacing } from './network-posture.ts';
|
|
11
|
+
import type { CliCommandRuntime } from './management.ts';
|
|
12
|
+
import {
|
|
13
|
+
formatJsonOrText,
|
|
14
|
+
isPresentConfigValue,
|
|
15
|
+
probeTcp,
|
|
16
|
+
readAuthPaths,
|
|
17
|
+
yesNo,
|
|
18
|
+
} from './management.ts';
|
|
19
|
+
|
|
20
|
+
export const SURFACE_CONFIGS = [
|
|
21
|
+
['slack', 'Slack', ['surfaces.slack.signingSecret', 'surfaces.slack.botToken']],
|
|
22
|
+
['discord', 'Discord', ['surfaces.discord.publicKey', 'surfaces.discord.botToken', 'surfaces.discord.applicationId']],
|
|
23
|
+
['telegram', 'Telegram', ['surfaces.telegram.botToken']],
|
|
24
|
+
['webhook', 'Webhook', ['surfaces.webhook.secret']],
|
|
25
|
+
['ntfy', 'ntfy', ['surfaces.ntfy.baseUrl']],
|
|
26
|
+
['googleChat', 'Google Chat', ['surfaces.googleChat.webhookUrl']],
|
|
27
|
+
['signal', 'Signal', ['surfaces.signal.bridgeUrl', 'surfaces.signal.account']],
|
|
28
|
+
['whatsapp', 'WhatsApp', ['surfaces.whatsapp.accessToken', 'surfaces.whatsapp.phoneNumberId']],
|
|
29
|
+
['imessage', 'iMessage', ['surfaces.imessage.bridgeUrl', 'surfaces.imessage.account']],
|
|
30
|
+
['msteams', 'Microsoft Teams', ['surfaces.msteams.appId', 'surfaces.msteams.appPassword']],
|
|
31
|
+
['bluebubbles', 'BlueBubbles', ['surfaces.bluebubbles.serverUrl', 'surfaces.bluebubbles.password']],
|
|
32
|
+
['mattermost', 'Mattermost', ['surfaces.mattermost.baseUrl', 'surfaces.mattermost.botToken']],
|
|
33
|
+
['matrix', 'Matrix', ['surfaces.matrix.homeserverUrl', 'surfaces.matrix.accessToken', 'surfaces.matrix.userId']],
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
export async function handleSurfacesCommand(runtime: CliCommandRuntime): Promise<{ readonly output: string; readonly exitCode: number }> {
|
|
37
|
+
const config = runtime.configManager;
|
|
38
|
+
const [sub = 'list', ...rest] = runtime.cli.commandArgs;
|
|
39
|
+
const target = rest[0];
|
|
40
|
+
if (sub === 'enable' || sub === 'disable') {
|
|
41
|
+
if (!target) return { output: `Usage: goodvibes-agent surfaces ${sub} <web|listener|control-plane|surfaceId>`, exitCode: 2 };
|
|
42
|
+
const text = [
|
|
43
|
+
'GoodVibes Agent does not mutate daemon, listener, web, or channel surface posture.',
|
|
44
|
+
'Configure those surfaces from GoodVibes TUI or the externally managed daemon host, then use `goodvibes-agent surfaces check` for read-only diagnostics.',
|
|
45
|
+
].join(' ');
|
|
46
|
+
return {
|
|
47
|
+
output: formatJsonOrText(runtime.cli)({
|
|
48
|
+
ok: false,
|
|
49
|
+
kind: 'daemon_lifecycle_external',
|
|
50
|
+
action: `surfaces.${sub}`,
|
|
51
|
+
target,
|
|
52
|
+
error: text,
|
|
53
|
+
}, text),
|
|
54
|
+
exitCode: 2,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (sub !== 'list' && sub !== 'status' && sub !== 'check' && sub !== 'show') {
|
|
58
|
+
return { output: 'Usage: goodvibes-agent surfaces [list|check|show <surfaceId>]', exitCode: 2 };
|
|
59
|
+
}
|
|
60
|
+
const controlPlane = resolveRuntimeEndpointBinding(config, 'controlPlane');
|
|
61
|
+
const web = resolveRuntimeEndpointBinding(config, 'web');
|
|
62
|
+
const httpListener = resolveRuntimeEndpointBinding(config, 'httpListener');
|
|
63
|
+
const includeProbe = sub === 'check';
|
|
64
|
+
const targetExternalSurface = target && SURFACE_CONFIGS.some(([id]) => id === target);
|
|
65
|
+
const shouldProbeControlPlane = includeProbe && !target;
|
|
66
|
+
const shouldProbeWeb = includeProbe && !target;
|
|
67
|
+
const shouldProbeListener = includeProbe && (!target || targetExternalSurface);
|
|
68
|
+
const [controlPlaneReachable, webReachable, listenerReachable] = includeProbe
|
|
69
|
+
? await Promise.all([
|
|
70
|
+
shouldProbeControlPlane ? probeTcp(controlPlane.host, controlPlane.port) : Promise.resolve(undefined),
|
|
71
|
+
shouldProbeWeb ? probeTcp(web.host, web.port) : Promise.resolve(undefined),
|
|
72
|
+
shouldProbeListener ? probeTcp(httpListener.host, httpListener.port) : Promise.resolve(undefined),
|
|
73
|
+
])
|
|
74
|
+
: [undefined, undefined, undefined];
|
|
75
|
+
const externalSurfaces = SURFACE_CONFIGS.map(([id, label, requiredKeys]) => {
|
|
76
|
+
const enabled = config.get(`surfaces.${id}.enabled` as ConfigKey);
|
|
77
|
+
const missing = requiredKeys.filter((key) => !isPresentConfigValue(config.get(key as ConfigKey)));
|
|
78
|
+
const missingFeatureFlags = enabled === true ? getMissingSurfaceFeatureFlags(config, id) : [];
|
|
79
|
+
return {
|
|
80
|
+
id,
|
|
81
|
+
label,
|
|
82
|
+
enabled,
|
|
83
|
+
ready: !enabled || (missing.length === 0 && missingFeatureFlags.length === 0),
|
|
84
|
+
missing,
|
|
85
|
+
missingFeatureFlags,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
const filteredSurfaces = target ? externalSurfaces.filter((surface) => surface.id === target) : externalSurfaces;
|
|
89
|
+
if (target && filteredSurfaces.length === 0) return { output: `Unknown surface: ${target}`, exitCode: 1 };
|
|
90
|
+
const ntfyTopics = resolveGoodVibesNtfyTopics({
|
|
91
|
+
chatTopic: String(config.get('surfaces.ntfy.chatTopic' as ConfigKey) || GOODVIBES_NTFY_CHAT_TOPIC),
|
|
92
|
+
agentTopic: String(config.get('surfaces.ntfy.agentTopic' as ConfigKey) || GOODVIBES_NTFY_AGENT_TOPIC),
|
|
93
|
+
remoteTopic: String(config.get('surfaces.ntfy.remoteTopic' as ConfigKey) || GOODVIBES_NTFY_REMOTE_TOPIC),
|
|
94
|
+
});
|
|
95
|
+
const readinessIssues: string[] = [];
|
|
96
|
+
if (shouldProbeControlPlane && config.get('controlPlane.enabled') === true && !controlPlaneReachable) {
|
|
97
|
+
readinessIssues.push(`Control plane is enabled but not reachable on ${controlPlane.host}:${controlPlane.port}.`);
|
|
98
|
+
}
|
|
99
|
+
if (shouldProbeWeb && config.get('web.enabled') === true && !webReachable) {
|
|
100
|
+
readinessIssues.push(`Web surface is enabled but not reachable on ${web.host}:${web.port}.`);
|
|
101
|
+
}
|
|
102
|
+
if (shouldProbeListener && config.get('danger.httpListener') === true && !listenerReachable) {
|
|
103
|
+
readinessIssues.push(`HTTP listener is enabled but not reachable on ${httpListener.host}:${httpListener.port}.`);
|
|
104
|
+
}
|
|
105
|
+
for (const surface of filteredSurfaces) {
|
|
106
|
+
if (surface.enabled !== true) continue;
|
|
107
|
+
if (config.get('danger.httpListener') !== true) {
|
|
108
|
+
readinessIssues.push(`${surface.label} is enabled but the HTTP listener is disabled.`);
|
|
109
|
+
}
|
|
110
|
+
if (surface.missing.length > 0) {
|
|
111
|
+
readinessIssues.push(`${surface.label} is enabled but missing ${surface.missing.join(', ')}.`);
|
|
112
|
+
}
|
|
113
|
+
if (surface.missingFeatureFlags.length > 0) {
|
|
114
|
+
readinessIssues.push(`${surface.label} is enabled but feature gates are disabled: ${surface.missingFeatureFlags.join(', ')}.`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const value = {
|
|
118
|
+
controlPlane: {
|
|
119
|
+
enabled: config.get('controlPlane.enabled'),
|
|
120
|
+
hostMode: controlPlane.hostMode,
|
|
121
|
+
configuredHost: controlPlane.configuredHost,
|
|
122
|
+
host: controlPlane.host,
|
|
123
|
+
port: controlPlane.port,
|
|
124
|
+
reachable: controlPlaneReachable,
|
|
125
|
+
},
|
|
126
|
+
web: {
|
|
127
|
+
enabled: config.get('web.enabled'),
|
|
128
|
+
hostMode: web.hostMode,
|
|
129
|
+
configuredHost: web.configuredHost,
|
|
130
|
+
host: web.host,
|
|
131
|
+
port: web.port,
|
|
132
|
+
reachable: webReachable,
|
|
133
|
+
},
|
|
134
|
+
httpListener: {
|
|
135
|
+
enabled: config.get('danger.httpListener'),
|
|
136
|
+
hostMode: httpListener.hostMode,
|
|
137
|
+
configuredHost: httpListener.configuredHost,
|
|
138
|
+
host: httpListener.host,
|
|
139
|
+
port: httpListener.port,
|
|
140
|
+
reachable: listenerReachable,
|
|
141
|
+
},
|
|
142
|
+
surfaces: filteredSurfaces,
|
|
143
|
+
readinessIssues,
|
|
144
|
+
};
|
|
145
|
+
const output = formatJsonOrText(runtime.cli)(value, [
|
|
146
|
+
'GoodVibes Agent surfaces',
|
|
147
|
+
` control-plane: ${yesNo(value.controlPlane.enabled)} (${value.controlPlane.hostMode} ${value.controlPlane.host}:${value.controlPlane.port})${includeProbe ? ` reachable=${yesNo(value.controlPlane.reachable)}` : ''}`,
|
|
148
|
+
` web: ${yesNo(value.web.enabled)} (${value.web.hostMode} ${value.web.host}:${value.web.port})${includeProbe ? ` reachable=${yesNo(value.web.reachable)}` : ''}`,
|
|
149
|
+
` http-listener: ${yesNo(value.httpListener.enabled)} (${value.httpListener.hostMode} ${value.httpListener.host}:${value.httpListener.port})${includeProbe ? ` reachable=${yesNo(value.httpListener.reachable)}` : ''}`,
|
|
150
|
+
'',
|
|
151
|
+
'External surfaces:',
|
|
152
|
+
...value.surfaces.map((surface) => ` ${surface.label.padEnd(16)} enabled=${yesNo(surface.enabled)} ready=${yesNo(surface.ready)}${surface.enabled && surface.missing.length > 0 ? ` missing=${surface.missing.join(',')}` : ''}${surface.enabled && surface.missingFeatureFlags.length > 0 ? ` featureGates=${surface.missingFeatureFlags.join(',')}` : ''}`),
|
|
153
|
+
...(filteredSurfaces.some((surface) => surface.id === 'ntfy') ? [
|
|
154
|
+
'',
|
|
155
|
+
'ntfy inbound topics:',
|
|
156
|
+
` chat: ${ntfyTopics.chatTopic}`,
|
|
157
|
+
` agent: ${ntfyTopics.agentTopic}`,
|
|
158
|
+
` daemon-only remote: ${ntfyTopics.remoteTopic}`,
|
|
159
|
+
` default delivery topic: ${String(config.get('surfaces.ntfy.topic') || '(none)')}`,
|
|
160
|
+
] : []),
|
|
161
|
+
...(includeProbe ? [
|
|
162
|
+
readinessIssues.length === 0 ? 'Readiness: ready' : 'Readiness: needs attention',
|
|
163
|
+
...readinessIssues.map((issue) => ` - ${issue}`),
|
|
164
|
+
] : []),
|
|
165
|
+
].join('\n'));
|
|
166
|
+
return { output, exitCode: includeProbe && readinessIssues.length > 0 ? 1 : 0 };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface ListenerTestResult {
|
|
170
|
+
readonly enabled: unknown;
|
|
171
|
+
readonly hostMode: string;
|
|
172
|
+
readonly configuredHost: string;
|
|
173
|
+
readonly host: string;
|
|
174
|
+
readonly port: number;
|
|
175
|
+
readonly posture: ReturnType<typeof classifyBindPosture>;
|
|
176
|
+
readonly reachable: boolean;
|
|
177
|
+
readonly service: {
|
|
178
|
+
readonly enabled: unknown;
|
|
179
|
+
readonly autostart: unknown;
|
|
180
|
+
readonly restartOnFailure: unknown;
|
|
181
|
+
};
|
|
182
|
+
readonly auth: ReturnType<typeof readAuthPaths>;
|
|
183
|
+
readonly surfaces: readonly {
|
|
184
|
+
readonly id: string;
|
|
185
|
+
readonly label: string;
|
|
186
|
+
readonly enabled: unknown;
|
|
187
|
+
readonly ready: boolean;
|
|
188
|
+
readonly missing: readonly string[];
|
|
189
|
+
readonly missingFeatureFlags: readonly string[];
|
|
190
|
+
}[];
|
|
191
|
+
readonly issues: readonly string[];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function buildListenerTestResult(runtime: CliCommandRuntime): Promise<ListenerTestResult> {
|
|
195
|
+
const enabled = runtime.configManager.get('danger.httpListener');
|
|
196
|
+
const binding = resolveRuntimeEndpointBinding(runtime.configManager, 'httpListener');
|
|
197
|
+
const posture = classifyBindPosture(binding);
|
|
198
|
+
const reachable = enabled === true ? await probeTcp(binding.host, binding.port) : false;
|
|
199
|
+
const auth = readAuthPaths(runtime);
|
|
200
|
+
const service = {
|
|
201
|
+
enabled: runtime.configManager.get('service.enabled'),
|
|
202
|
+
autostart: runtime.configManager.get('service.autostart'),
|
|
203
|
+
restartOnFailure: runtime.configManager.get('service.restartOnFailure'),
|
|
204
|
+
};
|
|
205
|
+
const surfaces = SURFACE_CONFIGS.map(([id, label, requiredKeys]) => {
|
|
206
|
+
const surfaceEnabled = runtime.configManager.get(`surfaces.${id}.enabled` as ConfigKey);
|
|
207
|
+
const missing = requiredKeys.filter((key) => !isPresentConfigValue(runtime.configManager.get(key as ConfigKey)));
|
|
208
|
+
const missingFeatureFlags = surfaceEnabled === true ? getMissingSurfaceFeatureFlags(runtime.configManager, id) : [];
|
|
209
|
+
return {
|
|
210
|
+
id,
|
|
211
|
+
label,
|
|
212
|
+
enabled: surfaceEnabled,
|
|
213
|
+
ready: surfaceEnabled !== true || (missing.length === 0 && missingFeatureFlags.length === 0),
|
|
214
|
+
missing,
|
|
215
|
+
missingFeatureFlags,
|
|
216
|
+
};
|
|
217
|
+
}).filter((surface) => surface.enabled === true);
|
|
218
|
+
const issues: string[] = [];
|
|
219
|
+
if (enabled !== true) issues.push('HTTP listener is disabled.');
|
|
220
|
+
if (enabled === true && service.enabled !== true) issues.push('HTTP listener is enabled but service mode is off.');
|
|
221
|
+
if (enabled === true && service.autostart !== true) issues.push('HTTP listener is enabled but service autostart is off.');
|
|
222
|
+
if (enabled === true && service.restartOnFailure !== true) issues.push('HTTP listener is enabled but service restart-on-failure is off.');
|
|
223
|
+
if (isNetworkFacing(enabled, binding) && !auth.userStorePresent) issues.push('Network-facing listener has no local auth user store.');
|
|
224
|
+
if (isNetworkFacing(enabled, binding) && auth.bootstrapCredentialPresent) issues.push('Network-facing listener still has a bootstrap credential file.');
|
|
225
|
+
for (const surface of surfaces) {
|
|
226
|
+
if (surface.missing.length > 0) issues.push(`${surface.label} is enabled but missing ${surface.missing.join(', ')}.`);
|
|
227
|
+
if (surface.missingFeatureFlags.length > 0) issues.push(`${surface.label} is enabled but feature gates are disabled: ${surface.missingFeatureFlags.join(', ')}.`);
|
|
228
|
+
}
|
|
229
|
+
return { enabled, ...binding, posture, reachable, service, auth, surfaces, issues };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function formatListenerTestResult(runtime: CliCommandRuntime, value: ListenerTestResult): string {
|
|
233
|
+
return formatJsonOrText(runtime.cli)(value, [
|
|
234
|
+
'GoodVibes Agent listener test',
|
|
235
|
+
` enabled: ${yesNo(value.enabled)}`,
|
|
236
|
+
` endpoint: ${value.hostMode} ${value.host}:${value.port}`,
|
|
237
|
+
` bind posture: ${value.posture.label}`,
|
|
238
|
+
` reachable: ${yesNo(value.reachable)}`,
|
|
239
|
+
` service: enabled=${yesNo(value.service.enabled)} autostart=${yesNo(value.service.autostart)} restartOnFailure=${yesNo(value.service.restartOnFailure)}`,
|
|
240
|
+
` local auth users: ${value.auth.userStorePresent ? 'present' : 'missing'}`,
|
|
241
|
+
` bootstrap credential: ${value.auth.bootstrapCredentialPresent ? 'present' : 'missing'}`,
|
|
242
|
+
value.surfaces.length === 0 ? ' enabled webhook surfaces: none' : ' enabled webhook surfaces:',
|
|
243
|
+
...value.surfaces.map((surface) => ` ${surface.label}: ready=${yesNo(surface.ready)}${surface.missing.length > 0 ? ` missing=${surface.missing.join(',')}` : ''}${surface.missingFeatureFlags.length > 0 ? ` featureGates=${surface.missingFeatureFlags.join(',')}` : ''}`),
|
|
244
|
+
value.issues.length === 0 ? ' readiness: ready' : ' readiness: needs attention',
|
|
245
|
+
...value.issues.map((issue) => ` - ${issue}`),
|
|
246
|
+
].join('\n'));
|
|
247
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CommandContext, CommandRegistry } from '../input/command-registry.ts';
|
|
2
|
+
import type { InputHandler } from '../input/handler.ts';
|
|
3
|
+
import { readOnboardingCheckMarker } from '../runtime/onboarding/index.ts';
|
|
4
|
+
import type { GoodVibesCliParseResult } from './types.ts';
|
|
5
|
+
|
|
6
|
+
export function applyInitialTuiCliState(options: {
|
|
7
|
+
readonly cli: GoodVibesCliParseResult;
|
|
8
|
+
readonly input: InputHandler;
|
|
9
|
+
readonly commandRegistry: CommandRegistry;
|
|
10
|
+
readonly commandContext: CommandContext;
|
|
11
|
+
readonly shellPaths: Parameters<typeof readOnboardingCheckMarker>[0];
|
|
12
|
+
readonly render: () => void;
|
|
13
|
+
}): void {
|
|
14
|
+
const { cli, input, commandRegistry, commandContext, shellPaths, render } = options;
|
|
15
|
+
const globalOnboardingMarker = readOnboardingCheckMarker(shellPaths, 'user');
|
|
16
|
+
if (cli.command === 'onboarding') {
|
|
17
|
+
input.openOnboardingWizard({ mode: 'edit', reset: true });
|
|
18
|
+
} else if (cli.command === 'sessions' && cli.commandArgs[0] === 'resume') {
|
|
19
|
+
const target = cli.commandArgs.slice(1).join(' ').trim();
|
|
20
|
+
if (target) {
|
|
21
|
+
void commandRegistry.execute('session', ['resume', target], commandContext).then(() => render());
|
|
22
|
+
}
|
|
23
|
+
} else if (!globalOnboardingMarker.exists) {
|
|
24
|
+
input.openOnboardingWizard({ mode: 'new', reset: true });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const seededPrompt = cli.flags.prompt ?? (cli.rawCommand === undefined && cli.positionals.length > 0 ? cli.positionals.join(' ') : undefined);
|
|
28
|
+
if (seededPrompt) {
|
|
29
|
+
input.prompt = seededPrompt;
|
|
30
|
+
input.cursorPos = seededPrompt.length;
|
|
31
|
+
}
|
|
32
|
+
}
|