@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
@@ -4,6 +4,7 @@ import { AGENT_TEMPLATES } from '@pellux/goodvibes-sdk/platform/tools';
4
4
  import { handleRemoteSetupCommand } from './remote-runtime-setup.ts';
5
5
  import { handleRemotePoolCommand } from './remote-runtime-pool.ts';
6
6
  import { requirePeerClient } from './runtime-services.ts';
7
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
7
8
 
8
9
  type RemoteConnectionLike = { agentId: string };
9
10
  type RemoteCancelContext = Pick<CommandContext, 'print'>;
@@ -45,7 +46,7 @@ export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
45
46
  name: 'remote',
46
47
  aliases: [],
47
48
  description: 'Inspect, dispatch, and review self-hosted remote runners and artifacts',
48
- usage: '[list | show [agentId] | supervisor [runnerId] | capabilities [runnerId] | recover [runnerId] | setup [export <path>] | env [export <path>] | tunnel [review|export <path>] | bootstrap [export <path>|inspect <path>] | session <export|inspect|import> <path> | pool <list|show|create|assign|unassign> ... | dispatch [template] <description> | dispatch-pool <pool> [template] <description> | contract [agentId] | cancel <agentId> | export <agentId> [path] | artifact list | artifact show <id> | artifact export <id> [path] | review <id> | rerun-local <id> | import <path>]',
49
+ usage: '[list | show [agentId] | supervisor [runnerId] | capabilities [runnerId] | recover [runnerId] | setup [export <path> --yes] | env [export <path> --yes] | tunnel [review|export <path> --yes] | bootstrap [export <path> --yes|inspect <path>] | session <export|inspect|import> <path> [--yes] | pool <list|show|create|assign|unassign> ... | dispatch [template] <description> | dispatch-pool <pool> [template] <description> | contract [agentId] | cancel <agentId> | export <agentId> [path] --yes | artifact list | artifact show <id> | artifact export <id> [path] --yes | review <id> | rerun-local <id> | import <path> --yes]',
49
50
  async handler(args, ctx) {
50
51
  if (args.length === 0) {
51
52
  if (ctx.openRemotePanel) {
@@ -310,9 +311,14 @@ export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
310
311
  }
311
312
 
312
313
  if (subcommand === 'export') {
313
- const agentId = args[1];
314
+ const { rest, yes } = stripYesFlag(args);
315
+ const agentId = rest[1];
314
316
  if (!agentId) {
315
- ctx.print('Usage: /remote export <agentId> [path]');
317
+ ctx.print('Usage: /remote export <agentId> [path] --yes');
318
+ return;
319
+ }
320
+ if (!yes) {
321
+ requireYesFlag(ctx, `export remote review artifact for ${agentId}`, '/remote export <agentId> [path] --yes');
316
322
  return;
317
323
  }
318
324
  const artifact = remoteRunners.captureArtifactForRunner(agentId);
@@ -320,7 +326,7 @@ export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
320
326
  ctx.print(`Remote artifact export failed for ${agentId}.`);
321
327
  return;
322
328
  }
323
- const exported = await remoteRunners.exportArtifact(artifact.id, args[2]);
329
+ const exported = await remoteRunners.exportArtifact(artifact.id, rest[2]);
324
330
  if (!exported) {
325
331
  ctx.print(`Remote artifact export failed for ${agentId}.`);
326
332
  return;
@@ -356,12 +362,17 @@ export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
356
362
  return;
357
363
  }
358
364
  if (mode === 'export') {
359
- const artifactId = args[2];
365
+ const { rest, yes } = stripYesFlag(args);
366
+ const artifactId = rest[2];
360
367
  if (!artifactId) {
361
- ctx.print('Usage: /remote artifact export <artifactId> [path]');
368
+ ctx.print('Usage: /remote artifact export <artifactId> [path] --yes');
362
369
  return;
363
370
  }
364
- const exported = await remoteRunners.exportArtifact(artifactId, args[3]);
371
+ if (!yes) {
372
+ requireYesFlag(ctx, `export remote review artifact ${artifactId}`, '/remote artifact export <artifactId> [path] --yes');
373
+ return;
374
+ }
375
+ const exported = await remoteRunners.exportArtifact(artifactId, rest[3]);
365
376
  if (!exported) {
366
377
  ctx.print(`Unknown remote artifact: ${artifactId}`);
367
378
  return;
@@ -400,9 +411,14 @@ export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
400
411
  }
401
412
 
402
413
  if (subcommand === 'import') {
403
- const path = args[1];
414
+ const { rest, yes } = stripYesFlag(args);
415
+ const path = rest[1];
404
416
  if (!path) {
405
- ctx.print('Usage: /remote import <path>');
417
+ ctx.print('Usage: /remote import <path> --yes');
418
+ return;
419
+ }
420
+ if (!yes) {
421
+ requireYesFlag(ctx, `import remote review artifact from ${path}`, '/remote import <path> --yes');
406
422
  return;
407
423
  }
408
424
  const artifact = await remoteRunners.importArtifact(path);
@@ -1,17 +1,24 @@
1
1
  import type { CommandRegistry } from '../command-registry.ts';
2
2
  import { handleReplayCommand } from '@pellux/goodvibes-sdk/platform/core';
3
3
  import { requireReplayEngine } from './runtime-services.ts';
4
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
4
5
 
5
6
  export function registerReplayRuntimeCommands(registry: CommandRegistry): void {
6
7
  registry.register({
7
8
  name: 'replay',
8
9
  aliases: ['rep'],
9
10
  description: 'Deterministic replay: load, step, seek, diff, and export recorded runs',
10
- usage: '[load [runId] | step [n] | seek <rev> | diff | export <path>]',
11
+ usage: '[load [runId] | step [n] | seek <rev> | diff | export <path> --yes]',
11
12
  argsHint: '[load|step|seek|diff|export]',
12
13
  handler(args, ctx) {
14
+ const command = args[0] ?? 'help';
15
+ const { rest, yes } = stripYesFlag(args);
16
+ if (command === 'export' && !yes) {
17
+ requireYesFlag(ctx, `export replay run to ${args[1] ?? '<path>'}`, '/replay export <path> --yes');
18
+ return;
19
+ }
13
20
  const replayEngine = requireReplayEngine(ctx);
14
- const result = handleReplayCommand({ replayEngine }, args[0] ?? 'help', args.slice(1));
21
+ const result = handleReplayCommand({ replayEngine }, command, rest.slice(1));
15
22
  ctx.print(result.output);
16
23
  },
17
24
  });
@@ -5,15 +5,18 @@ import type { SelectionAction, SelectionItem } from '../selection-modal.ts';
5
5
  import { openCommandPanel, requireServiceRegistry, requireShellPaths } from './runtime-services.ts';
6
6
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
7
7
  import { GOODVIBES_AGENT_SURFACE_ROOT } from '../../config/surface.ts';
8
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
8
9
 
9
10
  export function registerServicesRuntimeCommands(registry: CommandRegistry): void {
10
11
  registry.register({
11
12
  name: 'services',
12
13
  aliases: ['svc'],
13
14
  description: 'Manage API service configurations',
14
- usage: '[open|list|inspect <name>|test <name>|resolve <name>|auth <name>|auth-review|doctor|export <path>|import <path>]',
15
+ usage: '[open|list|inspect <name>|test <name>|resolve <name>|auth <name>|auth-review|doctor|export <path> --yes|import <path> --yes]',
15
16
  async handler(args, ctx) {
16
- const sub = args[0] ?? 'open';
17
+ const parsed = stripYesFlag(args);
18
+ const commandArgs = [...parsed.rest];
19
+ const sub = commandArgs[0] ?? 'open';
17
20
  const shellPaths = requireShellPaths(ctx);
18
21
  if (sub === 'open' || sub === 'panel') {
19
22
  openCommandPanel(ctx, 'services');
@@ -23,7 +26,7 @@ export function registerServicesRuntimeCommands(registry: CommandRegistry): void
23
26
  const all = svcRegistry.getAll();
24
27
  const keys = Object.keys(all);
25
28
  if (sub === 'inspect') {
26
- const name = args[1];
29
+ const name = commandArgs[1];
27
30
  if (!name) {
28
31
  ctx.print('Usage: /services inspect <name>');
29
32
  return;
@@ -47,7 +50,7 @@ export function registerServicesRuntimeCommands(registry: CommandRegistry): void
47
50
  return;
48
51
  }
49
52
  if (sub === 'test') {
50
- const name = args[1];
53
+ const name = commandArgs[1];
51
54
  if (!name) {
52
55
  ctx.print('Usage: /services test <name>');
53
56
  return;
@@ -63,7 +66,7 @@ export function registerServicesRuntimeCommands(registry: CommandRegistry): void
63
66
  return;
64
67
  }
65
68
  if (sub === 'resolve') {
66
- const name = args[1];
69
+ const name = commandArgs[1];
67
70
  if (!name) {
68
71
  ctx.print('Usage: /services resolve <name>');
69
72
  return;
@@ -80,7 +83,7 @@ export function registerServicesRuntimeCommands(registry: CommandRegistry): void
80
83
  return;
81
84
  }
82
85
  if (sub === 'auth') {
83
- const name = args[1];
86
+ const name = commandArgs[1];
84
87
  if (!name) {
85
88
  ctx.print('Usage: /services auth <name>');
86
89
  return;
@@ -141,9 +144,13 @@ export function registerServicesRuntimeCommands(registry: CommandRegistry): void
141
144
  return;
142
145
  }
143
146
  if (sub === 'export') {
144
- const pathArg = args[1];
147
+ const pathArg = commandArgs[1];
145
148
  if (!pathArg) {
146
- ctx.print('Usage: /services export <path>');
149
+ ctx.print('Usage: /services export <path> --yes');
150
+ return;
151
+ }
152
+ if (!parsed.yes) {
153
+ requireYesFlag(ctx, `export services config to ${pathArg}`, '/services export <path> --yes');
147
154
  return;
148
155
  }
149
156
  const targetPath = shellPaths.resolveWorkspacePath(pathArg);
@@ -153,9 +160,13 @@ export function registerServicesRuntimeCommands(registry: CommandRegistry): void
153
160
  return;
154
161
  }
155
162
  if (sub === 'import') {
156
- const pathArg = args[1];
163
+ const pathArg = commandArgs[1];
157
164
  if (!pathArg) {
158
- ctx.print('Usage: /services import <path>');
165
+ ctx.print('Usage: /services import <path> --yes');
166
+ return;
167
+ }
168
+ if (!parsed.yes) {
169
+ requireYesFlag(ctx, `import services config from ${pathArg}`, '/services import <path> --yes');
159
170
  return;
160
171
  }
161
172
  const sourcePath = shellPaths.resolveWorkspacePath(pathArg);
@@ -1,5 +1,5 @@
1
1
  import { join, resolve } from 'path';
2
- import { existsSync, mkdirSync, unlinkSync } from 'node:fs';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
3
  import { writeFile } from 'node:fs/promises';
4
4
  import type { CommandRegistry } from '../command-registry.ts';
5
5
  import type { SelectionItem } from '../selection-modal.ts';
@@ -7,18 +7,20 @@ import { exportToMarkdown } from '@pellux/goodvibes-sdk/platform/export';
7
7
  import { TemplateManager, parseTemplateArgs } from '@pellux/goodvibes-sdk/platform/templates';
8
8
  import { requireSessionManager, requireSessionMemoryStore, requireShellPaths } from './runtime-services.ts';
9
9
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
10
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
10
11
 
11
12
  export function registerSessionContentCommands(registry: CommandRegistry): void {
12
13
  registry.register({
13
14
  name: 'export',
14
15
  description: 'Export conversation to a Markdown file',
15
- usage: '[format] [path]',
16
+ usage: '[format] [path] --yes',
16
17
  argsHint: '[markdown] [path]',
17
18
  async handler(args, ctx) {
18
19
  const shellPaths = requireShellPaths(ctx);
20
+ const { rest, yes } = stripYesFlag(args);
19
21
  let format = 'markdown';
20
22
  let outPath: string | undefined;
21
- for (const arg of args) {
23
+ for (const arg of rest) {
22
24
  if (arg === 'markdown' || arg === 'md' || arg === 'text' || arg === 'txt') {
23
25
  format = arg === 'md' ? 'markdown' : arg === 'txt' ? 'text' : arg;
24
26
  } else {
@@ -34,6 +36,10 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
34
36
  ctx.print('Error: Export path must be within the current directory.');
35
37
  return;
36
38
  }
39
+ if (!yes) {
40
+ requireYesFlag(ctx, `export conversation to ${resolvedPath}`, '/export [format] [path] --yes');
41
+ return;
42
+ }
37
43
 
38
44
  try {
39
45
  const data = ctx.session.conversationManager.toJSON() as { messages: Array<Record<string, unknown>> };
@@ -229,34 +235,21 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
229
235
  const sessionManager = requireSessionManager(ctx);
230
236
  const sessions = sessionManager.list();
231
237
  if (ctx.openSelection) {
232
- const deleteAction = new Map([['d', 'delete' as const]]);
233
238
  const items: SelectionItem[] = sessions.length === 0
234
239
  ? [{ id: '_empty', label: 'No saved sessions', detail: 'Use /save [name] to save' }]
235
- : sessions.map(s => ({ id: s.name, label: s.name, detail: s.title || '(untitled)', actions: '[d] delete' }));
236
- ctx.openSelection('Sessions', items, { allowSearch: true, customActions: deleteAction }, (result) => {
240
+ : sessions.map(s => ({ id: s.name, label: s.name, detail: s.title || '(untitled)', actions: 'Enter to load' }));
241
+ ctx.openSelection('Sessions', items, { allowSearch: true }, (result) => {
237
242
  if (!result) return;
238
- if (result.action === 'delete') {
239
- try {
240
- const sessionInfo = sessions.find(s => s.name === result.item.id);
241
- if (sessionInfo) {
242
- unlinkSync(sessionInfo.filePath);
243
- ctx.print(`Session deleted: ${result.item.id}`);
244
- }
245
- } catch (e) {
246
- ctx.print(`Failed to delete session: ${summarizeError(e)}`);
247
- }
248
- } else {
249
- try {
250
- const { meta, messages } = sessionManager.load(result.item.id);
251
- ctx.session.conversationManager.resetAll();
252
- ctx.session.conversationManager.fromJSON({ messages: messages as never[] });
253
- if (meta.title) ctx.session.conversationManager.title = meta.title;
254
- ctx.session.conversationManager.rebuildHistory();
255
- ctx.renderRequest();
256
- ctx.print(`Session loaded: ${result.item.id} (${messages.length} messages)`);
257
- } catch (e) {
258
- ctx.print(`Failed to load session: ${summarizeError(e)}`);
259
- }
243
+ try {
244
+ const { meta, messages } = sessionManager.load(result.item.id);
245
+ ctx.session.conversationManager.resetAll();
246
+ ctx.session.conversationManager.fromJSON({ messages: messages as never[] });
247
+ if (meta.title) ctx.session.conversationManager.title = meta.title;
248
+ ctx.session.conversationManager.rebuildHistory();
249
+ ctx.renderRequest();
250
+ ctx.print(`Session loaded: ${result.item.id} (${messages.length} messages)`);
251
+ } catch (e) {
252
+ ctx.print(`Failed to load session: ${summarizeError(e)}`);
260
253
  }
261
254
  });
262
255
  return;
@@ -271,7 +264,7 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
271
264
  name: 'template',
272
265
  aliases: ['tmpl'],
273
266
  description: 'Manage and use prompt templates',
274
- usage: 'save <name> | use <name> [args] | list | edit <name> | delete <name>',
267
+ usage: 'save <name> --yes | use <name> [args] | list | edit <name> | delete <name> --yes',
275
268
  argsHint: '<save|use|list|edit|delete> [name]',
276
269
  handler(args, ctx) {
277
270
  const shellPaths = requireShellPaths(ctx);
@@ -279,28 +272,24 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
279
272
  projectRoot: shellPaths.workingDirectory,
280
273
  homeDirectory: shellPaths.homeDirectory,
281
274
  });
282
- const sub = args[0];
283
- const rest = args.slice(1);
275
+ const parsed = stripYesFlag(args);
276
+ const sub = parsed.rest[0];
277
+ const rest = parsed.rest.slice(1);
284
278
  if (!sub || sub === 'list') {
285
279
  const templates = templateManager.list();
286
280
  if (ctx.openSelection) {
287
- const actions = new Map([['d', 'delete' as const], ['e', 'edit' as const]]);
281
+ const actions = new Map([['e', 'edit' as const]]);
288
282
  const items: SelectionItem[] = templates.length === 0
289
- ? [{ id: '_empty', label: 'No templates saved', detail: 'Use /template save <name>' }]
290
- : templates.map(t => ({ id: t.name, label: t.name, detail: t.preview, category: t.scope === 'project' ? 'project' : 'global', actions: '[d] delete [e] edit' }));
283
+ ? [{ id: '_empty', label: 'No templates saved', detail: 'Use /template save <name> --yes' }]
284
+ : templates.map(t => ({ id: t.name, label: t.name, detail: t.preview, category: t.scope === 'project' ? 'project' : 'global', actions: '[e] edit' }));
291
285
  ctx.openSelection('Templates', items, { allowSearch: true, customActions: actions }, (result) => {
292
286
  if (!result) return;
293
- if (result.action === 'delete') {
294
- const deleted = templateManager.delete(result.item.id);
295
- ctx.print(deleted ? `Template deleted: ${result.item.id}` : `Template not found: ${result.item.id}`);
287
+ const content = templateManager.load(result.item.id);
288
+ if (content !== null) {
289
+ if (result.action === 'edit') ctx.print(`Template: ${result.item.id}\n\n${content}`);
290
+ else ctx.submitInput?.(content);
296
291
  } else {
297
- const content = templateManager.load(result.item.id);
298
- if (content !== null) {
299
- if (result.action === 'edit') ctx.print(`Template: ${result.item.id}\n\n${content}`);
300
- else ctx.submitInput?.(content);
301
- } else {
302
- ctx.print(`Template not found: ${result.item.id}`);
303
- }
292
+ ctx.print(`Template not found: ${result.item.id}`);
304
293
  }
305
294
  });
306
295
  return;
@@ -311,7 +300,11 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
311
300
  if (sub === 'save') {
312
301
  const name = rest[0];
313
302
  if (!name) {
314
- ctx.print('Usage: /template save <name>');
303
+ ctx.print('Usage: /template save <name> --yes');
304
+ return;
305
+ }
306
+ if (!parsed.yes) {
307
+ requireYesFlag(ctx, `save prompt template ${name}`, '/template save <name> --yes');
315
308
  return;
316
309
  }
317
310
  try {
@@ -349,20 +342,24 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
349
342
  if (sub === 'delete') {
350
343
  const name = rest[0];
351
344
  if (!name) {
352
- ctx.print('Usage: /template delete <name>');
345
+ ctx.print('Usage: /template delete <name> --yes');
346
+ return;
347
+ }
348
+ if (!parsed.yes) {
349
+ requireYesFlag(ctx, `delete prompt template ${name}`, '/template delete <name> --yes');
353
350
  return;
354
351
  }
355
352
  ctx.print(templateManager.delete(name) ? `Template deleted: ${name}` : `Template not found: ${name}`);
356
353
  return;
357
354
  }
358
- ctx.print(`Unknown subcommand: ${sub}\nUsage: /template save|use|list|edit|delete`);
355
+ ctx.print(`Unknown subcommand: ${sub}\nUsage: /template save <name> --yes | use <name> | list | edit <name> | delete <name> --yes`);
359
356
  },
360
357
  });
361
358
 
362
359
  registry.register({
363
360
  name: 'memory',
364
361
  description: 'Manage session memories (pinned across context compaction)',
365
- usage: '[list|add <text>|remove <id>]',
362
+ usage: '[list|add <text>|remove <id> --yes]',
366
363
  argsHint: '[list|add|remove]',
367
364
  handler(args, ctx) {
368
365
  const sub = args[0] ?? 'list';
@@ -380,15 +377,20 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
380
377
  const id = requireSessionMemoryStore(ctx).add(text);
381
378
  ctx.print(`Memory added: [${id}] ${text}`);
382
379
  } else if (sub === 'remove') {
383
- const id = args[1];
380
+ const parsed = stripYesFlag(args);
381
+ const id = parsed.rest[1];
384
382
  if (!id) {
385
- ctx.print('Usage: /memory remove <id>');
383
+ ctx.print('Usage: /memory remove <id> --yes');
384
+ return;
385
+ }
386
+ if (!parsed.yes) {
387
+ requireYesFlag(ctx, `remove session memory ${id}`, '/memory remove <id> --yes');
386
388
  return;
387
389
  }
388
390
  const store = requireSessionMemoryStore(ctx);
389
391
  ctx.print(store.remove(id) ? `Memory removed: [${id}]` : `Memory not found: ${id}`);
390
392
  } else {
391
- ctx.print('Usage: /memory [list|add <text>|remove <id>]\n /memory — list all session memories\n /memory list — list all session memories\n /memory add <text> — add a memory without sending a message\n /memory remove <id> — remove a specific memory');
393
+ ctx.print('Usage: /memory [list|add <text>|remove <id> --yes]\n /memory — list all session memories\n /memory list — list all session memories\n /memory add <text> — add a memory without sending a message\n /memory remove <id> --yes — remove a specific memory');
392
394
  }
393
395
  },
394
396
  });
@@ -8,6 +8,7 @@ import type { SessionReturnContextSummary } from '@/runtime/index.ts';
8
8
  import { formatReturnContextForDisplay, getReturnContextMode, maybeAssistReturnContextSummary } from '@/runtime/index.ts';
9
9
  import { requirePanelManager, requireProviderApi, requireSessionManager } 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 parseTranscriptKind(raw: string | undefined): TranscriptEventKind | 'all' {
13
14
  const normalized = (raw ?? 'all').toLowerCase().replace(/-/g, '_');
@@ -414,9 +415,14 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
414
415
  }
415
416
 
416
417
  if (sub === 'delete') {
417
- const target = args[1];
418
+ const parsed = stripYesFlag(args);
419
+ const target = parsed.rest[1];
418
420
  if (!target) {
419
- ctx.print('Usage: /session delete <session-id>');
421
+ ctx.print('Usage: /session delete <session-id> --yes');
422
+ return true;
423
+ }
424
+ if (!parsed.yes) {
425
+ requireYesFlag(ctx, `delete saved session ${target}`, '/session delete <session-id> --yes');
420
426
  return true;
421
427
  }
422
428
  const sessions = sm.list();
@@ -446,12 +452,12 @@ export function registerSessionWorkflowCommands(registry: CommandRegistry): void
446
452
  name: 'session',
447
453
  aliases: ['sess'],
448
454
  description: 'Manage sessions, resume posture, and transcript structure',
449
- usage: '[list | rename <name> | resume <id|name> | fork | save | info <id> | events [kind] | groups [kind] | hotspots | export <id> [format] | search <query> | delete <id>]',
455
+ usage: '[list | rename <name> | resume <id|name> | fork | save | info <id> | events [kind] | groups [kind] | hotspots | export <id> [format] | search <query> | delete <id> --yes]',
450
456
  argsHint: '<list|rename|resume|fork|save|info|events|groups|hotspots|export|search|delete>',
451
457
  async handler(args, ctx) {
452
458
  const handled = await handleSessionWorkflowCommand(args, ctx);
453
459
  if (!handled) {
454
- ctx.print('Unknown subcommand: ' + (args[0] ?? '') + '\nUsage: /session [list | rename <name> | resume <id> | fork [name] | save [name] | info [id] | events [kind] | groups [kind] | hotspots | export <id> [format] | search <query> | delete <id>]');
460
+ ctx.print('Unknown subcommand: ' + (args[0] ?? '') + '\nUsage: /session [list | rename <name> | resume <id> | fork [name] | save [name] | info [id] | events [kind] | groups [kind] | hotspots | export <id> [format] | search <query> | delete <id> --yes]');
455
461
  }
456
462
  },
457
463
  });
@@ -352,7 +352,7 @@ export const sessionCommand: SlashCommand = {
352
352
  if (!handled) {
353
353
  const usage = [
354
354
  'Usage: /session <subcommand>',
355
- ' list | rename <name> | resume <id|name> | fork [name] | save [name] | info [id] | export <id> [format] | search <query> | delete <id>',
355
+ ' list | rename <name> | resume <id|name> | fork [name] | save [name] | info [id] | export <id> [format] | search <query> | delete <id> --yes',
356
356
  ' — Session continuity, export, resume, and pruning',
357
357
  ' graph [--session <sid>] [--format text|json]',
358
358
  ' — Display the cross-session task dependency graph',
@@ -21,23 +21,26 @@ import { CONFIG_KEYS } from '@pellux/goodvibes-sdk/platform/config';
21
21
  import type { CommandRegistry } from '../command-registry.ts';
22
22
  import { openCommandPanel, requireShellPaths } from './runtime-services.ts';
23
23
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
24
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
24
25
 
25
26
  export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry): void {
26
27
  registry.register({
27
28
  name: 'settingssync',
28
29
  aliases: ['settings-sync'],
29
30
  description: 'Review sync posture, export/import settings-sync bundles, and open the settings sync workspace',
30
- usage: '[review|panel|show <key>|staged|conflicts|resolve <key> <local|synced>|failures|rollback-history|export <path>|inspect <path>|pull <path>|push <path>|lock <key> <source> <reason...>|unlock <key>]',
31
+ usage: '[review|panel|show <key>|staged|conflicts|resolve <key> <local|synced> --yes|failures|rollback-history|export <path> --yes|inspect <path>|pull <path> --yes|push <path> --yes|lock <key> <source> <reason...> --yes|unlock <key> --yes]',
31
32
  handler(args, ctx) {
33
+ const parsed = stripYesFlag(args);
34
+ const commandArgs = [...parsed.rest];
32
35
  const shellPaths = requireShellPaths(ctx);
33
36
  const controlPlaneConfigDir = ctx.platform.configManager.getControlPlaneConfigDir();
34
- const sub = (args[0] ?? 'review').toLowerCase();
37
+ const sub = (commandArgs[0] ?? 'review').toLowerCase();
35
38
  if (sub === 'panel' || sub === 'open') {
36
39
  openCommandPanel(ctx, 'settings-sync');
37
40
  return;
38
41
  }
39
42
  if (sub === 'show') {
40
- const key = args[1] as ConfigKey | undefined;
43
+ const key = commandArgs[1] as ConfigKey | undefined;
41
44
  if (!key || !CONFIG_KEYS.has(key)) {
42
45
  ctx.print('Usage: /settingssync show <config-key>');
43
46
  return;
@@ -60,10 +63,14 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
60
63
  return;
61
64
  }
62
65
  if (sub === 'resolve') {
63
- const key = args[1] as ConfigKey | undefined;
64
- const resolution = (args[2] ?? '').toLowerCase();
66
+ const key = commandArgs[1] as ConfigKey | undefined;
67
+ const resolution = (commandArgs[2] ?? '').toLowerCase();
65
68
  if (!key || !CONFIG_KEYS.has(key) || (resolution !== 'local' && resolution !== 'synced')) {
66
- ctx.print('Usage: /settingssync resolve <config-key> <local|synced>');
69
+ ctx.print('Usage: /settingssync resolve <config-key> <local|synced> --yes');
70
+ return;
71
+ }
72
+ if (!parsed.yes) {
73
+ requireYesFlag(ctx, `resolve synced conflict for ${key}`, '/settingssync resolve <config-key> <local|synced> --yes');
67
74
  return;
68
75
  }
69
76
  const changed = resolveSettingsSyncConflict(ctx.platform.configManager, key, resolution);
@@ -100,9 +107,13 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
100
107
  return;
101
108
  }
102
109
  if (sub === 'export' || sub === 'push') {
103
- const pathArg = args[1];
110
+ const pathArg = commandArgs[1];
104
111
  if (!pathArg) {
105
- ctx.print(`Usage: /settingssync ${sub} <path>`);
112
+ ctx.print(`Usage: /settingssync ${sub} <path> --yes`);
113
+ return;
114
+ }
115
+ if (!parsed.yes) {
116
+ requireYesFlag(ctx, `${sub === 'push' ? 'push' : 'export'} settings sync bundle to ${pathArg}`, `/settingssync ${sub} <path> --yes`);
106
117
  return;
107
118
  }
108
119
  const targetPath = shellPaths.resolveWorkspacePath(pathArg);
@@ -120,7 +131,7 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
120
131
  return;
121
132
  }
122
133
  if (sub === 'inspect') {
123
- const pathArg = args[1];
134
+ const pathArg = commandArgs[1];
124
135
  if (!pathArg) {
125
136
  ctx.print('Usage: /settingssync inspect <path>');
126
137
  return;
@@ -131,9 +142,13 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
131
142
  return;
132
143
  }
133
144
  if (sub === 'pull') {
134
- const pathArg = args[1];
145
+ const pathArg = commandArgs[1];
135
146
  if (!pathArg) {
136
- ctx.print('Usage: /settingssync pull <path>');
147
+ ctx.print('Usage: /settingssync pull <path> --yes');
148
+ return;
149
+ }
150
+ if (!parsed.yes) {
151
+ requireYesFlag(ctx, `pull settings sync bundle from ${pathArg}`, '/settingssync pull <path> --yes');
137
152
  return;
138
153
  }
139
154
  const sourcePath = shellPaths.resolveWorkspacePath(pathArg);
@@ -148,11 +163,15 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
148
163
  return;
149
164
  }
150
165
  if (sub === 'lock') {
151
- const key = args[1] as ConfigKey | undefined;
152
- const source = args[2];
153
- const reason = args.slice(3).join(' ').trim();
166
+ const key = commandArgs[1] as ConfigKey | undefined;
167
+ const source = commandArgs[2];
168
+ const reason = commandArgs.slice(3).join(' ').trim();
154
169
  if (!key || !source || !reason || !CONFIG_KEYS.has(key)) {
155
- ctx.print('Usage: /settingssync lock <config-key> <source> <reason...>');
170
+ ctx.print('Usage: /settingssync lock <config-key> <source> <reason...> --yes');
171
+ return;
172
+ }
173
+ if (!parsed.yes) {
174
+ requireYesFlag(ctx, `lock managed setting ${key}`, '/settingssync lock <config-key> <source> <reason...> --yes');
156
175
  return;
157
176
  }
158
177
  setManagedSettingLock(key, source, reason, controlPlaneConfigDir);
@@ -160,9 +179,13 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
160
179
  return;
161
180
  }
162
181
  if (sub === 'unlock') {
163
- const key = args[1] as ConfigKey | undefined;
182
+ const key = commandArgs[1] as ConfigKey | undefined;
164
183
  if (!key || !CONFIG_KEYS.has(key)) {
165
- ctx.print('Usage: /settingssync unlock <config-key>');
184
+ ctx.print('Usage: /settingssync unlock <config-key> --yes');
185
+ return;
186
+ }
187
+ if (!parsed.yes) {
188
+ requireYesFlag(ctx, `unlock managed setting ${key}`, '/settingssync unlock <config-key> --yes');
166
189
  return;
167
190
  }
168
191
  ctx.print(clearManagedSettingLock(key, controlPlaneConfigDir) ? `Managed lock cleared for ${key}.` : `No managed lock found for ${key}.`);
@@ -11,23 +11,26 @@ import {
11
11
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
12
12
  import { requireShellPaths } from './runtime-services.ts';
13
13
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
14
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
14
15
 
15
16
  export function registerShareRuntimeCommands(registry: CommandRegistry): void {
16
17
  registry.register({
17
18
  name: 'share',
18
19
  aliases: [],
19
20
  description: 'Export the current session to a shareable format (html, json, md)',
20
- usage: '<html|json|md> [path] [--redact]',
21
+ usage: '<html|json|md> [path] [--redact] --yes',
21
22
  argsHint: '<html|json|md> [path]',
22
23
  async handler(args, ctx) {
24
+ const parsed = stripYesFlag(args);
25
+ const commandArgs = [...parsed.rest];
23
26
  const shellPaths = requireShellPaths(ctx);
24
27
  const FORMATS = ['html', 'json', 'md'] as const;
25
28
  type Format = typeof FORMATS[number];
26
29
 
27
- const format = args[0]?.toLowerCase() as Format | undefined;
30
+ const format = commandArgs[0]?.toLowerCase() as Format | undefined;
28
31
  if (!format || !FORMATS.includes(format)) {
29
32
  ctx.print(
30
- 'Usage: /share <html|json|md> [path] [--redact]\n'
33
+ 'Usage: /share <html|json|md> [path] [--redact] --yes\n'
31
34
  + ' html — self-contained HTML with syntax highlighting\n'
32
35
  + ' json — structured JSON (machine-readable)\n'
33
36
  + ' md — Markdown\n\n'
@@ -38,13 +41,18 @@ export function registerShareRuntimeCommands(registry: CommandRegistry): void {
38
41
  return;
39
42
  }
40
43
 
41
- const remainingArgs = args.slice(1);
44
+ const remainingArgs = commandArgs.slice(1);
42
45
  const redact = remainingArgs.includes('--redact');
43
46
  const pathArgs = remainingArgs.filter(a => a !== '--redact');
44
47
  const outputPath = pathArgs.length > 0
45
48
  ? shellPaths.resolveWorkspacePath(pathArgs[0])
46
49
  : defaultExportPath(format, shellPaths.homeDirectory);
47
50
 
51
+ if (!parsed.yes) {
52
+ requireYesFlag(ctx, `export ${format.toUpperCase()} session to ${outputPath}`, '/share <html|json|md> [path] [--redact] --yes');
53
+ return;
54
+ }
55
+
48
56
  const convData = ctx.session.conversationManager.toJSON() as {
49
57
  messages: Array<{
50
58
  role: string;