@pellux/goodvibes-agent 0.1.10 → 0.1.12
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 +32 -0
- package/package.json +1 -1
- package/src/cli/agent-knowledge-command.ts +30 -3
- package/src/cli/help.ts +2 -2
- package/src/input/commands/cloudflare-runtime.ts +20 -5
- package/src/input/commands/confirmation.ts +24 -0
- package/src/input/commands/discovery-runtime.ts +16 -7
- package/src/input/commands/eval.ts +27 -14
- package/src/input/commands/experience-runtime.ts +65 -26
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/hooks-runtime.ts +50 -19
- package/src/input/commands/incident-runtime.ts +17 -6
- package/src/input/commands/integration-runtime.ts +93 -50
- package/src/input/commands/knowledge.ts +38 -12
- package/src/input/commands/local-auth-runtime.ts +36 -13
- package/src/input/commands/local-provider-runtime.ts +22 -11
- package/src/input/commands/local-runtime.ts +21 -11
- package/src/input/commands/local-setup.ts +35 -16
- package/src/input/commands/managed-runtime.ts +51 -20
- package/src/input/commands/marketplace-runtime.ts +31 -16
- package/src/input/commands/mcp-runtime.ts +65 -34
- package/src/input/commands/memory-product-runtime.ts +72 -35
- package/src/input/commands/memory.ts +9 -9
- package/src/input/commands/notify-runtime.ts +27 -8
- package/src/input/commands/operator-runtime.ts +85 -17
- package/src/input/commands/planning-runtime.ts +14 -2
- package/src/input/commands/platform-access-runtime.ts +88 -45
- package/src/input/commands/platform-services-runtime.ts +51 -25
- package/src/input/commands/product-runtime.ts +54 -27
- package/src/input/commands/profile-sync-runtime.ts +17 -6
- package/src/input/commands/recall-bundle.ts +38 -17
- package/src/input/commands/recall-query.ts +15 -4
- package/src/input/commands/recall-review.ts +9 -3
- package/src/input/commands/remote-runtime-setup.ts +45 -18
- package/src/input/commands/remote-runtime.ts +25 -9
- package/src/input/commands/replay-runtime.ts +9 -2
- package/src/input/commands/services-runtime.ts +21 -10
- package/src/input/commands/session-content.ts +53 -51
- package/src/input/commands/session-workflow.ts +10 -4
- package/src/input/commands/session.ts +1 -1
- package/src/input/commands/settings-sync-runtime.ts +40 -17
- package/src/input/commands/share-runtime.ts +12 -4
- package/src/input/commands/shell-core.ts +3 -3
- package/src/input/commands/subscription-runtime.ts +35 -20
- package/src/input/commands/teleport-runtime.ts +16 -5
- package/src/input/commands/work-plan-runtime.ts +23 -12
- package/src/input/handler-content-actions.ts +11 -62
- package/src/input/handler-interactions.ts +1 -1
- package/src/input/handler-onboarding-cloudflare.ts +48 -117
- package/src/input/keybindings.ts +1 -1
- package/src/input/mcp-workspace.ts +25 -49
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +8 -8
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +1 -6
- package/src/input/profile-picker-modal.ts +13 -31
- package/src/input/session-picker-modal.ts +4 -30
- package/src/input/settings-modal-subscriptions.ts +3 -3
- package/src/panels/incident-review-panel.ts +1 -1
- package/src/panels/local-auth-panel.ts +4 -4
- package/src/panels/provider-account-snapshot.ts +1 -1
- package/src/panels/provider-health-domains.ts +2 -2
- package/src/panels/settings-sync-panel.ts +2 -2
- package/src/panels/subscription-panel.ts +7 -7
- package/src/renderer/block-actions.ts +1 -1
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/mcp-workspace.ts +12 -12
- package/src/renderer/profile-picker-modal.ts +3 -11
- package/src/renderer/session-picker-modal.ts +2 -10
- package/src/verification/live-verifier.ts +100 -68
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GoodVibes Agent will be recorded here.
|
|
4
4
|
|
|
5
|
+
## 0.1.12 - 2026-05-31
|
|
6
|
+
|
|
7
|
+
- 1843a77 Handle external daemon SDK mismatch in live verification
|
|
8
|
+
- 2b1a3f4 Align agent-owned test paths
|
|
9
|
+
|
|
10
|
+
## 0.1.11 - 2026-05-31
|
|
11
|
+
|
|
12
|
+
- d20a93e Allow explicit recall review without yes
|
|
13
|
+
- 601f41c Require confirmation for eval execution
|
|
14
|
+
- f41befb Block Cloudflare onboarding mutations
|
|
15
|
+
- 79071ec Block implicit block file saves
|
|
16
|
+
- 40aca02 Block inline diff file edits in Agent
|
|
17
|
+
- 854eda8 Block MCP workspace config mutations
|
|
18
|
+
- 665c423 Require confirmation for CLI Agent Knowledge ingest
|
|
19
|
+
- f07255a Require confirmation for recall review mutations
|
|
20
|
+
- 6e78ec3 Require confirmation for Agent Knowledge mutations
|
|
21
|
+
- c1e5ac1 Require confirmation for operator control mutations
|
|
22
|
+
- 5d6fc3c Require confirmation for local state mutations
|
|
23
|
+
- 3afd788 Require confirmation for remaining export paths
|
|
24
|
+
- d36ae96 Require confirmation for portable state bundles
|
|
25
|
+
- 1f0caab Require confirmation for setup and remote exports
|
|
26
|
+
- 2e149c9 Require confirmation for incident and handoff exports
|
|
27
|
+
- b439c55 Require confirmation for settings mutations
|
|
28
|
+
- f7c8fe6 Require confirmation for platform auth mutations
|
|
29
|
+
- 09064e0 Require confirmation for auth and service mutations
|
|
30
|
+
- 55e8b8d Require confirmation for marketplace mutations
|
|
31
|
+
- 88becca Require confirmation for plugin mutations
|
|
32
|
+
- 89c0584 Require confirmation for local provider mutations
|
|
33
|
+
- 7f3af1b Require confirmation for managed hook mutations
|
|
34
|
+
- 323426c Require confirmation for destructive workplan cleanup
|
|
35
|
+
- 191350e Require confirmation for side-effecting slash commands
|
|
36
|
+
|
|
5
37
|
## 0.1.10 - 2026-05-31
|
|
6
38
|
|
|
7
39
|
- 93aba19 Block agent-spawning hook authoring
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Near-fork GoodVibes operator assistant with the GoodVibes TUI shell, renderer, input, fullscreen workspace, and daemon-connected Agent product brain.",
|
|
6
6
|
"type": "module",
|
|
@@ -149,6 +149,19 @@ function hasFlag(args: readonly string[], flag: string): boolean {
|
|
|
149
149
|
return args.includes(flag);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
function stripCommandFlag(args: readonly string[], flag: string): { readonly rest: readonly string[]; readonly present: boolean } {
|
|
153
|
+
const rest: string[] = [];
|
|
154
|
+
let present = false;
|
|
155
|
+
for (const arg of args) {
|
|
156
|
+
if (arg === flag) {
|
|
157
|
+
present = true;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
rest.push(arg);
|
|
161
|
+
}
|
|
162
|
+
return { rest, present };
|
|
163
|
+
}
|
|
164
|
+
|
|
152
165
|
function readPackageMetadata(): { readonly version: string; readonly sdkVersion: string } {
|
|
153
166
|
return { version: VERSION, sdkVersion: SDK_VERSION };
|
|
154
167
|
}
|
|
@@ -444,7 +457,9 @@ async function runKnowledgeCall<TData>(
|
|
|
444
457
|
}
|
|
445
458
|
|
|
446
459
|
export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): Promise<CliCommandOutput> {
|
|
447
|
-
const [sub = 'status', ...
|
|
460
|
+
const [sub = 'status', ...rawRest] = runtime.cli.commandArgs;
|
|
461
|
+
const confirmation = stripCommandFlag(rawRest, '--yes');
|
|
462
|
+
const rest = confirmation.rest;
|
|
448
463
|
const normalized = sub.toLowerCase();
|
|
449
464
|
const json = runtime.cli.flags.outputFormat === 'json';
|
|
450
465
|
const disallowedScopeFlag = findDisallowedKnowledgeScopeFlag(rest);
|
|
@@ -512,7 +527,19 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
|
|
|
512
527
|
if (normalized === 'ingest-url') {
|
|
513
528
|
const values = commandValues(rest);
|
|
514
529
|
const url = values[0];
|
|
515
|
-
if (!url) return { output: 'Usage: goodvibes-agent knowledge ingest-url <url> [--title <title>] [--tags a,b]', exitCode: 2 };
|
|
530
|
+
if (!url) return { output: 'Usage: goodvibes-agent knowledge ingest-url <url> [--title <title>] [--tags a,b] --yes', exitCode: 2 };
|
|
531
|
+
if (!confirmation.present) {
|
|
532
|
+
const failure = {
|
|
533
|
+
ok: false,
|
|
534
|
+
kind: 'confirmation_required',
|
|
535
|
+
error: `Refusing to ingest URL into Agent Knowledge ${url} without --yes.`,
|
|
536
|
+
route: AGENT_KNOWLEDGE_METHODS.ingestUrl.route,
|
|
537
|
+
};
|
|
538
|
+
return {
|
|
539
|
+
output: json ? JSON.stringify(failure, null, 2) : `${failure.error}\nUsage: goodvibes-agent knowledge ingest-url <url> [--title <title>] [--tags a,b] --yes`,
|
|
540
|
+
exitCode: 2,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
516
543
|
const title = readOptionValue(rest, '--title');
|
|
517
544
|
const tags = readStringList(rest, '--tags');
|
|
518
545
|
const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.ingestUrl, async (connection) => (
|
|
@@ -532,7 +559,7 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
|
|
|
532
559
|
}
|
|
533
560
|
|
|
534
561
|
return {
|
|
535
|
-
output: 'Usage: goodvibes-agent knowledge [status|ask <question>|search <query>|ingest-url <url>]',
|
|
562
|
+
output: 'Usage: goodvibes-agent knowledge [status|ask <question>|search <query>|ingest-url <url> --yes]',
|
|
536
563
|
exitCode: 2,
|
|
537
564
|
};
|
|
538
565
|
}
|
package/src/cli/help.ts
CHANGED
|
@@ -167,14 +167,14 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
167
167
|
'knowledge status',
|
|
168
168
|
'knowledge ask <question> [--limit <n>] [--mode concise|standard|detailed]',
|
|
169
169
|
'knowledge search <query> [--limit <n>]',
|
|
170
|
-
'knowledge ingest-url <url> [--title <title>] [--tags a,b]',
|
|
170
|
+
'knowledge ingest-url <url> [--title <title>] [--tags a,b] --yes',
|
|
171
171
|
],
|
|
172
172
|
summary: 'Call isolated Agent Knowledge/Wiki routes under /api/goodvibes-agent/knowledge. No default wiki or HomeGraph fallback.',
|
|
173
173
|
examples: [
|
|
174
174
|
'knowledge status',
|
|
175
175
|
'knowledge ask "What is GoodVibes Agent?"',
|
|
176
176
|
'knowledge search "release checklist"',
|
|
177
|
-
'knowledge ingest-url https://example.com/page --title "Reference"',
|
|
177
|
+
'knowledge ingest-url https://example.com/page --title "Reference" --yes',
|
|
178
178
|
],
|
|
179
179
|
},
|
|
180
180
|
ask: {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from '../../runtime/cloudflare-control-plane.ts';
|
|
12
12
|
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
13
13
|
import { requireShellPaths } from './runtime-services.ts';
|
|
14
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
14
15
|
|
|
15
16
|
interface ParsedCloudflareArgs {
|
|
16
17
|
readonly positional: readonly string[];
|
|
@@ -22,10 +23,12 @@ export function registerCloudflareRuntimeCommands(registry: CommandRegistry): vo
|
|
|
22
23
|
name: 'cloudflare',
|
|
23
24
|
aliases: ['cf'],
|
|
24
25
|
description: 'Inspect and manage optional Cloudflare batch/control-plane integration through daemon SDK routes',
|
|
25
|
-
usage: '[status|setup|requirements|create-token|discover|validate|provision|verify|disable] [flags]',
|
|
26
|
+
usage: '[status|setup|requirements|create-token --yes|discover|validate|provision --yes|verify|disable --yes] [flags]',
|
|
26
27
|
async handler(args, ctx) {
|
|
27
|
-
const
|
|
28
|
-
const
|
|
28
|
+
const confirmation = stripYesFlag(args);
|
|
29
|
+
const commandArgs = [...confirmation.rest];
|
|
30
|
+
const subcommand = (commandArgs[0] ?? 'status').toLowerCase();
|
|
31
|
+
const parsed = parseCloudflareArgs(commandArgs.slice(1));
|
|
29
32
|
if (subcommand === 'setup' || subcommand === 'onboarding') {
|
|
30
33
|
ctx.openOnboardingWizard?.({ mode: 'edit', reset: true });
|
|
31
34
|
ctx.print('Opening onboarding wizard. Select the Cloudflare batch capability to configure Cloudflare.');
|
|
@@ -80,9 +83,13 @@ export function registerCloudflareRuntimeCommands(registry: CommandRegistry): vo
|
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
if (subcommand === 'create-token') {
|
|
86
|
+
if (!confirmation.yes) {
|
|
87
|
+
requireYesFlag(ctx, 'create and store a Cloudflare operational token', '/cloudflare create-token [flags] --yes');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
83
90
|
const bootstrapToken = getFlag(parsed, 'bootstrap-token') || readTokenEnv(parsed, 'bootstrap-env');
|
|
84
91
|
if (!bootstrapToken) {
|
|
85
|
-
ctx.print('Usage: /cloudflare create-token --account <account-id> --bootstrap-token <token>
|
|
92
|
+
ctx.print('Usage: /cloudflare create-token --account <account-id> (--bootstrap-token <token> | --bootstrap-env <env-name>) --yes');
|
|
86
93
|
return;
|
|
87
94
|
}
|
|
88
95
|
const result = await client.createOperationalToken({
|
|
@@ -141,6 +148,10 @@ export function registerCloudflareRuntimeCommands(registry: CommandRegistry): vo
|
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
if (subcommand === 'provision') {
|
|
151
|
+
if (!confirmation.yes) {
|
|
152
|
+
requireYesFlag(ctx, 'provision Cloudflare resources and persist config', '/cloudflare provision [flags] --yes');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
144
155
|
const result = await client.provision({
|
|
145
156
|
...cloudflareAuthInput(parsed),
|
|
146
157
|
components: componentsFromArgs(parsed),
|
|
@@ -212,6 +223,10 @@ export function registerCloudflareRuntimeCommands(registry: CommandRegistry): vo
|
|
|
212
223
|
}
|
|
213
224
|
|
|
214
225
|
if (subcommand === 'disable') {
|
|
226
|
+
if (!confirmation.yes) {
|
|
227
|
+
requireYesFlag(ctx, 'disable Cloudflare integration and persist config', '/cloudflare disable [flags] --yes');
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
215
230
|
const result = await client.disable({
|
|
216
231
|
...cloudflareAuthInput(parsed),
|
|
217
232
|
...optionalString('workerName', getFlag(parsed, 'worker-name')),
|
|
@@ -227,7 +242,7 @@ export function registerCloudflareRuntimeCommands(registry: CommandRegistry): vo
|
|
|
227
242
|
return;
|
|
228
243
|
}
|
|
229
244
|
|
|
230
|
-
ctx.print('Usage: /cloudflare [status|setup|requirements|create-token|discover|validate|provision|verify|disable] [flags]');
|
|
245
|
+
ctx.print('Usage: /cloudflare [status|setup|requirements|create-token --yes|discover|validate|provision --yes|verify|disable --yes] [flags]');
|
|
231
246
|
} catch (error) {
|
|
232
247
|
ctx.print(`Cloudflare ${subcommand} failed: ${formatCloudflareError(error)}`);
|
|
233
248
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CommandContext } from '../command-registry.ts';
|
|
2
|
+
|
|
3
|
+
export interface ConfirmationArgs {
|
|
4
|
+
readonly rest: readonly string[];
|
|
5
|
+
readonly yes: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function stripYesFlag(args: readonly string[]): ConfirmationArgs {
|
|
9
|
+
const rest: string[] = [];
|
|
10
|
+
let yes = false;
|
|
11
|
+
for (const token of args) {
|
|
12
|
+
if (token === '--yes') {
|
|
13
|
+
yes = true;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
rest.push(token);
|
|
17
|
+
}
|
|
18
|
+
return { rest, yes };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function requireYesFlag(ctx: CommandContext, action: string, usage: string): boolean {
|
|
22
|
+
ctx.print(`Refusing to ${action} without --yes.\nUsage: ${usage}`);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
@@ -3,13 +3,16 @@ import { scan, persistProviders } from '@pellux/goodvibes-sdk/platform/discovery
|
|
|
3
3
|
import { requireProviderApi, requireShellPaths } from './runtime-services.ts';
|
|
4
4
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
5
5
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../../config/surface.ts';
|
|
6
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
6
7
|
|
|
7
8
|
export function registerDiscoveryRuntimeCommands(registry: CommandRegistry): void {
|
|
8
9
|
registry.register({
|
|
9
10
|
name: 'scan',
|
|
10
11
|
aliases: [],
|
|
11
12
|
description: 'Scan localhost and LAN for local LLM servers',
|
|
12
|
-
|
|
13
|
+
usage: '[--yes]',
|
|
14
|
+
async handler(args, ctx) {
|
|
15
|
+
const { yes } = stripYesFlag(args);
|
|
13
16
|
ctx.print('Scanning for local LLM servers...');
|
|
14
17
|
ctx.renderRequest();
|
|
15
18
|
|
|
@@ -33,18 +36,24 @@ export function registerDiscoveryRuntimeCommands(registry: CommandRegistry): voi
|
|
|
33
36
|
ctx.print(lines.join('\n'));
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
try {
|
|
37
|
-
await requireProviderApi(ctx).registerDiscoveredProviders(result.servers);
|
|
38
|
-
} catch (err) {
|
|
39
|
-
ctx.print(`[Scan] Warning: failed to register some providers: ${summarizeError(err)}`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
39
|
if (result.servers.length > 0) {
|
|
40
|
+
if (!yes) {
|
|
41
|
+
requireYesFlag(ctx, 'persist discovered local provider configuration', '/scan --yes');
|
|
42
|
+
ctx.print('[Scan] Discovery results were not saved. Rerun /scan --yes to register and persist providers.');
|
|
43
|
+
ctx.renderRequest();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
await requireProviderApi(ctx).registerDiscoveredProviders(result.servers);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
ctx.print(`[Scan] Warning: failed to register some providers: ${summarizeError(err)}`);
|
|
50
|
+
}
|
|
43
51
|
const shellPaths = requireShellPaths(ctx);
|
|
44
52
|
persistProviders({
|
|
45
53
|
homeDirectory: shellPaths.homeDirectory,
|
|
46
54
|
surfaceRoot: GOODVIBES_AGENT_SURFACE_ROOT,
|
|
47
55
|
}, result.servers);
|
|
56
|
+
ctx.print('[Scan] Discovered providers registered and persisted.');
|
|
48
57
|
}
|
|
49
58
|
ctx.renderRequest();
|
|
50
59
|
},
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Implements the Evaluation Harness commands:
|
|
5
5
|
*
|
|
6
6
|
* /eval list — List all available eval suites
|
|
7
|
-
* /eval run <suite>
|
|
7
|
+
* /eval run <suite> --yes — Run a named suite (or 'all')
|
|
8
8
|
* /eval compare <baseline-file> — Compare last run against a baseline file
|
|
9
|
-
* /eval gate <suite>
|
|
9
|
+
* /eval gate <suite> --yes — Run suite and apply CI gate (exits 1 on regression)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { SlashCommand, CommandContext } from '../command-registry.ts';
|
|
@@ -18,6 +18,7 @@ import type { EvalRegistry } from '../../panels/eval-panel.ts';
|
|
|
18
18
|
import { formatSuiteResult, formatGateResult } from '@/runtime/index.ts';
|
|
19
19
|
import { requireShellPaths } from './runtime-services.ts';
|
|
20
20
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
21
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
21
22
|
|
|
22
23
|
// ── Subcommand helpers ────────────────────────────────────────────────────────
|
|
23
24
|
|
|
@@ -29,7 +30,7 @@ function printSuiteList(context: CommandContext): void {
|
|
|
29
30
|
context.print(` - ${s.id}: ${s.name}`);
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
|
-
context.print('[eval] Usage: /eval run <suite> or /eval run all');
|
|
33
|
+
context.print('[eval] Usage: /eval run <suite> --yes or /eval run all --yes');
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
function getRegistry(context: CommandContext): EvalRegistry | undefined {
|
|
@@ -45,7 +46,8 @@ function handleList(_args: string[], context: CommandContext): void {
|
|
|
45
46
|
// ── /eval run ────────────────────────────────────────────────────────────────
|
|
46
47
|
|
|
47
48
|
async function handleRun(args: string[], context: CommandContext): Promise<void> {
|
|
48
|
-
const
|
|
49
|
+
const { rest, yes } = stripYesFlag(args);
|
|
50
|
+
const suiteName = rest[0] ?? 'all';
|
|
49
51
|
const registry = getRegistry(context);
|
|
50
52
|
|
|
51
53
|
const suitesToRun =
|
|
@@ -59,6 +61,10 @@ async function handleRun(args: string[], context: CommandContext): Promise<void>
|
|
|
59
61
|
context.print(`[eval] Unknown suite: "${suiteName}". Run /eval list to see available suites.`);
|
|
60
62
|
return;
|
|
61
63
|
}
|
|
64
|
+
if (!yes) {
|
|
65
|
+
requireYesFlag(context, `run eval suite ${suiteName}`, '/eval run <suite|all> --yes');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
62
68
|
|
|
63
69
|
const runner = new EvalRunner();
|
|
64
70
|
registry?.setRunning(true);
|
|
@@ -90,14 +96,14 @@ async function handleCompare(args: string[], context: CommandContext): Promise<v
|
|
|
90
96
|
const suiteResults = registry?.getSuiteResults() ?? [];
|
|
91
97
|
|
|
92
98
|
if (suiteResults.length === 0) {
|
|
93
|
-
context.print('[eval] No suite results to compare. Run /eval run <suite> first.');
|
|
99
|
+
context.print('[eval] No suite results to compare. Run /eval run <suite> --yes first.');
|
|
94
100
|
return;
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
const baseline = await loadBaseline(baselineFile, projectRoot);
|
|
98
104
|
if (!baseline) {
|
|
99
105
|
context.print(`[eval] Baseline file not found: ${baselineFile}`);
|
|
100
|
-
context.print('[eval] Tip: run /eval gate <suite> to create a baseline.');
|
|
106
|
+
context.print('[eval] Tip: run /eval gate <suite> [baseline-file] --save-baseline --yes to create a baseline.');
|
|
101
107
|
return;
|
|
102
108
|
}
|
|
103
109
|
|
|
@@ -109,13 +115,15 @@ async function handleCompare(args: string[], context: CommandContext): Promise<v
|
|
|
109
115
|
// ── /eval gate ────────────────────────────────────────────────────────────────
|
|
110
116
|
|
|
111
117
|
async function handleGate(args: string[], context: CommandContext): Promise<void> {
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
const
|
|
118
|
+
const { rest, yes } = stripYesFlag(args);
|
|
119
|
+
const positional = rest.filter((arg) => arg !== '--save-baseline');
|
|
120
|
+
const suiteName = positional[0];
|
|
121
|
+
const baselineFile = positional[1] ?? '.goodvibes/eval/baseline.json';
|
|
122
|
+
const saveFlag = rest.includes('--save-baseline');
|
|
115
123
|
const projectRoot = requireShellPaths(context).workingDirectory;
|
|
116
124
|
|
|
117
125
|
if (!suiteName) {
|
|
118
|
-
context.print('[eval] Usage: /eval gate <suite> [baseline-file] [--save-baseline]');
|
|
126
|
+
context.print('[eval] Usage: /eval gate <suite> [baseline-file] [--save-baseline] --yes');
|
|
119
127
|
return;
|
|
120
128
|
}
|
|
121
129
|
|
|
@@ -124,6 +132,10 @@ async function handleGate(args: string[], context: CommandContext): Promise<void
|
|
|
124
132
|
context.print(`[eval] Unknown suite: "${suiteName}". Run /eval list to see available suites.`);
|
|
125
133
|
return;
|
|
126
134
|
}
|
|
135
|
+
if (!yes) {
|
|
136
|
+
requireYesFlag(context, `run eval gate ${suiteName}`, '/eval gate <suite> [baseline-file] [--save-baseline] --yes');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
127
139
|
|
|
128
140
|
const registry = getRegistry(context);
|
|
129
141
|
const runner = new EvalRunner();
|
|
@@ -141,7 +153,7 @@ async function handleGate(args: string[], context: CommandContext): Promise<void
|
|
|
141
153
|
context.print(formatGateResult(gate));
|
|
142
154
|
|
|
143
155
|
if (saveFlag || !baseline) {
|
|
144
|
-
const label =
|
|
156
|
+
const label = suiteName ?? 'latest';
|
|
145
157
|
const newBaseline = captureBaseline(label, [fresh]);
|
|
146
158
|
try {
|
|
147
159
|
await writeBaseline(baselineFile, newBaseline, projectRoot);
|
|
@@ -164,7 +176,7 @@ export const evalCommand: SlashCommand = {
|
|
|
164
176
|
name: 'eval',
|
|
165
177
|
description: 'Evaluation harness: run benchmark suites, compare baselines, and gate regressions.',
|
|
166
178
|
usage: '<subcommand> [args]',
|
|
167
|
-
argsHint: 'list|run <suite
|
|
179
|
+
argsHint: 'list|run <suite> --yes|compare <baseline>|gate <suite> --yes',
|
|
168
180
|
handler: async (args: string[], context: CommandContext): Promise<void> => {
|
|
169
181
|
const [sub, ...rest] = args;
|
|
170
182
|
|
|
@@ -191,9 +203,10 @@ export const evalCommand: SlashCommand = {
|
|
|
191
203
|
const usage = [
|
|
192
204
|
'Usage: /eval <subcommand>',
|
|
193
205
|
' list — List all available eval suites',
|
|
194
|
-
' run <suite|all>
|
|
206
|
+
' run <suite|all> --yes — Run a named suite (or all suites)',
|
|
195
207
|
' compare [baseline-file] — Compare last results against baseline',
|
|
196
|
-
' gate <suite> [baseline-file]
|
|
208
|
+
' gate <suite> [baseline-file] --yes',
|
|
209
|
+
' — Run suite and apply regression gate',
|
|
197
210
|
' --save-baseline — Save fresh run as new baseline',
|
|
198
211
|
].join('\n');
|
|
199
212
|
context.print(usage);
|
|
@@ -2,6 +2,7 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { dirname, resolve } from 'node:path';
|
|
3
3
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
4
4
|
import { requirePanelManager, requireShellPaths } from './runtime-services.ts';
|
|
5
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
5
6
|
|
|
6
7
|
interface VoiceBundle {
|
|
7
8
|
readonly version: 1;
|
|
@@ -23,21 +24,27 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
23
24
|
registry.register({
|
|
24
25
|
name: 'remote-setup',
|
|
25
26
|
description: 'Dedicated front-door for remote setup review and portable setup bundles',
|
|
26
|
-
usage: '[review|export <path>]',
|
|
27
|
+
usage: '[review|export <path> --yes]',
|
|
27
28
|
async handler(args, ctx) {
|
|
28
|
-
const
|
|
29
|
+
const parsed = stripYesFlag(args);
|
|
30
|
+
const commandArgs = [...parsed.rest];
|
|
31
|
+
const sub = (commandArgs[0] ?? 'review').toLowerCase();
|
|
29
32
|
if (ctx.executeCommand) {
|
|
30
33
|
if (sub === 'review') {
|
|
31
34
|
await ctx.executeCommand('remote', ['setup']);
|
|
32
35
|
return;
|
|
33
36
|
}
|
|
34
37
|
if (sub === 'export') {
|
|
35
|
-
const pathArg =
|
|
38
|
+
const pathArg = commandArgs[1];
|
|
36
39
|
if (!pathArg) {
|
|
37
|
-
ctx.print('Usage: /remote-setup export <path>');
|
|
40
|
+
ctx.print('Usage: /remote-setup export <path> --yes');
|
|
38
41
|
return;
|
|
39
42
|
}
|
|
40
|
-
|
|
43
|
+
if (!parsed.yes) {
|
|
44
|
+
requireYesFlag(ctx, `export remote setup bundle to ${pathArg}`, '/remote-setup export <path> --yes');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
await ctx.executeCommand('remote', ['setup', 'export', pathArg, '--yes']);
|
|
41
48
|
return;
|
|
42
49
|
}
|
|
43
50
|
}
|
|
@@ -48,21 +55,27 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
48
55
|
registry.register({
|
|
49
56
|
name: 'remote-env',
|
|
50
57
|
description: 'Dedicated front-door for remote environment snippets and portable env exports',
|
|
51
|
-
usage: '[review|export <path>]',
|
|
58
|
+
usage: '[review|export <path> --yes]',
|
|
52
59
|
async handler(args, ctx) {
|
|
53
|
-
const
|
|
60
|
+
const parsed = stripYesFlag(args);
|
|
61
|
+
const commandArgs = [...parsed.rest];
|
|
62
|
+
const sub = (commandArgs[0] ?? 'review').toLowerCase();
|
|
54
63
|
if (ctx.executeCommand) {
|
|
55
64
|
if (sub === 'review') {
|
|
56
65
|
await ctx.executeCommand('remote', ['env']);
|
|
57
66
|
return;
|
|
58
67
|
}
|
|
59
68
|
if (sub === 'export') {
|
|
60
|
-
const pathArg =
|
|
69
|
+
const pathArg = commandArgs[1];
|
|
61
70
|
if (!pathArg) {
|
|
62
|
-
ctx.print('Usage: /remote-env export <path>');
|
|
71
|
+
ctx.print('Usage: /remote-env export <path> --yes');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!parsed.yes) {
|
|
75
|
+
requireYesFlag(ctx, `export remote environment snippet to ${pathArg}`, '/remote-env export <path> --yes');
|
|
63
76
|
return;
|
|
64
77
|
}
|
|
65
|
-
await ctx.executeCommand('remote', ['env', 'export', pathArg]);
|
|
78
|
+
await ctx.executeCommand('remote', ['env', 'export', pathArg, '--yes']);
|
|
66
79
|
return;
|
|
67
80
|
}
|
|
68
81
|
}
|
|
@@ -73,21 +86,27 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
73
86
|
registry.register({
|
|
74
87
|
name: 'tunnel',
|
|
75
88
|
description: 'Dedicated front-door for remote tunnel review and export flows',
|
|
76
|
-
usage: '[review|export <path>]',
|
|
89
|
+
usage: '[review|export <path> --yes]',
|
|
77
90
|
async handler(args, ctx) {
|
|
78
|
-
const
|
|
91
|
+
const parsed = stripYesFlag(args);
|
|
92
|
+
const commandArgs = [...parsed.rest];
|
|
93
|
+
const sub = (commandArgs[0] ?? 'review').toLowerCase();
|
|
79
94
|
if (ctx.executeCommand) {
|
|
80
95
|
if (sub === 'review') {
|
|
81
96
|
await ctx.executeCommand('remote', ['tunnel', 'review']);
|
|
82
97
|
return;
|
|
83
98
|
}
|
|
84
99
|
if (sub === 'export') {
|
|
85
|
-
const pathArg =
|
|
100
|
+
const pathArg = commandArgs[1];
|
|
86
101
|
if (!pathArg) {
|
|
87
|
-
ctx.print('Usage: /tunnel export <path>');
|
|
102
|
+
ctx.print('Usage: /tunnel export <path> --yes');
|
|
88
103
|
return;
|
|
89
104
|
}
|
|
90
|
-
|
|
105
|
+
if (!parsed.yes) {
|
|
106
|
+
requireYesFlag(ctx, `export remote tunnel review to ${pathArg}`, '/tunnel export <path> --yes');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
await ctx.executeCommand('remote', ['tunnel', 'export', pathArg, '--yes']);
|
|
91
110
|
return;
|
|
92
111
|
}
|
|
93
112
|
}
|
|
@@ -98,19 +117,29 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
98
117
|
registry.register({
|
|
99
118
|
name: 'bootstrap',
|
|
100
119
|
description: 'Dedicated front-door for remote bootstrap bundle export and inspection',
|
|
101
|
-
usage: '[export <path
|
|
120
|
+
usage: '[export <path> --yes|inspect <path>]',
|
|
102
121
|
async handler(args, ctx) {
|
|
103
|
-
const
|
|
104
|
-
const
|
|
122
|
+
const parsed = stripYesFlag(args);
|
|
123
|
+
const commandArgs = [...parsed.rest];
|
|
124
|
+
const sub = (commandArgs[0] ?? '').toLowerCase();
|
|
125
|
+
const pathArg = commandArgs[1];
|
|
105
126
|
if (!ctx.executeCommand) {
|
|
106
127
|
ctx.print('Bootstrap controls are not available in this runtime.');
|
|
107
128
|
return;
|
|
108
129
|
}
|
|
109
|
-
if (
|
|
130
|
+
if (sub === 'export' && pathArg) {
|
|
131
|
+
if (!parsed.yes) {
|
|
132
|
+
requireYesFlag(ctx, `export remote bootstrap bundle to ${pathArg}`, '/bootstrap export <path> --yes');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
await ctx.executeCommand('remote', ['bootstrap', sub, pathArg, '--yes']);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (sub === 'inspect' && pathArg) {
|
|
110
139
|
await ctx.executeCommand('remote', ['bootstrap', sub, pathArg]);
|
|
111
140
|
return;
|
|
112
141
|
}
|
|
113
|
-
ctx.print('Usage: /bootstrap [export <path
|
|
142
|
+
ctx.print('Usage: /bootstrap [export <path> --yes|inspect <path>]');
|
|
114
143
|
},
|
|
115
144
|
});
|
|
116
145
|
|
|
@@ -223,10 +252,12 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
223
252
|
registry.register({
|
|
224
253
|
name: 'voice',
|
|
225
254
|
description: 'Review voice posture and package portable voice-surface metadata',
|
|
226
|
-
usage: '[review|enable|disable|bundle export <path
|
|
255
|
+
usage: '[review|enable --yes|disable --yes|bundle export <path> --yes|bundle inspect <path>]',
|
|
227
256
|
handler(args, ctx) {
|
|
257
|
+
const parsed = stripYesFlag(args);
|
|
258
|
+
const commandArgs = [...parsed.rest];
|
|
228
259
|
const shellPaths = requireShellPaths(ctx);
|
|
229
|
-
const sub = (
|
|
260
|
+
const sub = (commandArgs[0] ?? 'review').toLowerCase();
|
|
230
261
|
if (sub === 'review') {
|
|
231
262
|
const enabled = Boolean(ctx.platform.configManager.get('ui.voiceEnabled') ?? false);
|
|
232
263
|
ctx.print([
|
|
@@ -238,20 +269,28 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
238
269
|
return;
|
|
239
270
|
}
|
|
240
271
|
if (sub === 'enable' || sub === 'disable') {
|
|
272
|
+
if (!parsed.yes) {
|
|
273
|
+
requireYesFlag(ctx, `${sub} voice surface`, `/voice ${sub} --yes`);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
241
276
|
const next = sub === 'enable';
|
|
242
277
|
ctx.platform.configManager.setDynamic('ui.voiceEnabled', next);
|
|
243
278
|
ctx.print(`Voice surface ${next ? 'enabled' : 'disabled'} for this runtime.`);
|
|
244
279
|
return;
|
|
245
280
|
}
|
|
246
281
|
if (sub === 'bundle') {
|
|
247
|
-
const mode =
|
|
248
|
-
const pathArg =
|
|
282
|
+
const mode = commandArgs[1];
|
|
283
|
+
const pathArg = commandArgs[2];
|
|
249
284
|
if ((mode === 'export' || mode === 'inspect') && !pathArg) {
|
|
250
|
-
ctx.print(`Usage: /voice bundle ${mode} <path
|
|
285
|
+
ctx.print(`Usage: /voice bundle ${mode} <path>${mode === 'export' ? ' --yes' : ''}`);
|
|
251
286
|
return;
|
|
252
287
|
}
|
|
253
288
|
const targetPath = shellPaths.resolveWorkspacePath(pathArg!);
|
|
254
289
|
if (mode === 'export') {
|
|
290
|
+
if (!parsed.yes) {
|
|
291
|
+
requireYesFlag(ctx, `export voice bundle to ${pathArg}`, '/voice bundle export <path> --yes');
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
255
294
|
const bundle: VoiceBundle = {
|
|
256
295
|
version: 1,
|
|
257
296
|
exportedAt: Date.now(),
|
|
@@ -272,7 +311,7 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
272
311
|
return;
|
|
273
312
|
}
|
|
274
313
|
}
|
|
275
|
-
ctx.print('Usage: /voice [review|enable|disable|bundle export <path
|
|
314
|
+
ctx.print('Usage: /voice [review|enable --yes|disable --yes|bundle export <path> --yes|bundle inspect <path>]');
|
|
276
315
|
},
|
|
277
316
|
});
|
|
278
317
|
}
|
|
@@ -299,7 +299,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
299
299
|
lines.push(' domain: auth');
|
|
300
300
|
lines.push(...(
|
|
301
301
|
auth.bootstrapCredentialPresent
|
|
302
|
-
? [' /auth local review', ' /auth local rotate-password admin <password>', ' /auth local clear-bootstrap-file']
|
|
302
|
+
? [' /auth local review', ' /auth local rotate-password admin <password> --yes', ' /auth local clear-bootstrap-file --yes']
|
|
303
303
|
: [' /auth local review']
|
|
304
304
|
));
|
|
305
305
|
lines.push(' verify: /health auth');
|