@pellux/goodvibes-tui 1.16.0 → 1.17.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/CHANGELOG.md +95 -9
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1091 -271
- package/package.json +7 -7
- package/src/cli/completions/generate.ts +5 -4
- package/src/cli/config-overrides.ts +51 -11
- package/src/cli/doctor.ts +4 -5
- package/src/cli/entrypoint.ts +13 -8
- package/src/cli/help.ts +7 -6
- package/src/cli/launch-auto-update.ts +214 -0
- package/src/cli/parser.ts +5 -0
- package/src/cli/status.ts +13 -0
- package/src/cli/surface-command.ts +5 -5
- package/src/cli/tui-startup.ts +60 -97
- package/src/cli/types.ts +2 -0
- package/src/config/index.ts +1 -1
- package/src/config/tui-extension-settings.ts +33 -0
- package/src/core/composer-state.ts +1 -1
- package/src/core/conversation-line-cache.ts +3 -3
- package/src/core/conversation-rendering.ts +4 -4
- package/src/core/conversation.ts +2 -2
- package/src/core/long-task-notifier.ts +7 -7
- package/src/core/stream-event-wiring.ts +8 -6
- package/src/core/system-message-noise.ts +1 -1
- package/src/core/system-message-router.ts +6 -6
- package/src/core/turn-cancellation.ts +1 -1
- package/src/core/turn-event-wiring.ts +8 -8
- package/src/daemon/cli.ts +20 -5
- package/src/daemon/lifecycle.ts +41 -0
- package/src/daemon/service-commands.ts +5 -5
- package/src/export/cost-utils.ts +128 -6
- package/src/input/autocomplete.ts +2 -2
- package/src/input/command-registry.ts +34 -9
- package/src/input/commands/ci-runtime.ts +5 -1
- package/src/input/commands/codebase-runtime.ts +6 -6
- package/src/input/commands/cost-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +2 -2
- package/src/input/commands/intelligence-runtime.ts +1 -1
- package/src/input/commands/local-auth-runtime.ts +1 -1
- package/src/input/commands/local-provider-runtime.ts +49 -10
- package/src/input/commands/local-runtime.ts +53 -5
- package/src/input/commands/memory.ts +2 -2
- package/src/input/commands/operator-panel-runtime.ts +5 -5
- package/src/input/commands/permissions-provenance.ts +1 -1
- package/src/input/commands/permissions-runtime.ts +99 -8
- package/src/input/commands/planning-runtime.ts +3 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -1
- package/src/input/commands/provider-accounts-runtime.ts +71 -6
- package/src/input/commands/provider.ts +7 -5
- package/src/input/commands/recall-files-git.ts +13 -51
- package/src/input/commands/recall-files.ts +8 -11
- package/src/input/commands/recall-review.ts +2 -2
- package/src/input/commands/relay-runtime.ts +1 -1
- package/src/input/commands/remote-runtime.ts +1 -1
- package/src/input/commands/runtime-services.ts +4 -4
- package/src/input/commands/schedule-runtime.ts +14 -7
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +2 -2
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/share-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +46 -68
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands/update-runtime.ts +153 -96
- package/src/input/commands/work-plan-runtime.ts +1 -1
- package/src/input/commands/workstream-runtime.ts +2 -2
- package/src/input/commands.ts +0 -2
- package/src/input/config-modal-types.ts +3 -3
- package/src/input/config-modal.ts +10 -11
- package/src/input/feature-unit-layout.ts +93 -82
- package/src/input/feed-context-factory.ts +2 -2
- package/src/input/handler-command-route.ts +2 -2
- package/src/input/handler-content-actions.ts +1 -1
- package/src/input/handler-feed-routes.ts +7 -7
- package/src/input/handler-feed.ts +4 -4
- package/src/input/handler-interactions.ts +3 -2
- package/src/input/handler-modal-routes.ts +12 -4
- package/src/input/handler-modal-token-routes.ts +1 -1
- package/src/input/handler-onboarding-daemon-adopt.ts +2 -2
- package/src/input/handler-onboarding.ts +3 -2
- package/src/input/handler-picker-routes.ts +26 -13
- package/src/input/handler-shortcuts.ts +6 -6
- package/src/input/handler.ts +2 -2
- package/src/input/keybindings.ts +2 -2
- package/src/input/onboarding/onboarding-feature-units.ts +34 -27
- package/src/input/onboarding/onboarding-provider-key-fields.ts +112 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +22 -21
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +17 -0
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +2 -2
- package/src/input/onboarding/onboarding-wizard-steps.ts +30 -42
- package/src/input/panel-integration-actions.ts +2 -2
- package/src/input/panel-mouse-geometry.ts +1 -1
- package/src/input/panel-paste-flood-guard.ts +1 -1
- package/src/input/provider-key-intake.ts +167 -0
- package/src/input/sandbox-exec-config.ts +2 -2
- package/src/input/selection-modal.ts +1 -1
- package/src/input/settings-modal-activation.ts +5 -0
- package/src/input/settings-modal-adjustment.ts +4 -31
- package/src/input/settings-modal-data.ts +22 -21
- package/src/input/settings-modal-mutations.ts +12 -68
- package/src/input/settings-modal-types.ts +23 -16
- package/src/input/settings-modal.ts +74 -55
- package/src/main.ts +48 -48
- package/src/panels/agent-inspector-shared.ts +4 -4
- package/src/panels/base-panel.ts +2 -2
- package/src/panels/builtin/agent.ts +4 -4
- package/src/panels/builtin/development.ts +4 -4
- package/src/panels/builtin/knowledge.ts +1 -1
- package/src/panels/builtin/operations.ts +29 -10
- package/src/panels/builtin/session.ts +5 -5
- package/src/panels/builtin/shared.ts +4 -4
- package/src/panels/builtin-modals.ts +10 -10
- package/src/panels/builtin-panels.ts +1 -1
- package/src/panels/cost-tracker-panel.ts +124 -7
- package/src/panels/diff-panel.ts +5 -5
- package/src/panels/eval-registry.ts +2 -2
- package/src/panels/fleet-deep-link.ts +1 -1
- package/src/panels/fleet-panel-format.ts +44 -8
- package/src/panels/fleet-panel.ts +16 -16
- package/src/panels/fleet-read-model.ts +52 -13
- package/src/panels/fleet-steer.ts +2 -2
- package/src/panels/fleet-stop.ts +3 -3
- package/src/panels/fleet-tabs.ts +13 -14
- package/src/panels/fleet-transcript.ts +5 -5
- package/src/panels/git-panel.ts +2 -2
- package/src/panels/index.ts +4 -1
- package/src/panels/local-auth-panel.ts +1 -1
- package/src/panels/modals/hooks-modal.ts +1 -1
- package/src/panels/modals/keybindings-modal.ts +2 -2
- package/src/panels/modals/knowledge-modal.ts +1 -1
- package/src/panels/modals/marketplace-modal.ts +4 -4
- package/src/panels/modals/memory-modal.ts +4 -4
- package/src/panels/modals/modal-surface-helpers.ts +1 -1
- package/src/panels/modals/modal-theme.ts +1 -1
- package/src/panels/modals/pairing-modal.ts +2 -2
- package/src/panels/modals/planning-modal.ts +2 -2
- package/src/panels/modals/plugins-modal.ts +1 -1
- package/src/panels/modals/policy-modal.ts +1 -1
- package/src/panels/modals/security-modal.ts +1 -1
- package/src/panels/modals/skills-modal.ts +1 -1
- package/src/panels/modals/work-plan-modal.ts +3 -3
- package/src/panels/notifications-feed.ts +127 -0
- package/src/panels/notifications-panel.ts +160 -0
- package/src/panels/panel-manager.ts +9 -9
- package/src/panels/plugins-panel.ts +4 -4
- package/src/panels/project-planning-answer-actions.ts +1 -1
- package/src/panels/provider-health-routes.ts +2 -2
- package/src/panels/skills-panel.ts +4 -5
- package/src/panels/token-budget-panel.ts +11 -3
- package/src/panels/types.ts +5 -5
- package/src/permissions/broker-approval-card.ts +201 -0
- package/src/permissions/hunk-selection.ts +28 -4
- package/src/permissions/prompt.ts +244 -29
- package/src/renderer/autocomplete-overlay.ts +107 -54
- package/src/renderer/code-block.ts +2 -2
- package/src/renderer/compaction-history-modal.ts +1 -1
- package/src/renderer/compaction-preview.ts +1 -1
- package/src/renderer/compaction-quality.ts +1 -1
- package/src/renderer/compositor.ts +2 -2
- package/src/renderer/config-modal.ts +1 -1
- package/src/renderer/conversation-overlays.ts +1 -1
- package/src/renderer/footer-tips.ts +3 -3
- package/src/renderer/hint-grammar.ts +1 -1
- package/src/renderer/modal-factory.ts +1 -1
- package/src/renderer/model-picker-overlay.ts +1 -1
- package/src/renderer/model-workspace.ts +7 -2
- package/src/renderer/onboarding/onboarding-wizard.ts +22 -8
- package/src/renderer/panel-composite.ts +2 -2
- package/src/renderer/panel-workspace-bar.ts +1 -1
- package/src/renderer/process-indicator.ts +1 -1
- package/src/renderer/selection-modal-overlay.ts +102 -24
- package/src/renderer/session-picker-modal.ts +2 -2
- package/src/renderer/settings-modal-helpers.ts +14 -5
- package/src/renderer/settings-modal.ts +127 -39
- package/src/renderer/shell-surface.ts +3 -0
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/theme.ts +3 -3
- package/src/renderer/tool-call.ts +1 -1
- package/src/renderer/tool-result-summary.ts +1 -1
- package/src/renderer/turn-injection.ts +7 -6
- package/src/renderer/ui-factory.ts +6 -1
- package/src/runtime/bootstrap-command-context.ts +4 -1
- package/src/runtime/bootstrap-command-parts.ts +6 -4
- package/src/runtime/bootstrap-core.ts +35 -35
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap-shell.ts +6 -5
- package/src/runtime/bootstrap.ts +34 -32
- package/src/runtime/channel-composition.ts +46 -0
- package/src/runtime/code-index-services.ts +3 -4
- package/src/runtime/daemon-attach-notices.ts +120 -0
- package/src/runtime/durability-services.ts +55 -0
- package/src/runtime/embedded-service-factories.ts +40 -0
- package/src/runtime/feature-settings.ts +147 -0
- package/src/runtime/index.ts +4 -21
- package/src/runtime/legacy-daemon-migration.ts +29 -19
- package/src/runtime/memory-fold.ts +1 -1
- package/src/runtime/notification-dispatch.ts +110 -0
- package/src/runtime/onboarding/apply.ts +3 -29
- package/src/runtime/onboarding/fast-path.ts +39 -6
- package/src/runtime/onboarding/provider-key-capture.ts +126 -0
- package/src/runtime/onboarding/snapshot.ts +0 -1
- package/src/runtime/onboarding/types.ts +9 -3
- package/src/runtime/provider-fallback.ts +55 -0
- package/src/runtime/provider-optimizer-wiring.ts +55 -0
- package/src/runtime/release-artifacts.ts +19 -103
- package/src/runtime/resume-notice.ts +1 -1
- package/src/runtime/services.ts +81 -82
- package/src/runtime/session-continuity-hints.ts +38 -0
- package/src/runtime/surface-feature-flags.ts +40 -9
- package/src/runtime/terminal-output-guard.ts +2 -2
- package/src/runtime/trust/workspace-registration.ts +10 -2
- package/src/runtime/trust/workspace-trust.ts +58 -31
- package/src/runtime/ui-services.ts +3 -1
- package/src/runtime/update-check.ts +15 -69
- package/src/runtime/workstream-services.ts +2 -2
- package/src/shell/blocking-input.ts +102 -6
- package/src/shell/ui-openers.ts +38 -6
- package/src/utils/format-duration.ts +1 -1
- package/src/verification/verification-ledger.ts +21 -4
- package/src/version.ts +1 -1
- package/src/input/commands/flags-runtime.ts +0 -328
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.ts",
|
|
@@ -104,8 +104,8 @@
|
|
|
104
104
|
"@anthropic-ai/vertex-sdk": "^0.16.0",
|
|
105
105
|
"@ast-grep/napi": "^0.42.0",
|
|
106
106
|
"@aws/bedrock-token-generator": "^1.1.0",
|
|
107
|
-
"@pellux/goodvibes-sdk": "1.
|
|
108
|
-
"@pellux/goodvibes-terminal-shell": "1.
|
|
107
|
+
"@pellux/goodvibes-sdk": "1.8.0",
|
|
108
|
+
"@pellux/goodvibes-terminal-shell": "1.8.0",
|
|
109
109
|
"bash-language-server": "^5.6.0",
|
|
110
110
|
"fuse.js": "^7.1.0",
|
|
111
111
|
"graphql": "^16.13.2",
|
|
@@ -131,10 +131,10 @@
|
|
|
131
131
|
"typescript": "^5.9.3"
|
|
132
132
|
},
|
|
133
133
|
"optionalDependencies": {
|
|
134
|
-
"@pellux/goodvibes-tui-linux-x64": "1.
|
|
135
|
-
"@pellux/goodvibes-tui-linux-arm64": "1.
|
|
136
|
-
"@pellux/goodvibes-tui-darwin-x64": "1.
|
|
137
|
-
"@pellux/goodvibes-tui-darwin-arm64": "1.
|
|
134
|
+
"@pellux/goodvibes-tui-linux-x64": "1.17.0",
|
|
135
|
+
"@pellux/goodvibes-tui-linux-arm64": "1.17.0",
|
|
136
|
+
"@pellux/goodvibes-tui-darwin-x64": "1.17.0",
|
|
137
|
+
"@pellux/goodvibes-tui-darwin-arm64": "1.17.0"
|
|
138
138
|
},
|
|
139
139
|
"trustedDependencies": [],
|
|
140
140
|
"overrides": {
|
|
@@ -93,8 +93,8 @@ export const GLOBAL_FLAGS: readonly CompletionFlag[] = [
|
|
|
93
93
|
takesValue: true,
|
|
94
94
|
description: 'Override a config value (key=value)',
|
|
95
95
|
},
|
|
96
|
-
{ name: '--enable', takesValue: true, description: 'Enable a feature
|
|
97
|
-
{ name: '--disable', takesValue: true, description: 'Disable a feature
|
|
96
|
+
{ name: '--enable', takesValue: true, description: 'Enable a platform feature for this launch (writes its domain setting)' },
|
|
97
|
+
{ name: '--disable', takesValue: true, description: 'Disable a platform feature for this launch (writes its domain setting)' },
|
|
98
98
|
{
|
|
99
99
|
name: '--prompt',
|
|
100
100
|
short: '-p',
|
|
@@ -139,6 +139,7 @@ export const GLOBAL_FLAGS: readonly CompletionFlag[] = [
|
|
|
139
139
|
{ name: '--fork', takesValue: false, description: 'Fork session when supported' },
|
|
140
140
|
{ name: '--yes', short: '-y', takesValue: false, description: 'Auto-confirm prompts (non-interactive)' },
|
|
141
141
|
{ name: '--non-interactive', takesValue: false, description: 'Disable all interactive prompts (implies --yes)' },
|
|
142
|
+
{ name: '--strict', takesValue: false, description: 'doctor: also fail on advisory findings, for CI' },
|
|
142
143
|
] as const;
|
|
143
144
|
|
|
144
145
|
// ---------------------------------------------------------------------------
|
|
@@ -504,8 +505,8 @@ export function generateZsh(surface: CompletionSurface): string {
|
|
|
504
505
|
lines.push(` '--working-dir[Alias for --cd]:dir:_files -/' \\`);
|
|
505
506
|
lines.push(` '--daemon-home[Override daemon home]:dir:_files -/' \\`);
|
|
506
507
|
lines.push(` '(-c --config)'{-c,--config}'[Override config value]:key=value:' \\`);
|
|
507
|
-
lines.push(` '--enable[Enable feature
|
|
508
|
-
lines.push(` '--disable[Disable feature
|
|
508
|
+
lines.push(` '--enable[Enable platform feature]:feature:' \\`);
|
|
509
|
+
lines.push(` '--disable[Disable platform feature]:feature:' \\`);
|
|
509
510
|
lines.push(` '(-p --prompt)'{-p,--prompt}'[Run non-interactive prompt]:text:' \\`);
|
|
510
511
|
lines.push(` '--print[Non-interactive run mode]' \\`);
|
|
511
512
|
lines.push(` '(-o --output --output-format)'{-o,--output}'[Output format]:format:(text json stream-json)' \\`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import type { ConfigKey, ConfigManager, ConfigSetting, GoodVibesConfig
|
|
2
|
+
import type { ConfigKey, ConfigManager, ConfigSetting, GoodVibesConfig } from '../config/index.ts';
|
|
3
3
|
import { CONFIG_SCHEMA, ConfigError } from '../config/index.ts';
|
|
4
|
+
import { featureEnablementWrite, getFeatureSetting } from '../runtime/feature-settings.ts';
|
|
4
5
|
import type { GoodVibesCliCommand, GoodVibesCliFlags } from './types.ts';
|
|
5
6
|
import { RUNTIME_ENDPOINT_CONFIG_KEYS, hostModeForHostname } from './endpoints.ts';
|
|
6
7
|
import type { RuntimeEndpointId } from './endpoints.ts';
|
|
@@ -130,6 +131,32 @@ export function applyRuntimeConfigDefault(configManager: ConfigManager, key: Con
|
|
|
130
131
|
applyRuntimeConfigValue(configManager, key, defaultValue);
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Every TUI-side config default applied at startup, in one place. Currently:
|
|
136
|
+
* show token speed ON (the SDK schema default is false). applyRuntimeConfigDefault
|
|
137
|
+
* reads the global + project settings files and only applies the default
|
|
138
|
+
* in-memory when the key is absent from both (e.g. a new install); an explicit
|
|
139
|
+
* user value is respected. No disk write.
|
|
140
|
+
*/
|
|
141
|
+
export function applyTuiRuntimeConfigDefaults(configManager: ConfigManager): void {
|
|
142
|
+
applyRuntimeConfigDefault(configManager, 'display.showTokenSpeed', true);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Applies the persisted `behavior.hitlMode` config value to the live mode
|
|
147
|
+
* manager at startup, ignoring unset or unrecognized values. Extracted from
|
|
148
|
+
* main() verbatim (a plain move, no behavior change).
|
|
149
|
+
*/
|
|
150
|
+
export function applyConfiguredHitlMode(
|
|
151
|
+
configManager: ConfigManager,
|
|
152
|
+
modeManager: { setHITLMode(mode: 'quiet' | 'balanced' | 'operator'): void },
|
|
153
|
+
): void {
|
|
154
|
+
const hitlMode = configManager.get('behavior.hitlMode');
|
|
155
|
+
if (hitlMode === 'quiet' || hitlMode === 'balanced' || hitlMode === 'operator') {
|
|
156
|
+
modeManager.setHITLMode(hitlMode);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
133
160
|
export function applyRuntimeConfigValue(configManager: ConfigManager, key: ConfigKey, value: unknown): void {
|
|
134
161
|
const setting = CONFIG_SCHEMA_BY_KEY.get(key);
|
|
135
162
|
if (!setting) {
|
|
@@ -161,23 +188,36 @@ export function applyRuntimeConfigOverrides(
|
|
|
161
188
|
return errors;
|
|
162
189
|
}
|
|
163
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Session-only feature overrides (--enable-feature / --disable-feature).
|
|
193
|
+
* Each feature is switched through its real domain settings key (e.g.
|
|
194
|
+
* sandbox.enabled, behavior.compactionStrategy) in the runtime config layer;
|
|
195
|
+
* features without an off position (constant capabilities on non-boolean
|
|
196
|
+
* keys) and unknown ids are reported as errors rather than silently ignored.
|
|
197
|
+
*/
|
|
164
198
|
export function applyRuntimeFeatureFlagOverrides(
|
|
165
199
|
configManager: ConfigManager,
|
|
166
200
|
options: {
|
|
167
201
|
readonly enableFeatures: readonly string[];
|
|
168
202
|
readonly disableFeatures: readonly string[];
|
|
169
203
|
},
|
|
170
|
-
):
|
|
171
|
-
if (options.enableFeatures.length === 0 && options.disableFeatures.length === 0) return;
|
|
204
|
+
): readonly string[] {
|
|
205
|
+
if (options.enableFeatures.length === 0 && options.disableFeatures.length === 0) return [];
|
|
172
206
|
const config = getRuntimeConfig(configManager);
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
207
|
+
const errors: string[] = [];
|
|
208
|
+
const apply = (feature: string, enabled: boolean, flagName: string): void => {
|
|
209
|
+
const write = featureEnablementWrite(feature, enabled);
|
|
210
|
+
if (!write) {
|
|
211
|
+
errors.push(getFeatureSetting(feature)
|
|
212
|
+
? `${flagName} ${feature}: this capability has no ${enabled ? 'on' : 'off'} switch (its domain settings govern it directly).`
|
|
213
|
+
: `${flagName} ${feature}: unknown feature id.`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
setNestedConfigValue(config, write.key, write.value);
|
|
217
|
+
};
|
|
218
|
+
for (const feature of options.enableFeatures) apply(feature, true, '--enable-feature');
|
|
219
|
+
for (const feature of options.disableFeatures) apply(feature, false, '--disable-feature');
|
|
220
|
+
return errors;
|
|
181
221
|
}
|
|
182
222
|
|
|
183
223
|
export function applyRuntimeEndpointFlagOverrides(
|
package/src/cli/doctor.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
2
|
import { PermissionManager, createPermissionConfigReader } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
3
3
|
import type { PermissionCategory, PermissionCheckResult } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
4
|
-
import { PolicyRuntimeState, createFeatureFlagManager } from '@/runtime/index.ts';
|
|
5
|
-
import type { FlagState } from '@/runtime/index.ts';
|
|
4
|
+
import { PolicyRuntimeState, createFeatureFlagManager, deriveFeatureStates } from '@/runtime/index.ts';
|
|
6
5
|
import { getProviderIdFromModel } from '../config/provider-model.ts';
|
|
7
6
|
import type { CliCommandOutput } from './types.ts';
|
|
8
7
|
import type { GoodVibesCliOutputFormat } from './types.ts';
|
|
@@ -76,7 +75,7 @@ function resolveExplainTarget(args: readonly string[]): { tool: string; args: Re
|
|
|
76
75
|
const LAYER_NAMES: readonly string[] = [
|
|
77
76
|
'Auto-approve override (behavior.autoApprove / --no-worries-just-vibes)',
|
|
78
77
|
'Session permission mode (permissions.mode)',
|
|
79
|
-
'Runtime policy engine (
|
|
78
|
+
'Runtime policy engine (permissions.engine = policy-engine)',
|
|
80
79
|
'Per-tool config rule (permissions.tools.<tool>)',
|
|
81
80
|
'Default read auto-approve (prompt mode)',
|
|
82
81
|
'Session approval cache (remembered [A] decisions)',
|
|
@@ -164,7 +163,7 @@ async function explain(options: DoctorSubcommandOptions): Promise<CliCommandOutp
|
|
|
164
163
|
// Feature flags come from live config so the policy engine layer is walked
|
|
165
164
|
// exactly when the running app would walk it.
|
|
166
165
|
const featureFlags = createFeatureFlagManager();
|
|
167
|
-
featureFlags.loadFromConfig({ flags: (config
|
|
166
|
+
featureFlags.loadFromConfig({ flags: deriveFeatureStates(config) });
|
|
168
167
|
const policyEngineOn = featureFlags.isEnabled('permissions-policy-engine');
|
|
169
168
|
const policyRuntimeState = new PolicyRuntimeState();
|
|
170
169
|
const manager = new PermissionManager(
|
|
@@ -214,7 +213,7 @@ async function explain(options: DoctorSubcommandOptions): Promise<CliCommandOutp
|
|
|
214
213
|
'Current settings:',
|
|
215
214
|
` mode : ${mode} (${permissionModeLabel(mode)})`,
|
|
216
215
|
` autoApprove : ${autoApprove ? 'yes (behavior.autoApprove)' : 'no'}`,
|
|
217
|
-
` policyEngine: ${policyEngineOn ? 'enabled' : 'disabled'} (
|
|
216
|
+
` policyEngine: ${policyEngineOn ? 'enabled' : 'disabled'} (permissions.engine)`,
|
|
218
217
|
'',
|
|
219
218
|
'Analysis (from the platform analyzer):',
|
|
220
219
|
` risk : ${a.riskLevel.toUpperCase()} (${a.classification})`,
|
package/src/cli/entrypoint.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
renderGoodVibesHelp,
|
|
22
22
|
renderGoodVibesVersion,
|
|
23
23
|
renderOnboardingCliStatus,
|
|
24
|
+
resolveDoctorExitCode,
|
|
24
25
|
} from './index.ts';
|
|
25
26
|
import { buildCliServicePosture, getGoodVibesPackageRoot, resolveGoodVibesDaemonExecutable } from './service-posture.ts';
|
|
26
27
|
import { runInstallSelfCheck } from '../runtime/install-self-check.ts';
|
|
@@ -28,8 +29,7 @@ import { readPersistedWorkspaceTrust } from '../runtime/trust/workspace-trust.ts
|
|
|
28
29
|
import { WorkspaceRegistrationManager } from '../runtime/trust/workspace-registration.ts';
|
|
29
30
|
import type { CliWorkspaceStatus, CliSandboxStatus, CliRelayStatus } from './status.ts';
|
|
30
31
|
import { detectSandboxAvailability, probeSandboxHost } from '@pellux/goodvibes-sdk/platform/tools/exec/sandbox';
|
|
31
|
-
import { createFeatureFlagManager } from '@/runtime/index.ts';
|
|
32
|
-
import type { FlagState } from '@/runtime/index.ts';
|
|
32
|
+
import { createFeatureFlagManager, deriveFeatureStates } from '@/runtime/index.ts';
|
|
33
33
|
import { ensureGoodvibesGitignore } from './ensure-goodvibes-gitignore.ts';
|
|
34
34
|
|
|
35
35
|
type ShellEntrypointOwnership = {
|
|
@@ -117,10 +117,14 @@ export async function prepareShellCliRuntime(
|
|
|
117
117
|
console.error(overrideErrors.join('\n'));
|
|
118
118
|
process.exit(2);
|
|
119
119
|
}
|
|
120
|
-
applyRuntimeFeatureFlagOverrides(configManager, {
|
|
120
|
+
const featureOverrideErrors = applyRuntimeFeatureFlagOverrides(configManager, {
|
|
121
121
|
enableFeatures: cli.flags.enableFeatures,
|
|
122
122
|
disableFeatures: cli.flags.disableFeatures,
|
|
123
123
|
});
|
|
124
|
+
if (featureOverrideErrors.length > 0) {
|
|
125
|
+
console.error(featureOverrideErrors.join('\n'));
|
|
126
|
+
process.exit(2);
|
|
127
|
+
}
|
|
124
128
|
|
|
125
129
|
if (cli.flags.provider !== undefined || cli.flags.model !== undefined) {
|
|
126
130
|
const currentModel = configManager.get('provider.model');
|
|
@@ -181,9 +185,7 @@ export async function prepareShellCliRuntime(
|
|
|
181
185
|
// Linux, a no-op elsewhere) is what makes "available" trustworthy.
|
|
182
186
|
const sandboxAvailability = detectSandboxAvailability(probeSandboxHost());
|
|
183
187
|
const sandboxFeatureFlags = createFeatureFlagManager();
|
|
184
|
-
sandboxFeatureFlags.loadFromConfig({
|
|
185
|
-
flags: (configManager.getCategory('featureFlags') as Record<string, FlagState>) ?? {},
|
|
186
|
-
});
|
|
188
|
+
sandboxFeatureFlags.loadFromConfig({ flags: deriveFeatureStates(configManager) });
|
|
187
189
|
const sandboxStatus: CliSandboxStatus = {
|
|
188
190
|
configEnabled: Boolean(configManager.getCategory('sandbox').enabled),
|
|
189
191
|
featureEnabled: sandboxFeatureFlags.isEnabled('exec-sandbox'),
|
|
@@ -192,7 +194,7 @@ export async function prepareShellCliRuntime(
|
|
|
192
194
|
reason: sandboxAvailability.reason,
|
|
193
195
|
networkIsolationGuaranteed: sandboxAvailability.networkIsolationGuaranteed,
|
|
194
196
|
};
|
|
195
|
-
// Reuses sandboxFeatureFlags (already
|
|
197
|
+
// Reuses sandboxFeatureFlags (already derived from the same domain settings keys)
|
|
196
198
|
// for the relay-connect gate rather than constructing a second FeatureFlagManager.
|
|
197
199
|
const relayCategory = configManager.getCategory('relay');
|
|
198
200
|
const relayStatus: CliRelayStatus = {
|
|
@@ -232,7 +234,10 @@ export async function prepareShellCliRuntime(
|
|
|
232
234
|
console.log(cli.command === 'onboarding'
|
|
233
235
|
? renderOnboardingCliStatus(statusOptions)
|
|
234
236
|
: renderCliStatus(statusOptions));
|
|
235
|
-
|
|
237
|
+
// Advisory findings are notes on an otherwise-usable install and must
|
|
238
|
+
// never make a healthy install report failure — only a must-fix finding
|
|
239
|
+
// exits non-zero; --strict (for CI) flips advisories to failures too.
|
|
240
|
+
process.exit(cli.command === 'doctor' ? resolveDoctorExitCode(snapshot.findings, cli.flags.strict) : 0);
|
|
236
241
|
}
|
|
237
242
|
|
|
238
243
|
const cliCommandResult = await handleGoodVibesCliCommand({
|
package/src/cli/help.ts
CHANGED
|
@@ -68,8 +68,8 @@ export function renderGoodVibesHelp(binary = 'goodvibes'): string {
|
|
|
68
68
|
' --working-dir <dir> Alias for --cd',
|
|
69
69
|
' --daemon-home <dir> Override daemon home for daemon-backed commands',
|
|
70
70
|
' -c, --config <key=value> Override a config value for this launch',
|
|
71
|
-
' --enable <feature> Enable a feature
|
|
72
|
-
' --disable <feature> Disable a feature
|
|
71
|
+
' --enable <feature> Enable a platform feature for this launch',
|
|
72
|
+
' --disable <feature> Disable a platform feature for this launch',
|
|
73
73
|
' -p, --prompt <text> Run a non-interactive prompt',
|
|
74
74
|
' --print Alias for non-interactive run mode',
|
|
75
75
|
' -o, --output <format> text, json, or stream-json',
|
|
@@ -85,6 +85,7 @@ export function renderGoodVibesHelp(binary = 'goodvibes'): string {
|
|
|
85
85
|
' --fork [id] Fork session (current or specific id) when supported',
|
|
86
86
|
' -y, --yes Auto-confirm prompts (non-interactive)',
|
|
87
87
|
' --non-interactive Disable all interactive prompts (implies --yes)',
|
|
88
|
+
' --strict doctor: also fail on advisory findings, for CI',
|
|
88
89
|
' -h, --help Print help',
|
|
89
90
|
' -v, --version Print version',
|
|
90
91
|
'',
|
|
@@ -139,9 +140,9 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
139
140
|
examples: ['status', 'status --json'],
|
|
140
141
|
},
|
|
141
142
|
doctor: {
|
|
142
|
-
usage: ['doctor', 'doctor --json', 'doctor explain <tool|command>', 'doctor routing', 'doctor hooks'],
|
|
143
|
-
summary: 'Print status plus setup warnings, or explain a platform decision: explain (why a tool/command would be allowed/asked/denied under the current mode), routing (which model/provider serves which role), hooks (registered hooks, their source, and validation status).',
|
|
144
|
-
examples: ['doctor', 'doctor explain "rm -rf build"', 'doctor explain write ./src/app.ts', 'doctor routing', 'doctor hooks --json'],
|
|
143
|
+
usage: ['doctor', 'doctor --json', 'doctor --strict', 'doctor explain <tool|command>', 'doctor routing', 'doctor hooks'],
|
|
144
|
+
summary: 'Print status plus setup warnings, or explain a platform decision: explain (why a tool/command would be allowed/asked/denied under the current mode), routing (which model/provider serves which role), hooks (registered hooks, their source, and validation status). Exits 0 when the install is usable (advisory findings render as notes); non-zero only for a must-fix finding. --strict also fails on advisory findings, for CI.',
|
|
145
|
+
examples: ['doctor', 'doctor --strict', 'doctor explain "rm -rf build"', 'doctor explain write ./src/app.ts', 'doctor routing', 'doctor hooks --json'],
|
|
145
146
|
},
|
|
146
147
|
providers: {
|
|
147
148
|
usage: ['providers [list]', 'providers current', 'providers inspect <provider>', 'providers use <provider> [modelRegistryKey]'],
|
|
@@ -311,7 +312,7 @@ export function renderGoodVibesDaemonHelp(binary = 'goodvibes-daemon'): string {
|
|
|
311
312
|
' install-service Install + enable the daemon as a systemd user service (survives reboots)',
|
|
312
313
|
' uninstall-service Disable + remove the daemon systemd user service',
|
|
313
314
|
' service-status Show whether the daemon service is installed / enabled / active',
|
|
314
|
-
' migrate-service Guided
|
|
315
|
+
' migrate-service Guided migration from an install-script goodvibes-daemon.service unit; prints a plan',
|
|
315
316
|
' unless run with -y/--yes (never auto-migrates)',
|
|
316
317
|
'',
|
|
317
318
|
'Options:',
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launch-time self-update — clients land on the newest release at startup so
|
|
3
|
+
* an installed binary never drifts behind. At TUI launch (before any runtime
|
|
4
|
+
* bootstrap or terminal mode change) this runs a quick version check and, when
|
|
5
|
+
* a newer release exists, installs it through the SAME checksum-verified
|
|
6
|
+
* download/verify/swap path `/update apply` and scripts/install.sh use
|
|
7
|
+
* (src/input/commands/update-runtime.ts — there is deliberately no second
|
|
8
|
+
* updater), then asks the caller to restart onto the new binary.
|
|
9
|
+
*
|
|
10
|
+
* Honesty rules, in both directions:
|
|
11
|
+
* - the check gets a short timeout; when it cannot complete (offline, slow
|
|
12
|
+
* network) the CURRENT version starts with exactly one line saying the
|
|
13
|
+
* check was skipped — launch is never held hostage by the network.
|
|
14
|
+
* - a successful update restarts into the new binary and the restarted
|
|
15
|
+
* process prints a receipt naming both versions, so the swap is never
|
|
16
|
+
* silent.
|
|
17
|
+
* - every swap keeps the outgoing file at `<path>.previous`; rollback is
|
|
18
|
+
* one command (`/update rollback`).
|
|
19
|
+
*
|
|
20
|
+
* The feature is a real configurable setting (`update.autoUpdateAtLaunch` in
|
|
21
|
+
* settings.json, read via readUpdateSettings): default ON, off with an
|
|
22
|
+
* explicit false. Only binary installs self-update — package-manager and
|
|
23
|
+
* from-source runs are skipped silently because a swap there would fight the
|
|
24
|
+
* package manager (see detectInstallKind).
|
|
25
|
+
*
|
|
26
|
+
* Everything effectful is injectable; tests drive the decision logic with a
|
|
27
|
+
* stubbed fetch, a stubbed apply, and pinned fixture versions — never the
|
|
28
|
+
* live build VERSION and never the real network.
|
|
29
|
+
*/
|
|
30
|
+
import { spawnSync } from 'node:child_process';
|
|
31
|
+
import { detectInstallKind, normalizeVersion, type UpdateFetchLike } from '../runtime/update-check.ts';
|
|
32
|
+
import { applyUpdate, checkForUpdate, type ApplyUpdateOptions } from '../input/commands/update-runtime.ts';
|
|
33
|
+
import { readUpdateSettings, type UpdateSettings } from '../config/tui-extension-settings.ts';
|
|
34
|
+
import type { ConfigManager } from '../config/index.ts';
|
|
35
|
+
import { VERSION } from '../version.ts';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Set on the restarted process by restartOntoUpdatedBinary, carrying the
|
|
39
|
+
* version that performed the update. Its presence means "an update already
|
|
40
|
+
* happened this launch": the restarted process prints the receipt and skips
|
|
41
|
+
* its own check, which also makes an update-that-didn't-change-the-version
|
|
42
|
+
* structurally unable to restart-loop.
|
|
43
|
+
*/
|
|
44
|
+
export const LAUNCH_UPDATED_FROM_ENV = 'GOODVIBES_LAUNCH_UPDATED_FROM';
|
|
45
|
+
|
|
46
|
+
/** Default budget for the launch-time version check; user-tunable via update.launchCheckTimeoutMs. */
|
|
47
|
+
export const LAUNCH_UPDATE_CHECK_TIMEOUT_MS = 2500;
|
|
48
|
+
|
|
49
|
+
export type LaunchAutoUpdateOutcome =
|
|
50
|
+
| {
|
|
51
|
+
readonly action: 'continue';
|
|
52
|
+
readonly reason: 'just-updated' | 'disabled' | 'not-swappable-install' | 'already-current' | 'check-skipped' | 'update-failed';
|
|
53
|
+
}
|
|
54
|
+
| { readonly action: 'restart'; readonly latestTag: string };
|
|
55
|
+
|
|
56
|
+
export interface RunLaunchAutoUpdateOptions {
|
|
57
|
+
readonly fetchImpl: UpdateFetchLike;
|
|
58
|
+
readonly execPath: string;
|
|
59
|
+
readonly platform: NodeJS.Platform;
|
|
60
|
+
readonly arch: string;
|
|
61
|
+
readonly currentVersion: string;
|
|
62
|
+
readonly settings: UpdateSettings;
|
|
63
|
+
readonly env: NodeJS.ProcessEnv;
|
|
64
|
+
readonly print: (line: string) => void;
|
|
65
|
+
readonly configManager: { get(key: string): unknown };
|
|
66
|
+
/** Injectable so tests observe the install step instead of swapping real files. */
|
|
67
|
+
readonly apply?: (options: ApplyUpdateOptions) => Promise<void>;
|
|
68
|
+
readonly timeoutMs?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Resolves to 'timeout' when `promise` does not settle within `ms`; the timer never keeps the process alive. */
|
|
72
|
+
async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T | 'timeout'> {
|
|
73
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
74
|
+
try {
|
|
75
|
+
return await Promise.race([
|
|
76
|
+
promise,
|
|
77
|
+
new Promise<'timeout'>((resolve) => {
|
|
78
|
+
timer = setTimeout(() => resolve('timeout'), ms);
|
|
79
|
+
timer.unref?.();
|
|
80
|
+
}),
|
|
81
|
+
]);
|
|
82
|
+
} finally {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The launch-time decision: receipt-and-continue (restarted process), skip
|
|
89
|
+
* (disabled / unswappable / current / unreachable), install-and-restart, or
|
|
90
|
+
* fail-and-continue on the current version. Never throws.
|
|
91
|
+
*/
|
|
92
|
+
export async function runLaunchAutoUpdate(options: RunLaunchAutoUpdateOptions): Promise<LaunchAutoUpdateOutcome> {
|
|
93
|
+
const updatedFrom = options.env[LAUNCH_UPDATED_FROM_ENV];
|
|
94
|
+
if (typeof updatedFrom === 'string' && updatedFrom.length > 0) {
|
|
95
|
+
// Consumed here so sessions spawned from inside this one never inherit it.
|
|
96
|
+
delete options.env[LAUNCH_UPDATED_FROM_ENV];
|
|
97
|
+
options.print(`auto-update: updated from v${normalizeVersion(updatedFrom)} to v${normalizeVersion(options.currentVersion)} at launch`);
|
|
98
|
+
return { action: 'continue', reason: 'just-updated' };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!(options.settings.autoUpdateAtLaunch ?? true)) {
|
|
102
|
+
return { action: 'continue', reason: 'disabled' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (detectInstallKind(options.execPath) !== 'binary') {
|
|
106
|
+
return { action: 'continue', reason: 'not-swappable-install' };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const timeoutMs = options.timeoutMs ?? options.settings.launchCheckTimeoutMs ?? LAUNCH_UPDATE_CHECK_TIMEOUT_MS;
|
|
110
|
+
let check: Awaited<ReturnType<typeof checkForUpdate>> | 'timeout';
|
|
111
|
+
try {
|
|
112
|
+
check = await withTimeout(checkForUpdate(options.fetchImpl, options.currentVersion), timeoutMs);
|
|
113
|
+
} catch {
|
|
114
|
+
check = 'timeout';
|
|
115
|
+
}
|
|
116
|
+
if (check === 'timeout') {
|
|
117
|
+
options.print('update check skipped: offline');
|
|
118
|
+
return { action: 'continue', reason: 'check-skipped' };
|
|
119
|
+
}
|
|
120
|
+
if (check.isCurrent) {
|
|
121
|
+
return { action: 'continue', reason: 'already-current' };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const apply = options.apply ?? applyUpdate;
|
|
126
|
+
await apply({
|
|
127
|
+
fetchImpl: options.fetchImpl,
|
|
128
|
+
execPath: options.execPath,
|
|
129
|
+
platform: options.platform,
|
|
130
|
+
arch: options.arch,
|
|
131
|
+
currentVersion: options.currentVersion,
|
|
132
|
+
print: options.print,
|
|
133
|
+
configManager: options.configManager,
|
|
134
|
+
});
|
|
135
|
+
options.print(`auto-update: ${check.latestTag} installed — restarting onto the new version`);
|
|
136
|
+
return { action: 'restart', latestTag: check.latestTag };
|
|
137
|
+
} catch (error) {
|
|
138
|
+
options.print(
|
|
139
|
+
`auto-update failed: ${error instanceof Error ? error.message : String(error)} — starting the current version v${normalizeVersion(options.currentVersion)}`,
|
|
140
|
+
);
|
|
141
|
+
return { action: 'continue', reason: 'update-failed' };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface SelfUpdateAtLaunchParams {
|
|
146
|
+
readonly configManager: Pick<ConfigManager, 'get' | 'getRaw'>;
|
|
147
|
+
readonly stdout: { write(chunk: string): unknown };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The complete launch wiring for main(), with real host inputs: run the
|
|
152
|
+
* decision above; when an update was installed, restart onto the swapped
|
|
153
|
+
* binary and EXIT THIS PROCESS with the new instance's exit code (this call
|
|
154
|
+
* never returns in that case); otherwise return the honest lines that were
|
|
155
|
+
* printed (receipt / skipped check / failed update) so the caller can
|
|
156
|
+
* re-surface them through the system message router once it exists — the
|
|
157
|
+
* stdout copies written here are wiped by the TUI's alternate screen.
|
|
158
|
+
*/
|
|
159
|
+
export async function selfUpdateAtLaunch(params: SelfUpdateAtLaunchParams): Promise<readonly string[]> {
|
|
160
|
+
const lines: string[] = [];
|
|
161
|
+
const outcome = await runLaunchAutoUpdate({
|
|
162
|
+
fetchImpl: fetch as UpdateFetchLike,
|
|
163
|
+
execPath: process.execPath,
|
|
164
|
+
platform: process.platform,
|
|
165
|
+
arch: process.arch,
|
|
166
|
+
currentVersion: VERSION,
|
|
167
|
+
settings: readUpdateSettings(params.configManager),
|
|
168
|
+
env: process.env,
|
|
169
|
+
configManager: params.configManager,
|
|
170
|
+
print: (line) => {
|
|
171
|
+
lines.push(line);
|
|
172
|
+
params.stdout.write(`${line}\n`);
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
if (outcome.action === 'restart') {
|
|
176
|
+
process.exit(
|
|
177
|
+
restartOntoUpdatedBinary({
|
|
178
|
+
execPath: process.execPath,
|
|
179
|
+
argv: process.argv.slice(2),
|
|
180
|
+
env: process.env,
|
|
181
|
+
fromVersion: VERSION,
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
return lines;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface RestartOntoUpdatedBinaryOptions {
|
|
189
|
+
readonly execPath: string;
|
|
190
|
+
readonly argv: readonly string[];
|
|
191
|
+
readonly env: NodeJS.ProcessEnv;
|
|
192
|
+
/** The version that performed the update — the restarted process prints it in its receipt line. */
|
|
193
|
+
readonly fromVersion: string;
|
|
194
|
+
/** Injectable so tests observe the restart instead of spawning a process. */
|
|
195
|
+
readonly spawn?: (execPath: string, argv: readonly string[], env: NodeJS.ProcessEnv) => number | null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Runs the (just-swapped) binary at execPath with the original CLI arguments
|
|
200
|
+
* and an inherited terminal, blocking until it exits, and returns its exit
|
|
201
|
+
* code for the caller to process.exit with. The receipt marker travels in the
|
|
202
|
+
* child's environment, never on the command line.
|
|
203
|
+
*/
|
|
204
|
+
export function restartOntoUpdatedBinary(options: RestartOntoUpdatedBinaryOptions): number {
|
|
205
|
+
const spawn =
|
|
206
|
+
options.spawn ??
|
|
207
|
+
((execPath: string, argv: readonly string[], env: NodeJS.ProcessEnv) =>
|
|
208
|
+
spawnSync(execPath, [...argv], { stdio: 'inherit', env }).status);
|
|
209
|
+
const status = spawn(options.execPath, options.argv, {
|
|
210
|
+
...options.env,
|
|
211
|
+
[LAUNCH_UPDATED_FROM_ENV]: options.fromVersion,
|
|
212
|
+
});
|
|
213
|
+
return status ?? 0;
|
|
214
|
+
}
|
package/src/cli/parser.ts
CHANGED
|
@@ -87,6 +87,7 @@ function createDefaultFlags(): GoodVibesCliFlags {
|
|
|
87
87
|
fork: undefined,
|
|
88
88
|
yes: false,
|
|
89
89
|
nonInteractive: false,
|
|
90
|
+
strict: false,
|
|
90
91
|
};
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -260,6 +261,10 @@ export function parseGoodVibesCli(
|
|
|
260
261
|
flags = withFlag(withFlag(flags, 'nonInteractive', true), 'yes', true);
|
|
261
262
|
continue;
|
|
262
263
|
}
|
|
264
|
+
if (name === '--strict') {
|
|
265
|
+
flags = withFlag(flags, 'strict', true);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
263
268
|
|
|
264
269
|
if (name === '--provider') {
|
|
265
270
|
const consumed = getValue(argv, index, inlineValue, name, errors);
|
package/src/cli/status.ts
CHANGED
|
@@ -502,6 +502,19 @@ export function buildCliDoctorFindings(options: CliStatusOptions): readonly CliD
|
|
|
502
502
|
return findings;
|
|
503
503
|
}
|
|
504
504
|
|
|
505
|
+
/**
|
|
506
|
+
* The doctor command's exit code. Advisory findings (`severity: 'warning'`)
|
|
507
|
+
* are notes on an otherwise-usable install and must never make a healthy
|
|
508
|
+
* install report failure — only a must-fix (`severity: 'risk'`) finding
|
|
509
|
+
* exits non-zero. `strict` (for CI) flips warnings to failures too, so a
|
|
510
|
+
* pipeline can require a fully clean report instead of just "usable".
|
|
511
|
+
*/
|
|
512
|
+
export function resolveDoctorExitCode(findings: readonly CliDoctorFinding[], strict = false): number {
|
|
513
|
+
if (findings.some((finding) => finding.severity === 'risk')) return 1;
|
|
514
|
+
if (strict && findings.length > 0) return 1;
|
|
515
|
+
return 0;
|
|
516
|
+
}
|
|
517
|
+
|
|
505
518
|
export function buildCliStatusSnapshot(options: CliStatusOptions): CliStatusSnapshot {
|
|
506
519
|
const config = options.configManager;
|
|
507
520
|
const controlPlaneBinding = resolveRuntimeEndpointBinding(config, 'controlPlane');
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
GOODVIBES_NTFY_REMOTE_TOPIC,
|
|
6
6
|
resolveGoodVibesNtfyTopics,
|
|
7
7
|
} from '@pellux/goodvibes-sdk/platform/integrations';
|
|
8
|
-
import { enableFeatureFlags, getMissingSurfaceFeatureFlags, getServerSurfaceFeatureFlags } from '../runtime/surface-feature-flags.ts';
|
|
8
|
+
import { enableFeatureFlags, getMissingSurfaceFeatureFlags, getServerSurfaceFeatureFlags, surfaceFeatureGateSettingsKeys } from '../runtime/surface-feature-flags.ts';
|
|
9
9
|
import { resolveRuntimeEndpointBinding } from './endpoints.ts';
|
|
10
10
|
import { classifyBindPosture, isNetworkFacing } from './network-posture.ts';
|
|
11
11
|
import type { CliCommandRuntime } from './types.ts';
|
|
@@ -147,7 +147,7 @@ export async function handleSurfacesCommand(runtime: CliCommandRuntime): Promise
|
|
|
147
147
|
readinessIssues.push(`${surface.label} is enabled but missing ${surface.missing.join(', ')}.`);
|
|
148
148
|
}
|
|
149
149
|
if (surface.missingFeatureFlags.length > 0) {
|
|
150
|
-
readinessIssues.push(`${surface.label} is enabled but
|
|
150
|
+
readinessIssues.push(`${surface.label} is enabled but these settings are off: ${surfaceFeatureGateSettingsKeys(surface.missingFeatureFlags).join(', ')}.`);
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
const value = {
|
|
@@ -185,7 +185,7 @@ export async function handleSurfacesCommand(runtime: CliCommandRuntime): Promise
|
|
|
185
185
|
` http-listener: ${yesNo(value.httpListener.enabled)} (${value.httpListener.hostMode} ${value.httpListener.host}:${value.httpListener.port})${includeProbe ? ` reachable=${yesNo(value.httpListener.reachable)}` : ''}`,
|
|
186
186
|
'',
|
|
187
187
|
'External surfaces:',
|
|
188
|
-
...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 ? `
|
|
188
|
+
...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 ? ` settingsOff=${surfaceFeatureGateSettingsKeys(surface.missingFeatureFlags).join(',')}` : ''}`),
|
|
189
189
|
...(filteredSurfaces.some((surface) => surface.id === 'ntfy') ? [
|
|
190
190
|
'',
|
|
191
191
|
'ntfy inbound topics:',
|
|
@@ -260,7 +260,7 @@ export async function buildListenerTestResult(runtime: CliCommandRuntime): Promi
|
|
|
260
260
|
if (isNetworkFacing(enabled, binding) && auth.bootstrapCredentialPresent) issues.push('Network-facing listener still has a bootstrap credential file.');
|
|
261
261
|
for (const surface of surfaces) {
|
|
262
262
|
if (surface.missing.length > 0) issues.push(`${surface.label} is enabled but missing ${surface.missing.join(', ')}.`);
|
|
263
|
-
if (surface.missingFeatureFlags.length > 0) issues.push(`${surface.label} is enabled but
|
|
263
|
+
if (surface.missingFeatureFlags.length > 0) issues.push(`${surface.label} is enabled but these settings are off: ${surfaceFeatureGateSettingsKeys(surface.missingFeatureFlags).join(', ')}.`);
|
|
264
264
|
}
|
|
265
265
|
return { enabled, ...binding, posture, reachable, service, auth, surfaces, issues };
|
|
266
266
|
}
|
|
@@ -276,7 +276,7 @@ export function formatListenerTestResult(runtime: CliCommandRuntime, value: List
|
|
|
276
276
|
` local auth users: ${value.auth.userStorePresent ? 'present' : 'missing'}`,
|
|
277
277
|
` bootstrap credential: ${value.auth.bootstrapCredentialPresent ? 'present' : 'missing'}`,
|
|
278
278
|
value.surfaces.length === 0 ? ' enabled webhook surfaces: none' : ' enabled webhook surfaces:',
|
|
279
|
-
...value.surfaces.map((surface) => ` ${surface.label}: ready=${yesNo(surface.ready)}${surface.missing.length > 0 ? ` missing=${surface.missing.join(',')}` : ''}${surface.missingFeatureFlags.length > 0 ? `
|
|
279
|
+
...value.surfaces.map((surface) => ` ${surface.label}: ready=${yesNo(surface.ready)}${surface.missing.length > 0 ? ` missing=${surface.missing.join(',')}` : ''}${surface.missingFeatureFlags.length > 0 ? ` settingsOff=${surfaceFeatureGateSettingsKeys(surface.missingFeatureFlags).join(',')}` : ''}`),
|
|
280
280
|
value.issues.length === 0 ? ' readiness: ready' : ' readiness: needs attention',
|
|
281
281
|
...value.issues.map((issue) => ` - ${issue}`),
|
|
282
282
|
].join('\n'));
|