@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.
- package/CHANGELOG.md +41 -0
- package/README.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/release-and-publishing.md +2 -2
- package/package.json +4 -1
- package/src/cli/agent-knowledge-command.ts +46 -20
- package/src/cli/help.ts +15 -2
- package/src/cli/management-commands.ts +3 -3
- package/src/cli/management.ts +7 -1
- package/src/cli/parser.ts +3 -0
- package/src/cli/service-posture.ts +6 -6
- package/src/cli/status.ts +9 -9
- package/src/cli/surface-command.ts +3 -3
- package/src/cli/types.ts +2 -0
- package/src/input/commands/cloudflare-runtime.ts +20 -5
- package/src/input/commands/confirmation.ts +24 -0
- package/src/input/commands/discovery-runtime.ts +16 -7
- package/src/input/commands/eval.ts +27 -14
- package/src/input/commands/experience-runtime.ts +66 -27
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/hooks-runtime.ts +79 -20
- package/src/input/commands/incident-runtime.ts +17 -6
- package/src/input/commands/integration-runtime.ts +93 -50
- package/src/input/commands/knowledge.ts +38 -12
- package/src/input/commands/local-auth-runtime.ts +36 -13
- package/src/input/commands/local-provider-runtime.ts +22 -11
- package/src/input/commands/local-runtime.ts +21 -11
- package/src/input/commands/local-setup.ts +35 -16
- package/src/input/commands/managed-runtime.ts +51 -20
- package/src/input/commands/marketplace-runtime.ts +31 -16
- package/src/input/commands/mcp-runtime.ts +65 -34
- package/src/input/commands/memory-product-runtime.ts +72 -35
- package/src/input/commands/memory.ts +9 -9
- package/src/input/commands/notify-runtime.ts +27 -8
- package/src/input/commands/operator-runtime.ts +85 -17
- package/src/input/commands/planning-runtime.ts +14 -2
- package/src/input/commands/platform-access-runtime.ts +88 -45
- package/src/input/commands/platform-services-runtime.ts +51 -25
- package/src/input/commands/product-runtime.ts +54 -27
- package/src/input/commands/profile-sync-runtime.ts +17 -6
- package/src/input/commands/recall-bundle.ts +38 -17
- package/src/input/commands/recall-query.ts +15 -4
- package/src/input/commands/recall-review.ts +9 -3
- package/src/input/commands/remote-runtime-setup.ts +45 -18
- package/src/input/commands/remote-runtime.ts +25 -9
- package/src/input/commands/replay-runtime.ts +9 -2
- package/src/input/commands/services-runtime.ts +21 -10
- package/src/input/commands/session-content.ts +53 -51
- package/src/input/commands/session-workflow.ts +10 -4
- package/src/input/commands/session.ts +1 -1
- package/src/input/commands/settings-sync-runtime.ts +40 -17
- package/src/input/commands/share-runtime.ts +12 -4
- package/src/input/commands/shell-core.ts +3 -3
- package/src/input/commands/subscription-runtime.ts +35 -20
- package/src/input/commands/teleport-runtime.ts +16 -5
- package/src/input/commands/work-plan-runtime.ts +23 -12
- package/src/input/handler-content-actions.ts +11 -62
- package/src/input/handler-interactions.ts +1 -1
- package/src/input/handler-onboarding-cloudflare.ts +48 -117
- package/src/input/handler.ts +1 -0
- package/src/input/keybindings.ts +1 -1
- package/src/input/mcp-workspace.ts +25 -49
- package/src/input/onboarding/onboarding-runtime-status.ts +8 -8
- package/src/input/onboarding/onboarding-wizard-apply.ts +13 -53
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +12 -12
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +2 -7
- package/src/input/onboarding/onboarding-wizard-constants.ts +7 -7
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +4 -4
- package/src/input/onboarding/onboarding-wizard-steps.ts +13 -13
- package/src/input/profile-picker-modal.ts +13 -31
- package/src/input/session-picker-modal.ts +4 -30
- package/src/input/settings-modal-agent-policy.ts +18 -0
- package/src/input/settings-modal-subscriptions.ts +3 -3
- package/src/input/settings-modal-types.ts +17 -0
- package/src/input/settings-modal.ts +30 -29
- package/src/main.ts +3 -26
- package/src/panels/incident-review-panel.ts +1 -1
- package/src/panels/local-auth-panel.ts +4 -4
- package/src/panels/provider-account-snapshot.ts +1 -1
- package/src/panels/provider-health-domains.ts +2 -2
- package/src/panels/settings-sync-panel.ts +2 -2
- package/src/panels/subscription-panel.ts +7 -7
- package/src/renderer/block-actions.ts +1 -1
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/mcp-workspace.ts +12 -12
- package/src/renderer/process-modal.ts +17 -8
- package/src/renderer/profile-picker-modal.ts +3 -11
- package/src/renderer/session-picker-modal.ts +2 -10
- package/src/renderer/settings-modal.ts +12 -8
- package/src/renderer/ui-factory.ts +4 -32
- package/src/runtime/bootstrap-shell.ts +0 -13
- package/src/runtime/bootstrap.ts +0 -10
- package/src/runtime/onboarding/derivation.ts +6 -6
- package/src/verification/live-verifier.ts +148 -13
- package/src/version.ts +10 -3
- package/src/input/commands/quit-shared.ts +0 -162
- package/src/renderer/git-status.ts +0 -89
|
@@ -21,23 +21,26 @@ import { CONFIG_KEYS } from '@pellux/goodvibes-sdk/platform/config';
|
|
|
21
21
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
22
22
|
import { openCommandPanel, requireShellPaths } from './runtime-services.ts';
|
|
23
23
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
24
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
24
25
|
|
|
25
26
|
export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry): void {
|
|
26
27
|
registry.register({
|
|
27
28
|
name: 'settingssync',
|
|
28
29
|
aliases: ['settings-sync'],
|
|
29
30
|
description: 'Review sync posture, export/import settings-sync bundles, and open the settings sync workspace',
|
|
30
|
-
usage: '[review|panel|show <key>|staged|conflicts|resolve <key> <local|synced
|
|
31
|
+
usage: '[review|panel|show <key>|staged|conflicts|resolve <key> <local|synced> --yes|failures|rollback-history|export <path> --yes|inspect <path>|pull <path> --yes|push <path> --yes|lock <key> <source> <reason...> --yes|unlock <key> --yes]',
|
|
31
32
|
handler(args, ctx) {
|
|
33
|
+
const parsed = stripYesFlag(args);
|
|
34
|
+
const commandArgs = [...parsed.rest];
|
|
32
35
|
const shellPaths = requireShellPaths(ctx);
|
|
33
36
|
const controlPlaneConfigDir = ctx.platform.configManager.getControlPlaneConfigDir();
|
|
34
|
-
const sub = (
|
|
37
|
+
const sub = (commandArgs[0] ?? 'review').toLowerCase();
|
|
35
38
|
if (sub === 'panel' || sub === 'open') {
|
|
36
39
|
openCommandPanel(ctx, 'settings-sync');
|
|
37
40
|
return;
|
|
38
41
|
}
|
|
39
42
|
if (sub === 'show') {
|
|
40
|
-
const key =
|
|
43
|
+
const key = commandArgs[1] as ConfigKey | undefined;
|
|
41
44
|
if (!key || !CONFIG_KEYS.has(key)) {
|
|
42
45
|
ctx.print('Usage: /settingssync show <config-key>');
|
|
43
46
|
return;
|
|
@@ -60,10 +63,14 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
|
|
|
60
63
|
return;
|
|
61
64
|
}
|
|
62
65
|
if (sub === 'resolve') {
|
|
63
|
-
const key =
|
|
64
|
-
const resolution = (
|
|
66
|
+
const key = commandArgs[1] as ConfigKey | undefined;
|
|
67
|
+
const resolution = (commandArgs[2] ?? '').toLowerCase();
|
|
65
68
|
if (!key || !CONFIG_KEYS.has(key) || (resolution !== 'local' && resolution !== 'synced')) {
|
|
66
|
-
ctx.print('Usage: /settingssync resolve <config-key> <local|synced>');
|
|
69
|
+
ctx.print('Usage: /settingssync resolve <config-key> <local|synced> --yes');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!parsed.yes) {
|
|
73
|
+
requireYesFlag(ctx, `resolve synced conflict for ${key}`, '/settingssync resolve <config-key> <local|synced> --yes');
|
|
67
74
|
return;
|
|
68
75
|
}
|
|
69
76
|
const changed = resolveSettingsSyncConflict(ctx.platform.configManager, key, resolution);
|
|
@@ -100,9 +107,13 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
|
|
|
100
107
|
return;
|
|
101
108
|
}
|
|
102
109
|
if (sub === 'export' || sub === 'push') {
|
|
103
|
-
const pathArg =
|
|
110
|
+
const pathArg = commandArgs[1];
|
|
104
111
|
if (!pathArg) {
|
|
105
|
-
ctx.print(`Usage: /settingssync ${sub} <path
|
|
112
|
+
ctx.print(`Usage: /settingssync ${sub} <path> --yes`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!parsed.yes) {
|
|
116
|
+
requireYesFlag(ctx, `${sub === 'push' ? 'push' : 'export'} settings sync bundle to ${pathArg}`, `/settingssync ${sub} <path> --yes`);
|
|
106
117
|
return;
|
|
107
118
|
}
|
|
108
119
|
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
@@ -120,7 +131,7 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
|
|
|
120
131
|
return;
|
|
121
132
|
}
|
|
122
133
|
if (sub === 'inspect') {
|
|
123
|
-
const pathArg =
|
|
134
|
+
const pathArg = commandArgs[1];
|
|
124
135
|
if (!pathArg) {
|
|
125
136
|
ctx.print('Usage: /settingssync inspect <path>');
|
|
126
137
|
return;
|
|
@@ -131,9 +142,13 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
|
|
|
131
142
|
return;
|
|
132
143
|
}
|
|
133
144
|
if (sub === 'pull') {
|
|
134
|
-
const pathArg =
|
|
145
|
+
const pathArg = commandArgs[1];
|
|
135
146
|
if (!pathArg) {
|
|
136
|
-
ctx.print('Usage: /settingssync pull <path>');
|
|
147
|
+
ctx.print('Usage: /settingssync pull <path> --yes');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!parsed.yes) {
|
|
151
|
+
requireYesFlag(ctx, `pull settings sync bundle from ${pathArg}`, '/settingssync pull <path> --yes');
|
|
137
152
|
return;
|
|
138
153
|
}
|
|
139
154
|
const sourcePath = shellPaths.resolveWorkspacePath(pathArg);
|
|
@@ -148,11 +163,15 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
|
|
|
148
163
|
return;
|
|
149
164
|
}
|
|
150
165
|
if (sub === 'lock') {
|
|
151
|
-
const key =
|
|
152
|
-
const source =
|
|
153
|
-
const reason =
|
|
166
|
+
const key = commandArgs[1] as ConfigKey | undefined;
|
|
167
|
+
const source = commandArgs[2];
|
|
168
|
+
const reason = commandArgs.slice(3).join(' ').trim();
|
|
154
169
|
if (!key || !source || !reason || !CONFIG_KEYS.has(key)) {
|
|
155
|
-
ctx.print('Usage: /settingssync lock <config-key> <source> <reason...>');
|
|
170
|
+
ctx.print('Usage: /settingssync lock <config-key> <source> <reason...> --yes');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (!parsed.yes) {
|
|
174
|
+
requireYesFlag(ctx, `lock managed setting ${key}`, '/settingssync lock <config-key> <source> <reason...> --yes');
|
|
156
175
|
return;
|
|
157
176
|
}
|
|
158
177
|
setManagedSettingLock(key, source, reason, controlPlaneConfigDir);
|
|
@@ -160,9 +179,13 @@ export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry):
|
|
|
160
179
|
return;
|
|
161
180
|
}
|
|
162
181
|
if (sub === 'unlock') {
|
|
163
|
-
const key =
|
|
182
|
+
const key = commandArgs[1] as ConfigKey | undefined;
|
|
164
183
|
if (!key || !CONFIG_KEYS.has(key)) {
|
|
165
|
-
ctx.print('Usage: /settingssync unlock <config-key>');
|
|
184
|
+
ctx.print('Usage: /settingssync unlock <config-key> --yes');
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (!parsed.yes) {
|
|
188
|
+
requireYesFlag(ctx, `unlock managed setting ${key}`, '/settingssync unlock <config-key> --yes');
|
|
166
189
|
return;
|
|
167
190
|
}
|
|
168
191
|
ctx.print(clearManagedSettingLock(key, controlPlaneConfigDir) ? `Managed lock cleared for ${key}.` : `No managed lock found for ${key}.`);
|
|
@@ -11,23 +11,26 @@ import {
|
|
|
11
11
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
12
12
|
import { requireShellPaths } from './runtime-services.ts';
|
|
13
13
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
14
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
14
15
|
|
|
15
16
|
export function registerShareRuntimeCommands(registry: CommandRegistry): void {
|
|
16
17
|
registry.register({
|
|
17
18
|
name: 'share',
|
|
18
19
|
aliases: [],
|
|
19
20
|
description: 'Export the current session to a shareable format (html, json, md)',
|
|
20
|
-
usage: '<html|json|md> [path] [--redact]',
|
|
21
|
+
usage: '<html|json|md> [path] [--redact] --yes',
|
|
21
22
|
argsHint: '<html|json|md> [path]',
|
|
22
23
|
async handler(args, ctx) {
|
|
24
|
+
const parsed = stripYesFlag(args);
|
|
25
|
+
const commandArgs = [...parsed.rest];
|
|
23
26
|
const shellPaths = requireShellPaths(ctx);
|
|
24
27
|
const FORMATS = ['html', 'json', 'md'] as const;
|
|
25
28
|
type Format = typeof FORMATS[number];
|
|
26
29
|
|
|
27
|
-
const format =
|
|
30
|
+
const format = commandArgs[0]?.toLowerCase() as Format | undefined;
|
|
28
31
|
if (!format || !FORMATS.includes(format)) {
|
|
29
32
|
ctx.print(
|
|
30
|
-
'Usage: /share <html|json|md> [path] [--redact]\n'
|
|
33
|
+
'Usage: /share <html|json|md> [path] [--redact] --yes\n'
|
|
31
34
|
+ ' html — self-contained HTML with syntax highlighting\n'
|
|
32
35
|
+ ' json — structured JSON (machine-readable)\n'
|
|
33
36
|
+ ' md — Markdown\n\n'
|
|
@@ -38,13 +41,18 @@ export function registerShareRuntimeCommands(registry: CommandRegistry): void {
|
|
|
38
41
|
return;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
const remainingArgs =
|
|
44
|
+
const remainingArgs = commandArgs.slice(1);
|
|
42
45
|
const redact = remainingArgs.includes('--redact');
|
|
43
46
|
const pathArgs = remainingArgs.filter(a => a !== '--redact');
|
|
44
47
|
const outputPath = pathArgs.length > 0
|
|
45
48
|
? shellPaths.resolveWorkspacePath(pathArgs[0])
|
|
46
49
|
: defaultExportPath(format, shellPaths.homeDirectory);
|
|
47
50
|
|
|
51
|
+
if (!parsed.yes) {
|
|
52
|
+
requireYesFlag(ctx, `export ${format.toUpperCase()} session to ${outputPath}`, '/share <html|json|md> [path] [--redact] --yes');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
48
56
|
const convData = ctx.session.conversationManager.toJSON() as {
|
|
49
57
|
messages: Array<{
|
|
50
58
|
role: string;
|
|
@@ -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
|
|
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 = (
|
|
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 =
|
|
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 =
|
|
157
|
-
const mode =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
406
|
-
const pathArg =
|
|
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
|
|
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
|
|
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 = (
|
|
26
|
-
const pathArg =
|
|
28
|
+
const mode = (commandArgs[0] ?? 'export').toLowerCase();
|
|
29
|
+
const pathArg = commandArgs[1];
|
|
27
30
|
if (!pathArg) {
|
|
28
|
-
ctx.print('Usage: /teleport [export <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
|
|
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
|
|
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 = (
|
|
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
|
|
109
|
-
if (!
|
|
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
|
-
...(
|
|
115
|
-
source:
|
|
116
|
-
...(
|
|
117
|
+
...(addArgs.owner ? { owner: addArgs.owner } : {}),
|
|
118
|
+
source: addArgs.source ?? 'manual',
|
|
119
|
+
...(addArgs.notes ? { notes: addArgs.notes } : {}),
|
|
117
120
|
};
|
|
118
|
-
const item = store.addItem(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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:
|
|
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);
|