@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
@@ -127,7 +127,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
127
127
  { id: '/clear', label: '/clear', detail: 'Clear display (keep context)', category: 'Conversation' },
128
128
  { id: '/reset', label: '/reset', detail: 'Clear display + context', category: 'Conversation' },
129
129
  { id: '/compact', label: '/compact', detail: 'Summarize to free context', category: 'Conversation' },
130
- { id: '/export', label: '/export [file]', detail: 'Export as markdown', category: 'Conversation' },
130
+ { id: '/export', label: '/export [file] --yes', detail: 'Export as markdown', category: 'Conversation' },
131
131
  { id: '/title', label: '/title [text]', detail: 'Show or set title', category: 'Conversation' },
132
132
  { id: '/save', label: '/save [name]', detail: 'Save session', category: 'Conversation' },
133
133
  { id: '/load', label: '/load <name>', detail: 'Load session', category: 'Conversation' },
@@ -141,7 +141,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
141
141
  { id: '/session info', label: '/session info [id]', detail: 'Show session details', category: 'Conversation' },
142
142
  { id: '/session export', label: '/session export <id> [format]', detail: 'Export session as markdown/text', category: 'Conversation' },
143
143
  { id: '/session search', label: '/session search <query>', detail: 'Search across all sessions', category: 'Conversation' },
144
- { id: '/session delete', label: '/session delete <id>', detail: 'Delete a session', category: 'Conversation' },
144
+ { id: '/session delete', label: '/session delete <id> --yes', detail: 'Delete a session', category: 'Conversation' },
145
145
  { id: '/undo', label: '/undo', detail: 'Remove last turn', category: 'Conversation' },
146
146
  { id: '/redo', label: '/redo', detail: 'Restore undone turn', category: 'Conversation' },
147
147
  { id: '/retry', label: '/retry [text]', detail: 'Re-send last message', category: 'Conversation' },
@@ -149,7 +149,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
149
149
  { id: '/branch', label: '/branch [name]', detail: 'List branches or switch to one', category: 'Conversation' },
150
150
  { id: '/merge', label: '/merge <name>', detail: 'Append messages from a branch', category: 'Conversation' },
151
151
  { id: '/template', label: '/template', detail: 'Browse templates', category: 'Templates' },
152
- { id: '/template save', label: '/template save <name>', detail: 'Save prompt as template', category: 'Templates' },
152
+ { id: '/template save', label: '/template save <name> --yes', detail: 'Save prompt as template', category: 'Templates' },
153
153
  { id: '/template use', label: '/template use <name>', detail: 'Execute template', category: 'Templates' },
154
154
  { id: '/tools', label: '/tools', detail: 'List available tools', category: 'Tools & System' },
155
155
  { id: '/paste', label: '/paste', detail: 'Insert clipboard text or image into the prompt', category: 'Tools & System' },
@@ -9,6 +9,7 @@ import { inspectProviderAuth } from '@/runtime/index.ts';
9
9
  import { openExternalUrl } from '@pellux/goodvibes-sdk/platform/utils';
10
10
  import { requireSecretsManager, requireServiceRegistry, requireShellPaths, requireSubscriptionManager } from './runtime-services.ts';
11
11
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
12
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
12
13
 
13
14
  interface SubscriptionBundle {
14
15
  readonly version: 1;
@@ -76,14 +77,16 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
76
77
  name: 'subscription',
77
78
  aliases: ['subs'],
78
79
  description: 'Manage provider subscription sessions and, when supported, let them override ambient API keys for matching providers',
79
- usage: '[review|list|providers|inspect <provider>|login <provider> start [--no-browser] [--manual]|finish <code-or-url>|logout <provider>|bundle export <path>|bundle inspect <path>]',
80
+ usage: '[review|list|providers|inspect <provider>|login <provider> start [--no-browser] [--manual] --yes|login <provider> finish <code-or-url> --yes|logout <provider> --yes|bundle export <path> --yes|bundle inspect <path>]',
80
81
  async handler(args, ctx) {
82
+ const parsed = stripYesFlag(args);
83
+ const commandArgs = [...parsed.rest];
81
84
  const shellPaths = requireShellPaths(ctx);
82
85
  if (args.length === 0 && ctx.openSubscriptionPanel) {
83
86
  ctx.openSubscriptionPanel();
84
87
  return;
85
88
  }
86
- const sub = (args[0] ?? 'review').toLowerCase();
89
+ const sub = (commandArgs[0] ?? 'review').toLowerCase();
87
90
  const manager = requireSubscriptionManager(ctx);
88
91
  const services = requireServiceRegistry(ctx);
89
92
 
@@ -108,7 +111,7 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
108
111
  }
109
112
 
110
113
  if (sub === 'inspect') {
111
- const provider = args[1];
114
+ const provider = commandArgs[1];
112
115
  if (!provider) {
113
116
  ctx.print('Usage: /subscription inspect <provider>');
114
117
  return;
@@ -153,10 +156,14 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
153
156
  }
154
157
 
155
158
  if (sub === 'login') {
156
- const provider = args[1];
157
- const mode = args[2]?.toLowerCase();
159
+ const provider = commandArgs[1];
160
+ const mode = commandArgs[2]?.toLowerCase();
158
161
  if (!provider || !mode) {
159
- ctx.print('Usage: /subscription login <provider> start|finish <code>');
162
+ ctx.print('Usage: /subscription login <provider> start|finish <code> --yes');
163
+ return;
164
+ }
165
+ if (!parsed.yes) {
166
+ requireYesFlag(ctx, `${mode} provider subscription login for ${provider}`, '/subscription login <provider> start|finish <code> --yes');
160
167
  return;
161
168
  }
162
169
  const service = services.get(provider);
@@ -170,7 +177,7 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
170
177
  return;
171
178
  }
172
179
  if (mode === 'start') {
173
- const flags = new Set(args.slice(3));
180
+ const flags = new Set(commandArgs.slice(3));
174
181
  const openBrowser = !flags.has('--no-browser');
175
182
  const useManualMode = flags.has('--manual');
176
183
  if (provider === 'openai' && resolved.source === 'builtin') {
@@ -240,7 +247,7 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
240
247
  ` state: ${started.state}`,
241
248
  ` redirectUri: ${started.redirectUri}`,
242
249
  ` browser: ${openBrowser ? (browserOpened ? 'opened' : 'open failed') : 'skipped'}`,
243
- ` next: /subscription login ${provider} finish <code-or-url>`,
250
+ ` next: /subscription login ${provider} finish <code-or-url> --yes`,
244
251
  ` listener: ${summarizeError(error)}`,
245
252
  ' authorizationUrl:',
246
253
  ` ${started.authorizationUrl}`,
@@ -256,7 +263,7 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
256
263
  ` state: ${started.state}`,
257
264
  ` redirectUri: ${started.redirectUri}`,
258
265
  ` browser: ${openBrowser ? (browserOpened ? 'opened' : 'open failed') : 'skipped'}`,
259
- ` next: /subscription login ${provider} finish <code-or-url>`,
266
+ ` next: /subscription login ${provider} finish <code-or-url> --yes`,
260
267
  ' authorizationUrl:',
261
268
  ` ${started.authorizationUrl}`,
262
269
  ].join('\n'));
@@ -316,7 +323,7 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
316
323
  ` state: ${started.pending.state}`,
317
324
  ` redirectUri: ${activeConfig.redirectUri}`,
318
325
  ` browser: ${openBrowser ? (browserOpened ? 'opened' : 'open failed') : 'skipped'}`,
319
- ` next: /subscription login ${provider} finish <code-or-url>`,
326
+ ` next: /subscription login ${provider} finish <code-or-url> --yes`,
320
327
  ` listener: ${summarizeError(error)}`,
321
328
  ' authorizationUrl:',
322
329
  ` ${started.authorizationUrl}`,
@@ -333,23 +340,23 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
333
340
  ` state: ${started.pending.state}`,
334
341
  ` redirectUri: ${activeConfig.redirectUri}`,
335
342
  ` browser: ${openBrowser ? (browserOpened ? 'opened' : 'open failed') : 'skipped'}`,
336
- ` next: /subscription login ${provider} finish <code-or-url>`,
343
+ ` next: /subscription login ${provider} finish <code-or-url> --yes`,
337
344
  ' authorizationUrl:',
338
345
  ` ${started.authorizationUrl}`,
339
346
  ].join('\n'));
340
347
  return;
341
348
  }
342
349
  if (mode === 'finish') {
343
- const codeInput = args[3];
350
+ const codeInput = commandArgs[3];
344
351
  if (!codeInput) {
345
- ctx.print(`Usage: /subscription login ${provider} finish <code-or-url>`);
352
+ ctx.print(`Usage: /subscription login ${provider} finish <code-or-url> --yes`);
346
353
  return;
347
354
  }
348
355
  const code = extractAuthorizationCode(codeInput) ?? codeInput;
349
356
  if (provider === 'openai' && resolved.source === 'builtin') {
350
357
  const pending = manager.getPending(provider);
351
358
  if (!pending) {
352
- ctx.print(`No pending OAuth login for ${provider}. Start with /subscription login ${provider} start.`);
359
+ ctx.print(`No pending OAuth login for ${provider}. Start with /subscription login ${provider} start --yes.`);
353
360
  return;
354
361
  }
355
362
  const token = await exchangeOpenAICodexCode(code, pending.verifier);
@@ -384,14 +391,18 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
384
391
  ].join('\n'));
385
392
  return;
386
393
  }
387
- ctx.print('Usage: /subscription login <provider> start|finish <code-or-url>');
394
+ ctx.print('Usage: /subscription login <provider> start|finish <code-or-url> --yes');
388
395
  return;
389
396
  }
390
397
 
391
398
  if (sub === 'logout') {
392
- const provider = args[1];
399
+ const provider = commandArgs[1];
393
400
  if (!provider) {
394
- ctx.print('Usage: /subscription logout <provider>');
401
+ ctx.print('Usage: /subscription logout <provider> --yes');
402
+ return;
403
+ }
404
+ if (!parsed.yes) {
405
+ requireYesFlag(ctx, `log out provider subscription ${provider}`, '/subscription logout <provider> --yes');
395
406
  return;
396
407
  }
397
408
  const removed = manager.logout(provider);
@@ -402,14 +413,18 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
402
413
  }
403
414
 
404
415
  if (sub === 'bundle') {
405
- const mode = args[1]?.toLowerCase();
406
- const pathArg = args[2];
416
+ const mode = commandArgs[1]?.toLowerCase();
417
+ const pathArg = commandArgs[2];
407
418
  if (!mode || !pathArg) {
408
419
  ctx.print('Usage: /subscription bundle <export|inspect> <path>');
409
420
  return;
410
421
  }
411
422
  const targetPath = shellPaths.resolveWorkspacePath(pathArg);
412
423
  if (mode === 'export') {
424
+ if (!parsed.yes) {
425
+ requireYesFlag(ctx, `export subscription bundle to ${pathArg}`, '/subscription bundle export <path> --yes');
426
+ return;
427
+ }
413
428
  const bundle: SubscriptionBundle = {
414
429
  version: 1,
415
430
  exportedAt: Date.now(),
@@ -428,7 +443,7 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
428
443
  return;
429
444
  }
430
445
 
431
- ctx.print('Usage: /subscription [review|list|providers|inspect <provider>|login <provider> start [--no-browser] [--manual]|finish <code-or-url>|logout <provider>|bundle export <path>|bundle inspect <path>]');
446
+ ctx.print('Usage: /subscription [review|list|providers|inspect <provider>|login <provider> start [--no-browser] [--manual] --yes|login <provider> finish <code-or-url> --yes|logout <provider> --yes|bundle export <path> --yes|bundle inspect <path>]');
432
447
  },
433
448
  });
434
449
  }
@@ -2,6 +2,7 @@ import { readFileSync } from 'node:fs';
2
2
  import type { CommandRegistry } from '../command-registry.ts';
3
3
  import type { RemoteSessionBundle } from '@/runtime/index.ts';
4
4
  import { requirePeerClient, requireShellPaths } from './runtime-services.ts';
5
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
5
6
 
6
7
  function inspectRemoteSessionBundle(bundle: RemoteSessionBundle): string {
7
8
  return [
@@ -19,13 +20,15 @@ export function registerTeleportRuntimeCommands(registry: CommandRegistry): void
19
20
  registry.register({
20
21
  name: 'teleport',
21
22
  description: 'Package, inspect, and import portable remote-session handoff bundles',
22
- usage: '[export <path>|inspect <path>|import <path>]',
23
+ usage: '[export <path> --yes|inspect <path>|import <path> --yes]',
23
24
  async handler(args, ctx) {
25
+ const parsed = stripYesFlag(args);
26
+ const commandArgs = [...parsed.rest];
24
27
  const shellPaths = requireShellPaths(ctx);
25
- const mode = (args[0] ?? 'export').toLowerCase();
26
- const pathArg = args[1];
28
+ const mode = (commandArgs[0] ?? 'export').toLowerCase();
29
+ const pathArg = commandArgs[1];
27
30
  if (!pathArg) {
28
- ctx.print('Usage: /teleport [export <path>|inspect <path>|import <path>]');
31
+ ctx.print('Usage: /teleport [export <path> --yes|inspect <path>|import <path> --yes]');
29
32
  return;
30
33
  }
31
34
  let peerClient;
@@ -37,6 +40,10 @@ export function registerTeleportRuntimeCommands(registry: CommandRegistry): void
37
40
  }
38
41
  const targetPath = shellPaths.resolveWorkspacePath(pathArg);
39
42
  if (mode === 'export') {
43
+ if (!parsed.yes) {
44
+ requireYesFlag(ctx, `export teleport bundle to ${pathArg}`, '/teleport export <path> --yes');
45
+ return;
46
+ }
40
47
  const exported = await peerClient.runners.exportSessionBundle(targetPath);
41
48
  ctx.print(`Teleport bundle exported for session ${exported.bundle.sessionId} to ${exported.path}`);
42
49
  return;
@@ -47,11 +54,15 @@ export function registerTeleportRuntimeCommands(registry: CommandRegistry): void
47
54
  return;
48
55
  }
49
56
  if (mode === 'import') {
57
+ if (!parsed.yes) {
58
+ requireYesFlag(ctx, `import teleport bundle from ${pathArg}`, '/teleport import <path> --yes');
59
+ return;
60
+ }
50
61
  const bundle = await peerClient.runners.importSessionBundle(targetPath);
51
62
  ctx.print(`Imported teleport bundle ${bundle.sessionId} with ${bundle.contracts.length} contracts.`);
52
63
  return;
53
64
  }
54
- ctx.print('Usage: /teleport [export <path>|inspect <path>|import <path>]');
65
+ ctx.print('Usage: /teleport [export <path> --yes|inspect <path>|import <path> --yes]');
55
66
  },
56
67
  });
57
68
  }
@@ -3,6 +3,7 @@ import type { WorkPlanItemStatus, WorkPlanStore } from '../../work-plans/work-pl
3
3
  import { WORK_PLAN_STATUSES } from '../../work-plans/work-plan-store.ts';
4
4
  import { requirePanelManager } from './runtime-services.ts';
5
5
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
6
+ import { requireYesFlag, stripYesFlag } from './confirmation.ts';
6
7
 
7
8
  const STATUS_COMMANDS: Record<string, WorkPlanItemStatus> = {
8
9
  pending: 'pending',
@@ -81,15 +82,17 @@ export function registerWorkPlanRuntimeCommands(registry: CommandRegistry): void
81
82
  name: 'workplan',
82
83
  aliases: ['wp', 'todo'],
83
84
  description: 'Track a persistent workspace-scoped work plan',
84
- usage: '[panel|list|show|add <title> [--owner name] [--source label] [--notes text]|done <id>|start <id>|block <id>|fail <id>|cancel <id>|pending <id>|remove <id>|clear-done]',
85
+ usage: '[panel|list|show|add <title> [--owner name] [--source label] [--notes text]|done <id>|start <id>|block <id>|fail <id>|cancel <id>|pending <id>|remove <id> --yes|clear-done --yes]',
85
86
  argsHint: '[panel|add|list|done]',
86
87
  handler(args, ctx) {
88
+ const parsed = stripYesFlag(args);
89
+ const commandArgs = [...parsed.rest];
87
90
  const store = getStore(ctx);
88
91
  if (!store) {
89
92
  ctx.print('Work plan store is not available in this runtime.');
90
93
  return;
91
94
  }
92
- const subcommand = (args[0] ?? 'panel').toLowerCase();
95
+ const subcommand = (commandArgs[0] ?? 'panel').toLowerCase();
93
96
  try {
94
97
  if (subcommand === 'panel' || subcommand === 'open') {
95
98
  openPanel(ctx);
@@ -105,25 +108,29 @@ export function registerWorkPlanRuntimeCommands(registry: CommandRegistry): void
105
108
  return;
106
109
  }
107
110
  if (subcommand === 'add') {
108
- const parsed = parseAddArgs(args.slice(1));
109
- if (!parsed.title) {
111
+ const addArgs = parseAddArgs(commandArgs.slice(1));
112
+ if (!addArgs.title) {
110
113
  ctx.print('Usage: /workplan add <title> [--owner name] [--source label] [--notes text]');
111
114
  return;
112
115
  }
113
116
  const addOptions = {
114
- ...(parsed.owner ? { owner: parsed.owner } : {}),
115
- source: parsed.source ?? 'manual',
116
- ...(parsed.notes ? { notes: parsed.notes } : {}),
117
+ ...(addArgs.owner ? { owner: addArgs.owner } : {}),
118
+ source: addArgs.source ?? 'manual',
119
+ ...(addArgs.notes ? { notes: addArgs.notes } : {}),
117
120
  };
118
- const item = store.addItem(parsed.title, addOptions);
121
+ const item = store.addItem(addArgs.title, addOptions);
119
122
  openPanel(ctx);
120
123
  ctx.print(`Added work plan item ${item.id}.`);
121
124
  return;
122
125
  }
123
126
  if (subcommand === 'remove' || subcommand === 'delete' || subcommand === 'rm') {
124
- const id = args[1];
127
+ const id = commandArgs[1];
125
128
  if (!id) {
126
- ctx.print(`Usage: /workplan ${subcommand} <id>`);
129
+ ctx.print(`Usage: /workplan ${subcommand} <id> --yes`);
130
+ return;
131
+ }
132
+ if (!parsed.yes) {
133
+ requireYesFlag(ctx, `remove work plan item ${id}`, `/workplan ${subcommand} <id> --yes`);
127
134
  return;
128
135
  }
129
136
  const item = store.removeItem(id);
@@ -131,12 +138,16 @@ export function registerWorkPlanRuntimeCommands(registry: CommandRegistry): void
131
138
  return;
132
139
  }
133
140
  if (subcommand === 'clear-done' || subcommand === 'clear-completed') {
141
+ if (!parsed.yes) {
142
+ requireYesFlag(ctx, 'clear completed work plan items', `/workplan ${subcommand} --yes`);
143
+ return;
144
+ }
134
145
  const count = store.clearCompleted();
135
146
  ctx.print(`Cleared ${count} completed/cancelled work plan item${count === 1 ? '' : 's'}.`);
136
147
  return;
137
148
  }
138
149
  if (subcommand === 'cycle' || subcommand === 'toggle') {
139
- const id = args[1];
150
+ const id = commandArgs[1];
140
151
  if (!id) {
141
152
  ctx.print(`Usage: /workplan ${subcommand} <id>`);
142
153
  return;
@@ -147,7 +158,7 @@ export function registerWorkPlanRuntimeCommands(registry: CommandRegistry): void
147
158
  }
148
159
  const status = STATUS_COMMANDS[subcommand];
149
160
  if (status) {
150
- const id = args[1];
161
+ const id = commandArgs[1];
151
162
  if (!id) {
152
163
  ctx.print(`Usage: /workplan ${subcommand} <id>`);
153
164
  return;
@@ -1,4 +1,4 @@
1
- import { readFileSync, writeFileSync, existsSync } from 'node:fs';
1
+ import { readFileSync, existsSync } from 'node:fs';
2
2
  import { copyToClipboard, pasteFromClipboard, pasteImageFromClipboard } from '../utils/clipboard.ts';
3
3
  import type { InfiniteBuffer } from '../core/history.ts';
4
4
  import type { ConversationManager } from '../core/conversation';
@@ -7,7 +7,6 @@ import type { ContentPart } from '@pellux/goodvibes-sdk/platform/providers';
7
7
  import type { CommandContext } from './command-registry.ts';
8
8
  import type { BookmarkManager } from '@pellux/goodvibes-sdk/platform/bookmarks';
9
9
  import { resolveAndValidatePath } from '@pellux/goodvibes-sdk/platform/utils';
10
- import { analyzePermissionRequest } from '@pellux/goodvibes-sdk/platform/permissions';
11
10
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
12
11
  import type { SelectionManager } from './selection.ts';
13
12
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
@@ -296,6 +295,7 @@ export function handleBlockSave(
296
295
  requestRender: () => void,
297
296
  bookmarkManager: BookmarkManager,
298
297
  ): void {
298
+ void bookmarkManager;
299
299
  if (!conversationManager) return;
300
300
  const lineIndex = getScrollTop();
301
301
  const content = conversationManager.getBlockContentAtLine(lineIndex);
@@ -304,17 +304,7 @@ export function handleBlockSave(
304
304
  requestRender();
305
305
  return;
306
306
  }
307
- const nearest = conversationManager.findNearestBlock(lineIndex);
308
- const label = nearest?.type ?? 'block';
309
- try {
310
- const filePath = bookmarkManager.saveToFile(content, label);
311
- const homePath = process.env.HOME || process.env.USERPROFILE || '';
312
- const displayPath = homePath ? filePath.replace(homePath, '~') : filePath;
313
- conversationManager.log(`[Saved to: ${displayPath}]`, { fg: '#22c55e' });
314
- } catch (err) {
315
- const msg = summarizeError(err);
316
- conversationManager.log(`[Save failed: ${msg}]`, { fg: '#ef4444' });
317
- }
307
+ conversationManager.log('[Block save blocked in GoodVibes Agent: use /share <html|json|md> <path> --yes or copy the block explicitly.]', { fg: '#f59e0b' });
318
308
  requestRender();
319
309
  }
320
310
 
@@ -355,59 +345,18 @@ export function handleDiffApply(
355
345
  getCallId: () => string,
356
346
  category: PermissionCategory,
357
347
  ): boolean {
348
+ void commandContext;
349
+ void getCallId;
350
+ void category;
358
351
  if (!conversationManager) return false;
359
352
  const lineIndex = getScrollTop();
360
353
  const diff = conversationManager.getDiffAtLine(lineIndex);
361
354
  if (!diff || !diff.filePath) return false;
362
- const projectRoot = commandContext?.workspace.shellPaths?.workingDirectory
363
- ?? commandContext?.platform.configManager.getWorkingDirectory();
364
-
365
- commandContext?.requestPermission?.({
366
- callId: getCallId(),
367
- tool: 'edit',
368
- args: { path: diff.filePath, original: diff.original, updated: diff.updated },
369
- category,
370
- analysis: analyzePermissionRequest(
371
- 'edit',
372
- { path: diff.filePath, original: diff.original, updated: diff.updated },
373
- category,
374
- ),
375
- }).then(({ approved }) => {
376
- if (!approved) return;
377
- if (!projectRoot) {
378
- conversationManager.log('[Diff apply failed: missing working directory]', { fg: '#ef4444' });
379
- return;
380
- }
381
- let resolvedPath: string;
382
- try {
383
- resolvedPath = resolveAndValidatePath(diff.filePath!, projectRoot);
384
- } catch (err) {
385
- conversationManager.log(`[Diff apply failed: ${err instanceof Error ? err.message : err}]`, { fg: '#ef4444' });
386
- return;
387
- }
388
- try {
389
- const content = readFileSync(resolvedPath, 'utf-8');
390
- if (diff.original && content.includes(diff.original)) {
391
- const occurrenceCount = content.split(diff.original).length - 1;
392
- if (occurrenceCount > 1) {
393
- conversationManager.log(`[Diff apply failed: pattern found ${occurrenceCount} times in ${diff.filePath} - ambiguous]`, { fg: '#ef4444' });
394
- } else {
395
- const newContent = content.replace(diff.original, diff.updated);
396
- writeFileSync(resolvedPath, newContent, 'utf-8');
397
- conversationManager.log(`[Applied diff to ${diff.filePath}]`, { fg: '#22c55e' });
398
- }
399
- } else {
400
- conversationManager.log(`[Diff apply failed: original text not found in ${diff.filePath}]`, { fg: '#ef4444' });
401
- }
402
- } catch (err) {
403
- const msg = summarizeError(err);
404
- conversationManager.log(`[Diff apply error: ${msg}]`, { fg: '#ef4444' });
405
- }
406
- requestRender();
407
- }).catch((err) => {
408
- conversationManager.log(`[Diff apply error: ${summarizeError(err)}]`, { fg: '#ef4444' });
409
- requestRender();
410
- });
355
+ conversationManager.log(
356
+ `[Diff apply blocked in GoodVibes Agent: ${diff.filePath}. Delegate build/fix work to GoodVibes TUI with /delegate <task>.]`,
357
+ { fg: '#f59e0b' },
358
+ );
359
+ requestRender();
411
360
  return true;
412
361
  }
413
362
 
@@ -137,7 +137,7 @@ export function handleBookmarkForHandler(handler: InputHandler): void {
137
137
  }
138
138
 
139
139
  /**
140
- * handleBlockSave - Ctrl+S: Save nearest block content to a file.
140
+ * handleBlockSave - Ctrl+S: Explain that implicit block file saves are blocked.
141
141
  */
142
142
  export function handleBlockSaveForHandler(handler: InputHandler): void {
143
143
  handleBlockSave(handler.conversationManager, handler.getScrollTop, handler.requestRender, handler.uiServices.shell.bookmarkManager);