@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.
Files changed (69) hide show
  1. package/CHANGELOG.md +32 -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/verification/live-verifier.ts +100 -68
  69. package/src/version.ts +1 -1
@@ -1,5 +1,6 @@
1
1
  import type { KnowledgeService } from '@pellux/goodvibes-sdk/platform/knowledge';
2
2
  import type { CommandContext, SlashCommand } from '../command-registry.ts';
3
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
3
4
 
4
5
  const KNOWLEDGE_REVIEW_ACTIONS = ['accept', 'reject', 'resolve', 'reopen', 'edit', 'forget'] as const;
5
6
 
@@ -158,7 +159,7 @@ export const knowledgeCommand: SlashCommand = {
158
159
  aliases: ['know', 'kb'],
159
160
  description: 'Agent Knowledge/Wiki: isolated Agent-owned sources, graph, review queue, and compact prompt packets.',
160
161
  usage: '<subcommand> [args]',
161
- argsHint: 'status|ask|ingest-url|import-bookmarks|import-urls|list|search|get|queue|review-issue|candidates|reports|schedules|lint|packet|explain|reindex|consolidate',
162
+ argsHint: 'status|ask|ingest-url --yes|import-bookmarks --yes|list|search|get|queue|review-issue --yes',
162
163
  handler: async (args: string[], context: CommandContext): Promise<void> => {
163
164
  const knowledge = requireAgentKnowledgeApi(context);
164
165
  if (!knowledge) {
@@ -169,7 +170,8 @@ export const knowledgeCommand: SlashCommand = {
169
170
  return;
170
171
  }
171
172
  const sub = (args[0] ?? 'status').toLowerCase();
172
- const rest = args.slice(1);
173
+ const confirmation = stripYesFlag(args.slice(1));
174
+ const rest = [...confirmation.rest];
173
175
  const disallowedScopeFlag = findDisallowedKnowledgeScopeFlag(rest);
174
176
  if (disallowedScopeFlag) {
175
177
  printScopeFlagRejection(context, disallowedScopeFlag);
@@ -219,7 +221,11 @@ export const knowledgeCommand: SlashCommand = {
219
221
  case 'ingest-url': {
220
222
  const [url] = positionalArgs(rest, ['--title', '--tags', '--folder']);
221
223
  if (!url) {
222
- context.print('[knowledge] Usage: /knowledge ingest-url <url> [--title <title>] [--tags <a,b>] [--folder <path>]');
224
+ context.print('[knowledge] Usage: /knowledge ingest-url <url> [--title <title>] [--tags <a,b>] [--folder <path>] --yes');
225
+ return;
226
+ }
227
+ if (!confirmation.yes) {
228
+ requireYesFlag(context, `ingest URL into Agent Knowledge ${url}`, '/knowledge ingest-url <url> [--title <title>] [--tags <a,b>] [--folder <path>] --yes');
223
229
  return;
224
230
  }
225
231
  const result = await knowledge.ingest.url({
@@ -240,7 +246,11 @@ export const knowledgeCommand: SlashCommand = {
240
246
  case 'import-bookmarks': {
241
247
  const [path] = positionalArgs(rest);
242
248
  if (!path) {
243
- context.print('[knowledge] Usage: /knowledge import-bookmarks <path>');
249
+ context.print('[knowledge] Usage: /knowledge import-bookmarks <path> --yes');
250
+ return;
251
+ }
252
+ if (!confirmation.yes) {
253
+ requireYesFlag(context, `import bookmark file into Agent Knowledge ${path}`, '/knowledge import-bookmarks <path> --yes');
244
254
  return;
245
255
  }
246
256
  const result = await knowledge.ingest.bookmarksFile({ path, sessionId: context.session.runtime.sessionId });
@@ -254,7 +264,11 @@ export const knowledgeCommand: SlashCommand = {
254
264
  case 'import-urls': {
255
265
  const [path] = positionalArgs(rest);
256
266
  if (!path) {
257
- context.print('[knowledge] Usage: /knowledge import-urls <path>');
267
+ context.print('[knowledge] Usage: /knowledge import-urls <path> --yes');
268
+ return;
269
+ }
270
+ if (!confirmation.yes) {
271
+ requireYesFlag(context, `import URL list into Agent Knowledge ${path}`, '/knowledge import-urls <path> --yes');
258
272
  return;
259
273
  }
260
274
  const result = await knowledge.ingest.urlsFile({ path, sessionId: context.session.runtime.sessionId });
@@ -401,7 +415,11 @@ export const knowledgeCommand: SlashCommand = {
401
415
  const [issueId, actionValue] = positionalArgs(rest, ['--reviewer', '--value']);
402
416
  const action = actionValue?.toLowerCase();
403
417
  if (!issueId || !action || !KNOWLEDGE_REVIEW_ACTIONS.includes(action as KnowledgeReviewAction)) {
404
- context.print('[knowledge] Usage: /knowledge review-issue <issueId> <accept|reject|resolve|reopen|edit|forget> [--reviewer <name>] [--value <json-object>]');
418
+ context.print('[knowledge] Usage: /knowledge review-issue <issueId> <accept|reject|resolve|reopen|edit|forget> [--reviewer <name>] [--value <json-object>] --yes');
419
+ return;
420
+ }
421
+ if (!confirmation.yes) {
422
+ requireYesFlag(context, `review Agent Knowledge issue ${issueId}`, '/knowledge review-issue <issueId> <action> [--reviewer <name>] [--value <json-object>] --yes');
405
423
  return;
406
424
  }
407
425
  const value = readJsonObjectFlag(rest, '--value');
@@ -513,6 +531,10 @@ export const knowledgeCommand: SlashCommand = {
513
531
  }
514
532
 
515
533
  case 'reindex': {
534
+ if (!confirmation.yes) {
535
+ requireYesFlag(context, 'reindex Agent Knowledge', '/knowledge reindex --yes');
536
+ return;
537
+ }
516
538
  const result = await knowledge.status.reindex();
517
539
  context.print([
518
540
  '[knowledge] Reindex complete',
@@ -525,6 +547,10 @@ export const knowledgeCommand: SlashCommand = {
525
547
  }
526
548
 
527
549
  case 'consolidate': {
550
+ if (!confirmation.yes) {
551
+ requireYesFlag(context, 'run Agent Knowledge consolidation', '/knowledge consolidate [light|deep] --yes');
552
+ return;
553
+ }
528
554
  const mode = (positionalArgs(rest)[0] ?? 'light').toLowerCase();
529
555
  const jobId = mode === 'deep' ? 'knowledge-deep-consolidation' : 'knowledge-light-consolidation';
530
556
  const run = await knowledge.jobs.run(jobId, { mode: 'inline' });
@@ -537,22 +563,22 @@ export const knowledgeCommand: SlashCommand = {
537
563
  'Usage: /knowledge <subcommand>',
538
564
  ' status',
539
565
  ' ask <query> [--limit <n>] [--mode <concise|standard|detailed>]',
540
- ' ingest-url <url> [--title <title>] [--tags <a,b>] [--folder <path>]',
541
- ' import-bookmarks <path>',
542
- ' import-urls <path>',
566
+ ' ingest-url <url> [--title <title>] [--tags <a,b>] [--folder <path>] --yes',
567
+ ' import-bookmarks <path> --yes',
568
+ ' import-urls <path> --yes',
543
569
  ' list [--kind <sources|nodes|issues>] [--limit <n>]',
544
570
  ' search <query> [--limit <n>]',
545
571
  ' get <id>',
546
572
  ' queue [limit]',
547
- ' review-issue <issueId> <accept|reject|resolve|reopen|edit|forget> [--reviewer <name>] [--value <json-object>]',
573
+ ' review-issue <issueId> <accept|reject|resolve|reopen|edit|forget> [--reviewer <name>] [--value <json-object>] --yes',
548
574
  ' candidates [limit]',
549
575
  ' reports [limit]',
550
576
  ' schedules',
551
577
  ' lint',
552
578
  ' packet <task...> [--scope <path> ...]',
553
579
  ' explain <task...> [--scope <path> ...]',
554
- ' reindex',
555
- ' consolidate [light|deep]',
580
+ ' reindex --yes',
581
+ ' consolidate [light|deep] --yes',
556
582
  ].join('\n'));
557
583
  }
558
584
  },
@@ -1,13 +1,16 @@
1
1
  import type { CommandContext, CommandRegistry } from '../command-registry.ts';
2
2
  import { openCommandPanel, requireLocalUserAuthManager } from './runtime-services.ts';
3
3
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
4
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
4
5
 
5
6
  function formatRoles(roles: readonly string[]): string {
6
7
  return roles.length > 0 ? roles.join(', ') : '(none)';
7
8
  }
8
9
 
9
10
  export function handleLocalAuthCommand(args: string[], ctx: CommandContext): void {
10
- const sub = (args[0] ?? 'review').toLowerCase();
11
+ const parsed = stripYesFlag(args);
12
+ const commandArgs = [...parsed.rest];
13
+ const sub = (commandArgs[0] ?? 'review').toLowerCase();
11
14
  const auth = requireLocalUserAuthManager(ctx);
12
15
  if (sub === 'panel' || sub === 'open') {
13
16
  openCommandPanel(ctx, 'local-auth');
@@ -15,11 +18,15 @@ export function handleLocalAuthCommand(args: string[], ctx: CommandContext): voi
15
18
  }
16
19
 
17
20
  if (sub === 'add-user') {
18
- const username = args[1];
19
- const password = args[2];
20
- const roles = args[3]?.split(',').map((value) => value.trim()).filter(Boolean) ?? ['admin'];
21
+ const username = commandArgs[1];
22
+ const password = commandArgs[2];
23
+ const roles = commandArgs[3]?.split(',').map((value) => value.trim()).filter(Boolean) ?? ['admin'];
21
24
  if (!username || !password) {
22
- ctx.print('Usage: /auth local add-user <username> <password> [roles]');
25
+ ctx.print('Usage: /auth local add-user <username> <password> [roles] --yes');
26
+ return;
27
+ }
28
+ if (!parsed.yes) {
29
+ requireYesFlag(ctx, `add local auth user ${username}`, '/auth local add-user <username> <password> [roles] --yes');
23
30
  return;
24
31
  }
25
32
  try {
@@ -32,9 +39,13 @@ export function handleLocalAuthCommand(args: string[], ctx: CommandContext): voi
32
39
  }
33
40
 
34
41
  if (sub === 'delete-user') {
35
- const username = args[1];
42
+ const username = commandArgs[1];
36
43
  if (!username) {
37
- ctx.print('Usage: /auth local delete-user <username>');
44
+ ctx.print('Usage: /auth local delete-user <username> --yes');
45
+ return;
46
+ }
47
+ if (!parsed.yes) {
48
+ requireYesFlag(ctx, `delete local auth user ${username}`, '/auth local delete-user <username> --yes');
38
49
  return;
39
50
  }
40
51
  try {
@@ -47,10 +58,14 @@ export function handleLocalAuthCommand(args: string[], ctx: CommandContext): voi
47
58
  }
48
59
 
49
60
  if (sub === 'rotate-password') {
50
- const username = args[1];
51
- const password = args[2];
61
+ const username = commandArgs[1];
62
+ const password = commandArgs[2];
52
63
  if (!username || !password) {
53
- ctx.print('Usage: /auth local rotate-password <username> <password>');
64
+ ctx.print('Usage: /auth local rotate-password <username> <password> --yes');
65
+ return;
66
+ }
67
+ if (!parsed.yes) {
68
+ requireYesFlag(ctx, `rotate password for local auth user ${username}`, '/auth local rotate-password <username> <password> --yes');
54
69
  return;
55
70
  }
56
71
  try {
@@ -63,9 +78,13 @@ export function handleLocalAuthCommand(args: string[], ctx: CommandContext): voi
63
78
  }
64
79
 
65
80
  if (sub === 'revoke-session') {
66
- const token = args[1];
81
+ const token = commandArgs[1];
67
82
  if (!token) {
68
- ctx.print('Usage: /auth local revoke-session <token-or-fingerprint>');
83
+ ctx.print('Usage: /auth local revoke-session <token-or-fingerprint> --yes');
84
+ return;
85
+ }
86
+ if (!parsed.yes) {
87
+ requireYesFlag(ctx, 'revoke local auth session', '/auth local revoke-session <token-or-fingerprint> --yes');
69
88
  return;
70
89
  }
71
90
  ctx.print(auth.revokeSession(token) ? `Revoked session ${token.slice(0, 12)}…` : `Unknown session token or fingerprint: ${token}`);
@@ -73,6 +92,10 @@ export function handleLocalAuthCommand(args: string[], ctx: CommandContext): voi
73
92
  }
74
93
 
75
94
  if (sub === 'clear-bootstrap-file') {
95
+ if (!parsed.yes) {
96
+ requireYesFlag(ctx, 'clear the local auth bootstrap credential file', '/auth local clear-bootstrap-file --yes');
97
+ return;
98
+ }
76
99
  ctx.print(auth.clearBootstrapCredentialFile()
77
100
  ? 'Removed bootstrap credential file.'
78
101
  : 'No bootstrap credential file was present.');
@@ -97,7 +120,7 @@ export function registerLocalAuthRuntimeCommands(registry: CommandRegistry): voi
97
120
  name: 'local-auth',
98
121
  aliases: ['auth-local'],
99
122
  description: 'Inspect and manage local daemon/listener auth users, sessions, and bootstrap credentials',
100
- usage: '[review|panel|add-user <username> <password> [roles]|delete-user <username>|rotate-password <username> <password>|revoke-session <token-or-fingerprint>|clear-bootstrap-file]',
123
+ usage: '[review|panel|add-user <username> <password> [roles] --yes|delete-user <username> --yes|rotate-password <username> <password> --yes|revoke-session <token-or-fingerprint> --yes|clear-bootstrap-file --yes]',
101
124
  handler(args, ctx) {
102
125
  handleLocalAuthCommand(args, ctx);
103
126
  },
@@ -7,6 +7,7 @@ import type { CustomProviderConfig } from '@pellux/goodvibes-sdk/platform/provid
7
7
  import { requireProviderApi, requireShellPaths } from './runtime-services.ts';
8
8
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
9
9
  import { GOODVIBES_AGENT_SURFACE_ROOT } from '../../config/surface.ts';
10
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
10
11
 
11
12
  function isValidProviderName(name: string): boolean {
12
13
  return /^[a-zA-Z0-9_-]+$/.test(name);
@@ -17,17 +18,23 @@ export function registerLocalProviderRuntimeCommands(registry: CommandRegistry):
17
18
  name: 'provider',
18
19
  aliases: ['p'],
19
20
  description: 'Switch provider or manage custom providers (add/remove)',
20
- usage: '[add <name> <baseURL> [apiKey] | remove <name> | <provider-name>]',
21
- argsHint: '[add|remove|name]',
21
+ usage: '[add <name> <baseURL> [apiKey] --yes | remove <name> --yes | <provider-name>]',
22
+ argsHint: '[name|add --yes|remove --yes]',
22
23
  async handler(args, ctx) {
24
+ const parsed = stripYesFlag(args);
25
+ const commandArgs = [...parsed.rest];
23
26
  const shellPaths = requireShellPaths(ctx);
24
- if (args[0] === 'add') {
25
- const addArgs = args.slice(1);
27
+ if (commandArgs[0] === 'add') {
28
+ const addArgs = commandArgs.slice(1);
26
29
  if (addArgs.length < 2) {
27
- ctx.print('Usage: /provider add <name> <baseURL> [apiKey]\nExample: /provider add my-server http://192.168.0.85:8001/v1');
30
+ ctx.print('Usage: /provider add <name> <baseURL> [apiKey] --yes\nExample: /provider add my-server http://192.168.0.85:8001/v1 --yes');
28
31
  return;
29
32
  }
30
33
  const [name, baseURL, apiKey] = addArgs;
34
+ if (!parsed.yes) {
35
+ requireYesFlag(ctx, `add custom provider ${name}`, '/provider add <name> <baseURL> [apiKey] --yes');
36
+ return;
37
+ }
31
38
  if (!isValidProviderName(name)) {
32
39
  ctx.print('Error: Provider name must contain only letters, numbers, hyphens, and underscores.');
33
40
  return;
@@ -42,7 +49,7 @@ export function registerLocalProviderRuntimeCommands(registry: CommandRegistry):
42
49
  const providersDir = shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'providers');
43
50
  const providerFile = join(providersDir, `${name}.json`);
44
51
  if (existsSync(providerFile)) {
45
- ctx.print(`Error: Provider '${name}' already exists at ${providerFile}\nRemove it first with: /provider remove ${name}`);
52
+ ctx.print(`Error: Provider '${name}' already exists at ${providerFile}\nRemove it first with: /provider remove ${name} --yes`);
46
53
  return;
47
54
  }
48
55
 
@@ -111,10 +118,14 @@ export function registerLocalProviderRuntimeCommands(registry: CommandRegistry):
111
118
  return;
112
119
  }
113
120
 
114
- if (args[0] === 'remove' || args[0] === 'rm') {
115
- const name = args[1];
121
+ if (commandArgs[0] === 'remove' || commandArgs[0] === 'rm') {
122
+ const name = commandArgs[1];
116
123
  if (!name) {
117
- ctx.print('Usage: /provider remove <name>');
124
+ ctx.print('Usage: /provider remove <name> --yes');
125
+ return;
126
+ }
127
+ if (!parsed.yes) {
128
+ requireYesFlag(ctx, `remove custom provider ${name}`, '/provider remove <name> --yes');
118
129
  return;
119
130
  }
120
131
  if (!isValidProviderName(name)) {
@@ -135,7 +146,7 @@ export function registerLocalProviderRuntimeCommands(registry: CommandRegistry):
135
146
  return;
136
147
  }
137
148
 
138
- if (args.length === 0) {
149
+ if (commandArgs.length === 0) {
139
150
  if (ctx.openProviderPicker) {
140
151
  ctx.openProviderPicker();
141
152
  return;
@@ -145,7 +156,7 @@ export function registerLocalProviderRuntimeCommands(registry: CommandRegistry):
145
156
  return;
146
157
  }
147
158
 
148
- const providerName = args[0];
159
+ const providerName = commandArgs[0];
149
160
  const providerApi = requireProviderApi(ctx);
150
161
  const selectable = await providerApi.listModels({
151
162
  providerId: providerName,
@@ -8,6 +8,7 @@ import { resolveAndValidatePath } from '@pellux/goodvibes-sdk/platform/utils';
8
8
  import { BUILTIN_SECRET_PROVIDER_SOURCES, describeSecretRef, isSecretRefInput, resolveSecretRef } from '@pellux/goodvibes-sdk/platform/config';
9
9
  import { openCommandPanel, requireBookmarkManager, requireProviderApi, requireSecretsManager } from './runtime-services.ts';
10
10
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
11
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
11
12
 
12
13
  function isGoodVibesSecretRefInput(value: string): boolean {
13
14
  const normalized = value.trim();
@@ -143,16 +144,17 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
143
144
  registry.register({
144
145
  name: 'secrets',
145
146
  description: 'Manage hierarchy-aware secrets, external secret refs, and secure/plaintext storage policy controls',
146
- usage: 'set <KEY> <value> [--user|--project] [--secure|--plaintext] | link <KEY> <secret-ref> [--user|--project] [--secure|--plaintext] | get <KEY> | test <secret-ref> | providers | list | delete <KEY> [--user|--project] [--secure|--plaintext]',
147
+ usage: 'set <KEY> <value> [--user|--project] [--secure|--plaintext] --yes | link <KEY> <secret-ref> [--user|--project] [--secure|--plaintext] --yes | get <KEY> | test <secret-ref> | providers | list | delete <KEY> [--user|--project] [--secure|--plaintext] --yes',
147
148
  argsHint: '<set|link|get|test|providers|list|delete> [KEY]',
148
149
  async handler(args, ctx) {
149
150
  const mgr = requireSecretsManager(ctx);
150
- const [sub, ...rest] = args;
151
+ const parsed = stripYesFlag(args);
152
+ const [sub, ...rest] = parsed.rest;
151
153
  if (!sub || sub === 'list') {
152
154
  const records = await mgr.listDetailed();
153
155
  const storedRecords = records.filter((record) => record.source !== 'env');
154
156
  ctx.print(storedRecords.length === 0
155
- ? '[secrets] No secrets stored. Use: /secrets set <KEY> <value>'
157
+ ? '[secrets] No secrets stored. Use: /secrets set <KEY> <value> --yes'
156
158
  : [
157
159
  '[secrets] Stored keys:',
158
160
  ...storedRecords.map((record) => ` ${record.key} (${record.source}${record.refSource ? `, ref:${record.refSource}` : ''}${record.overriddenByEnv ? ', env override' : ''})`),
@@ -165,11 +167,11 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
165
167
  ...BUILTIN_SECRET_PROVIDER_SOURCES.map((source) => ` ${source}`),
166
168
  '',
167
169
  'Examples:',
168
- ' /secrets link OPENAI_API_KEY goodvibes://secrets/env/OPENAI_API_KEY',
169
- ' /secrets link SLACK_BOT_TOKEN goodvibes://secrets/bitwarden?item=GoodVibes%20Slack&field=password&sessionEnv=BW_SESSION',
170
- ' /secrets link SLACK_BOT_TOKEN goodvibes://secrets/vaultwarden?item=GoodVibes%20Slack&field=password&server=https%3A%2F%2Fvault.example.test',
171
- ' /secrets link STRIPE_TOKEN goodvibes://secrets/bws/00000000-0000-0000-0000-000000000000?field=value&accessTokenEnv=BWS_ACCESS_TOKEN',
172
- ' /secrets link OPENAI_API_KEY goodvibes://secrets/1password?vault=Private&item=GoodVibes%20OpenAI&field=API%20Key',
170
+ ' /secrets link OPENAI_API_KEY goodvibes://secrets/env/OPENAI_API_KEY --yes',
171
+ ' /secrets link SLACK_BOT_TOKEN goodvibes://secrets/bitwarden?item=GoodVibes%20Slack&field=password&sessionEnv=BW_SESSION --yes',
172
+ ' /secrets link SLACK_BOT_TOKEN goodvibes://secrets/vaultwarden?item=GoodVibes%20Slack&field=password&server=https%3A%2F%2Fvault.example.test --yes',
173
+ ' /secrets link STRIPE_TOKEN goodvibes://secrets/bws/00000000-0000-0000-0000-000000000000?field=value&accessTokenEnv=BWS_ACCESS_TOKEN --yes',
174
+ ' /secrets link OPENAI_API_KEY goodvibes://secrets/1password?vault=Private&item=GoodVibes%20OpenAI&field=API%20Key --yes',
173
175
  ].join('\n'));
174
176
  return;
175
177
  }
@@ -196,10 +198,14 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
196
198
  const valueParts = rest.filter((value) => !value.startsWith('--'));
197
199
  const [key, ...rawValueParts] = valueParts;
198
200
  if (!key || valueParts.length === 0) {
199
- ctx.print(`[secrets] Usage: /secrets ${sub} <KEY> <${sub === 'link' ? 'secret-ref' : 'value'}> [--user|--project] [--secure|--plaintext]`);
201
+ ctx.print(`[secrets] Usage: /secrets ${sub} <KEY> <${sub === 'link' ? 'secret-ref' : 'value'}> [--user|--project] [--secure|--plaintext] --yes`);
200
202
  return;
201
203
  }
202
204
  const value = rawValueParts.join(' ');
205
+ if (!parsed.yes) {
206
+ requireYesFlag(ctx, `${sub === 'link' ? 'link secret reference for' : 'store secret value for'} ${key}`, `/secrets ${sub} <KEY> <${sub === 'link' ? 'secret-ref' : 'value'}> [--user|--project] [--secure|--plaintext] --yes`);
207
+ return;
208
+ }
203
209
  if (sub === 'link' && !isGoodVibesSecretRefInput(value)) {
204
210
  ctx.print('[secrets] Invalid secret reference. Use /secrets providers for examples.');
205
211
  return;
@@ -230,7 +236,11 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
230
236
  const flags = new Set(rest.filter((value) => value.startsWith('--')));
231
237
  const [key] = rest.filter((value) => !value.startsWith('--'));
232
238
  if (!key) {
233
- ctx.print('[secrets] Usage: /secrets delete <KEY> [--user|--project] [--secure|--plaintext]');
239
+ ctx.print('[secrets] Usage: /secrets delete <KEY> [--user|--project] [--secure|--plaintext] --yes');
240
+ return;
241
+ }
242
+ if (!parsed.yes) {
243
+ requireYesFlag(ctx, `delete secret ${key}`, '/secrets delete <KEY> [--user|--project] [--secure|--plaintext] --yes');
234
244
  return;
235
245
  }
236
246
  await mgr.delete(key, {
@@ -240,7 +250,7 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
240
250
  ctx.print(`[secrets] Deleted: ${key}`);
241
251
  return;
242
252
  }
243
- ctx.print('[secrets] Usage: /secrets set <KEY> <value> [--user|--project] [--secure|--plaintext] | link <KEY> <secret-ref> [--user|--project] [--secure|--plaintext] | get <KEY> | test <secret-ref> | providers | list | delete <KEY> [--user|--project] [--secure|--plaintext]');
253
+ ctx.print('[secrets] Usage: /secrets set <KEY> <value> [--user|--project] [--secure|--plaintext] --yes | link <KEY> <secret-ref> [--user|--project] [--secure|--plaintext] --yes | get <KEY> | test <secret-ref> | providers | list | delete <KEY> [--user|--project] [--secure|--plaintext] --yes');
244
254
  },
245
255
  });
246
256
 
@@ -16,6 +16,7 @@ import { buildSetupReviewSnapshot, exportSetupSupportBundle } from './local-setu
16
16
  import { openOnboardingWizard, requirePanelManager, requireShellPaths } from './runtime-services.ts';
17
17
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
18
18
  import { GOODVIBES_AGENT_SURFACE_ROOT } from '../../config/surface.ts';
19
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
19
20
 
20
21
  type SetupSnapshot = Awaited<ReturnType<typeof buildSetupReviewSnapshot>>;
21
22
 
@@ -24,9 +25,11 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
24
25
  name: 'setup',
25
26
  aliases: ['startup'],
26
27
  description: 'Launch the onboarding wizard and review Agent startup readiness',
27
- usage: '[review|doctor|services|hooks|remote|sandbox|onboarding|support-bundle <dir>|export <path>|transfer <export|inspect|import> <path>|link <surface> [target]|open-link <uri>]',
28
+ usage: '[review|doctor|services|hooks|remote|sandbox|onboarding|support-bundle <dir> --yes|export <path> --yes|transfer <export|inspect|import> <path> [--yes]|link <surface> [target]|open-link <uri>]',
28
29
  async handler(args, ctx) {
29
- const sub = args[0] ?? 'review';
30
+ const parsed = stripYesFlag(args);
31
+ const commandArgs = [...parsed.rest];
32
+ const sub = commandArgs[0] ?? 'review';
30
33
  let shellPaths: ReturnType<typeof requireShellPaths> | null = null;
31
34
  let snapshotPromise: Promise<SetupSnapshot> | null = null;
32
35
  const getShellPaths = () => (shellPaths ??= requireShellPaths(ctx));
@@ -134,12 +137,16 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
134
137
  }
135
138
 
136
139
  if (sub === 'support-bundle') {
137
- const snapshot = await getSnapshot();
138
- const dirArg = args[1];
140
+ const dirArg = commandArgs[1];
139
141
  if (!dirArg) {
140
- ctx.print('Usage: /setup support-bundle <dir>');
142
+ ctx.print('Usage: /setup support-bundle <dir> --yes');
143
+ return;
144
+ }
145
+ if (!parsed.yes) {
146
+ requireYesFlag(ctx, `export setup support bundle to ${dirArg}`, '/setup support-bundle <dir> --yes');
141
147
  return;
142
148
  }
149
+ const snapshot = await getSnapshot();
143
150
  const targetDir = exportSetupSupportBundle(dirArg, snapshot, ctx);
144
151
  writeFileSync(join(targetDir, 'remote-summary.json'), JSON.stringify({
145
152
  runners: ctx.ops.remoteRuntime?.listContracts() ?? [],
@@ -155,12 +162,16 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
155
162
  }
156
163
 
157
164
  if (sub === 'export') {
158
- const snapshot = await getSnapshot();
159
- const pathArg = args[1];
165
+ const pathArg = commandArgs[1];
160
166
  if (!pathArg) {
161
- ctx.print('Usage: /setup export <path>');
167
+ ctx.print('Usage: /setup export <path> --yes');
162
168
  return;
163
169
  }
170
+ if (!parsed.yes) {
171
+ requireYesFlag(ctx, `export startup review to ${pathArg}`, '/setup export <path> --yes');
172
+ return;
173
+ }
174
+ const snapshot = await getSnapshot();
164
175
  const targetPath = getShellPaths().resolveWorkspacePath(pathArg);
165
176
  mkdirSync(dirname(targetPath), { recursive: true });
166
177
  writeFileSync(targetPath, JSON.stringify(snapshot, null, 2) + '\n', 'utf-8');
@@ -169,14 +180,18 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
169
180
  }
170
181
 
171
182
  if (sub === 'transfer') {
172
- const mode = args[1]?.toLowerCase();
173
- const pathArg = args[2];
183
+ const mode = commandArgs[1]?.toLowerCase();
184
+ const pathArg = commandArgs[2];
174
185
  if (!mode || !pathArg) {
175
- ctx.print('Usage: /setup transfer <export|inspect|import> <path>');
186
+ ctx.print('Usage: /setup transfer <export|inspect|import> <path> [--yes]');
176
187
  return;
177
188
  }
178
189
  const targetPath = getShellPaths().resolveWorkspacePath(pathArg);
179
190
  if (mode === 'export') {
191
+ if (!parsed.yes) {
192
+ requireYesFlag(ctx, `export setup transfer bundle to ${pathArg}`, '/setup transfer export <path> --yes');
193
+ return;
194
+ }
180
195
  const snapshot = await getSnapshot();
181
196
  const bundle = buildSetupTransferBundle(ctx, snapshot);
182
197
  ctx.print(`Exported setup transfer bundle to ${exportSetupTransferBundle(ctx, pathArg, bundle)}`);
@@ -192,6 +207,10 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
192
207
  return;
193
208
  }
194
209
  if (mode === 'import') {
210
+ if (!parsed.yes) {
211
+ requireYesFlag(ctx, `import setup transfer bundle from ${pathArg}`, '/setup transfer import <path> --yes');
212
+ return;
213
+ }
195
214
  try {
196
215
  const bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as SetupTransferBundle;
197
216
  for (const entry of CONFIG_SCHEMA) {
@@ -220,13 +239,13 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
220
239
  }
221
240
  return;
222
241
  }
223
- ctx.print('Usage: /setup transfer <export|inspect|import> <path>');
242
+ ctx.print('Usage: /setup transfer <export|inspect|import> <path> [--yes]');
224
243
  return;
225
244
  }
226
245
 
227
246
  if (sub === 'link') {
228
- const surface = args[1];
229
- const target = args[2];
247
+ const surface = commandArgs[1];
248
+ const target = commandArgs[2];
230
249
  if (!surface) {
231
250
  ctx.print('Usage: /setup link <cockpit|security|remote|knowledge|incident|hooks|orchestration|tasks> [target]');
232
251
  return;
@@ -236,7 +255,7 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
236
255
  }
237
256
 
238
257
  if (sub === 'open-link') {
239
- const link = args[1];
258
+ const link = commandArgs[1];
240
259
  if (!link) {
241
260
  ctx.print('Usage: /setup open-link <goodvibes://...>');
242
261
  return;
@@ -276,7 +295,7 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
276
295
  return;
277
296
  }
278
297
 
279
- ctx.print('Usage: /setup [review|doctor|services|hooks|remote|sandbox|onboarding|support-bundle <dir>|export <path>|transfer <export|inspect|import> <path>|link <surface> [target]|open-link <uri>]');
298
+ ctx.print('Usage: /setup [review|doctor|services|hooks|remote|sandbox|onboarding|support-bundle <dir> --yes|export <path> --yes|transfer <export|inspect|import> <path> [--yes]|link <surface> [target]|open-link <uri>]');
280
299
  },
281
300
  });
282
301
  }