@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
@@ -4,6 +4,7 @@ import { getDefaultAcpAgentCommand } from '@pellux/goodvibes-sdk/platform/acp';
4
4
  import type { CommandContext, RemoteCommandService } from '../command-registry.ts';
5
5
  import type { RemoteSessionBundle } from '@/runtime/index.ts';
6
6
  import { requireShellPaths } from './runtime-services.ts';
7
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
7
8
 
8
9
  type RemoteRegistryLike = Pick<RemoteCommandService, 'listContracts' | 'exportSessionBundle' | 'importSessionBundle'>;
9
10
 
@@ -33,7 +34,9 @@ export async function handleRemoteSetupCommand(
33
34
  activeConnections: ActiveConnectionLike[],
34
35
  remoteRegistry: RemoteRegistryLike,
35
36
  ): Promise<boolean> {
36
- const subcommand = args[0]?.toLowerCase() ?? 'show';
37
+ const parsed = stripYesFlag(args);
38
+ const commandArgs = [...parsed.rest];
39
+ const subcommand = commandArgs[0]?.toLowerCase() ?? 'show';
37
40
  if (subcommand === 'setup') {
38
41
  const command = getDefaultAcpAgentCommand();
39
42
  const danger = ctx.platform.configManager.getCategory('danger');
@@ -50,10 +53,14 @@ export async function handleRemoteSetupCommand(
50
53
  ' - use /remote env to export a reusable shell snippet',
51
54
  ' - enable danger.daemon / danger.httpListener only when you actually need those remote surfaces',
52
55
  ];
53
- if (args[1]?.toLowerCase() === 'export') {
54
- const pathArg = args[2];
56
+ if (commandArgs[1]?.toLowerCase() === 'export') {
57
+ const pathArg = commandArgs[2];
55
58
  if (!pathArg) {
56
- ctx.print('Usage: /remote setup export <path>');
59
+ ctx.print('Usage: /remote setup export <path> --yes');
60
+ return true;
61
+ }
62
+ if (!parsed.yes) {
63
+ requireYesFlag(ctx, `export remote setup bundle to ${pathArg}`, '/remote setup export <path> --yes');
57
64
  return true;
58
65
  }
59
66
  const shellPaths = requireShellPaths(ctx);
@@ -79,10 +86,14 @@ export async function handleRemoteSetupCommand(
79
86
  `export ACP_AGENT_CMD='${command.join(' ')}'`,
80
87
  `export GOODVIBES_REMOTE_SESSION='${ctx.session.runtime.sessionId}'`,
81
88
  ].join('\n');
82
- if (args[1]?.toLowerCase() === 'export') {
83
- const pathArg = args[2];
89
+ if (commandArgs[1]?.toLowerCase() === 'export') {
90
+ const pathArg = commandArgs[2];
84
91
  if (!pathArg) {
85
- ctx.print('Usage: /remote env export <path>');
92
+ ctx.print('Usage: /remote env export <path> --yes');
93
+ return true;
94
+ }
95
+ if (!parsed.yes) {
96
+ requireYesFlag(ctx, `export remote environment snippet to ${pathArg}`, '/remote env export <path> --yes');
86
97
  return true;
87
98
  }
88
99
  const shellPaths = requireShellPaths(ctx);
@@ -97,7 +108,7 @@ export async function handleRemoteSetupCommand(
97
108
  }
98
109
 
99
110
  if (subcommand === 'tunnel') {
100
- const mode = args[1]?.toLowerCase() ?? 'review';
111
+ const mode = commandArgs[1]?.toLowerCase() ?? 'review';
101
112
  const lines = [
102
113
  'Remote Tunnel Review',
103
114
  ' transport: self-hosted ACP / daemon relay',
@@ -106,9 +117,13 @@ export async function handleRemoteSetupCommand(
106
117
  ' guidance: forward ACP agent traffic through your chosen self-hosted tunnel or SSH transport',
107
118
  ];
108
119
  if (mode === 'export') {
109
- const pathArg = args[2];
120
+ const pathArg = commandArgs[2];
110
121
  if (!pathArg) {
111
- ctx.print('Usage: /remote tunnel export <path>');
122
+ ctx.print('Usage: /remote tunnel export <path> --yes');
123
+ return true;
124
+ }
125
+ if (!parsed.yes) {
126
+ requireYesFlag(ctx, `export remote tunnel review to ${pathArg}`, '/remote tunnel export <path> --yes');
112
127
  return true;
113
128
  }
114
129
  const shellPaths = requireShellPaths(ctx);
@@ -123,7 +138,7 @@ export async function handleRemoteSetupCommand(
123
138
  }
124
139
 
125
140
  if (subcommand === 'bootstrap') {
126
- const mode = args[1]?.toLowerCase() ?? 'export';
141
+ const mode = commandArgs[1]?.toLowerCase() ?? 'export';
127
142
  const payload = {
128
143
  exportedAt: Date.now(),
129
144
  sessionId: ctx.session.runtime.sessionId,
@@ -138,7 +153,7 @@ export async function handleRemoteSetupCommand(
138
153
  ],
139
154
  };
140
155
  if (mode === 'inspect') {
141
- const pathArg = args[2];
156
+ const pathArg = commandArgs[2];
142
157
  if (!pathArg) {
143
158
  ctx.print('Usage: /remote bootstrap inspect <path>');
144
159
  return true;
@@ -154,9 +169,13 @@ export async function handleRemoteSetupCommand(
154
169
  ].join('\n'));
155
170
  return true;
156
171
  }
157
- const pathArg = args[2] ?? args[1];
172
+ const pathArg = commandArgs[2] ?? commandArgs[1];
158
173
  if (!pathArg || mode !== 'export') {
159
- ctx.print('Usage: /remote bootstrap export <path> | /remote bootstrap inspect <path>');
174
+ ctx.print('Usage: /remote bootstrap export <path> --yes | /remote bootstrap inspect <path>');
175
+ return true;
176
+ }
177
+ if (!parsed.yes) {
178
+ requireYesFlag(ctx, `export remote bootstrap bundle to ${pathArg}`, '/remote bootstrap export <path> --yes');
160
179
  return true;
161
180
  }
162
181
  const shellPaths = requireShellPaths(ctx);
@@ -168,15 +187,19 @@ export async function handleRemoteSetupCommand(
168
187
  }
169
188
 
170
189
  if (subcommand === 'session') {
171
- const mode = args[1]?.toLowerCase();
172
- const pathArg = args[2];
190
+ const mode = commandArgs[1]?.toLowerCase();
191
+ const pathArg = commandArgs[2];
173
192
  if (!mode || !pathArg) {
174
- ctx.print('Usage: /remote session <export|inspect|import> <path>');
193
+ ctx.print('Usage: /remote session <export|inspect|import> <path> [--yes]');
175
194
  return true;
176
195
  }
177
196
  const shellPaths = requireShellPaths(ctx);
178
197
  const targetPath = shellPaths.resolveWorkspacePath(pathArg);
179
198
  if (mode === 'export') {
199
+ if (!parsed.yes) {
200
+ requireYesFlag(ctx, `export remote session bundle to ${pathArg}`, '/remote session export <path> --yes');
201
+ return true;
202
+ }
180
203
  const exported = await remoteRegistry.exportSessionBundle(targetPath);
181
204
  ctx.print(`Exported remote session bundle ${exported.bundle.sessionId} to ${exported.path}`);
182
205
  return true;
@@ -187,11 +210,15 @@ export async function handleRemoteSetupCommand(
187
210
  return true;
188
211
  }
189
212
  if (mode === 'import') {
213
+ if (!parsed.yes) {
214
+ requireYesFlag(ctx, `import remote session bundle from ${pathArg}`, '/remote session import <path> --yes');
215
+ return true;
216
+ }
190
217
  const bundle = await remoteRegistry.importSessionBundle(targetPath);
191
218
  ctx.print(`Imported remote session bundle ${bundle.sessionId} with ${bundle.contracts.length} contracts.`);
192
219
  return true;
193
220
  }
194
- ctx.print('Usage: /remote session <export|inspect|import> <path>');
221
+ ctx.print('Usage: /remote session <export|inspect|import> <path> [--yes]');
195
222
  return true;
196
223
  }
197
224
 
@@ -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',