@pellux/goodvibes-agent 0.1.10 → 0.1.11

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/package.json +1 -1
  3. package/src/cli/agent-knowledge-command.ts +30 -3
  4. package/src/cli/help.ts +2 -2
  5. package/src/input/commands/cloudflare-runtime.ts +20 -5
  6. package/src/input/commands/confirmation.ts +24 -0
  7. package/src/input/commands/discovery-runtime.ts +16 -7
  8. package/src/input/commands/eval.ts +27 -14
  9. package/src/input/commands/experience-runtime.ts +65 -26
  10. package/src/input/commands/health-runtime.ts +1 -1
  11. package/src/input/commands/hooks-runtime.ts +50 -19
  12. package/src/input/commands/incident-runtime.ts +17 -6
  13. package/src/input/commands/integration-runtime.ts +93 -50
  14. package/src/input/commands/knowledge.ts +38 -12
  15. package/src/input/commands/local-auth-runtime.ts +36 -13
  16. package/src/input/commands/local-provider-runtime.ts +22 -11
  17. package/src/input/commands/local-runtime.ts +21 -11
  18. package/src/input/commands/local-setup.ts +35 -16
  19. package/src/input/commands/managed-runtime.ts +51 -20
  20. package/src/input/commands/marketplace-runtime.ts +31 -16
  21. package/src/input/commands/mcp-runtime.ts +65 -34
  22. package/src/input/commands/memory-product-runtime.ts +72 -35
  23. package/src/input/commands/memory.ts +9 -9
  24. package/src/input/commands/notify-runtime.ts +27 -8
  25. package/src/input/commands/operator-runtime.ts +85 -17
  26. package/src/input/commands/planning-runtime.ts +14 -2
  27. package/src/input/commands/platform-access-runtime.ts +88 -45
  28. package/src/input/commands/platform-services-runtime.ts +51 -25
  29. package/src/input/commands/product-runtime.ts +54 -27
  30. package/src/input/commands/profile-sync-runtime.ts +17 -6
  31. package/src/input/commands/recall-bundle.ts +38 -17
  32. package/src/input/commands/recall-query.ts +15 -4
  33. package/src/input/commands/recall-review.ts +9 -3
  34. package/src/input/commands/remote-runtime-setup.ts +45 -18
  35. package/src/input/commands/remote-runtime.ts +25 -9
  36. package/src/input/commands/replay-runtime.ts +9 -2
  37. package/src/input/commands/services-runtime.ts +21 -10
  38. package/src/input/commands/session-content.ts +53 -51
  39. package/src/input/commands/session-workflow.ts +10 -4
  40. package/src/input/commands/session.ts +1 -1
  41. package/src/input/commands/settings-sync-runtime.ts +40 -17
  42. package/src/input/commands/share-runtime.ts +12 -4
  43. package/src/input/commands/shell-core.ts +3 -3
  44. package/src/input/commands/subscription-runtime.ts +35 -20
  45. package/src/input/commands/teleport-runtime.ts +16 -5
  46. package/src/input/commands/work-plan-runtime.ts +23 -12
  47. package/src/input/handler-content-actions.ts +11 -62
  48. package/src/input/handler-interactions.ts +1 -1
  49. package/src/input/handler-onboarding-cloudflare.ts +48 -117
  50. package/src/input/keybindings.ts +1 -1
  51. package/src/input/mcp-workspace.ts +25 -49
  52. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +8 -8
  53. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +1 -6
  54. package/src/input/profile-picker-modal.ts +13 -31
  55. package/src/input/session-picker-modal.ts +4 -30
  56. package/src/input/settings-modal-subscriptions.ts +3 -3
  57. package/src/panels/incident-review-panel.ts +1 -1
  58. package/src/panels/local-auth-panel.ts +4 -4
  59. package/src/panels/provider-account-snapshot.ts +1 -1
  60. package/src/panels/provider-health-domains.ts +2 -2
  61. package/src/panels/settings-sync-panel.ts +2 -2
  62. package/src/panels/subscription-panel.ts +7 -7
  63. package/src/renderer/block-actions.ts +1 -1
  64. package/src/renderer/help-overlay.ts +2 -2
  65. package/src/renderer/mcp-workspace.ts +12 -12
  66. package/src/renderer/profile-picker-modal.ts +3 -11
  67. package/src/renderer/session-picker-modal.ts +2 -10
  68. package/src/version.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  All notable changes to GoodVibes Agent will be recorded here.
4
4
 
5
+ ## 0.1.11 - 2026-05-31
6
+
7
+ - d20a93e Allow explicit recall review without yes
8
+ - 601f41c Require confirmation for eval execution
9
+ - f41befb Block Cloudflare onboarding mutations
10
+ - 79071ec Block implicit block file saves
11
+ - 40aca02 Block inline diff file edits in Agent
12
+ - 854eda8 Block MCP workspace config mutations
13
+ - 665c423 Require confirmation for CLI Agent Knowledge ingest
14
+ - f07255a Require confirmation for recall review mutations
15
+ - 6e78ec3 Require confirmation for Agent Knowledge mutations
16
+ - c1e5ac1 Require confirmation for operator control mutations
17
+ - 5d6fc3c Require confirmation for local state mutations
18
+ - 3afd788 Require confirmation for remaining export paths
19
+ - d36ae96 Require confirmation for portable state bundles
20
+ - 1f0caab Require confirmation for setup and remote exports
21
+ - 2e149c9 Require confirmation for incident and handoff exports
22
+ - b439c55 Require confirmation for settings mutations
23
+ - f7c8fe6 Require confirmation for platform auth mutations
24
+ - 09064e0 Require confirmation for auth and service mutations
25
+ - 55e8b8d Require confirmation for marketplace mutations
26
+ - 88becca Require confirmation for plugin mutations
27
+ - 89c0584 Require confirmation for local provider mutations
28
+ - 7f3af1b Require confirmation for managed hook mutations
29
+ - 323426c Require confirmation for destructive workplan cleanup
30
+ - 191350e Require confirmation for side-effecting slash commands
31
+
5
32
  ## 0.1.10 - 2026-05-31
6
33
 
7
34
  - 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.10",
3
+ "version": "0.1.11",
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', ...rest] = runtime.cli.commandArgs;
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 subcommand = (args[0] ?? 'status').toLowerCase();
28
- const parsed = parseCloudflareArgs(args.slice(1));
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> or --bootstrap-env <env-name>');
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
- async handler(_args, ctx) {
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> — Run a named suite (or 'all')
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> — Run suite and apply CI gate (exits 1 on regression)
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 suiteName = args[0] ?? 'all';
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 suiteName = args[0];
113
- const baselineFile = args[1] ?? '.goodvibes/eval/baseline.json';
114
- const saveFlag = args.includes('--save-baseline');
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 = args[0] ?? 'latest';
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>|compare <baseline>|gate <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> — Run a named suite (or all suites)',
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] Run suite and apply regression gate',
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 sub = (args[0] ?? 'review').toLowerCase();
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 = args[1];
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
- await ctx.executeCommand('remote', ['setup', 'export', pathArg]);
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 sub = (args[0] ?? 'review').toLowerCase();
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 = args[1];
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 sub = (args[0] ?? 'review').toLowerCase();
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 = args[1];
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
- await ctx.executeCommand('remote', ['tunnel', 'export', pathArg]);
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>|inspect <path>]',
120
+ usage: '[export <path> --yes|inspect <path>]',
102
121
  async handler(args, ctx) {
103
- const sub = (args[0] ?? '').toLowerCase();
104
- const pathArg = args[1];
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 ((sub === 'export' || sub === 'inspect') && pathArg) {
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>|inspect <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>|bundle inspect <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 = (args[0] ?? 'review').toLowerCase();
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 = args[1];
248
- const pathArg = args[2];
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>|bundle inspect <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');