@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 type { CommandContext, CommandRegistry } from '../command-registry.ts';
4
4
  import { listInstalledEcosystemEntries, loadEcosystemCatalog } from '@/runtime/index.ts';
5
5
  import { BUILTIN_SUITES } from '@/runtime/index.ts';
6
6
  import { requireEcosystemCatalogPaths, requireReadModels, requireSecretsManager, requireServiceRegistry, requireShellPaths } from './runtime-services.ts';
7
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
7
8
 
8
9
  interface TrustReviewBundle {
9
10
  readonly version: 1;
@@ -155,23 +156,29 @@ export function registerProductRuntimeCommands(registry: CommandRegistry): void
155
156
  registry.register({
156
157
  name: 'trust',
157
158
  description: 'Review trust posture and export portable trust bundles',
158
- usage: '[review|bundle export <path>|bundle inspect <path>]',
159
+ usage: '[review|bundle export <path> --yes|bundle inspect <path>]',
159
160
  async handler(args, ctx) {
161
+ const parsed = stripYesFlag(args);
162
+ const commandArgs = [...parsed.rest];
160
163
  const shellPaths = requireShellPaths(ctx);
161
- const sub = args[0] ?? 'review';
164
+ const sub = commandArgs[0] ?? 'review';
162
165
  if (sub === 'review') {
163
166
  const bundle = await buildTrustReviewBundle(ctx);
164
167
  ctx.print(formatTrustReview(bundle));
165
168
  return;
166
169
  }
167
170
  if (sub === 'bundle') {
168
- const mode = args[1];
169
- const pathArg = args[2];
171
+ const mode = commandArgs[1];
172
+ const pathArg = commandArgs[2];
170
173
  if ((mode === 'export' || mode === 'inspect') && !pathArg) {
171
- ctx.print(`Usage: /trust bundle ${mode} <path>`);
174
+ ctx.print(`Usage: /trust bundle ${mode} <path>${mode === 'export' ? ' --yes' : ''}`);
172
175
  return;
173
176
  }
174
177
  if (mode === 'export') {
178
+ if (!parsed.yes) {
179
+ requireYesFlag(ctx, `export trust bundle to ${pathArg}`, '/trust bundle export <path> --yes');
180
+ return;
181
+ }
175
182
  const bundle = await buildTrustReviewBundle(ctx);
176
183
  const targetPath = shellPaths.resolveWorkspacePath(pathArg!);
177
184
  mkdirSync(dirname(targetPath), { recursive: true });
@@ -184,21 +191,23 @@ export function registerProductRuntimeCommands(registry: CommandRegistry): void
184
191
  return;
185
192
  }
186
193
  }
187
- ctx.print('Usage: /trust [review|bundle export <path>|bundle inspect <path>]');
194
+ ctx.print('Usage: /trust [review|bundle export <path> --yes|bundle inspect <path>]');
188
195
  },
189
196
  });
190
197
  registry.register({
191
198
  name: 'bridge',
192
199
  description: 'Review and operate self-hosted bridge and remote runner flows',
193
- usage: '[status|pools|assign <pool> <runner>|runner <id>|review <artifactId>|export <artifactId> [path]|import <path>]',
200
+ usage: '[status|pools|assign <pool> <runner> --yes|runner <id>|review <artifactId>|export <artifactId> [path] --yes|import <path> --yes]',
194
201
  async handler(args, ctx) {
202
+ const parsed = stripYesFlag(args);
203
+ const commandArgs = [...parsed.rest];
195
204
  const shellPaths = requireShellPaths(ctx);
196
205
  if (!ctx.ops.remoteRuntime) {
197
206
  ctx.print('Remote runner registry is not available in this runtime.');
198
207
  return;
199
208
  }
200
209
  const remoteRegistry = ctx.ops.remoteRuntime;
201
- const sub = args[0] ?? 'status';
210
+ const sub = commandArgs[0] ?? 'status';
202
211
  if (sub === 'status') {
203
212
  const remote = requireReadModels(ctx).remote.getSnapshot();
204
213
  ctx.print([
@@ -217,10 +226,14 @@ export function registerProductRuntimeCommands(registry: CommandRegistry): void
217
226
  return;
218
227
  }
219
228
  if (sub === 'assign') {
220
- const poolId = args[1];
221
- const runnerId = args[2];
229
+ const poolId = commandArgs[1];
230
+ const runnerId = commandArgs[2];
222
231
  if (!poolId || !runnerId) {
223
- ctx.print('Usage: /bridge assign <pool> <runner>');
232
+ ctx.print('Usage: /bridge assign <pool> <runner> --yes');
233
+ return;
234
+ }
235
+ if (!parsed.yes) {
236
+ requireYesFlag(ctx, `assign bridge runner ${runnerId} to pool ${poolId}`, '/bridge assign <pool> <runner> --yes');
224
237
  return;
225
238
  }
226
239
  const pool = remoteRegistry.assignRunnerToPool(poolId, runnerId);
@@ -232,7 +245,7 @@ export function registerProductRuntimeCommands(registry: CommandRegistry): void
232
245
  return;
233
246
  }
234
247
  if (sub === 'runner') {
235
- const runnerId = args[1];
248
+ const runnerId = commandArgs[1];
236
249
  if (!runnerId) {
237
250
  ctx.print('Usage: /bridge runner <id>');
238
251
  return;
@@ -253,7 +266,7 @@ export function registerProductRuntimeCommands(registry: CommandRegistry): void
253
266
  return;
254
267
  }
255
268
  if (sub === 'review') {
256
- const artifactId = args[1];
269
+ const artifactId = commandArgs[1];
257
270
  if (!artifactId) {
258
271
  ctx.print('Usage: /bridge review <artifactId>');
259
272
  return;
@@ -263,14 +276,18 @@ export function registerProductRuntimeCommands(registry: CommandRegistry): void
263
276
  return;
264
277
  }
265
278
  if (sub === 'export') {
266
- const artifactId = args[1];
279
+ const artifactId = commandArgs[1];
267
280
  if (!artifactId) {
268
- ctx.print('Usage: /bridge export <artifactId> [path]');
281
+ ctx.print('Usage: /bridge export <artifactId> [path] --yes');
282
+ return;
283
+ }
284
+ if (!parsed.yes) {
285
+ requireYesFlag(ctx, `export bridge artifact ${artifactId}`, '/bridge export <artifactId> [path] --yes');
269
286
  return;
270
287
  }
271
288
  const exported = await remoteRegistry.exportArtifact(
272
289
  artifactId,
273
- args[2] ? shellPaths.resolveWorkspacePath(args[2]) : undefined,
290
+ commandArgs[2] ? shellPaths.resolveWorkspacePath(commandArgs[2]) : undefined,
274
291
  );
275
292
  if (!exported) {
276
293
  ctx.print(`Unknown remote artifact: ${artifactId}`);
@@ -280,26 +297,32 @@ export function registerProductRuntimeCommands(registry: CommandRegistry): void
280
297
  return;
281
298
  }
282
299
  if (sub === 'import') {
283
- const pathArg = args[1];
300
+ const pathArg = commandArgs[1];
284
301
  if (!pathArg) {
285
- ctx.print('Usage: /bridge import <path>');
302
+ ctx.print('Usage: /bridge import <path> --yes');
303
+ return;
304
+ }
305
+ if (!parsed.yes) {
306
+ requireYesFlag(ctx, `import bridge artifact from ${pathArg}`, '/bridge import <path> --yes');
286
307
  return;
287
308
  }
288
309
  const artifact = await remoteRegistry.importArtifact(shellPaths.resolveWorkspacePath(pathArg));
289
310
  ctx.print(`Imported remote bridge artifact ${artifact.id} for runner ${artifact.runnerId}.`);
290
311
  return;
291
312
  }
292
- ctx.print('Usage: /bridge [status|pools|assign <pool> <runner>|runner <id>|review <artifactId>|export <artifactId> [path]|import <path>]');
313
+ ctx.print('Usage: /bridge [status|pools|assign <pool> <runner> --yes|runner <id>|review <artifactId>|export <artifactId> [path] --yes|import <path> --yes]');
293
314
  },
294
315
  });
295
316
 
296
317
  registry.register({
297
318
  name: 'release',
298
319
  description: 'Package certification and release-readiness operations',
299
- usage: '[review|checklist|bundle export <path>|bundle inspect <path>]',
320
+ usage: '[review|checklist|bundle export <path> --yes|bundle inspect <path>]',
300
321
  handler(args, ctx) {
322
+ const parsed = stripYesFlag(args);
323
+ const commandArgs = [...parsed.rest];
301
324
  const shellPaths = requireShellPaths(ctx);
302
- const sub = args[0] ?? 'review';
325
+ const sub = commandArgs[0] ?? 'review';
303
326
  if (sub === 'review') {
304
327
  const bundle = buildReleaseBundle(ctx);
305
328
  ctx.print([
@@ -319,20 +342,24 @@ export function registerProductRuntimeCommands(registry: CommandRegistry): void
319
342
  ' 1. Run /setup review and /setup doctor',
320
343
  ' 2. Run /security review and /trust review',
321
344
  ' 3. Run /policy preflight and /policy simulate',
322
- ' 4. Run /eval gate <suite> for required certification suites',
345
+ ' 4. Run /eval gate <suite> --yes for required certification suites',
323
346
  ' 5. Review /incident latest and /bridge status',
324
- ' 6. Export /release bundle export <path> for release evidence',
347
+ ' 6. Export /release bundle export <path> --yes for release evidence',
325
348
  ].join('\n'));
326
349
  return;
327
350
  }
328
351
  if (sub === 'bundle') {
329
- const mode = args[1];
330
- const pathArg = args[2];
352
+ const mode = commandArgs[1];
353
+ const pathArg = commandArgs[2];
331
354
  if ((mode === 'export' || mode === 'inspect') && !pathArg) {
332
- ctx.print(`Usage: /release bundle ${mode} <path>`);
355
+ ctx.print(`Usage: /release bundle ${mode} <path>${mode === 'export' ? ' --yes' : ''}`);
333
356
  return;
334
357
  }
335
358
  if (mode === 'export') {
359
+ if (!parsed.yes) {
360
+ requireYesFlag(ctx, `export release bundle to ${pathArg}`, '/release bundle export <path> --yes');
361
+ return;
362
+ }
336
363
  const bundle = buildReleaseBundle(ctx);
337
364
  const targetPath = shellPaths.resolveWorkspacePath(pathArg!);
338
365
  mkdirSync(dirname(targetPath), { recursive: true });
@@ -345,7 +372,7 @@ export function registerProductRuntimeCommands(registry: CommandRegistry): void
345
372
  return;
346
373
  }
347
374
  }
348
- ctx.print('Usage: /release [review|checklist|bundle export <path>|bundle inspect <path>]');
375
+ ctx.print('Usage: /release [review|checklist|bundle export <path> --yes|bundle inspect <path>]');
349
376
  },
350
377
  });
351
378
  }
@@ -4,6 +4,7 @@ import type { CommandRegistry } from '../command-registry.ts';
4
4
  import type { ProfileBundleEntry, ProfileSyncBundle } from '@/runtime/index.ts';
5
5
  import { recordSettingsSyncEvent, recordSettingsSyncFailure } from '@/runtime/index.ts';
6
6
  import { requireProfileManager, requireShellPaths } from './runtime-services.ts';
7
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
7
8
 
8
9
  function inspectProfileSyncBundle(bundle: ProfileSyncBundle): string {
9
10
  return [
@@ -18,11 +19,13 @@ export function registerProfileSyncRuntimeCommands(registry: CommandRegistry): v
18
19
  registry.register({
19
20
  name: 'profilesync',
20
21
  description: 'Export, import, and inspect profile sync bundles',
21
- usage: '[list|export <path>|inspect <path>|import <path> [prefix]]',
22
+ usage: '[list|export <path> --yes|inspect <path>|import <path> [prefix] --yes]',
22
23
  handler(args, ctx) {
24
+ const parsed = stripYesFlag(args);
25
+ const commandArgs = [...parsed.rest];
23
26
  const shellPaths = requireShellPaths(ctx);
24
27
  const controlPlaneConfigDir = ctx.platform.configManager.getControlPlaneConfigDir();
25
- const sub = args[0] ?? 'list';
28
+ const sub = commandArgs[0] ?? 'list';
26
29
  const pm = requireProfileManager(ctx);
27
30
  if (sub === 'list') {
28
31
  const profiles = pm.list();
@@ -34,14 +37,18 @@ export function registerProfileSyncRuntimeCommands(registry: CommandRegistry): v
34
37
  return;
35
38
  }
36
39
 
37
- const pathArg = args[1];
40
+ const pathArg = commandArgs[1];
38
41
  if (!pathArg) {
39
- ctx.print(`Usage: /profilesync ${sub} <path>${sub === 'import' ? ' [prefix]' : ''}`);
42
+ ctx.print(`Usage: /profilesync ${sub} <path>${sub === 'import' ? ' [prefix]' : ''}${sub === 'export' || sub === 'import' ? ' --yes' : ''}`);
40
43
  return;
41
44
  }
42
45
  const targetPath = shellPaths.resolveWorkspacePath(pathArg);
43
46
 
44
47
  if (sub === 'export') {
48
+ if (!parsed.yes) {
49
+ requireYesFlag(ctx, `export profile sync bundle to ${pathArg}`, '/profilesync export <path> --yes');
50
+ return;
51
+ }
45
52
  const profiles = pm.list().map((profile) => {
46
53
  const loaded = pm.load(profile.name);
47
54
  return {
@@ -75,8 +82,12 @@ export function registerProfileSyncRuntimeCommands(registry: CommandRegistry): v
75
82
  }
76
83
 
77
84
  if (sub === 'import') {
85
+ if (!parsed.yes) {
86
+ requireYesFlag(ctx, `import profile sync bundle from ${pathArg}`, '/profilesync import <path> [prefix] --yes');
87
+ return;
88
+ }
78
89
  const bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as ProfileSyncBundle;
79
- const prefix = args[2]?.trim() ?? '';
90
+ const prefix = commandArgs[2]?.trim() ?? '';
80
91
  for (const entry of bundle.profiles) {
81
92
  const name = prefix ? `${prefix}-${entry.name}` : entry.name;
82
93
  pm.save(name, entry.data);
@@ -93,7 +104,7 @@ export function registerProfileSyncRuntimeCommands(registry: CommandRegistry): v
93
104
  }
94
105
 
95
106
  recordSettingsSyncFailure('profiles', `unsupported subcommand: ${sub}`, controlPlaneConfigDir);
96
- ctx.print('Usage: /profilesync [list|export <path>|inspect <path>|import <path> [prefix]]');
107
+ ctx.print('Usage: /profilesync [list|export <path> --yes|inspect <path>|import <path> [prefix] --yes]');
97
108
  },
98
109
  });
99
110
  }
@@ -6,23 +6,30 @@ import { VALID_CLASSES, VALID_SCOPES, isValidClass, isValidScope, resolveBundleP
6
6
  import { requireShellPaths } from './runtime-services.ts';
7
7
  import { getMemoryApi } from './recall-query.ts';
8
8
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
9
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
9
10
 
10
11
  export function handleRecallExport(args: string[], context: CommandContext): void {
12
+ const parsed = stripYesFlag(args);
13
+ const commandArgs = [...parsed.rest];
11
14
  const memory = getMemoryApi(context);
12
15
  if (!memory) {
13
16
  return;
14
17
  }
15
18
 
16
- const pathArg = args[0];
19
+ const pathArg = commandArgs[0];
17
20
  if (!pathArg) {
18
- context.print('[recall] Usage: /recall export <path> [--scope <scope>] [--cls <class>]');
21
+ context.print('[recall] Usage: /recall export <path> [--scope <scope>] [--cls <class>] --yes');
22
+ return;
23
+ }
24
+ if (!parsed.yes) {
25
+ requireYesFlag(context, `export durable memory bundle to ${pathArg}`, '/recall export <path> [--scope <scope>] [--cls <class>] --yes');
19
26
  return;
20
27
  }
21
28
 
22
29
  const filter: MemorySearchFilter = {};
23
- const scopeIdx = args.indexOf('--scope');
24
- if (scopeIdx !== -1 && args[scopeIdx + 1]) {
25
- const scope = args[scopeIdx + 1];
30
+ const scopeIdx = commandArgs.indexOf('--scope');
31
+ if (scopeIdx !== -1 && commandArgs[scopeIdx + 1]) {
32
+ const scope = commandArgs[scopeIdx + 1];
26
33
  if (!isValidScope(scope)) {
27
34
  context.print(`[recall] Unknown scope "${scope}". Valid: ${VALID_SCOPES.join(', ')}`);
28
35
  return;
@@ -30,9 +37,9 @@ export function handleRecallExport(args: string[], context: CommandContext): voi
30
37
  filter.scope = scope;
31
38
  }
32
39
 
33
- const clsIdx = args.indexOf('--cls');
34
- if (clsIdx !== -1 && args[clsIdx + 1]) {
35
- const cls = args[clsIdx + 1];
40
+ const clsIdx = commandArgs.indexOf('--cls');
41
+ if (clsIdx !== -1 && commandArgs[clsIdx + 1]) {
42
+ const cls = commandArgs[clsIdx + 1];
36
43
  if (!isValidClass(cls)) {
37
44
  context.print(`[recall] Unknown class "${cls}". Valid: ${VALID_CLASSES.join(', ')}`);
38
45
  return;
@@ -48,14 +55,20 @@ export function handleRecallExport(args: string[], context: CommandContext): voi
48
55
  }
49
56
 
50
57
  export async function handleRecallImport(args: string[], context: CommandContext): Promise<void> {
58
+ const parsed = stripYesFlag(args);
59
+ const commandArgs = [...parsed.rest];
51
60
  const memory = getMemoryApi(context);
52
61
  if (!memory) {
53
62
  return;
54
63
  }
55
64
 
56
- const pathArg = args[0];
65
+ const pathArg = commandArgs[0];
57
66
  if (!pathArg) {
58
- context.print('[recall] Usage: /recall import <path>');
67
+ context.print('[recall] Usage: /recall import <path> --yes');
68
+ return;
69
+ }
70
+ if (!parsed.yes) {
71
+ requireYesFlag(context, `import durable memory bundle from ${pathArg}`, '/recall import <path> --yes');
59
72
  return;
60
73
  }
61
74
 
@@ -85,17 +98,23 @@ function inspectBundle(bundle: MemoryBundle): string {
85
98
  }
86
99
 
87
100
  export function handleRecallHandoffExport(args: string[], context: CommandContext): void {
101
+ const parsed = stripYesFlag(args);
102
+ const commandArgs = [...parsed.rest];
88
103
  const memory = getMemoryApi(context);
89
104
  if (!memory) {
90
105
  return;
91
106
  }
92
- const pathArg = args[0];
107
+ const pathArg = commandArgs[0];
93
108
  if (!pathArg) {
94
- context.print('[recall] Usage: /recall handoff-export <path> [--scope <scope>]');
109
+ context.print('[recall] Usage: /recall handoff-export <path> [--scope <scope>] --yes');
95
110
  return;
96
111
  }
97
- const scopeIdx = args.indexOf('--scope');
98
- const scopeRaw = scopeIdx !== -1 ? args[scopeIdx + 1] : 'team';
112
+ if (!parsed.yes) {
113
+ requireYesFlag(context, `export memory handoff bundle to ${pathArg}`, '/recall handoff-export <path> [--scope <scope>] --yes');
114
+ return;
115
+ }
116
+ const scopeIdx = commandArgs.indexOf('--scope');
117
+ const scopeRaw = scopeIdx !== -1 ? commandArgs[scopeIdx + 1] : 'team';
99
118
  if (!scopeRaw || !isValidScope(scopeRaw)) {
100
119
  context.print(`[recall] Unknown scope "${scopeRaw ?? ''}". Valid: ${VALID_SCOPES.join(', ')}`);
101
120
  return;
@@ -123,10 +142,12 @@ export function handleRecallHandoffInspect(args: string[], context: CommandConte
123
142
  }
124
143
 
125
144
  export async function handleRecallHandoffImport(args: string[], context: CommandContext): Promise<void> {
126
- const pathArg = args[0];
145
+ const parsed = stripYesFlag(args);
146
+ const commandArgs = [...parsed.rest];
147
+ const pathArg = commandArgs[0];
127
148
  if (!pathArg) {
128
- context.print('[recall] Usage: /recall handoff-import <path>');
149
+ context.print('[recall] Usage: /recall handoff-import <path> --yes');
129
150
  return;
130
151
  }
131
- await handleRecallImport([pathArg], context);
152
+ await handleRecallImport([pathArg, ...(parsed.yes ? ['--yes'] : [])], context);
132
153
  }
@@ -2,6 +2,7 @@ import type { CommandContext } from '../command-registry.ts';
2
2
  import type { MemoryApi } from '@pellux/goodvibes-sdk/platform/knowledge';
3
3
  import type { MemorySearchFilter } from '@pellux/goodvibes-sdk/platform/state';
4
4
  import { VALID_CLASSES, VALID_SCOPES, isValidClass, isValidScope } from './recall-shared.ts';
5
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
5
6
 
6
7
  export function getMemoryApi(context: CommandContext): MemoryApi | null {
7
8
  const memoryApi = context.clients?.agentKnowledgeApi?.memory;
@@ -159,9 +160,14 @@ export async function handleRecallLink(args: string[], context: CommandContext):
159
160
  if (!memory) {
160
161
  return;
161
162
  }
162
- const [fromId, toId, relation] = args;
163
+ const parsed = stripYesFlag(args);
164
+ const [fromId, toId, relation] = parsed.rest;
163
165
  if (!fromId || !toId || !relation) {
164
- context.print('[recall] Usage: /recall link <fromId> <toId> <relation>');
166
+ context.print('[recall] Usage: /recall link <fromId> <toId> <relation> --yes');
167
+ return;
168
+ }
169
+ if (!parsed.yes) {
170
+ requireYesFlag(context, `link memory records ${fromId} and ${toId}`, '/recall link <fromId> <toId> <relation> --yes');
165
171
  return;
166
172
  }
167
173
  const link = await memory.link(fromId, toId, relation);
@@ -177,9 +183,14 @@ export function handleRecallRemove(args: string[], context: CommandContext): voi
177
183
  if (!memory) {
178
184
  return;
179
185
  }
180
- const id = args[0];
186
+ const parsed = stripYesFlag(args);
187
+ const id = parsed.rest[0];
181
188
  if (!id) {
182
- context.print('[recall] Usage: /recall remove <id>');
189
+ context.print('[recall] Usage: /recall remove <id> --yes');
190
+ return;
191
+ }
192
+ if (!parsed.yes) {
193
+ requireYesFlag(context, `delete durable memory record ${id}`, '/recall remove <id> --yes');
183
194
  return;
184
195
  }
185
196
  const removed = memory.delete(id);
@@ -1,6 +1,7 @@
1
1
  import type { CommandContext } from '../command-registry.ts';
2
2
  import { VALID_REVIEW_STATES, VALID_SCOPES, isValidReviewState, isValidScope } from './recall-shared.ts';
3
3
  import { getMemoryApi } from './recall-query.ts';
4
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
4
5
 
5
6
  export function handleRecallQueue(args: string[], context: CommandContext): void {
6
7
  const memory = getMemoryApi(context);
@@ -83,10 +84,15 @@ export function handleRecallPromote(args: string[], context: CommandContext): vo
83
84
  if (!memory) {
84
85
  return;
85
86
  }
86
- const id = args[0];
87
- const scope = args[1];
87
+ const parsed = stripYesFlag(args);
88
+ const id = parsed.rest[0];
89
+ const scope = parsed.rest[1];
88
90
  if (!id || !scope || !isValidScope(scope)) {
89
- context.print(`[recall] Usage: /recall promote <id> <${VALID_SCOPES.join('|')}>`);
91
+ context.print(`[recall] Usage: /recall promote <id> <${VALID_SCOPES.join('|')}> --yes`);
92
+ return;
93
+ }
94
+ if (!parsed.yes) {
95
+ requireYesFlag(context, `promote durable memory record ${id} to ${scope} scope`, '/recall promote <id> <scope> --yes');
90
96
  return;
91
97
  }
92
98
  const record = memory.update(id, { scope });
@@ -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