@pellux/goodvibes-agent 0.1.116 → 1.0.0
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 +48 -661
- package/README.md +64 -57
- package/bin/goodvibes-agent.ts +16 -2
- package/dist/package/main.js +199180 -192185
- package/docs/README.md +15 -8
- package/docs/channels-remote-and-api.md +50 -0
- package/docs/connected-host.md +59 -0
- package/docs/getting-started.md +61 -50
- package/docs/knowledge-artifacts-and-multimodal.md +91 -0
- package/docs/project-planning.md +79 -0
- package/docs/providers-and-routing.md +46 -0
- package/docs/release-and-publishing.md +48 -11
- package/docs/tools-and-commands.md +123 -0
- package/docs/voice-and-live-tts.md +51 -0
- package/package.json +3 -6
- package/src/agent/behavior-discovery-summary.ts +1 -3
- package/src/agent/channel-delivery.ts +201 -0
- package/src/agent/media-generation.ts +159 -0
- package/src/agent/memory-prompt.ts +0 -1
- package/src/agent/note-registry.ts +329 -0
- package/src/agent/operator-actions.ts +343 -0
- package/src/agent/persona-discovery.ts +1 -3
- package/src/agent/persona-registry.ts +19 -16
- package/src/agent/record-labels.ts +107 -0
- package/src/agent/reminder-schedule-format.ts +33 -24
- package/src/agent/reminder-schedule.ts +27 -26
- package/src/agent/routine-registry.ts +54 -14
- package/src/agent/routine-schedule-args.ts +2 -1
- package/src/agent/routine-schedule-format.ts +77 -53
- package/src/agent/routine-schedule-promotion.ts +35 -33
- package/src/agent/routine-schedule-receipts.ts +29 -27
- package/src/agent/runtime-profile-starters.ts +5 -5
- package/src/agent/runtime-profile.ts +91 -12
- package/src/agent/skill-registry.ts +210 -32
- package/src/cli/agent-knowledge-args.ts +5 -1
- package/src/cli/agent-knowledge-command.ts +40 -34
- package/src/cli/agent-knowledge-format.ts +80 -67
- package/src/cli/agent-knowledge-methods.ts +1 -1
- package/src/cli/agent-knowledge-runtime.ts +32 -26
- package/src/cli/bundle-command.ts +14 -9
- package/src/cli/config-overrides.ts +37 -36
- package/src/cli/entrypoint.ts +16 -7
- package/src/cli/external-runtime.ts +10 -4
- package/src/cli/help.ts +54 -29
- package/src/cli/local-library-command.ts +203 -83
- package/src/cli/management-commands.ts +105 -68
- package/src/cli/management.ts +55 -74
- package/src/cli/memory-command.ts +66 -32
- package/src/cli/parser.ts +37 -3
- package/src/cli/profiles-command.ts +166 -31
- package/src/cli/provider-auth-routes.ts +2 -1
- package/src/cli/routines-command.ts +176 -40
- package/src/cli/service-posture.ts +16 -16
- package/src/cli/status.ts +56 -135
- package/src/core/conversation-message-snapshot.ts +131 -0
- package/src/input/agent-workspace-access-command-editor-submission.ts +149 -0
- package/src/input/agent-workspace-access-command-editors.ts +170 -0
- package/src/input/agent-workspace-activation.ts +35 -8
- package/src/input/agent-workspace-basic-command-editor-submission.ts +351 -94
- package/src/input/agent-workspace-basic-command-editors.ts +502 -9
- package/src/input/agent-workspace-categories.ts +423 -51
- package/src/input/agent-workspace-channel-command-editor-submission.ts +83 -0
- package/src/input/agent-workspace-channel-command-editors.ts +65 -0
- package/src/input/agent-workspace-channels.ts +35 -2
- package/src/input/agent-workspace-command-editor.ts +41 -3
- package/src/input/agent-workspace-config-reader.ts +16 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +55 -0
- package/src/input/agent-workspace-editors.ts +166 -1
- package/src/input/agent-workspace-knowledge-command-editor-submission.ts +132 -0
- package/src/input/agent-workspace-knowledge-command-editors.ts +110 -0
- package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
- package/src/input/agent-workspace-learned-behavior.ts +67 -0
- package/src/input/agent-workspace-library-command-editor-submission.ts +50 -0
- package/src/input/agent-workspace-library-command-editors.ts +35 -0
- package/src/input/agent-workspace-local-operations.ts +218 -0
- package/src/input/agent-workspace-local-selection.ts +75 -0
- package/src/input/agent-workspace-mcp-command-editor-submission.ts +95 -0
- package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
- package/src/input/agent-workspace-media-command-editors.ts +27 -0
- package/src/input/agent-workspace-memory-command-editor-submission.ts +232 -0
- package/src/input/agent-workspace-memory-command-editors.ts +180 -0
- package/src/input/agent-workspace-memory-editor.ts +2 -2
- package/src/input/agent-workspace-navigation.ts +81 -0
- package/src/input/agent-workspace-notify-editor-submission.ts +121 -0
- package/src/input/agent-workspace-operations-command-editor-submission.ts +208 -0
- package/src/input/agent-workspace-operations-command-editors.ts +196 -0
- package/src/input/agent-workspace-panel-route.ts +43 -0
- package/src/input/agent-workspace-provider-command-editor-submission.ts +155 -0
- package/src/input/agent-workspace-provider-command-editors.ts +93 -0
- package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
- package/src/input/agent-workspace-requirements.ts +11 -0
- package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
- package/src/input/agent-workspace-search.ts +169 -0
- package/src/input/agent-workspace-secret-editor-submission.ts +153 -0
- package/src/input/agent-workspace-session-command-editor-submission.ts +199 -0
- package/src/input/agent-workspace-session-command-editors.ts +249 -0
- package/src/input/agent-workspace-setup.ts +37 -16
- package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +101 -0
- package/src/input/agent-workspace-skill-bundle-command-editors.ts +110 -0
- package/src/input/agent-workspace-snapshot.ts +162 -21
- package/src/input/agent-workspace-task-command-editor-submission.ts +61 -0
- package/src/input/agent-workspace-task-command-editors.ts +47 -0
- package/src/input/agent-workspace-token.ts +18 -2
- package/src/input/agent-workspace-types.ts +212 -4
- package/src/input/agent-workspace-voice-media.ts +4 -7
- package/src/input/agent-workspace-web-research-editor.ts +104 -0
- package/src/input/agent-workspace-workplan-editor-submission.ts +153 -0
- package/src/input/agent-workspace.ts +178 -240
- package/src/input/command-registry.ts +5 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +126 -37
- package/src/input/commands/agent-skills-runtime.ts +149 -72
- package/src/input/commands/agent-workspace-runtime.ts +23 -6
- package/src/input/commands/brief-runtime.ts +55 -25
- package/src/input/commands/channels-runtime.ts +147 -33
- package/src/input/commands/compat-runtime.ts +32 -0
- package/src/input/commands/delegation-runtime.ts +29 -23
- package/src/input/commands/experience-runtime.ts +6 -4
- package/src/input/commands/guidance-runtime.ts +4 -4
- package/src/input/commands/health-runtime.ts +149 -126
- package/src/input/commands/knowledge.ts +82 -60
- package/src/input/commands/local-provider-runtime.ts +43 -21
- package/src/input/commands/local-runtime.ts +138 -16
- package/src/input/commands/local-setup-review.ts +10 -20
- package/src/input/commands/mcp-runtime.ts +63 -46
- package/src/input/commands/notify-runtime.ts +38 -9
- package/src/input/commands/operator-actions-runtime.ts +138 -0
- package/src/input/commands/operator-runtime.ts +6 -76
- package/src/input/commands/personas-runtime.ts +46 -31
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/platform-access-runtime.ts +35 -98
- package/src/input/commands/product-runtime.ts +2 -2
- package/src/input/commands/provider-accounts-runtime.ts +60 -39
- package/src/input/commands/qrcode-runtime.ts +50 -9
- package/src/input/commands/recall-bundle.ts +11 -11
- package/src/input/commands/recall-capture.ts +13 -11
- package/src/input/commands/recall-query.ts +29 -21
- package/src/input/commands/recall-review.ts +2 -1
- package/src/input/commands/routines-runtime.ts +99 -45
- package/src/input/commands/runtime-services.ts +1 -14
- package/src/input/commands/schedule-runtime.ts +33 -20
- package/src/input/commands/security-runtime.ts +6 -11
- package/src/input/commands/session-content.ts +80 -78
- package/src/input/commands/session-workflow.ts +134 -104
- package/src/input/commands/session.ts +3 -174
- package/src/input/commands/shell-core.ts +38 -17
- package/src/input/commands/subscription-runtime.ts +54 -185
- package/src/input/commands/support-bundle-runtime.ts +53 -0
- package/src/input/commands/tasks-runtime.ts +21 -21
- package/src/input/commands/work-plan-runtime.ts +33 -8
- package/src/input/commands.ts +6 -2
- package/src/input/feed-context-factory.ts +2 -2
- package/src/input/file-picker.ts +3 -2
- package/src/input/handler-command-route.ts +4 -7
- package/src/input/handler-content-actions.ts +89 -26
- package/src/input/handler-feed-routes.ts +19 -12
- package/src/input/handler-feed.ts +6 -5
- package/src/input/handler-interactions.ts +9 -36
- package/src/input/handler-modal-stack.ts +3 -3
- package/src/input/handler-onboarding.ts +52 -91
- package/src/input/handler-shortcuts.ts +16 -7
- package/src/input/handler.ts +50 -23
- package/src/input/input-history.ts +5 -6
- package/src/input/keybindings.ts +27 -16
- package/src/input/mcp-workspace.ts +90 -26
- package/src/input/onboarding/handler-onboarding-routes.ts +3 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +62 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
- package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-operator-steps.ts +223 -55
- package/src/input/onboarding/onboarding-wizard-steps.ts +59 -35
- package/src/input/onboarding/onboarding-wizard-types.ts +4 -1
- package/src/input/onboarding/onboarding-wizard.ts +87 -2
- package/src/input/profile-picker-modal.ts +31 -12
- package/src/input/session-picker-modal.ts +21 -4
- package/src/input/settings-modal-agent-policy.ts +6 -6
- package/src/input/settings-modal-behavior.ts +0 -3
- package/src/input/settings-modal-subscriptions.ts +3 -3
- package/src/input/settings-modal-types.ts +2 -13
- package/src/input/settings-modal.ts +20 -66
- package/src/input/submission-intent.ts +0 -1
- package/src/input/submission-router.ts +3 -3
- package/src/main.ts +18 -10
- package/src/panels/approval-panel.ts +8 -8
- package/src/panels/automation-control-panel.ts +4 -4
- package/src/panels/base-panel.ts +1 -1
- package/src/panels/builtin/agent.ts +1 -1
- package/src/panels/builtin/operations.ts +3 -12
- package/src/panels/builtin/session.ts +32 -24
- package/src/panels/builtin/shared.ts +7 -7
- package/src/panels/cost-tracker-panel.ts +1 -1
- package/src/panels/docs-panel.ts +5 -3
- package/src/panels/index.ts +0 -1
- package/src/panels/knowledge-panel.ts +6 -5
- package/src/panels/memory-panel.ts +7 -6
- package/src/panels/panel-list-panel.ts +36 -80
- package/src/panels/project-planning-panel.ts +19 -12
- package/src/panels/provider-account-snapshot.ts +51 -25
- package/src/panels/provider-accounts-panel.ts +33 -18
- package/src/panels/provider-health-domains.ts +48 -7
- package/src/panels/provider-health-panel.ts +5 -4
- package/src/panels/qr-panel.ts +44 -20
- package/src/panels/schedule-panel.ts +9 -17
- package/src/panels/security-panel.ts +8 -8
- package/src/panels/session-browser-panel.ts +10 -10
- package/src/panels/subscription-panel.ts +6 -6
- package/src/panels/system-messages-panel.ts +3 -3
- package/src/panels/tasks-panel.ts +21 -14
- package/src/panels/tool-inspector-panel.ts +19 -12
- package/src/panels/work-plan-panel.ts +5 -5
- package/src/planning/project-planning-coordinator.ts +3 -3
- package/src/provider-auth-route-display.ts +9 -0
- package/src/renderer/agent-workspace-style.ts +34 -0
- package/src/renderer/agent-workspace.ts +254 -57
- package/src/renderer/autocomplete-overlay.ts +25 -6
- package/src/renderer/block-actions.ts +1 -3
- package/src/renderer/bookmark-modal.ts +19 -4
- package/src/renderer/buffer.ts +4 -2
- package/src/renderer/context-inspector.ts +50 -13
- package/src/renderer/diff.ts +1 -1
- package/src/renderer/file-picker-overlay.ts +19 -6
- package/src/renderer/help-overlay.ts +112 -34
- package/src/renderer/history-search-overlay.ts +19 -4
- package/src/renderer/live-tail-modal.ts +27 -5
- package/src/renderer/mcp-workspace.ts +176 -43
- package/src/renderer/model-picker-overlay.ts +58 -3
- package/src/renderer/model-workspace.ts +104 -22
- package/src/renderer/onboarding/onboarding-wizard.ts +20 -4
- package/src/renderer/process-modal.ts +27 -6
- package/src/renderer/profile-picker-modal.ts +21 -5
- package/src/renderer/search-overlay.ts +25 -5
- package/src/renderer/selection-modal-overlay.ts +46 -14
- package/src/renderer/session-picker-modal.ts +18 -1
- package/src/renderer/settings-modal-helpers.ts +2 -40
- package/src/renderer/settings-modal.ts +88 -55
- package/src/renderer/system-message.ts +1 -1
- package/src/renderer/tool-call.ts +20 -11
- package/src/runtime/agent-runtime-events.ts +129 -0
- package/src/runtime/bootstrap-command-context.ts +7 -1
- package/src/runtime/bootstrap-command-parts.ts +11 -8
- package/src/runtime/bootstrap-core.ts +23 -65
- package/src/runtime/bootstrap-hook-bridge.ts +7 -18
- package/src/runtime/bootstrap-shell.ts +46 -8
- package/src/runtime/bootstrap.ts +36 -46
- package/src/runtime/connected-host-auth.ts +47 -0
- package/src/runtime/diagnostics/panels/index.ts +2 -2
- package/src/runtime/diagnostics/panels/panel-resources.ts +1 -1
- package/src/runtime/diagnostics/panels/policy.ts +7 -7
- package/src/runtime/index.ts +2 -5
- package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
- package/src/runtime/onboarding/apply.ts +288 -74
- package/src/runtime/onboarding/derivation.ts +10 -13
- package/src/runtime/onboarding/snapshot.ts +2 -17
- package/src/runtime/onboarding/types.ts +34 -20
- package/src/runtime/onboarding/verify.ts +105 -4
- package/src/runtime/operator-token-cleanup.ts +3 -3
- package/src/runtime/services.ts +95 -11
- package/src/runtime/store/selectors/index.ts +3 -3
- package/src/runtime/store/state.ts +1 -4
- package/src/runtime/surface-feature-flags.ts +41 -6
- package/src/runtime/ui-read-models.ts +3 -4
- package/src/runtime/ui-services.ts +6 -6
- package/src/shell/blocking-input.ts +2 -1
- package/src/shell/service-settings-sync.ts +7 -7
- package/src/shell/ui-openers.ts +17 -38
- package/src/tools/agent-analysis-registry-policy.ts +0 -1
- package/src/tools/agent-channel-send-tool.ts +133 -0
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
- package/src/tools/agent-knowledge-tool.ts +170 -0
- package/src/tools/agent-local-registry-tool.ts +279 -73
- package/src/tools/agent-media-generate-tool.ts +133 -0
- package/src/tools/agent-notify-tool.ts +143 -0
- package/src/tools/agent-operator-action-tool.ts +137 -0
- package/src/tools/agent-operator-briefing-tool.ts +217 -0
- package/src/tools/agent-reminder-schedule-tool.ts +237 -0
- package/src/tools/agent-tool-policy-guard.ts +13 -15
- package/src/tools/agent-work-plan-tool.ts +256 -0
- package/src/version.ts +1 -1
- package/docs/connected-services.md +0 -51
- package/src/cli/package-verification.ts +0 -274
- package/src/input/commands/policy-dispatch.ts +0 -339
- package/src/input/commands/policy.ts +0 -17
- package/src/panels/panel-picker.ts +0 -105
- package/src/panels/policy-panel.ts +0 -308
- package/src/renderer/panel-picker-overlay.ts +0 -202
|
@@ -9,6 +9,10 @@ import { BUILTIN_SECRET_PROVIDER_SOURCES, describeSecretRef, isSecretRefInput, r
|
|
|
9
9
|
import { requireBookmarkManager, requireProviderApi, requireSecretsManager } from './runtime-services.ts';
|
|
10
10
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
11
11
|
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
12
|
+
import {
|
|
13
|
+
formatAgentMediaGenerationResult,
|
|
14
|
+
generateAgentMedia,
|
|
15
|
+
} from '../../agent/media-generation.ts';
|
|
12
16
|
|
|
13
17
|
function isGoodVibesSecretRefInput(value: string): boolean {
|
|
14
18
|
const normalized = value.trim();
|
|
@@ -23,7 +27,7 @@ function isMalformedGoodVibesSecretRefInput(value: string): boolean {
|
|
|
23
27
|
function toggleBlocks(typeFilter: string, collapsed: boolean, ctx: CommandContext): void {
|
|
24
28
|
const VALID_TYPES = ['all', 'thinking', 'tool', 'code'] as const;
|
|
25
29
|
if (!VALID_TYPES.includes(typeFilter as typeof VALID_TYPES[number])) {
|
|
26
|
-
ctx.print(`Unknown type
|
|
30
|
+
ctx.print(`Unknown type ${typeFilter}\nValid types ${VALID_TYPES.join(', ')}`);
|
|
27
31
|
return;
|
|
28
32
|
}
|
|
29
33
|
const blockRegistry = ctx.session.conversationManager.getBlockRegistry();
|
|
@@ -49,6 +53,60 @@ function toggleBlocks(typeFilter: string, collapsed: boolean, ctx: CommandContex
|
|
|
49
53
|
ctx.renderRequest();
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
interface MediaGenerateArgs {
|
|
57
|
+
readonly prompt: string;
|
|
58
|
+
readonly providerId?: string;
|
|
59
|
+
readonly modelId?: string;
|
|
60
|
+
readonly outputMimeType?: string;
|
|
61
|
+
readonly yes: boolean;
|
|
62
|
+
readonly errors: readonly string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readFlagValue(args: readonly string[], index: number, flag: string, errors: string[]): string | null {
|
|
66
|
+
const value = args[index + 1];
|
|
67
|
+
if (!value || value.startsWith('--')) {
|
|
68
|
+
errors.push(`${flag} requires a value.`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseMediaGenerateArgs(args: readonly string[]): MediaGenerateArgs {
|
|
75
|
+
const parsed = stripYesFlag([...args]);
|
|
76
|
+
const errors: string[] = [];
|
|
77
|
+
const promptParts: string[] = [];
|
|
78
|
+
let providerId: string | undefined;
|
|
79
|
+
let modelId: string | undefined;
|
|
80
|
+
let outputMimeType: string | undefined;
|
|
81
|
+
for (let index = 0; index < parsed.rest.length; index += 1) {
|
|
82
|
+
const arg = parsed.rest[index];
|
|
83
|
+
if (arg === '--provider') {
|
|
84
|
+
providerId = readFlagValue(parsed.rest, index, arg, errors) ?? providerId;
|
|
85
|
+
index += 1;
|
|
86
|
+
} else if (arg === '--model') {
|
|
87
|
+
modelId = readFlagValue(parsed.rest, index, arg, errors) ?? modelId;
|
|
88
|
+
index += 1;
|
|
89
|
+
} else if (arg === '--mime') {
|
|
90
|
+
outputMimeType = readFlagValue(parsed.rest, index, arg, errors) ?? outputMimeType;
|
|
91
|
+
index += 1;
|
|
92
|
+
} else if (arg?.startsWith('--')) {
|
|
93
|
+
errors.push(`Unknown media generation flag ${arg}`);
|
|
94
|
+
} else if (arg) {
|
|
95
|
+
promptParts.push(arg);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const prompt = promptParts.join(' ').trim();
|
|
99
|
+
if (!prompt) errors.push('Media generation prompt is required.');
|
|
100
|
+
return {
|
|
101
|
+
prompt,
|
|
102
|
+
...(providerId ? { providerId } : {}),
|
|
103
|
+
...(modelId ? { modelId } : {}),
|
|
104
|
+
...(outputMimeType ? { outputMimeType } : {}),
|
|
105
|
+
yes: parsed.yes,
|
|
106
|
+
errors,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
52
110
|
export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
53
111
|
registry.register({ name: 'expand', description: 'Expand blocks by type', usage: '[all|thinking|tool|code]', argsHint: '[all|thinking|tool|code]', handler(args, ctx) { toggleBlocks(args[0] || 'all', false, ctx); } });
|
|
54
112
|
registry.register({ name: 'collapse', description: 'Collapse blocks by type', usage: '[all|thinking|tool|code]', argsHint: '[all|thinking|tool|code]', handler(args, ctx) { toggleBlocks(args[0] || 'all', true, ctx); } });
|
|
@@ -130,9 +188,12 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
130
188
|
}
|
|
131
189
|
try {
|
|
132
190
|
const resolved = await resolveSecretRef(refText, { resolveLocalSecret: (key) => mgr.get(key) });
|
|
133
|
-
ctx.print(
|
|
191
|
+
ctx.print([
|
|
192
|
+
`[secrets] ${describeSecretRef(refText)}`,
|
|
193
|
+
` status ${resolved.value ? 'resolved <redacted>' : 'missing'}`,
|
|
194
|
+
].join('\n'));
|
|
134
195
|
} catch (error) {
|
|
135
|
-
ctx.print(`[secrets] ${describeSecretRef(refText)} failed
|
|
196
|
+
ctx.print(`[secrets] ${describeSecretRef(refText)} failed ${summarizeError(error)}`);
|
|
136
197
|
}
|
|
137
198
|
return;
|
|
138
199
|
}
|
|
@@ -190,7 +251,10 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
190
251
|
scope: flags.has('--user') ? 'user' : flags.has('--project') ? 'project' : undefined,
|
|
191
252
|
medium: flags.has('--plaintext') ? 'plaintext' : flags.has('--secure') ? 'secure' : undefined,
|
|
192
253
|
});
|
|
193
|
-
ctx.print(
|
|
254
|
+
ctx.print([
|
|
255
|
+
'[secrets] Deleted',
|
|
256
|
+
` key ${key}`,
|
|
257
|
+
].join('\n'));
|
|
194
258
|
return;
|
|
195
259
|
}
|
|
196
260
|
ctx.print('[secrets] Usage: /secrets set <KEY> <value> [--user|--project] [--secure|--plaintext] --yes | link <KEY> <secret-ref> [--user|--project] [--secure|--plaintext] --yes | get <KEY> | test <secret-ref> | providers | list | delete <KEY> [--user|--project] [--secure|--plaintext] --yes');
|
|
@@ -212,36 +276,42 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
212
276
|
const promptText = args.slice(1).join(' ') || `Attached image: ${rawPath.split('/').pop() ?? rawPath}`;
|
|
213
277
|
const projectRoot = ctx.workspace.shellPaths?.workingDirectory ?? ctx.platform.configManager.getWorkingDirectory();
|
|
214
278
|
if (!projectRoot) {
|
|
215
|
-
ctx.print(
|
|
279
|
+
ctx.print([
|
|
280
|
+
'Error',
|
|
281
|
+
' message working directory is unavailable.',
|
|
282
|
+
].join('\n'));
|
|
216
283
|
return;
|
|
217
284
|
}
|
|
218
285
|
let resolvedPath: string;
|
|
219
286
|
try {
|
|
220
287
|
resolvedPath = resolveAndValidatePath(rawPath, projectRoot);
|
|
221
288
|
} catch (err) {
|
|
222
|
-
ctx.print(
|
|
289
|
+
ctx.print([
|
|
290
|
+
'Error',
|
|
291
|
+
` message ${summarizeError(err)}`,
|
|
292
|
+
].join('\n'));
|
|
223
293
|
return;
|
|
224
294
|
}
|
|
225
295
|
if (!existsSync(resolvedPath)) {
|
|
226
|
-
ctx.print(`File not found
|
|
296
|
+
ctx.print(`File not found ${rawPath}`);
|
|
227
297
|
return;
|
|
228
298
|
}
|
|
229
299
|
const ext = resolvedPath.slice(resolvedPath.lastIndexOf('.')).toLowerCase();
|
|
230
300
|
const mediaType = ({ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp', '.gif': 'image/gif' } as Record<string, string>)[ext];
|
|
231
301
|
if (!mediaType) {
|
|
232
|
-
ctx.print(`Unsupported image format
|
|
302
|
+
ctx.print(`Unsupported image format ${ext}\nSupported .png, .jpg, .jpeg, .webp, .gif`);
|
|
233
303
|
return;
|
|
234
304
|
}
|
|
235
305
|
const stat = statSync(resolvedPath);
|
|
236
306
|
if (stat.size > 20 * 1024 * 1024) {
|
|
237
|
-
ctx.print(`Image too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Maximum
|
|
307
|
+
ctx.print(`Image too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Maximum 20MB`);
|
|
238
308
|
return;
|
|
239
309
|
}
|
|
240
310
|
let data: string;
|
|
241
311
|
try {
|
|
242
312
|
data = (await readFile(resolvedPath)).toString('base64');
|
|
243
313
|
} catch (err) {
|
|
244
|
-
ctx.print(`Failed to read image
|
|
314
|
+
ctx.print(`Failed to read image ${summarizeError(err)}`);
|
|
245
315
|
return;
|
|
246
316
|
}
|
|
247
317
|
const currentModel = await requireProviderApi(ctx).getCurrentModel();
|
|
@@ -253,6 +323,53 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
253
323
|
},
|
|
254
324
|
});
|
|
255
325
|
|
|
326
|
+
registry.register({
|
|
327
|
+
name: 'media',
|
|
328
|
+
description: 'Inspect media providers or generate media through configured providers',
|
|
329
|
+
usage: 'providers | generate [--provider <id>] [--model <id>] [--mime <mime>] <prompt> --yes',
|
|
330
|
+
argsHint: '<providers|generate>',
|
|
331
|
+
async handler(args, ctx) {
|
|
332
|
+
const subcommand = args[0]?.toLowerCase() || 'providers';
|
|
333
|
+
const registry = ctx.platform.mediaProviderRegistry;
|
|
334
|
+
if (!registry) {
|
|
335
|
+
ctx.print('[media] Media providers are not available in this runtime.');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (subcommand === 'providers' || subcommand === 'status' || subcommand === 'list') {
|
|
339
|
+
const statuses = await registry.status();
|
|
340
|
+
ctx.print([
|
|
341
|
+
'[media] Providers:',
|
|
342
|
+
...statuses.map((provider) => ` ${provider.id}: ${provider.state}${provider.configured ? '' : ' (setup needed)'} - ${provider.capabilities.join(', ')}${provider.detail ? ` - ${provider.detail}` : ''}`),
|
|
343
|
+
].join('\n'));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (subcommand !== 'generate') {
|
|
347
|
+
ctx.print('[media] Usage: /media providers | /media generate [--provider <id>] [--model <id>] [--mime <mime>] <prompt> --yes');
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const artifactStore = ctx.platform.artifactStore;
|
|
351
|
+
if (!artifactStore) {
|
|
352
|
+
ctx.print('[media] Artifact storage is not available in this runtime.');
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const parsed = parseMediaGenerateArgs(args.slice(1));
|
|
356
|
+
if (parsed.errors.length > 0) {
|
|
357
|
+
ctx.print(`[media] ${parsed.errors.join('\n[media] ')}`);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (!parsed.yes) {
|
|
361
|
+
requireYesFlag(ctx, `generate media for "${parsed.prompt}"`, '/media generate [--provider <id>] [--model <id>] [--mime <mime>] <prompt> --yes');
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
const result = await generateAgentMedia(registry, artifactStore, parsed);
|
|
366
|
+
ctx.print(formatAgentMediaGenerationResult(result));
|
|
367
|
+
} catch (error) {
|
|
368
|
+
ctx.print(`[media] Generation failed ${summarizeError(error)}`);
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
|
|
256
373
|
registry.register({
|
|
257
374
|
name: 'refresh-models',
|
|
258
375
|
description: 'Refresh model catalog, benchmarks, and token limits',
|
|
@@ -267,15 +384,17 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
267
384
|
catalogOk = true;
|
|
268
385
|
ctx.print(`Model catalog refreshed: ${catalog.modelCount} models from ${catalog.providerCount} providers`);
|
|
269
386
|
} catch (e) {
|
|
270
|
-
ctx.print(`Catalog refresh failed
|
|
387
|
+
ctx.print(`Catalog refresh failed ${summarizeError(e)}`);
|
|
271
388
|
}
|
|
272
389
|
ctx.print('Refreshing benchmarks...');
|
|
273
390
|
try {
|
|
274
391
|
const benchmarkCount = await providerApi.refreshBenchmarks();
|
|
275
392
|
benchmarksOk = true;
|
|
276
|
-
ctx.print(
|
|
393
|
+
ctx.print(benchmarkCount > 0
|
|
394
|
+
? `Benchmarks refreshed: ${benchmarkCount} model records available.`
|
|
395
|
+
: 'Benchmarks refreshed.');
|
|
277
396
|
} catch (e) {
|
|
278
|
-
ctx.print(`Benchmarks refresh failed
|
|
397
|
+
ctx.print(`Benchmarks refresh failed ${summarizeError(e)}`);
|
|
279
398
|
}
|
|
280
399
|
ctx.print('Refreshing token limits...');
|
|
281
400
|
try {
|
|
@@ -283,7 +402,7 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
283
402
|
limitsOk = true;
|
|
284
403
|
ctx.print(`Token limits refreshed: ${count} models updated.`);
|
|
285
404
|
} catch (e) {
|
|
286
|
-
ctx.print(`Token limits refresh failed
|
|
405
|
+
ctx.print(`Token limits refresh failed ${summarizeError(e)}`);
|
|
287
406
|
}
|
|
288
407
|
if (!catalogOk || !benchmarksOk || !limitsOk) ctx.print('Some refreshes failed — see messages above.');
|
|
289
408
|
},
|
|
@@ -308,14 +427,17 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
308
427
|
}
|
|
309
428
|
const favorites = await providerApi.getFavorites();
|
|
310
429
|
if (favorites.pinned.some((entry) => entry.modelId === modelId || entry.registryKey === modelId)) {
|
|
311
|
-
ctx.print(`Model already pinned
|
|
430
|
+
ctx.print(`Model already pinned ${modelId}`);
|
|
312
431
|
return;
|
|
313
432
|
}
|
|
314
433
|
try {
|
|
315
434
|
await providerApi.pinModel(modelId);
|
|
316
435
|
ctx.print(`Pinned: ${modelId}`);
|
|
317
436
|
} catch (e) {
|
|
318
|
-
ctx.print(
|
|
437
|
+
ctx.print([
|
|
438
|
+
'Error',
|
|
439
|
+
` message ${summarizeError(e)}`,
|
|
440
|
+
].join('\n'));
|
|
319
441
|
}
|
|
320
442
|
},
|
|
321
443
|
});
|
|
@@ -13,7 +13,7 @@ export interface SetupReviewSnapshot {
|
|
|
13
13
|
readonly builtinSubscriptionProviderCount: number;
|
|
14
14
|
readonly activeSubscriptionCount: number;
|
|
15
15
|
readonly pendingSubscriptionCount: number;
|
|
16
|
-
readonly
|
|
16
|
+
readonly hostIntegrationIssues: string[];
|
|
17
17
|
readonly skillCount: number;
|
|
18
18
|
readonly pluginCount: number;
|
|
19
19
|
readonly quarantinedPluginCount: number;
|
|
@@ -33,11 +33,11 @@ export async function buildSetupReviewSnapshot(ctx: CommandContext): Promise<Set
|
|
|
33
33
|
const serviceRegistry = requireServiceRegistry(ctx);
|
|
34
34
|
const services = Object.keys(serviceRegistry.getAll()).sort((a, b) => a.localeCompare(b));
|
|
35
35
|
const serviceConfigs = serviceRegistry.getAll();
|
|
36
|
-
const
|
|
36
|
+
const hostIntegrationIssues: string[] = [];
|
|
37
37
|
for (const name of services) {
|
|
38
38
|
const inspection = await serviceRegistry.inspect(name);
|
|
39
39
|
if (!inspection?.hasPrimaryCredential) {
|
|
40
|
-
|
|
40
|
+
hostIntegrationIssues.push(`${name}: missing primary credential`);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -79,13 +79,13 @@ export async function buildSetupReviewSnapshot(ctx: CommandContext): Promise<Set
|
|
|
79
79
|
message: providerCount > 0 ? `${providerCount} model(s) available` : 'no models available',
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
|
-
severity: (services.length === 0 && oauthProviderCount === 0 && builtinSubscriptionProviderCount === 0) ? 'warn' :
|
|
83
|
-
area: '
|
|
82
|
+
severity: (services.length === 0 && oauthProviderCount === 0 && builtinSubscriptionProviderCount === 0) ? 'warn' : hostIntegrationIssues.length === 0 ? 'pass' : 'warn',
|
|
83
|
+
area: 'host',
|
|
84
84
|
message: (services.length === 0 && oauthProviderCount === 0 && builtinSubscriptionProviderCount === 0)
|
|
85
|
-
? 'no
|
|
86
|
-
:
|
|
87
|
-
? `${services.length}
|
|
88
|
-
: `${
|
|
85
|
+
? 'no connected host integrations configured'
|
|
86
|
+
: hostIntegrationIssues.length === 0
|
|
87
|
+
? `${services.length} host integration(s), ${oauthProviderCount + builtinSubscriptionProviderCount} oauth provider(s), ${activeSubscriptionCount} active subscription override(s)`
|
|
88
|
+
: `${hostIntegrationIssues.length} host integration configuration issue(s)`,
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
severity: quarantinedPluginCount === 0 ? 'pass' : 'warn',
|
|
@@ -101,16 +101,6 @@ export async function buildSetupReviewSnapshot(ctx: CommandContext): Promise<Set
|
|
|
101
101
|
? `${quarantinedMcpCount} quarantined, ${elevatedMcpCount} elevated`
|
|
102
102
|
: `${mcpServers.length} server(s) known`,
|
|
103
103
|
},
|
|
104
|
-
{
|
|
105
|
-
severity: managedHookCount > 0 || managedHookChainCount > 0 ? 'pass' : 'warn',
|
|
106
|
-
area: 'hooks',
|
|
107
|
-
message: `${managedHookCount} managed hook(s), ${managedHookChainCount} chain(s)`,
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
severity: remoteRunnerCount > 0 ? 'pass' : 'warn',
|
|
111
|
-
area: 'remote',
|
|
112
|
-
message: remoteRunnerCount > 0 ? `${remoteRunnerCount} remote worker contract(s)` : 'no remote worker contracts registered',
|
|
113
|
-
},
|
|
114
104
|
];
|
|
115
105
|
|
|
116
106
|
return {
|
|
@@ -121,7 +111,7 @@ export async function buildSetupReviewSnapshot(ctx: CommandContext): Promise<Set
|
|
|
121
111
|
builtinSubscriptionProviderCount,
|
|
122
112
|
activeSubscriptionCount,
|
|
123
113
|
pendingSubscriptionCount,
|
|
124
|
-
|
|
114
|
+
hostIntegrationIssues,
|
|
125
115
|
skillCount: skills.length,
|
|
126
116
|
pluginCount: plugins.length,
|
|
127
117
|
quarantinedPluginCount,
|
|
@@ -12,6 +12,11 @@ interface ParsedMcpAddArgs {
|
|
|
12
12
|
readonly server: McpServerConfig;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
type McpSecurityServer = ReturnType<NonNullable<NonNullable<CommandContext['clients']>['mcpApi']>['listServerSecurity']>[number];
|
|
16
|
+
type McpDisplayTrustMode = NonNullable<McpServerConfig['trustMode']> | McpSecurityServer['trustMode'];
|
|
17
|
+
type McpDisplayRole = NonNullable<McpServerConfig['role']> | McpSecurityServer['role'];
|
|
18
|
+
type McpDisplayFreshness = McpSecurityServer['schemaFreshness'];
|
|
19
|
+
|
|
15
20
|
function isMcpRole(value: string): value is NonNullable<McpServerConfig['role']> {
|
|
16
21
|
return MCP_ROLES.includes(value as NonNullable<McpServerConfig['role']>);
|
|
17
22
|
}
|
|
@@ -24,6 +29,21 @@ function isMcpScope(value: string): value is McpConfigScope {
|
|
|
24
29
|
return value === 'project' || value === 'global';
|
|
25
30
|
}
|
|
26
31
|
|
|
32
|
+
function formatMcpTrustMode(mode: McpDisplayTrustMode): string {
|
|
33
|
+
if (mode === 'ask-on-risk') return 'ask on risky actions';
|
|
34
|
+
if (mode === 'allow-all') return 'allow all actions';
|
|
35
|
+
return mode.replace(/[_-]+/g, ' ');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatMcpFreshness(value: McpDisplayFreshness): string {
|
|
39
|
+
if (value === 'quarantined') return 'needs review';
|
|
40
|
+
return value.replace(/[_-]+/g, ' ');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function formatMcpRole(value: McpDisplayRole): string {
|
|
44
|
+
return value.replace(/[_-]+/g, ' ');
|
|
45
|
+
}
|
|
46
|
+
|
|
27
47
|
function validateServerName(name: string): string | null {
|
|
28
48
|
if (!name.trim()) return 'MCP server name is required.';
|
|
29
49
|
if (!/^[a-zA-Z0-9._-]+$/.test(name)) {
|
|
@@ -71,7 +91,7 @@ function parseAddServerArgs(args: string[]): ParsedMcpAddArgs {
|
|
|
71
91
|
}
|
|
72
92
|
if (token === '--role') {
|
|
73
93
|
const value = readFlagValue(tokens, index, token);
|
|
74
|
-
if (!isMcpRole(value)) throw new Error(`Invalid MCP role "${value}". Expected one of
|
|
94
|
+
if (!isMcpRole(value)) throw new Error(`Invalid MCP role "${value}". Expected one of ${MCP_ROLES.join(', ')}.`);
|
|
75
95
|
role = value;
|
|
76
96
|
index += 1;
|
|
77
97
|
continue;
|
|
@@ -85,7 +105,7 @@ function parseAddServerArgs(args: string[]): ParsedMcpAddArgs {
|
|
|
85
105
|
}
|
|
86
106
|
if (token === '--trust') {
|
|
87
107
|
const value = readFlagValue(tokens, index, token);
|
|
88
|
-
if (!isMcpTrustMode(value)) throw new Error(`Invalid MCP trust mode "${value}". Expected one of
|
|
108
|
+
if (!isMcpTrustMode(value)) throw new Error(`Invalid MCP trust mode "${value}". Expected one of ${MCP_TRUST_MODES.join(', ')}.`);
|
|
89
109
|
trustMode = value;
|
|
90
110
|
index += 1;
|
|
91
111
|
continue;
|
|
@@ -157,7 +177,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
157
177
|
` servers: ${servers.length}`,
|
|
158
178
|
` connected: ${servers.filter((server) => server.connected).length}`,
|
|
159
179
|
` auth or repair attention: ${authRequired.length}`,
|
|
160
|
-
...servers.map((server) => ` ${server.name} trust=${server.trustMode} role=${server.role}
|
|
180
|
+
...servers.map((server) => ` ${server.name} trust=${formatMcpTrustMode(server.trustMode)} role=${formatMcpRole(server.role)} status=${formatMcpFreshness(server.schemaFreshness)} connected=${server.connected ? 'yes' : 'no'}`),
|
|
161
181
|
' next: /mcp auth-review',
|
|
162
182
|
' next: /mcp repair <server>',
|
|
163
183
|
].join('\n'));
|
|
@@ -174,8 +194,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
174
194
|
+ ' ~/.config/claude/claude_desktop_config.json (Claude Desktop)\n'
|
|
175
195
|
+ ' .mcp/mcp.json (project-local)\n'
|
|
176
196
|
+ ' .goodvibes/mcp.json (goodvibes project)\n'
|
|
177
|
-
+ '\
|
|
178
|
-
+ ' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --yes'
|
|
197
|
+
+ '\nOpen /mcp and choose Add or update server, or use Agent Workspace -> Tools & MCP -> Add MCP server.'
|
|
179
198
|
);
|
|
180
199
|
return;
|
|
181
200
|
}
|
|
@@ -219,7 +238,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
219
238
|
? [
|
|
220
239
|
'MCP Auth Review',
|
|
221
240
|
...needingAttention.map((server) => (
|
|
222
|
-
` ${server.name} connected
|
|
241
|
+
` ${server.name} connected ${server.connected ? 'yes' : 'no'} status ${formatMcpFreshness(server.schemaFreshness)} trust ${formatMcpTrustMode(server.trustMode)}`
|
|
223
242
|
)),
|
|
224
243
|
' next: /auth review',
|
|
225
244
|
' next: /mcp repair <server>',
|
|
@@ -234,7 +253,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
234
253
|
const selected = serverName ? servers.find((server) => server.name === serverName) : servers.find((server) => !server.connected || server.schemaFreshness === 'quarantined');
|
|
235
254
|
if (!selected) {
|
|
236
255
|
ctx.print(serverName
|
|
237
|
-
? `Unknown MCP server
|
|
256
|
+
? `Unknown MCP server ${serverName}`
|
|
238
257
|
: 'MCP Repair\n No MCP server currently needs repair.');
|
|
239
258
|
return;
|
|
240
259
|
}
|
|
@@ -249,9 +268,9 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
249
268
|
ctx.print([
|
|
250
269
|
`MCP Repair: ${selected.name}`,
|
|
251
270
|
` connected: ${selected.connected ? 'yes' : 'no'}`,
|
|
252
|
-
` trust: ${selected.trustMode}`,
|
|
253
|
-
` role: ${selected.role}`,
|
|
254
|
-
`
|
|
271
|
+
` trust: ${formatMcpTrustMode(selected.trustMode)}`,
|
|
272
|
+
` role: ${formatMcpRole(selected.role)}`,
|
|
273
|
+
` status: ${formatMcpFreshness(selected.schemaFreshness)}`,
|
|
255
274
|
...(selected.quarantineReason ? [` quarantine: ${selected.quarantineReason}`] : []),
|
|
256
275
|
...(selected.quarantineDetail ? [` detail: ${selected.quarantineDetail}`] : []),
|
|
257
276
|
' next:',
|
|
@@ -265,7 +284,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
265
284
|
const mode = commandArgs[2] as 'constrained' | 'ask-on-risk' | 'allow-all' | 'blocked' | undefined;
|
|
266
285
|
if (serverName && mode) {
|
|
267
286
|
if (mode === 'allow-all') {
|
|
268
|
-
ctx.print(`Use /settings → MCP to explicitly enable allow-all for ${serverName}. Direct
|
|
287
|
+
ctx.print(`Use /settings → MCP to explicitly enable allow-all for ${serverName}. Direct command escalation is blocked.`);
|
|
269
288
|
ctx.openSettingsModal?.();
|
|
270
289
|
return;
|
|
271
290
|
}
|
|
@@ -274,7 +293,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
274
293
|
return;
|
|
275
294
|
}
|
|
276
295
|
mcpApi.setServerTrustMode(serverName, mode);
|
|
277
|
-
ctx.print(`Updated MCP trust mode for ${serverName} to ${mode}.`);
|
|
296
|
+
ctx.print(`Updated MCP trust mode for ${serverName} to ${formatMcpTrustMode(mode)}.`);
|
|
278
297
|
return;
|
|
279
298
|
}
|
|
280
299
|
if (serverName || mode) {
|
|
@@ -292,7 +311,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
292
311
|
return;
|
|
293
312
|
}
|
|
294
313
|
mcpApi.setServerRole(serverName, role);
|
|
295
|
-
ctx.print(`Updated MCP role for ${serverName} to ${role}.`);
|
|
314
|
+
ctx.print(`Updated MCP role for ${serverName} to ${formatMcpRole(role)}.`);
|
|
296
315
|
return;
|
|
297
316
|
}
|
|
298
317
|
if (serverName || role) {
|
|
@@ -318,13 +337,16 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
318
337
|
const result = await mcpApi.upsertServerConfig(shellPaths, parsedAdd.scope, parsedAdd.server);
|
|
319
338
|
const connected = listServerSecurity().find((entry) => entry.name === parsedAdd.server.name)?.connected ?? false;
|
|
320
339
|
ctx.print([
|
|
321
|
-
`MCP server "${parsedAdd.server.name}" saved
|
|
322
|
-
`
|
|
323
|
-
`
|
|
324
|
-
'
|
|
340
|
+
`MCP server "${parsedAdd.server.name}" saved`,
|
|
341
|
+
` scope ${parsedAdd.scope}`,
|
|
342
|
+
` path ${result.path}`,
|
|
343
|
+
...(parsedAdd.scope === 'global' ? [' global config'] : []),
|
|
344
|
+
` Runtime reload: ${connected ? 'connected' : 'server saved; connection needs attention'} (+${result.reload.added} ~${result.reload.changed} -${result.reload.removed}, unchanged ${result.reload.unchanged})`,
|
|
345
|
+
` command ${parsedAdd.server.command}${parsedAdd.server.args?.length ? ` ${parsedAdd.server.args.join(' ')}` : ''}`,
|
|
346
|
+
' next /mcp tools',
|
|
325
347
|
].join('\n'));
|
|
326
348
|
} catch (error) {
|
|
327
|
-
ctx.print(`MCP add failed
|
|
349
|
+
ctx.print(`MCP add failed ${summarizeError(error)}`);
|
|
328
350
|
}
|
|
329
351
|
return;
|
|
330
352
|
}
|
|
@@ -360,10 +382,10 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
360
382
|
try {
|
|
361
383
|
const result = await mcpApi.removeServerConfig(shellPaths, scope, serverName);
|
|
362
384
|
ctx.print(result.removed
|
|
363
|
-
? `Removed MCP server "${serverName}" from ${scope} config ${result.path}. Reload
|
|
385
|
+
? `Removed MCP server "${serverName}" from ${scope} config ${result.path}. Reload +${result.reload.added} ~${result.reload.changed} -${result.reload.removed}, unchanged ${result.reload.unchanged}.`
|
|
364
386
|
: `No ${scope} MCP server named "${serverName}" exists in ${result.path}.\nIf it still appears, it is coming from another config scope or external MCP config.`);
|
|
365
387
|
} catch (error) {
|
|
366
|
-
ctx.print(`MCP remove failed
|
|
388
|
+
ctx.print(`MCP remove failed ${summarizeError(error)}`);
|
|
367
389
|
}
|
|
368
390
|
return;
|
|
369
391
|
}
|
|
@@ -376,9 +398,9 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
376
398
|
try {
|
|
377
399
|
const result = await reloadMcpRuntime(ctx);
|
|
378
400
|
const servers = listServerSecurity();
|
|
379
|
-
ctx.print(`Reloaded MCP runtime from config. ${servers.filter((server) => server.connected).length}/${servers.length} server(s) connected. Result
|
|
401
|
+
ctx.print(`Reloaded MCP runtime from config. ${servers.filter((server) => server.connected).length}/${servers.length} server(s) connected. Result +${result.added} ~${result.changed} -${result.removed}, unchanged ${result.unchanged}.`);
|
|
380
402
|
} catch (error) {
|
|
381
|
-
ctx.print(`MCP reload failed
|
|
403
|
+
ctx.print(`MCP reload failed ${summarizeError(error)}`);
|
|
382
404
|
}
|
|
383
405
|
return;
|
|
384
406
|
}
|
|
@@ -389,22 +411,22 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
389
411
|
const effective = mcpApi.getEffectiveConfig(shellPaths);
|
|
390
412
|
ctx.print([
|
|
391
413
|
'MCP Config',
|
|
392
|
-
' locations
|
|
414
|
+
' locations',
|
|
393
415
|
...effective.locations.map((location) => ` ${location.scope}/${location.kind}${location.writable ? ' writable' : ' read-only'} ${location.path}`),
|
|
394
|
-
` effective servers
|
|
416
|
+
` effective servers ${effective.servers.length}`,
|
|
395
417
|
...effective.servers.map((entry) => {
|
|
396
418
|
const server = entry.server;
|
|
397
419
|
const envKeys = Object.keys(server.env ?? {});
|
|
398
|
-
return ` - ${server.name}: ${server.command}${server.args?.length ? ` ${server.args.join(' ')}` : ''}
|
|
420
|
+
return ` - ${server.name}: ${server.command}${server.args?.length ? ` ${server.args.join(' ')}` : ''} origin ${entry.source.scope}/${entry.source.kind}${envKeys.length ? ` envKeys=${envKeys.join(',')}` : ''}`;
|
|
399
421
|
}),
|
|
400
422
|
'',
|
|
401
|
-
'Add or update from inside Agent with explicit confirmation
|
|
423
|
+
'Add or update from inside Agent with explicit confirmation',
|
|
424
|
+
' Open /mcp and choose Add or update server, or use Agent Workspace -> Tools & MCP -> Add MCP server.',
|
|
425
|
+
'Automation equivalent',
|
|
402
426
|
' /mcp add <name> <command> [args...] [--scope project|global] [--role <role>] [--trust <mode>] --yes',
|
|
403
|
-
'Example:',
|
|
404
|
-
' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --trust constrained --yes',
|
|
405
427
|
].join('\n'));
|
|
406
428
|
} catch (error) {
|
|
407
|
-
ctx.print(`MCP config read failed
|
|
429
|
+
ctx.print(`MCP config read failed ${summarizeError(error)}`);
|
|
408
430
|
}
|
|
409
431
|
return;
|
|
410
432
|
}
|
|
@@ -418,12 +440,12 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
418
440
|
}
|
|
419
441
|
if (action === 'approve') {
|
|
420
442
|
if (!confirmation.yes) {
|
|
421
|
-
requireYesFlag(ctx, `approve MCP
|
|
443
|
+
requireYesFlag(ctx, `approve MCP tool-definition quarantine override for ${serverName}`, '/mcp quarantine <server> approve [operatorId] --yes');
|
|
422
444
|
return;
|
|
423
445
|
}
|
|
424
446
|
const operatorId = commandArgs[3] || 'operator';
|
|
425
447
|
mcpApi.approveSchemaQuarantine(serverName, operatorId);
|
|
426
|
-
ctx.print(`Approved MCP
|
|
448
|
+
ctx.print(`Approved MCP tool-definition quarantine override for ${serverName} as ${operatorId}. Refresh is still recommended.`);
|
|
427
449
|
return;
|
|
428
450
|
}
|
|
429
451
|
if (!confirmation.yes) {
|
|
@@ -432,7 +454,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
432
454
|
}
|
|
433
455
|
const detail = commandArgs.slice(2).join(' ') || 'quarantined by operator';
|
|
434
456
|
mcpApi.quarantineSchema(serverName, 'operator_flagged', detail);
|
|
435
|
-
ctx.print(`Quarantined MCP
|
|
457
|
+
ctx.print(`Quarantined MCP tool definitions for ${serverName}.\nReason: ${detail}`);
|
|
436
458
|
return;
|
|
437
459
|
}
|
|
438
460
|
|
|
@@ -446,8 +468,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
446
468
|
+ ' ~/.config/claude/claude_desktop_config.json (Claude Desktop)\n'
|
|
447
469
|
+ ' .mcp/mcp.json (project-local)\n'
|
|
448
470
|
+ ' .goodvibes/mcp.json (goodvibes project)\n'
|
|
449
|
-
+ '\
|
|
450
|
-
+ ' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --yes\n'
|
|
471
|
+
+ '\nOpen /mcp and choose Add or update server, or use Agent Workspace -> Tools & MCP -> Add MCP server.\n'
|
|
451
472
|
+ '\nFormat: { "servers": [{ "name": "my-server", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] }] }'
|
|
452
473
|
);
|
|
453
474
|
return;
|
|
@@ -458,26 +479,22 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
458
479
|
});
|
|
459
480
|
}
|
|
460
481
|
|
|
461
|
-
type McpSecurityServer = ReturnType<NonNullable<NonNullable<CommandContext['clients']>['mcpApi']>['listServerSecurity']>[number];
|
|
462
|
-
|
|
463
482
|
function formatMcpServerList(servers: readonly McpSecurityServer[]): string {
|
|
464
483
|
const connected = servers.filter(s => s.connected);
|
|
465
484
|
const disconnected = servers.filter(s => !s.connected);
|
|
466
485
|
const lines: string[] = [`MCP Servers (${connected.length}/${servers.length} connected):`];
|
|
467
486
|
for (const s of servers) {
|
|
468
|
-
const pathScope = s.allowedPaths.length > 0 ? ` paths
|
|
469
|
-
const hostScope = s.allowedHosts.length > 0 ? ` hosts
|
|
470
|
-
const freshness = `
|
|
471
|
-
const quarantine = s.schemaFreshness === 'quarantined' ? `
|
|
472
|
-
lines.push(` ${s.connected ? '[connected] ' : '[disconnected]'} ${s.name} trust=${s.trustMode} role=${s.role}${freshness}${quarantine}${pathScope}${hostScope}`);
|
|
487
|
+
const pathScope = s.allowedPaths.length > 0 ? ` paths ${s.allowedPaths.length}` : '';
|
|
488
|
+
const hostScope = s.allowedHosts.length > 0 ? ` hosts ${s.allowedHosts.length}` : '';
|
|
489
|
+
const freshness = ` status ${formatMcpFreshness(s.schemaFreshness)}`;
|
|
490
|
+
const quarantine = s.schemaFreshness === 'quarantined' ? ` review reason ${s.quarantineReason ?? 'unknown'}` : '';
|
|
491
|
+
lines.push(` ${s.connected ? '[connected] ' : '[disconnected]'} ${s.name} trust=${formatMcpTrustMode(s.trustMode)} role=${formatMcpRole(s.role)}${freshness}${quarantine}${pathScope}${hostScope}`);
|
|
473
492
|
}
|
|
474
493
|
if (connected.length > 0) {
|
|
475
494
|
lines.push('');
|
|
476
|
-
lines.push('
|
|
477
|
-
lines.push('
|
|
478
|
-
lines.push('
|
|
479
|
-
lines.push('Run "/mcp trust <server> <mode> --yes" to change trust mode, or "/mcp role <server> <role> --yes" to change its coherence role.');
|
|
480
|
-
lines.push('Run "/mcp quarantine <server> [detail] --yes" to block a server, or "/mcp quarantine <server> approve [operatorId] --yes" to approve a temporary override.');
|
|
495
|
+
lines.push('Open "/mcp" for the fullscreen MCP workspace with server status, tools, config paths, and confirmed add/remove/reload actions.');
|
|
496
|
+
lines.push('Use "/mcp tools" for a compact tool list, or "/mcp tools <server>" for one server.');
|
|
497
|
+
lines.push('Use Settings -> MCP for allow-all decisions; trust, role, and quarantine command actions still require explicit --yes confirmation.');
|
|
481
498
|
lines.push('Use /settings -> MCP to explicitly enable allow-all for a server.');
|
|
482
499
|
}
|
|
483
500
|
if (disconnected.length > 0) {
|