@pellux/goodvibes-agent 0.1.9 → 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 (97) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +1 -1
  3. package/docs/getting-started.md +1 -1
  4. package/docs/release-and-publishing.md +2 -2
  5. package/package.json +4 -1
  6. package/src/cli/agent-knowledge-command.ts +46 -20
  7. package/src/cli/help.ts +15 -2
  8. package/src/cli/management-commands.ts +3 -3
  9. package/src/cli/management.ts +7 -1
  10. package/src/cli/parser.ts +3 -0
  11. package/src/cli/service-posture.ts +6 -6
  12. package/src/cli/status.ts +9 -9
  13. package/src/cli/surface-command.ts +3 -3
  14. package/src/cli/types.ts +2 -0
  15. package/src/input/commands/cloudflare-runtime.ts +20 -5
  16. package/src/input/commands/confirmation.ts +24 -0
  17. package/src/input/commands/discovery-runtime.ts +16 -7
  18. package/src/input/commands/eval.ts +27 -14
  19. package/src/input/commands/experience-runtime.ts +66 -27
  20. package/src/input/commands/health-runtime.ts +1 -1
  21. package/src/input/commands/hooks-runtime.ts +79 -20
  22. package/src/input/commands/incident-runtime.ts +17 -6
  23. package/src/input/commands/integration-runtime.ts +93 -50
  24. package/src/input/commands/knowledge.ts +38 -12
  25. package/src/input/commands/local-auth-runtime.ts +36 -13
  26. package/src/input/commands/local-provider-runtime.ts +22 -11
  27. package/src/input/commands/local-runtime.ts +21 -11
  28. package/src/input/commands/local-setup.ts +35 -16
  29. package/src/input/commands/managed-runtime.ts +51 -20
  30. package/src/input/commands/marketplace-runtime.ts +31 -16
  31. package/src/input/commands/mcp-runtime.ts +65 -34
  32. package/src/input/commands/memory-product-runtime.ts +72 -35
  33. package/src/input/commands/memory.ts +9 -9
  34. package/src/input/commands/notify-runtime.ts +27 -8
  35. package/src/input/commands/operator-runtime.ts +85 -17
  36. package/src/input/commands/planning-runtime.ts +14 -2
  37. package/src/input/commands/platform-access-runtime.ts +88 -45
  38. package/src/input/commands/platform-services-runtime.ts +51 -25
  39. package/src/input/commands/product-runtime.ts +54 -27
  40. package/src/input/commands/profile-sync-runtime.ts +17 -6
  41. package/src/input/commands/recall-bundle.ts +38 -17
  42. package/src/input/commands/recall-query.ts +15 -4
  43. package/src/input/commands/recall-review.ts +9 -3
  44. package/src/input/commands/remote-runtime-setup.ts +45 -18
  45. package/src/input/commands/remote-runtime.ts +25 -9
  46. package/src/input/commands/replay-runtime.ts +9 -2
  47. package/src/input/commands/services-runtime.ts +21 -10
  48. package/src/input/commands/session-content.ts +53 -51
  49. package/src/input/commands/session-workflow.ts +10 -4
  50. package/src/input/commands/session.ts +1 -1
  51. package/src/input/commands/settings-sync-runtime.ts +40 -17
  52. package/src/input/commands/share-runtime.ts +12 -4
  53. package/src/input/commands/shell-core.ts +3 -3
  54. package/src/input/commands/subscription-runtime.ts +35 -20
  55. package/src/input/commands/teleport-runtime.ts +16 -5
  56. package/src/input/commands/work-plan-runtime.ts +23 -12
  57. package/src/input/handler-content-actions.ts +11 -62
  58. package/src/input/handler-interactions.ts +1 -1
  59. package/src/input/handler-onboarding-cloudflare.ts +48 -117
  60. package/src/input/handler.ts +1 -0
  61. package/src/input/keybindings.ts +1 -1
  62. package/src/input/mcp-workspace.ts +25 -49
  63. package/src/input/onboarding/onboarding-runtime-status.ts +8 -8
  64. package/src/input/onboarding/onboarding-wizard-apply.ts +13 -53
  65. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +12 -12
  66. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +2 -7
  67. package/src/input/onboarding/onboarding-wizard-constants.ts +7 -7
  68. package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +4 -4
  69. package/src/input/onboarding/onboarding-wizard-steps.ts +13 -13
  70. package/src/input/profile-picker-modal.ts +13 -31
  71. package/src/input/session-picker-modal.ts +4 -30
  72. package/src/input/settings-modal-agent-policy.ts +18 -0
  73. package/src/input/settings-modal-subscriptions.ts +3 -3
  74. package/src/input/settings-modal-types.ts +17 -0
  75. package/src/input/settings-modal.ts +30 -29
  76. package/src/main.ts +3 -26
  77. package/src/panels/incident-review-panel.ts +1 -1
  78. package/src/panels/local-auth-panel.ts +4 -4
  79. package/src/panels/provider-account-snapshot.ts +1 -1
  80. package/src/panels/provider-health-domains.ts +2 -2
  81. package/src/panels/settings-sync-panel.ts +2 -2
  82. package/src/panels/subscription-panel.ts +7 -7
  83. package/src/renderer/block-actions.ts +1 -1
  84. package/src/renderer/help-overlay.ts +2 -2
  85. package/src/renderer/mcp-workspace.ts +12 -12
  86. package/src/renderer/process-modal.ts +17 -8
  87. package/src/renderer/profile-picker-modal.ts +3 -11
  88. package/src/renderer/session-picker-modal.ts +2 -10
  89. package/src/renderer/settings-modal.ts +12 -8
  90. package/src/renderer/ui-factory.ts +4 -32
  91. package/src/runtime/bootstrap-shell.ts +0 -13
  92. package/src/runtime/bootstrap.ts +0 -10
  93. package/src/runtime/onboarding/derivation.ts +6 -6
  94. package/src/verification/live-verifier.ts +148 -13
  95. package/src/version.ts +10 -3
  96. package/src/input/commands/quit-shared.ts +0 -162
  97. package/src/renderer/git-status.ts +0 -89
@@ -14,15 +14,18 @@ import {
14
14
  uninstallEcosystemCatalogEntry,
15
15
  } from '@/runtime/index.ts';
16
16
  import { requireEcosystemCatalogPaths, requirePluginPathOptions } from './runtime-services.ts';
17
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
17
18
 
18
19
  export function registerIntegrationRuntimeCommands(registry: CommandRegistry): void {
19
20
  registry.register({
20
21
  name: 'plugin',
21
22
  aliases: [],
22
23
  description: 'Manage plugins, trust, review, and ecosystem paths',
23
- usage: 'list | dirs | inspect <name> | review | installed | catalog-review <id> | publish-local <id> <path> <summary...> | unpublish <id> | install <id> [project|user] | update <id> [project|user] | uninstall <id> [project|user] | enable <name> | disable <name> | reload',
24
- argsHint: 'list | dirs | inspect | review | installed | catalog-review | publish-local | unpublish | install | update | uninstall | enable | disable | reload',
24
+ usage: 'list | dirs | inspect <name> | review | installed | catalog-review <id> | publish-local <id> <path> <summary...> --yes | unpublish <id> --yes | install <id> [project|user] --yes | update <id> [project|user] --yes | uninstall <id> [project|user] --yes | enable <name> --yes | disable <name> --yes | reload --yes',
25
+ argsHint: 'list | dirs | inspect | review | installed | catalog-review | publish-local --yes | unpublish --yes | install --yes | update --yes | uninstall --yes | enable --yes | disable --yes | reload --yes',
25
26
  async handler(args, ctx) {
27
+ const parsed = stripYesFlag(args);
28
+ const commandArgs = [...parsed.rest];
26
29
  const pluginManager = ctx.extensions.pluginManager;
27
30
  const ecosystemPaths = requireEcosystemCatalogPaths(ctx);
28
31
  const pluginPaths = requirePluginPathOptions(ctx);
@@ -30,7 +33,7 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
30
33
  ctx.print('Plugin manager is not available in this runtime.');
31
34
  return;
32
35
  }
33
- const sub = args[0];
36
+ const sub = commandArgs[0];
34
37
 
35
38
  if (!sub || sub === 'open' || sub === 'panel') {
36
39
  if (ctx.showPanel) ctx.showPanel('plugins');
@@ -55,7 +58,7 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
55
58
  if (p.author) lines.push(` by ${p.author}`);
56
59
  }
57
60
  lines.push('');
58
- lines.push('Use /plugin enable <name> or /plugin disable <name> to toggle plugins.');
61
+ lines.push('Use /plugin enable <name> --yes or /plugin disable <name> --yes to toggle plugins.');
59
62
  ctx.print(lines.join('\n'));
60
63
  return;
61
64
  }
@@ -70,7 +73,7 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
70
73
  return;
71
74
  }
72
75
  if (sub === 'inspect') {
73
- const name = args[1];
76
+ const name = commandArgs[1];
74
77
  if (!name) {
75
78
  ctx.print('Usage: /plugin inspect <name>');
76
79
  return;
@@ -111,7 +114,7 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
111
114
  return;
112
115
  }
113
116
  if (sub === 'browse' || sub === 'catalog') {
114
- const query = args.slice(1).join(' ');
117
+ const query = commandArgs.slice(1).join(' ');
115
118
  const entries = query
116
119
  ? searchEcosystemCatalog('plugin', query, ecosystemPaths)
117
120
  : loadEcosystemCatalog('plugin', ecosystemPaths);
@@ -140,7 +143,7 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
140
143
  return;
141
144
  }
142
145
  if (sub === 'catalog-review') {
143
- const entryId = args[1];
146
+ const entryId = commandArgs[1];
144
147
  if (!entryId) {
145
148
  ctx.print('Usage: /plugin catalog-review <catalog-id>');
146
149
  return;
@@ -166,7 +169,7 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
166
169
  return;
167
170
  }
168
171
  if (sub === 'install-hint') {
169
- const entryId = args[1];
172
+ const entryId = commandArgs[1];
170
173
  if (!entryId) {
171
174
  ctx.print('Usage: /plugin install-hint <catalog-id>');
172
175
  return;
@@ -182,16 +185,20 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
182
185
  ` source: ${entry.source}`,
183
186
  ` tags: ${entry.tags.join(', ') || '(none)'}`,
184
187
  ` trust notes: ${entry.trustNotes ?? '(none)'}`,
185
- ` install hint: ${entry.installHint ?? 'Place the plugin under a configured plugin search directory and use /plugin reload.'}`,
188
+ ` install hint: ${entry.installHint ?? 'Place the plugin under a configured plugin search directory and use /plugin reload --yes.'}`,
186
189
  ].join('\n'));
187
190
  return;
188
191
  }
189
192
  if (sub === 'publish-local') {
190
- const entryId = args[1];
191
- const sourcePath = args[2];
192
- const summary = args.slice(3).join(' ').trim();
193
+ const entryId = commandArgs[1];
194
+ const sourcePath = commandArgs[2];
195
+ const summary = commandArgs.slice(3).join(' ').trim();
193
196
  if (!entryId || !sourcePath || !summary) {
194
- ctx.print('Usage: /plugin publish-local <catalog-id> <path> <summary...>');
197
+ ctx.print('Usage: /plugin publish-local <catalog-id> <path> <summary...> --yes');
198
+ return;
199
+ }
200
+ if (!parsed.yes) {
201
+ requireYesFlag(ctx, `publish curated plugin ${entryId}`, '/plugin publish-local <catalog-id> <path> <summary...> --yes');
195
202
  return;
196
203
  }
197
204
  const result = upsertEcosystemCatalogEntry({
@@ -210,9 +217,13 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
210
217
  return;
211
218
  }
212
219
  if (sub === 'unpublish') {
213
- const entryId = args[1];
220
+ const entryId = commandArgs[1];
214
221
  if (!entryId) {
215
- ctx.print('Usage: /plugin unpublish <catalog-id>');
222
+ ctx.print('Usage: /plugin unpublish <catalog-id> --yes');
223
+ return;
224
+ }
225
+ if (!parsed.yes) {
226
+ requireYesFlag(ctx, `unpublish curated plugin ${entryId}`, '/plugin unpublish <catalog-id> --yes');
216
227
  return;
217
228
  }
218
229
  const result = removeEcosystemCatalogEntry('plugin', entryId, ecosystemPaths);
@@ -222,10 +233,14 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
222
233
  return;
223
234
  }
224
235
  if (sub === 'install') {
225
- const entryId = args[1];
226
- const scopeArg = args[2];
236
+ const entryId = commandArgs[1];
237
+ const scopeArg = commandArgs[2];
227
238
  if (!entryId) {
228
- ctx.print('Usage: /plugin install <catalog-id> [project|user]');
239
+ ctx.print('Usage: /plugin install <catalog-id> [project|user] --yes');
240
+ return;
241
+ }
242
+ if (!parsed.yes) {
243
+ requireYesFlag(ctx, `install curated plugin ${entryId}`, '/plugin install <catalog-id> [project|user] --yes');
229
244
  return;
230
245
  }
231
246
  const scope = scopeArg === 'user' ? 'user' : 'project';
@@ -236,10 +251,14 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
236
251
  return;
237
252
  }
238
253
  if (sub === 'update') {
239
- const entryId = args[1];
240
- const scopeArg = args[2];
254
+ const entryId = commandArgs[1];
255
+ const scopeArg = commandArgs[2];
241
256
  if (!entryId) {
242
- ctx.print('Usage: /plugin update <catalog-id> [project|user]');
257
+ ctx.print('Usage: /plugin update <catalog-id> [project|user] --yes');
258
+ return;
259
+ }
260
+ if (!parsed.yes) {
261
+ requireYesFlag(ctx, `update curated plugin ${entryId}`, '/plugin update <catalog-id> [project|user] --yes');
243
262
  return;
244
263
  }
245
264
  const scope = scopeArg === 'user' ? 'user' : 'project';
@@ -250,10 +269,14 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
250
269
  return;
251
270
  }
252
271
  if (sub === 'uninstall') {
253
- const entryId = args[1];
254
- const scopeArg = args[2];
272
+ const entryId = commandArgs[1];
273
+ const scopeArg = commandArgs[2];
255
274
  if (!entryId) {
256
- ctx.print('Usage: /plugin uninstall <catalog-id> [project|user]');
275
+ ctx.print('Usage: /plugin uninstall <catalog-id> [project|user] --yes');
276
+ return;
277
+ }
278
+ if (!parsed.yes) {
279
+ requireYesFlag(ctx, `uninstall curated plugin ${entryId}`, '/plugin uninstall <catalog-id> [project|user] --yes');
257
280
  return;
258
281
  }
259
282
  const scope = scopeArg === 'user' ? 'user' : 'project';
@@ -264,30 +287,46 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
264
287
  return;
265
288
  }
266
289
  if (sub === 'enable') {
267
- const name = args[1];
268
- if (!name) { ctx.print('Usage: /plugin enable <name>'); return; }
290
+ const name = commandArgs[1];
291
+ if (!name) { ctx.print('Usage: /plugin enable <name> --yes'); return; }
292
+ if (!parsed.yes) {
293
+ requireYesFlag(ctx, `enable plugin ${name}`, '/plugin enable <name> --yes');
294
+ return;
295
+ }
269
296
  const result = await pluginManager.enable(name);
270
297
  ctx.print(result.ok ? `Plugin '${name}' enabled and activated.` : `Error: ${result.error}`);
271
298
  return;
272
299
  }
273
300
  if (sub === 'disable') {
274
- const name = args[1];
275
- if (!name) { ctx.print('Usage: /plugin disable <name>'); return; }
301
+ const name = commandArgs[1];
302
+ if (!name) { ctx.print('Usage: /plugin disable <name> --yes'); return; }
303
+ if (!parsed.yes) {
304
+ requireYesFlag(ctx, `disable plugin ${name}`, '/plugin disable <name> --yes');
305
+ return;
306
+ }
276
307
  const result = await pluginManager.disable(name);
277
308
  ctx.print(result.ok ? `Plugin '${name}' disabled.` : `Error: ${result.error}`);
278
309
  return;
279
310
  }
280
311
  if (sub === 'reload') {
312
+ if (!parsed.yes) {
313
+ requireYesFlag(ctx, 'reload plugins', '/plugin reload --yes');
314
+ return;
315
+ }
281
316
  ctx.print('Reloading plugins...');
282
317
  const { reloaded, failed } = await pluginManager.reload();
283
318
  ctx.print(`Done. ${reloaded} plugin(s) reloaded${failed > 0 ? `, ${failed} failed` : ''}.`);
284
319
  return;
285
320
  }
286
321
  if (sub === 'trust') {
287
- const name = args[1];
288
- const rawTier = args[2];
322
+ const name = commandArgs[1];
323
+ const rawTier = commandArgs[2];
289
324
  if (!name || !rawTier) {
290
- ctx.print('Usage: /plugin trust <name> <untrusted|limited|trusted> [note]');
325
+ ctx.print('Usage: /plugin trust <name> <untrusted|limited|trusted> [note] --yes');
326
+ return;
327
+ }
328
+ if (!parsed.yes) {
329
+ requireYesFlag(ctx, `set plugin ${name} trust tier`, '/plugin trust <name> <untrusted|limited|trusted> [note] --yes');
291
330
  return;
292
331
  }
293
332
  if (rawTier !== 'untrusted' && rawTier !== 'limited' && rawTier !== 'trusted') {
@@ -295,7 +334,7 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
295
334
  return;
296
335
  }
297
336
  const tier = rawTier as 'untrusted' | 'limited' | 'trusted';
298
- const note = args.slice(3).join(' ') || undefined;
337
+ const note = commandArgs.slice(3).join(' ') || undefined;
299
338
  if (tier === 'trusted') {
300
339
  const sigResult = pluginManager.trustSigned(name);
301
340
  if (sigResult.ok) {
@@ -311,7 +350,7 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
311
350
  return;
312
351
  }
313
352
  if (sub === 'verify') {
314
- const name = args[1];
353
+ const name = commandArgs[1];
315
354
  if (!name) { ctx.print('Usage: /plugin verify <name>'); return; }
316
355
  const result = pluginManager.verify(name);
317
356
  if (!result.ok && result.reason?.includes('not found')) {
@@ -324,7 +363,7 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
324
363
  return;
325
364
  }
326
365
  if (sub === 'capabilities') {
327
- const name = args[1];
366
+ const name = commandArgs[1];
328
367
  if (!name) { ctx.print('Usage: /plugin capabilities <name>'); return; }
329
368
  const info = pluginManager.capabilities(name);
330
369
  if (!info) {
@@ -344,16 +383,20 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
344
383
  if (info.blocked.length > 0) {
345
384
  lines.push('');
346
385
  lines.push(`${info.blocked.length} high-risk capability/capabilities blocked by trust tier '${info.tier}'.`);
347
- lines.push(`Use /plugin trust ${name} trusted to escalate.`);
386
+ lines.push(`Use /plugin trust ${name} trusted --yes to escalate.`);
348
387
  }
349
388
  ctx.print(lines.join('\n'));
350
389
  return;
351
390
  }
352
391
  if (sub === 'quarantine') {
353
- const name = args[1];
354
- const action = args[2] ?? 'add';
392
+ const name = commandArgs[1];
393
+ const action = commandArgs[2] ?? 'add';
355
394
  if (!name) {
356
- ctx.print('Usage: /plugin quarantine <name> [add|lift] [reason]');
395
+ ctx.print('Usage: /plugin quarantine <name> [add|lift] [reason] --yes');
396
+ return;
397
+ }
398
+ if (!parsed.yes) {
399
+ requireYesFlag(ctx, `${action === 'lift' ? 'lift quarantine for' : 'quarantine'} plugin ${name}`, '/plugin quarantine <name> [add|lift] [reason] --yes');
357
400
  return;
358
401
  }
359
402
  if (action === 'lift') {
@@ -361,10 +404,10 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
361
404
  ctx.print(result.ok ? `Plugin '${name}' quarantine lifted. Reload to restore safe capabilities.` : `Error: ${result.error}`);
362
405
  return;
363
406
  }
364
- const reason = args.slice(2).join(' ') || 'quarantined by operator';
407
+ const reason = (action === 'add' ? commandArgs.slice(3) : commandArgs.slice(2)).join(' ') || 'quarantined by operator';
365
408
  const result = pluginManager.quarantine(name, reason);
366
409
  ctx.print(result.ok
367
- ? `Plugin '${name}' quarantined.\nReason: ${reason}\nHigh-risk capabilities revoked. Reload to fully apply. Use /plugin quarantine <name> lift to restore.`
410
+ ? `Plugin '${name}' quarantined.\nReason: ${reason}\nHigh-risk capabilities revoked. Reload to fully apply. Use /plugin quarantine <name> lift --yes to restore.`
368
411
  : `Error: ${result.error}`);
369
412
  return;
370
413
  }
@@ -372,22 +415,22 @@ export function registerIntegrationRuntimeCommands(registry: CommandRegistry): v
372
415
  ctx.print(
373
416
  'Usage: /plugin <subcommand>\n'
374
417
  + ' list — show installed plugins and their status\n'
375
- + ' enable <name> — enable a plugin\n'
376
- + ' disable <name> — disable a plugin\n'
377
- + ' reload — reload all enabled plugins\n'
378
- + ' trust <name> <tier> [note] — set trust tier (untrusted|limited|trusted)\n'
418
+ + ' enable <name> --yes — enable a plugin\n'
419
+ + ' disable <name> --yes — disable a plugin\n'
420
+ + ' reload --yes — reload all enabled plugins\n'
421
+ + ' trust <name> <tier> [note] --yes — set trust tier (untrusted|limited|trusted)\n'
379
422
  + ' verify <name> — inspect a plugin manifest signature\n'
380
423
  + ' capabilities <name> — show capability grants and blocks\n'
381
424
  + ' browse [query] — browse curated local-first plugin catalog entries\n'
382
425
  + ' installed — list curated catalog installs with provenance receipts\n'
383
426
  + ' catalog-review <id> — review source, provenance, and risk for a curated plugin\n'
384
- + ' publish-local <id> <path> <summary...> — publish a local plugin directory into the curated catalog\n'
385
- + ' unpublish <id> — remove a local curated plugin catalog entry\n'
427
+ + ' publish-local <id> <path> <summary...> --yes — publish a local plugin directory into the curated catalog\n'
428
+ + ' unpublish <id> --yes — remove a local curated plugin catalog entry\n'
386
429
  + ' install-hint <catalog-id> — show install guidance for a curated plugin entry\n'
387
- + ' install <catalog-id> [scope] — install a local-path curated plugin into project|user scope\n'
388
- + ' uninstall <catalog-id> [scope] — remove a curated plugin install receipt and target path\n'
389
- + ' quarantine <name> [reason] — quarantine a plugin (revoke high-risk caps)\n'
390
- + ' quarantine <name> lift — lift quarantine from a plugin'
430
+ + ' install <catalog-id> [scope] --yes — install a local-path curated plugin into project|user scope\n'
431
+ + ' uninstall <catalog-id> [scope] --yes — remove a curated plugin install receipt and target path\n'
432
+ + ' quarantine <name> [reason] --yes — quarantine a plugin (revoke high-risk caps)\n'
433
+ + ' quarantine <name> lift --yes — lift quarantine from a plugin'
391
434
  );
392
435
  },
393
436
  });
@@ -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,