@pellux/goodvibes-tui 0.19.25 → 0.19.27
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 +13 -0
- package/README.md +1 -1
- package/bin/goodvibes +5 -0
- package/bin/goodvibes-daemon +5 -0
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +3 -2
- package/src/cli/bundle-command.ts +225 -0
- package/src/cli/completion.ts +1 -0
- package/src/cli/entrypoint.ts +16 -2
- package/src/cli/help.ts +180 -1
- package/src/cli/index.ts +3 -0
- package/src/cli/management-commands.ts +53 -203
- package/src/cli/management.ts +59 -33
- package/src/cli/network-posture.ts +46 -0
- package/src/cli/package-verification.ts +119 -0
- package/src/cli/parser.ts +2 -0
- package/src/cli/provider-classification.ts +107 -0
- package/src/cli/redaction.ts +105 -0
- package/src/cli/service-command.ts +45 -0
- package/src/cli/service-posture.ts +247 -0
- package/src/cli/status.ts +289 -19
- package/src/cli/surface-command.ts +248 -0
- package/src/cli/types.ts +6 -0
- package/src/cli-flags.ts +1 -0
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to GoodVibes TUI.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.19.27] — 2026-04-24
|
|
8
|
+
|
|
9
|
+
### Changes
|
|
10
|
+
- ac02372 chore: update goodvibes sdk to 0.25.1
|
|
11
|
+
|
|
12
|
+
## [0.19.26] — 2026-04-24
|
|
13
|
+
|
|
14
|
+
### Changes
|
|
15
|
+
- f1e0ac3 feat: harden CLI operations and support bundles
|
|
16
|
+
- 0c94095 feat: enforce readiness command exit codes
|
|
17
|
+
- 566bc34 feat: improve CLI help and listener readiness
|
|
18
|
+
- d620637 feat: harden CLI diagnostics and provider posture
|
|
19
|
+
|
|
7
20
|
## [0.19.25] — 2026-04-24
|
|
8
21
|
|
|
9
22
|
### Changes
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://github.com/mgd34msu/goodvibes-tui)
|
|
6
6
|
|
|
7
7
|
A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
|
|
8
8
|
|
package/bin/goodvibes
CHANGED
|
@@ -39,9 +39,14 @@ function run(command, args) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const artifactName = resolveArtifactName(process.platform, process.arch);
|
|
42
|
+
const localPlatformBuild = artifactName ? join(packageRoot, 'dist', artifactName) : null;
|
|
42
43
|
const localBuild = join(packageRoot, 'dist', 'goodvibes');
|
|
43
44
|
const vendoredBinary = artifactName ? join(packageRoot, 'vendor', artifactName) : null;
|
|
44
45
|
|
|
46
|
+
if (localPlatformBuild && isExecutable(localPlatformBuild)) {
|
|
47
|
+
run(localPlatformBuild, process.argv.slice(2));
|
|
48
|
+
}
|
|
49
|
+
|
|
45
50
|
if (isExecutable(localBuild)) {
|
|
46
51
|
run(localBuild, process.argv.slice(2));
|
|
47
52
|
}
|
package/bin/goodvibes-daemon
CHANGED
|
@@ -39,9 +39,14 @@ function run(command, args) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const artifactName = resolveArtifactName(process.platform, process.arch);
|
|
42
|
+
const localPlatformBuild = artifactName ? join(packageRoot, 'dist', artifactName) : null;
|
|
42
43
|
const localBuild = join(packageRoot, 'dist', 'goodvibes-daemon');
|
|
43
44
|
const vendoredBinary = artifactName ? join(packageRoot, 'vendor', artifactName) : null;
|
|
44
45
|
|
|
46
|
+
if (localPlatformBuild && isExecutable(localPlatformBuild)) {
|
|
47
|
+
run(localPlatformBuild, process.argv.slice(2));
|
|
48
|
+
}
|
|
49
|
+
|
|
45
50
|
if (isExecutable(localBuild)) {
|
|
46
51
|
run(localBuild, process.argv.slice(2));
|
|
47
52
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.27",
|
|
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",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"publish:dry-run": "bun run scripts/publish-package.ts --dry-run",
|
|
50
50
|
"publish:dry-run:github": "GOODVIBES_PUBLIC_PACKAGE_NAME=@mgd34msu/goodvibes-tui GOODVIBES_PUBLISH_REGISTRY=https://npm.pkg.github.com bun run scripts/publish-package.ts --dry-run",
|
|
51
51
|
"publish:check": "bun run scripts/publish-check.ts",
|
|
52
|
+
"package:install-check": "bun run scripts/package-install-check.ts",
|
|
52
53
|
"build:prod": "bun run scripts/build.ts",
|
|
53
54
|
"build:all": "bun run scripts/build.ts --all",
|
|
54
55
|
"perf:check": "bun run scripts/perf-check.ts",
|
|
@@ -90,7 +91,7 @@
|
|
|
90
91
|
"@anthropic-ai/vertex-sdk": "^0.16.0",
|
|
91
92
|
"@ast-grep/napi": "^0.42.0",
|
|
92
93
|
"@aws/bedrock-token-generator": "^1.1.0",
|
|
93
|
-
"@pellux/goodvibes-sdk": "^0.25.
|
|
94
|
+
"@pellux/goodvibes-sdk": "^0.25.1",
|
|
94
95
|
"bash-language-server": "^5.6.0",
|
|
95
96
|
"fuse.js": "^7.1.0",
|
|
96
97
|
"graphql": "^16.13.2",
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { RuntimeEventBus } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
|
|
4
|
+
import { createShellPathService } from '@pellux/goodvibes-sdk/platform/runtime/shell-paths';
|
|
5
|
+
import { listProviderRuntimeSnapshots } from '@pellux/goodvibes-sdk/platform/providers/runtime-snapshot';
|
|
6
|
+
import { createRuntimeServices } from '../runtime/services.ts';
|
|
7
|
+
import { createRuntimeStore } from '../runtime/store/index.ts';
|
|
8
|
+
import { CONFIG_SCHEMA } from '../config/index.ts';
|
|
9
|
+
import { SecretsManager } from '../config/secrets.ts';
|
|
10
|
+
import type { ConfigKey } from '../config/index.ts';
|
|
11
|
+
import type { CliCommandRuntime } from './management.ts';
|
|
12
|
+
import type { CliCommandOutput } from './types.ts';
|
|
13
|
+
import { getPackageVersion } from './help.ts';
|
|
14
|
+
import { classifyProviderSetup } from './provider-classification.ts';
|
|
15
|
+
import { buildCliServicePosture } from './service-posture.ts';
|
|
16
|
+
import { REDACTED_VALUE, collectSensitiveConfigValues, isRedactedValue, redactConfig, redactSerializedSecrets } from './redaction.ts';
|
|
17
|
+
|
|
18
|
+
interface BundleInspectSummary {
|
|
19
|
+
readonly type: string;
|
|
20
|
+
readonly version: string;
|
|
21
|
+
readonly path: string;
|
|
22
|
+
readonly capturedAt: number | null;
|
|
23
|
+
readonly configKeys: number;
|
|
24
|
+
readonly redactedConfigPaths: readonly string[];
|
|
25
|
+
readonly hasDiagnostics: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function formatJsonOrText(runtime: CliCommandRuntime, value: unknown, text: string): string {
|
|
29
|
+
return runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(value, null, 2) : text;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getNestedValue(source: unknown, key: string): unknown {
|
|
33
|
+
let cursor = source;
|
|
34
|
+
for (const part of key.split('.')) {
|
|
35
|
+
if (cursor == null || typeof cursor !== 'object') return undefined;
|
|
36
|
+
cursor = (cursor as Record<string, unknown>)[part];
|
|
37
|
+
}
|
|
38
|
+
return cursor;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readJsonFile(path: string): { readonly ok: true; readonly value: Record<string, unknown> } | { readonly ok: false; readonly error: string } {
|
|
42
|
+
try {
|
|
43
|
+
return { ok: true, value: JSON.parse(readFileSync(path, 'utf-8')) as Record<string, unknown> };
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function inspectBundle(path: string, parsed: Record<string, unknown>): BundleInspectSummary {
|
|
50
|
+
return {
|
|
51
|
+
type: String(parsed['type'] ?? 'unknown'),
|
|
52
|
+
version: String(parsed['version'] ?? 'unknown'),
|
|
53
|
+
path,
|
|
54
|
+
capturedAt: typeof parsed['capturedAt'] === 'number' ? parsed['capturedAt'] : null,
|
|
55
|
+
configKeys: parsed['config'] && typeof parsed['config'] === 'object'
|
|
56
|
+
? CONFIG_SCHEMA.filter((setting) => getNestedValue(parsed['config'], setting.key) !== undefined).length
|
|
57
|
+
: 0,
|
|
58
|
+
redactedConfigPaths: Array.isArray((parsed['redaction'] as { redactedConfigPaths?: unknown } | undefined)?.redactedConfigPaths)
|
|
59
|
+
? (parsed['redaction'] as { redactedConfigPaths: string[] }).redactedConfigPaths
|
|
60
|
+
: [],
|
|
61
|
+
hasDiagnostics: Boolean(parsed['diagnostics'] && typeof parsed['diagnostics'] === 'object'),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readAuthPosture(runtime: CliCommandRuntime) {
|
|
66
|
+
const shellPaths = createShellPathService({
|
|
67
|
+
workingDirectory: runtime.workingDirectory,
|
|
68
|
+
homeDirectory: runtime.homeDirectory,
|
|
69
|
+
});
|
|
70
|
+
const userStorePath = shellPaths.resolveUserPath('tui', 'auth-users.json');
|
|
71
|
+
const bootstrapCredentialPath = shellPaths.resolveUserPath('tui', 'auth-bootstrap.txt');
|
|
72
|
+
const operatorTokenPath = join(runtime.homeDirectory, '.goodvibes', 'daemon', 'operator-tokens.json');
|
|
73
|
+
return {
|
|
74
|
+
userStorePath,
|
|
75
|
+
userStorePresent: existsSync(userStorePath),
|
|
76
|
+
bootstrapCredentialPath,
|
|
77
|
+
bootstrapCredentialPresent: existsSync(bootstrapCredentialPath),
|
|
78
|
+
operatorTokenPath,
|
|
79
|
+
operatorTokenPresent: existsSync(operatorTokenPath),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function buildProviderReadiness(runtime: CliCommandRuntime) {
|
|
84
|
+
const runtimeBus = new RuntimeEventBus();
|
|
85
|
+
const runtimeStore = createRuntimeStore();
|
|
86
|
+
const services = createRuntimeServices({
|
|
87
|
+
configManager: runtime.configManager,
|
|
88
|
+
runtimeBus,
|
|
89
|
+
runtimeStore,
|
|
90
|
+
workingDir: runtime.workingDirectory,
|
|
91
|
+
homeDirectory: runtime.homeDirectory,
|
|
92
|
+
});
|
|
93
|
+
services.providerRegistry.initModelLimits();
|
|
94
|
+
services.benchmarkStore.initBenchmarks();
|
|
95
|
+
services.providerRegistry.initCatalog();
|
|
96
|
+
try {
|
|
97
|
+
await services.providerRegistry.ready();
|
|
98
|
+
const snapshots = await listProviderRuntimeSnapshots(services.providerRegistry);
|
|
99
|
+
return snapshots.map((snapshot) => ({
|
|
100
|
+
provider: snapshot.providerId,
|
|
101
|
+
active: snapshot.active,
|
|
102
|
+
configured: snapshot.runtime.auth?.configured ?? true,
|
|
103
|
+
configuredVia: snapshot.runtime.auth?.mode ?? 'unknown',
|
|
104
|
+
models: snapshot.modelCount,
|
|
105
|
+
setup: classifyProviderSetup({
|
|
106
|
+
providerId: snapshot.providerId,
|
|
107
|
+
authMode: snapshot.runtime.auth?.mode,
|
|
108
|
+
configured: snapshot.runtime.auth?.configured ?? true,
|
|
109
|
+
modelCount: snapshot.modelCount,
|
|
110
|
+
}),
|
|
111
|
+
}));
|
|
112
|
+
} finally {
|
|
113
|
+
services.providerRegistry.stopWatching();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function handleBundleCommand(runtime: CliCommandRuntime): Promise<CliCommandOutput> {
|
|
118
|
+
const [sub = 'inspect', ...rest] = runtime.cli.commandArgs;
|
|
119
|
+
const shellPaths = createShellPathService({
|
|
120
|
+
workingDirectory: runtime.workingDirectory,
|
|
121
|
+
homeDirectory: runtime.homeDirectory,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (sub === 'inspect') {
|
|
125
|
+
const path = rest[0];
|
|
126
|
+
if (!path) return { output: 'Usage: goodvibes bundle inspect <path>', exitCode: 2 };
|
|
127
|
+
const sourcePath = shellPaths.resolveWorkspacePath(path);
|
|
128
|
+
const parsed = readJsonFile(sourcePath);
|
|
129
|
+
if (!parsed.ok) return { output: `Invalid bundle JSON: ${parsed.error}`, exitCode: 1 };
|
|
130
|
+
const summary = inspectBundle(sourcePath, parsed.value);
|
|
131
|
+
return {
|
|
132
|
+
output: formatJsonOrText(runtime, summary, [
|
|
133
|
+
'GoodVibes bundle',
|
|
134
|
+
` type: ${summary.type}`,
|
|
135
|
+
` version: ${summary.version}`,
|
|
136
|
+
` path: ${summary.path}`,
|
|
137
|
+
` capturedAt: ${summary.capturedAt === null ? 'n/a' : new Date(summary.capturedAt).toISOString()}`,
|
|
138
|
+
` configKeys: ${summary.configKeys}`,
|
|
139
|
+
` redactedConfigKeys: ${summary.redactedConfigPaths.length}`,
|
|
140
|
+
` diagnostics: ${summary.hasDiagnostics ? 'present' : 'missing'}`,
|
|
141
|
+
].join('\n')),
|
|
142
|
+
exitCode: 0,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (sub === 'export') {
|
|
147
|
+
const outputPath = rest[0] ?? 'goodvibes-bundle.json';
|
|
148
|
+
const secrets = new SecretsManager({
|
|
149
|
+
projectRoot: runtime.workingDirectory,
|
|
150
|
+
globalHome: runtime.homeDirectory,
|
|
151
|
+
configManager: runtime.configManager,
|
|
152
|
+
});
|
|
153
|
+
const rawConfig = runtime.configManager.getRaw();
|
|
154
|
+
const sensitiveValues = collectSensitiveConfigValues(rawConfig);
|
|
155
|
+
const redactedConfig = redactConfig(rawConfig);
|
|
156
|
+
const service = await buildCliServicePosture(runtime, { logTailBytes: 8192 });
|
|
157
|
+
const bundle = {
|
|
158
|
+
version: 2,
|
|
159
|
+
type: 'goodvibes.support',
|
|
160
|
+
capturedAt: Date.now(),
|
|
161
|
+
package: {
|
|
162
|
+
name: '@pellux/goodvibes-tui',
|
|
163
|
+
version: getPackageVersion(),
|
|
164
|
+
},
|
|
165
|
+
roots: {
|
|
166
|
+
workingDirectory: runtime.workingDirectory,
|
|
167
|
+
homeDirectory: runtime.homeDirectory,
|
|
168
|
+
},
|
|
169
|
+
config: redactedConfig.value,
|
|
170
|
+
redaction: {
|
|
171
|
+
sentinel: REDACTED_VALUE,
|
|
172
|
+
redactedConfigPaths: redactedConfig.redactedPaths,
|
|
173
|
+
},
|
|
174
|
+
diagnostics: {
|
|
175
|
+
service,
|
|
176
|
+
auth: readAuthPosture(runtime),
|
|
177
|
+
providers: await buildProviderReadiness(runtime),
|
|
178
|
+
},
|
|
179
|
+
secrets: await secrets.inspect(),
|
|
180
|
+
onboarding: {
|
|
181
|
+
projectMarker: existsSync(shellPaths.resolveProjectPath('tui', 'onboarding.json')),
|
|
182
|
+
userMarker: existsSync(shellPaths.resolveUserPath('tui', 'onboarding.json')),
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
const targetPath = shellPaths.resolveWorkspacePath(outputPath);
|
|
186
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
187
|
+
writeFileSync(targetPath, redactSerializedSecrets(JSON.stringify(bundle, null, 2), sensitiveValues) + '\n', 'utf-8');
|
|
188
|
+
return {
|
|
189
|
+
output: formatJsonOrText(runtime, {
|
|
190
|
+
path: targetPath,
|
|
191
|
+
redactedConfigPaths: redactedConfig.redactedPaths,
|
|
192
|
+
serviceIssues: service.issues,
|
|
193
|
+
}, `Bundle exported: ${targetPath}\n redactedConfigKeys: ${redactedConfig.redactedPaths.length}\n serviceIssues: ${service.issues.length}`),
|
|
194
|
+
exitCode: 0,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (sub === 'import') {
|
|
199
|
+
const path = rest[0];
|
|
200
|
+
if (!path) return { output: 'Usage: goodvibes bundle import <path>', exitCode: 2 };
|
|
201
|
+
const sourcePath = shellPaths.resolveWorkspacePath(path);
|
|
202
|
+
const parsed = readJsonFile(sourcePath);
|
|
203
|
+
if (!parsed.ok) return { output: `Invalid bundle JSON: ${parsed.error}`, exitCode: 1 };
|
|
204
|
+
const config = parsed.value['config'];
|
|
205
|
+
if (!config || typeof config !== 'object') return { output: 'Bundle has no config object to import.', exitCode: 1 };
|
|
206
|
+
let count = 0;
|
|
207
|
+
let skippedRedacted = 0;
|
|
208
|
+
for (const setting of CONFIG_SCHEMA) {
|
|
209
|
+
const value = getNestedValue(config, setting.key);
|
|
210
|
+
if (value === undefined) continue;
|
|
211
|
+
if (isRedactedValue(value)) {
|
|
212
|
+
skippedRedacted++;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
runtime.configManager.setDynamic(setting.key as ConfigKey, value as never);
|
|
216
|
+
count++;
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
output: `Bundle imported: ${count} config value${count === 1 ? '' : 's'} applied.${skippedRedacted ? ` ${skippedRedacted} redacted value${skippedRedacted === 1 ? '' : 's'} skipped.` : ''}`,
|
|
220
|
+
exitCode: 0,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return { output: 'Usage: goodvibes bundle export [path]|inspect <path>|import <path>', exitCode: 2 };
|
|
225
|
+
}
|
package/src/cli/completion.ts
CHANGED
package/src/cli/entrypoint.ts
CHANGED
|
@@ -10,14 +10,17 @@ import {
|
|
|
10
10
|
applyRuntimeConfigOverrides,
|
|
11
11
|
applyRuntimeConfigValue,
|
|
12
12
|
applyRuntimeFeatureFlagOverrides,
|
|
13
|
+
buildCliStatusSnapshot,
|
|
13
14
|
handleGoodVibesCliCommand,
|
|
14
15
|
parseGoodVibesCli,
|
|
15
16
|
renderCliStatus,
|
|
16
17
|
renderCompletion,
|
|
18
|
+
renderGoodVibesCommandHelp,
|
|
17
19
|
renderGoodVibesHelp,
|
|
18
20
|
renderGoodVibesVersion,
|
|
19
21
|
renderOnboardingCliStatus,
|
|
20
22
|
} from './index.ts';
|
|
23
|
+
import { buildCliServicePosture } from './service-posture.ts';
|
|
21
24
|
|
|
22
25
|
type ShellEntrypointOwnership = {
|
|
23
26
|
readonly workingDirectory: string;
|
|
@@ -58,7 +61,10 @@ export async function prepareShellCliRuntime(
|
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
if (cli.flags.help || cli.command === 'help') {
|
|
61
|
-
|
|
64
|
+
const helpTopic = cli.command === 'help'
|
|
65
|
+
? cli.commandArgs[0]
|
|
66
|
+
: cli.rawCommand ?? undefined;
|
|
67
|
+
console.log(helpTopic ? renderGoodVibesCommandHelp(helpTopic, binary) : renderGoodVibesHelp(binary));
|
|
62
68
|
process.exit(0);
|
|
63
69
|
}
|
|
64
70
|
|
|
@@ -120,6 +126,11 @@ export async function prepareShellCliRuntime(
|
|
|
120
126
|
const bootstrapCredentialPath = shellPaths.resolveUserPath('tui', 'auth-bootstrap.txt');
|
|
121
127
|
const operatorTokenPath = join(bootstrapHomeDirectory, '.goodvibes', 'daemon', 'operator-tokens.json');
|
|
122
128
|
const onboardingMarkers = readOnboardingCompletionMarkers(shellPaths);
|
|
129
|
+
const service = await buildCliServicePosture({
|
|
130
|
+
configManager,
|
|
131
|
+
workingDirectory: bootstrapWorkingDir,
|
|
132
|
+
homeDirectory: bootstrapHomeDirectory,
|
|
133
|
+
});
|
|
123
134
|
const statusOptions = {
|
|
124
135
|
configManager,
|
|
125
136
|
workingDirectory: bootstrapWorkingDir,
|
|
@@ -133,12 +144,15 @@ export async function prepareShellCliRuntime(
|
|
|
133
144
|
operatorTokenPath,
|
|
134
145
|
operatorTokenPresent: existsSync(operatorTokenPath),
|
|
135
146
|
},
|
|
147
|
+
service,
|
|
136
148
|
doctor: cli.command === 'doctor',
|
|
149
|
+
outputFormat: cli.flags.outputFormat,
|
|
137
150
|
};
|
|
151
|
+
const snapshot = buildCliStatusSnapshot(statusOptions);
|
|
138
152
|
console.log(cli.command === 'onboarding'
|
|
139
153
|
? renderOnboardingCliStatus(statusOptions)
|
|
140
154
|
: renderCliStatus(statusOptions));
|
|
141
|
-
process.exit(0);
|
|
155
|
+
process.exit(cli.command === 'doctor' && snapshot.findings.length > 0 ? 1 : 0);
|
|
142
156
|
}
|
|
143
157
|
|
|
144
158
|
const cliCommandResult = await handleGoodVibesCliCommand({
|
package/src/cli/help.ts
CHANGED
|
@@ -35,6 +35,7 @@ export function renderGoodVibesHelp(binary = 'goodvibes'): string {
|
|
|
35
35
|
' run|exec [prompt] Run non-interactively with text/json/stream-json output',
|
|
36
36
|
' serve|daemon Start the daemon/API host',
|
|
37
37
|
' web Show browser surface bind URL and enablement',
|
|
38
|
+
' service Inspect/manage daemon service lifecycle',
|
|
38
39
|
' status Print config, provider, service, and onboarding posture',
|
|
39
40
|
' doctor Print status plus setup warnings',
|
|
40
41
|
' onboarding [status] Open onboarding in the TUI, or print onboarding status',
|
|
@@ -53,7 +54,7 @@ export function renderGoodVibesHelp(binary = 'goodvibes'): string {
|
|
|
53
54
|
' Move setup/profile/trust/support bundles',
|
|
54
55
|
' remote|bridge Inspect remote runner/node posture',
|
|
55
56
|
' completion <shell> Generate shell completion script',
|
|
56
|
-
' help
|
|
57
|
+
' help [command] Print this help or command-specific help',
|
|
57
58
|
' version Print version',
|
|
58
59
|
'',
|
|
59
60
|
'Options:',
|
|
@@ -94,6 +95,7 @@ export function renderGoodVibesHelp(binary = 'goodvibes'): string {
|
|
|
94
95
|
` ${binary} surfaces`,
|
|
95
96
|
` ${binary} surfaces check`,
|
|
96
97
|
` ${binary} surfaces enable web`,
|
|
98
|
+
` ${binary} service check`,
|
|
97
99
|
` ${binary} listener test`,
|
|
98
100
|
` ${binary} control-plane status`,
|
|
99
101
|
` ${binary} subscription providers`,
|
|
@@ -102,6 +104,183 @@ export function renderGoodVibesHelp(binary = 'goodvibes'): string {
|
|
|
102
104
|
].join('\n');
|
|
103
105
|
}
|
|
104
106
|
|
|
107
|
+
type CommandHelp = {
|
|
108
|
+
readonly usage: readonly string[];
|
|
109
|
+
readonly summary: string;
|
|
110
|
+
readonly subcommands?: readonly string[];
|
|
111
|
+
readonly examples?: readonly string[];
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
115
|
+
tui: {
|
|
116
|
+
usage: ['tui [path]', '[prompt]'],
|
|
117
|
+
summary: 'Start the interactive terminal UI. A prompt starts the TUI with that prompt seeded.',
|
|
118
|
+
examples: ['', 'tui ~/work/project', '"review this repo"'],
|
|
119
|
+
},
|
|
120
|
+
run: {
|
|
121
|
+
usage: ['run [prompt] [--output text|json|stream-json]', 'exec [prompt]'],
|
|
122
|
+
summary: 'Run a single non-interactive agent turn and write the result to stdout.',
|
|
123
|
+
examples: ['run "summarize the current project"', 'run --output json "list risks"', 'exec --output stream-json "fix lint"'],
|
|
124
|
+
},
|
|
125
|
+
onboarding: {
|
|
126
|
+
usage: ['onboarding', 'setup', 'onboarding status'],
|
|
127
|
+
summary: 'Open the setup wizard on the next TUI startup, or inspect onboarding marker status from the CLI.',
|
|
128
|
+
examples: ['onboarding', 'onboarding status'],
|
|
129
|
+
},
|
|
130
|
+
status: {
|
|
131
|
+
usage: ['status', 'status --json'],
|
|
132
|
+
summary: 'Print config, provider, auth, service, surface, and onboarding posture.',
|
|
133
|
+
examples: ['status', 'status --json'],
|
|
134
|
+
},
|
|
135
|
+
doctor: {
|
|
136
|
+
usage: ['doctor', 'doctor --json'],
|
|
137
|
+
summary: 'Print status plus actionable setup warnings with cause, impact, and next action.',
|
|
138
|
+
examples: ['doctor', 'doctor --json'],
|
|
139
|
+
},
|
|
140
|
+
providers: {
|
|
141
|
+
usage: ['providers [list]', 'providers current', 'providers inspect <provider>', 'providers use <provider> [modelRegistryKey]'],
|
|
142
|
+
summary: 'Inspect and change provider setup, auth posture, model counts, and setup class.',
|
|
143
|
+
examples: ['providers', 'providers inspect openai-subscriber', 'providers use openai openai:gpt-5.4'],
|
|
144
|
+
},
|
|
145
|
+
models: {
|
|
146
|
+
usage: ['models [provider]', 'models current', 'models use <registryKey>', 'models pin <registryKey>', 'models recent'],
|
|
147
|
+
summary: 'List, inspect, select, pin, and review model choices.',
|
|
148
|
+
examples: ['models current', 'models openai', 'models use openai:gpt-5.4'],
|
|
149
|
+
},
|
|
150
|
+
auth: {
|
|
151
|
+
usage: ['auth status', 'auth users', 'auth sessions', 'auth add-user <username>', 'auth clear-bootstrap'],
|
|
152
|
+
summary: 'Inspect and manage local admin users, bootstrap auth, and local sessions.',
|
|
153
|
+
examples: ['auth', 'auth add-user admin --password-stdin', 'auth clear-bootstrap'],
|
|
154
|
+
},
|
|
155
|
+
subscription: {
|
|
156
|
+
usage: ['subscription list', 'subscription providers', 'subscription inspect <provider>', 'subscription login <provider> start|finish', 'subscription logout <provider>'],
|
|
157
|
+
summary: 'Manage OAuth/subscription-backed provider sessions such as OpenAI subscription access.',
|
|
158
|
+
examples: ['subscription providers', 'subscription login openai start --open', 'subscription inspect openai'],
|
|
159
|
+
},
|
|
160
|
+
secrets: {
|
|
161
|
+
usage: ['secrets list', 'secrets providers', 'secrets test goodvibes://secrets/<source>/...', 'secrets set <KEY> <value>', 'secrets link <KEY> <ref>'],
|
|
162
|
+
summary: 'Manage GoodVibes secret records and secret references. Secret refs never embed secret values.',
|
|
163
|
+
examples: ['secrets providers', 'secrets test goodvibes://secrets/env/OPENAI_API_KEY', 'secrets link OPENAI_API_KEY goodvibes://secrets/env/OPENAI_API_KEY'],
|
|
164
|
+
},
|
|
165
|
+
sessions: {
|
|
166
|
+
usage: ['sessions list', 'sessions show <id|name>', 'sessions export <id|name> [path]', 'sessions resume <id|name>'],
|
|
167
|
+
summary: 'List, inspect, export, or resume saved TUI sessions.',
|
|
168
|
+
examples: ['sessions list', 'sessions show latest-session', 'sessions export abc123 session.json'],
|
|
169
|
+
},
|
|
170
|
+
tasks: {
|
|
171
|
+
usage: ['tasks list', 'tasks show <taskId>', 'tasks submit <prompt>'],
|
|
172
|
+
summary: 'Inspect runtime tasks or submit a non-interactive task.',
|
|
173
|
+
examples: ['tasks list', 'tasks submit "check provider readiness"'],
|
|
174
|
+
},
|
|
175
|
+
surfaces: {
|
|
176
|
+
usage: ['surfaces [list]', 'surfaces check', 'surfaces show <surfaceId>', 'surfaces enable <web|listener|control-plane|surfaceId>', 'surfaces disable <surfaceId>'],
|
|
177
|
+
summary: 'Inspect and configure browser, control-plane, HTTP listener, and external integration surfaces.',
|
|
178
|
+
examples: ['surfaces check', 'surfaces enable web', 'surfaces enable slack'],
|
|
179
|
+
},
|
|
180
|
+
listener: {
|
|
181
|
+
usage: ['listener test'],
|
|
182
|
+
summary: 'Check HTTP listener/webhook readiness, network posture, service posture, auth, and enabled surface requirements.',
|
|
183
|
+
examples: ['listener test', 'listener test --json'],
|
|
184
|
+
},
|
|
185
|
+
'control-plane': {
|
|
186
|
+
usage: ['control-plane status'],
|
|
187
|
+
summary: 'Inspect daemon control-plane bind posture, reachability, local auth, bootstrap credentials, and operator tokens.',
|
|
188
|
+
examples: ['control-plane status', 'control-plane status --json'],
|
|
189
|
+
},
|
|
190
|
+
bundle: {
|
|
191
|
+
usage: ['bundle export [path]', 'bundle inspect <path>', 'bundle import <path>'],
|
|
192
|
+
summary: 'Export, inspect, or import setup/profile/trust/support bundle data.',
|
|
193
|
+
examples: ['bundle export goodvibes-bundle.json', 'bundle inspect goodvibes-bundle.json'],
|
|
194
|
+
},
|
|
195
|
+
pair: {
|
|
196
|
+
usage: ['pair', 'qrcode'],
|
|
197
|
+
summary: 'Print companion pairing connection details and a QR code.',
|
|
198
|
+
examples: ['pair', 'qrcode'],
|
|
199
|
+
},
|
|
200
|
+
web: {
|
|
201
|
+
usage: ['web [--open]'],
|
|
202
|
+
summary: 'Show the configured browser surface URL, bind address, and enablement state.',
|
|
203
|
+
examples: ['web', 'web --open', 'web --hostname 0.0.0.0 --port 3423'],
|
|
204
|
+
},
|
|
205
|
+
service: {
|
|
206
|
+
usage: ['service status', 'service check', 'service install|start|stop|restart|uninstall'],
|
|
207
|
+
summary: 'Inspect and manage the daemon service lifecycle, autostart, restart policy, PID, logs, and endpoint readiness.',
|
|
208
|
+
examples: ['service status', 'service check --json', 'service install'],
|
|
209
|
+
},
|
|
210
|
+
completion: {
|
|
211
|
+
usage: ['completion <bash|zsh|fish>'],
|
|
212
|
+
summary: 'Generate shell completion scripts.',
|
|
213
|
+
examples: ['completion bash', 'completion zsh'],
|
|
214
|
+
},
|
|
215
|
+
serve: {
|
|
216
|
+
usage: ['serve [--hostname <host>] [--port <port>]', 'daemon [--hostname <host>] [--port <port>]'],
|
|
217
|
+
summary: 'Start the headless GoodVibes daemon/API host.',
|
|
218
|
+
examples: ['serve', 'serve --hostname 0.0.0.0 --port 3421'],
|
|
219
|
+
},
|
|
220
|
+
remote: {
|
|
221
|
+
usage: ['remote', 'bridge'],
|
|
222
|
+
summary: 'Inspect remote runner/node posture and bridge readiness.',
|
|
223
|
+
examples: ['remote', 'bridge'],
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const HELP_ALIASES: Record<string, string> = {
|
|
228
|
+
app: 'tui',
|
|
229
|
+
exec: 'run',
|
|
230
|
+
setup: 'onboarding',
|
|
231
|
+
provider: 'providers',
|
|
232
|
+
model: 'models',
|
|
233
|
+
subscriptions: 'subscription',
|
|
234
|
+
secret: 'secrets',
|
|
235
|
+
session: 'sessions',
|
|
236
|
+
task: 'tasks',
|
|
237
|
+
surface: 'surfaces',
|
|
238
|
+
webhook: 'listener',
|
|
239
|
+
controlplane: 'control-plane',
|
|
240
|
+
cp: 'control-plane',
|
|
241
|
+
qrcode: 'pair',
|
|
242
|
+
qr: 'pair',
|
|
243
|
+
daemon: 'serve',
|
|
244
|
+
server: 'serve',
|
|
245
|
+
services: 'service',
|
|
246
|
+
bridge: 'remote',
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
function normalizeHelpTopic(topic: string): string {
|
|
250
|
+
const normalized = topic.trim().toLowerCase();
|
|
251
|
+
return HELP_ALIASES[normalized] ?? normalized;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function renderGoodVibesCommandHelp(topic: string, binary = 'goodvibes'): string {
|
|
255
|
+
const normalized = normalizeHelpTopic(topic);
|
|
256
|
+
const help = COMMAND_HELP[normalized];
|
|
257
|
+
if (!help) {
|
|
258
|
+
return [
|
|
259
|
+
`No detailed help is available for "${topic}".`,
|
|
260
|
+
'',
|
|
261
|
+
renderGoodVibesHelp(binary),
|
|
262
|
+
].join('\n');
|
|
263
|
+
}
|
|
264
|
+
return [
|
|
265
|
+
`GoodVibes ${normalized}`,
|
|
266
|
+
'',
|
|
267
|
+
help.summary,
|
|
268
|
+
'',
|
|
269
|
+
'Usage:',
|
|
270
|
+
...help.usage.map((usage) => ` ${binary} ${usage}`),
|
|
271
|
+
...(help.subcommands && help.subcommands.length > 0 ? [
|
|
272
|
+
'',
|
|
273
|
+
'Subcommands:',
|
|
274
|
+
...help.subcommands.map((subcommand) => ` ${subcommand}`),
|
|
275
|
+
] : []),
|
|
276
|
+
...(help.examples && help.examples.length > 0 ? [
|
|
277
|
+
'',
|
|
278
|
+
'Examples:',
|
|
279
|
+
...help.examples.map((example) => ` ${binary}${example ? ` ${example}` : ''}`),
|
|
280
|
+
] : []),
|
|
281
|
+
].join('\n');
|
|
282
|
+
}
|
|
283
|
+
|
|
105
284
|
export function renderGoodVibesDaemonHelp(binary = 'goodvibes-daemon'): string {
|
|
106
285
|
return [
|
|
107
286
|
`Usage: ${binary} [OPTIONS]`,
|
package/src/cli/index.ts
CHANGED
|
@@ -5,4 +5,7 @@ export * from './status.ts';
|
|
|
5
5
|
export * from './completion.ts';
|
|
6
6
|
export * from './config-overrides.ts';
|
|
7
7
|
export * from './endpoints.ts';
|
|
8
|
+
export * from './surface-command.ts';
|
|
9
|
+
export * from './service-command.ts';
|
|
10
|
+
export * from './bundle-command.ts';
|
|
8
11
|
export * from './management.ts';
|