@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
|
@@ -2,6 +2,7 @@ import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
|
2
2
|
import type { McpConfigScope, McpReloadResult, McpServerConfig } from '@pellux/goodvibes-sdk/platform/mcp';
|
|
3
3
|
import { requireMcpApi, requireShellPaths } from './runtime-services.ts';
|
|
4
4
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
5
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
5
6
|
|
|
6
7
|
const MCP_ROLES = ['general', 'docs', 'filesystem', 'git', 'database', 'browser', 'automation', 'ops', 'remote'] as const;
|
|
7
8
|
const MCP_TRUST_MODES = ['constrained', 'ask-on-risk', 'allow-all', 'blocked'] as const;
|
|
@@ -137,11 +138,13 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
137
138
|
aliases: [],
|
|
138
139
|
description: 'Manage MCP servers and their tools',
|
|
139
140
|
usage: '[add|remove|reload|config|review|tools [<server>]|auth-review|repair [server]]',
|
|
140
|
-
argsHint: '[
|
|
141
|
+
argsHint: '[review|tools|config|add --yes|remove --yes]',
|
|
141
142
|
async handler(args, ctx) {
|
|
142
143
|
const mcpApi = requireMcpApi(ctx);
|
|
143
144
|
const listServerSecurity = () => mcpApi.listServerSecurity();
|
|
144
|
-
const
|
|
145
|
+
const confirmation = stripYesFlag(args);
|
|
146
|
+
const commandArgs = [...confirmation.rest];
|
|
147
|
+
const subcommand = commandArgs[0];
|
|
145
148
|
if (!subcommand && ctx.openMcpWorkspace) {
|
|
146
149
|
ctx.openMcpWorkspace();
|
|
147
150
|
return;
|
|
@@ -161,7 +164,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
161
164
|
return;
|
|
162
165
|
}
|
|
163
166
|
if (subcommand === 'tools') {
|
|
164
|
-
const filterServer =
|
|
167
|
+
const filterServer = commandArgs[1];
|
|
165
168
|
ctx.print('Fetching MCP tool list...');
|
|
166
169
|
let allTools;
|
|
167
170
|
try {
|
|
@@ -207,7 +210,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
207
210
|
}
|
|
208
211
|
|
|
209
212
|
if (subcommand === 'repair') {
|
|
210
|
-
const serverName =
|
|
213
|
+
const serverName = commandArgs[1];
|
|
211
214
|
const servers = listServerSecurity();
|
|
212
215
|
const selected = serverName ? servers.find((server) => server.name === serverName) : servers.find((server) => !server.connected || server.schemaFreshness === 'quarantined');
|
|
213
216
|
if (!selected) {
|
|
@@ -218,7 +221,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
218
221
|
}
|
|
219
222
|
const nextSteps = [
|
|
220
223
|
selected.schemaFreshness === 'quarantined'
|
|
221
|
-
? `/mcp quarantine ${selected.name} approve operator`
|
|
224
|
+
? `/mcp quarantine ${selected.name} approve operator --yes`
|
|
222
225
|
: null,
|
|
223
226
|
!selected.connected ? '/services auth-review' : null,
|
|
224
227
|
'/mcp review',
|
|
@@ -239,14 +242,18 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
239
242
|
}
|
|
240
243
|
|
|
241
244
|
if (subcommand === 'trust') {
|
|
242
|
-
const serverName =
|
|
243
|
-
const mode =
|
|
245
|
+
const serverName = commandArgs[1];
|
|
246
|
+
const mode = commandArgs[2] as 'constrained' | 'ask-on-risk' | 'allow-all' | 'blocked' | undefined;
|
|
244
247
|
if (serverName && mode) {
|
|
245
248
|
if (mode === 'allow-all') {
|
|
246
249
|
ctx.print(`Use /settings → MCP to explicitly enable allow-all for ${serverName}. Direct CLI escalation is blocked.`);
|
|
247
250
|
ctx.openSettingsModal?.();
|
|
248
251
|
return;
|
|
249
252
|
}
|
|
253
|
+
if (!confirmation.yes) {
|
|
254
|
+
requireYesFlag(ctx, `change MCP trust mode for ${serverName}`, '/mcp trust <server> <constrained|ask-on-risk|blocked> --yes');
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
250
257
|
mcpApi.setServerTrustMode(serverName, mode);
|
|
251
258
|
ctx.print(`Updated MCP trust mode for ${serverName} to ${mode}.`);
|
|
252
259
|
return;
|
|
@@ -258,9 +265,13 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
258
265
|
}
|
|
259
266
|
|
|
260
267
|
if (subcommand === 'role') {
|
|
261
|
-
const serverName =
|
|
262
|
-
const role =
|
|
268
|
+
const serverName = commandArgs[1];
|
|
269
|
+
const role = commandArgs[2] as 'general' | 'docs' | 'filesystem' | 'git' | 'database' | 'browser' | 'automation' | 'ops' | 'remote' | undefined;
|
|
263
270
|
if (serverName && role) {
|
|
271
|
+
if (!confirmation.yes) {
|
|
272
|
+
requireYesFlag(ctx, `change MCP role for ${serverName}`, '/mcp role <server> <general|docs|filesystem|git|database|browser|automation|ops|remote> --yes');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
264
275
|
mcpApi.setServerRole(serverName, role);
|
|
265
276
|
ctx.print(`Updated MCP role for ${serverName} to ${role}.`);
|
|
266
277
|
return;
|
|
@@ -272,21 +283,25 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
272
283
|
}
|
|
273
284
|
|
|
274
285
|
if (subcommand === 'add') {
|
|
275
|
-
|
|
286
|
+
if (!confirmation.yes) {
|
|
287
|
+
requireYesFlag(ctx, 'add or update an MCP server config', '/mcp add <name> <command> [args...] [--scope project|global] [--role <role>] [--trust <mode>] --yes');
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
let parsedAdd: ParsedMcpAddArgs;
|
|
276
291
|
try {
|
|
277
|
-
|
|
292
|
+
parsedAdd = parseAddServerArgs(commandArgs);
|
|
278
293
|
} catch (error) {
|
|
279
294
|
ctx.print(summarizeError(error));
|
|
280
295
|
return;
|
|
281
296
|
}
|
|
282
297
|
const shellPaths = requireShellPaths(ctx);
|
|
283
298
|
try {
|
|
284
|
-
const result = await mcpApi.upsertServerConfig(shellPaths,
|
|
285
|
-
const connected = listServerSecurity().find((entry) => entry.name ===
|
|
299
|
+
const result = await mcpApi.upsertServerConfig(shellPaths, parsedAdd.scope, parsedAdd.server);
|
|
300
|
+
const connected = listServerSecurity().find((entry) => entry.name === parsedAdd.server.name)?.connected ?? false;
|
|
286
301
|
ctx.print([
|
|
287
|
-
`MCP server "${
|
|
302
|
+
`MCP server "${parsedAdd.server.name}" saved to ${parsedAdd.scope} config: ${result.path}.`,
|
|
288
303
|
`Runtime reload: ${connected ? 'connected' : 'server saved; connection needs attention'} (+${result.reload.added} ~${result.reload.changed} -${result.reload.removed}, unchanged ${result.reload.unchanged}).`,
|
|
289
|
-
`Command: ${
|
|
304
|
+
`Command: ${parsedAdd.server.command}${parsedAdd.server.args?.length ? ` ${parsedAdd.server.args.join(' ')}` : ''}`,
|
|
290
305
|
'Next: /mcp tools',
|
|
291
306
|
].join('\n'));
|
|
292
307
|
} catch (error) {
|
|
@@ -296,16 +311,20 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
296
311
|
}
|
|
297
312
|
|
|
298
313
|
if (subcommand === 'remove') {
|
|
299
|
-
const serverName =
|
|
314
|
+
const serverName = commandArgs[1]?.trim();
|
|
300
315
|
if (!serverName) {
|
|
301
|
-
ctx.print('Usage: /mcp remove <server> [--scope project|global]');
|
|
316
|
+
ctx.print('Usage: /mcp remove <server> [--scope project|global] --yes');
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (!confirmation.yes) {
|
|
320
|
+
requireYesFlag(ctx, `remove MCP server ${serverName}`, '/mcp remove <server> [--scope project|global] --yes');
|
|
302
321
|
return;
|
|
303
322
|
}
|
|
304
323
|
let scope: McpConfigScope = 'project';
|
|
305
324
|
try {
|
|
306
|
-
for (let index = 2; index <
|
|
307
|
-
if (
|
|
308
|
-
const value = readFlagValue(
|
|
325
|
+
for (let index = 2; index < commandArgs.length; index += 1) {
|
|
326
|
+
if (commandArgs[index] === '--scope') {
|
|
327
|
+
const value = readFlagValue(commandArgs, index, '--scope');
|
|
309
328
|
if (!isMcpScope(value)) {
|
|
310
329
|
ctx.print(`Invalid MCP scope "${value}". Expected project or global.`);
|
|
311
330
|
return;
|
|
@@ -331,6 +350,10 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
331
350
|
}
|
|
332
351
|
|
|
333
352
|
if (subcommand === 'reload') {
|
|
353
|
+
if (!confirmation.yes) {
|
|
354
|
+
requireYesFlag(ctx, 'reload the MCP runtime from config', '/mcp reload --yes');
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
334
357
|
try {
|
|
335
358
|
const result = await reloadMcpRuntime(ctx);
|
|
336
359
|
const servers = listServerSecurity();
|
|
@@ -356,10 +379,10 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
356
379
|
return ` - ${server.name}: ${server.command}${server.args?.length ? ` ${server.args.join(' ')}` : ''} source=${entry.source.scope}/${entry.source.kind}${envKeys.length ? ` envKeys=${envKeys.join(',')}` : ''}`;
|
|
357
380
|
}),
|
|
358
381
|
'',
|
|
359
|
-
'Add or update from inside
|
|
360
|
-
' /mcp add <name> <command> [args...] [--scope project|global] [--role <role>] [--trust <mode>]',
|
|
382
|
+
'Add or update from inside Agent with explicit confirmation:',
|
|
383
|
+
' /mcp add <name> <command> [args...] [--scope project|global] [--role <role>] [--trust <mode>] --yes',
|
|
361
384
|
'Example:',
|
|
362
|
-
' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --trust constrained',
|
|
385
|
+
' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --trust constrained --yes',
|
|
363
386
|
].join('\n'));
|
|
364
387
|
} catch (error) {
|
|
365
388
|
ctx.print(`MCP config read failed: ${summarizeError(error)}`);
|
|
@@ -368,19 +391,27 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
368
391
|
}
|
|
369
392
|
|
|
370
393
|
if (subcommand === 'quarantine') {
|
|
371
|
-
const serverName =
|
|
372
|
-
const action =
|
|
394
|
+
const serverName = commandArgs[1];
|
|
395
|
+
const action = commandArgs[2];
|
|
373
396
|
if (!serverName) {
|
|
374
|
-
ctx.print('Usage: /mcp quarantine <server> [detail]\n /mcp quarantine <server> approve [operatorId]');
|
|
397
|
+
ctx.print('Usage: /mcp quarantine <server> [detail] --yes\n /mcp quarantine <server> approve [operatorId] --yes');
|
|
375
398
|
return;
|
|
376
399
|
}
|
|
377
400
|
if (action === 'approve') {
|
|
378
|
-
|
|
401
|
+
if (!confirmation.yes) {
|
|
402
|
+
requireYesFlag(ctx, `approve MCP schema quarantine override for ${serverName}`, '/mcp quarantine <server> approve [operatorId] --yes');
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const operatorId = commandArgs[3] || 'operator';
|
|
379
406
|
mcpApi.approveSchemaQuarantine(serverName, operatorId);
|
|
380
407
|
ctx.print(`Approved MCP schema quarantine override for ${serverName} as ${operatorId}. Refresh is still recommended.`);
|
|
381
408
|
return;
|
|
382
409
|
}
|
|
383
|
-
|
|
410
|
+
if (!confirmation.yes) {
|
|
411
|
+
requireYesFlag(ctx, `quarantine MCP server ${serverName}`, '/mcp quarantine <server> [detail] --yes');
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const detail = commandArgs.slice(2).join(' ') || 'quarantined by operator';
|
|
384
415
|
mcpApi.quarantineSchema(serverName, 'operator_flagged', detail);
|
|
385
416
|
ctx.print(`Quarantined MCP schema for ${serverName}.\nReason: ${detail}`);
|
|
386
417
|
return;
|
|
@@ -396,8 +427,8 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
396
427
|
+ ' ~/.config/claude/claude_desktop_config.json (Claude Desktop)\n'
|
|
397
428
|
+ ' .mcp/mcp.json (project-local)\n'
|
|
398
429
|
+ ' .goodvibes/mcp.json (goodvibes project)\n'
|
|
399
|
-
+ '\nAdd one from inside
|
|
400
|
-
+ ' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem\n'
|
|
430
|
+
+ '\nAdd one from inside Agent with explicit confirmation:\n'
|
|
431
|
+
+ ' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --yes\n'
|
|
401
432
|
+ '\nFormat: { "servers": [{ "name": "my-server", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] }] }'
|
|
402
433
|
);
|
|
403
434
|
return;
|
|
@@ -416,10 +447,10 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
416
447
|
if (connected.length > 0) {
|
|
417
448
|
lines.push('');
|
|
418
449
|
lines.push('Run "/mcp tools" to list all tools, or "/mcp tools <server>" for a specific server.');
|
|
419
|
-
lines.push('Run "/mcp" to open the fullscreen MCP workspace, or "/mcp add <name> <command> [args...] [--scope project|global]" to add/update
|
|
420
|
-
lines.push('Run "/mcp reload" after editing MCP config outside
|
|
421
|
-
lines.push('Run "/mcp trust <server> <mode>" to change trust mode, or "/mcp role <server> <role>" to change its coherence role.');
|
|
422
|
-
lines.push('Run "/mcp quarantine <server> [detail]" to block a server, or "/mcp quarantine <server> approve [operatorId]" to approve a temporary override.');
|
|
450
|
+
lines.push('Run "/mcp" to open the fullscreen MCP workspace, or "/mcp add <name> <command> [args...] [--scope project|global] --yes" to add/update.');
|
|
451
|
+
lines.push('Run "/mcp reload --yes" after editing MCP config outside Agent.');
|
|
452
|
+
lines.push('Run "/mcp trust <server> <mode> --yes" to change trust mode, or "/mcp role <server> <role> --yes" to change its coherence role.');
|
|
453
|
+
lines.push('Run "/mcp quarantine <server> [detail] --yes" to block a server, or "/mcp quarantine <server> approve [operatorId] --yes" to approve a temporary override.');
|
|
423
454
|
lines.push('Use /settings → MCP to explicitly enable allow-all for a server.');
|
|
424
455
|
}
|
|
425
456
|
if (disconnected.length > 0) {
|
|
@@ -1,111 +1,148 @@
|
|
|
1
1
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
2
3
|
|
|
3
4
|
export function registerMemoryProductRuntimeCommands(registry: CommandRegistry): void {
|
|
4
5
|
registry.register({
|
|
5
6
|
name: 'memory-sync',
|
|
6
7
|
aliases: ['memsync'],
|
|
7
8
|
description: 'Dedicated front-door for durable memory export/import and bundle exchange',
|
|
8
|
-
usage: '[export <path> [scope] | import <path>]',
|
|
9
|
+
usage: '[export <path> [scope] --yes | import <path> --yes]',
|
|
9
10
|
async handler(args, ctx) {
|
|
10
|
-
const
|
|
11
|
+
const parsed = stripYesFlag(args);
|
|
12
|
+
const commandArgs = [...parsed.rest];
|
|
13
|
+
const sub = (commandArgs[0] ?? '').toLowerCase();
|
|
11
14
|
if (!ctx.executeCommand) {
|
|
12
15
|
ctx.print('Memory sync controls are not available in this runtime.');
|
|
13
16
|
return;
|
|
14
17
|
}
|
|
15
|
-
if (sub === 'export' &&
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
if (sub === 'export' && commandArgs[1]) {
|
|
19
|
+
if (!parsed.yes) {
|
|
20
|
+
requireYesFlag(ctx, `export durable memory bundle to ${commandArgs[1]}`, '/memory-sync export <path> [scope] --yes');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const scope = commandArgs[2];
|
|
24
|
+
const recallArgs = ['export', commandArgs[1], ...(scope ? ['--scope', scope] : []), '--yes'];
|
|
18
25
|
await ctx.executeCommand('recall', recallArgs);
|
|
19
26
|
return;
|
|
20
27
|
}
|
|
21
|
-
if (sub === 'import' &&
|
|
22
|
-
|
|
28
|
+
if (sub === 'import' && commandArgs[1]) {
|
|
29
|
+
if (!parsed.yes) {
|
|
30
|
+
requireYesFlag(ctx, `import durable memory bundle from ${commandArgs[1]}`, '/memory-sync import <path> --yes');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
await ctx.executeCommand('recall', ['import', commandArgs[1], '--yes']);
|
|
23
34
|
return;
|
|
24
35
|
}
|
|
25
|
-
ctx.print('Usage: /memory-sync [export <path> [scope] | import <path>]');
|
|
36
|
+
ctx.print('Usage: /memory-sync [export <path> [scope] --yes | import <path> --yes]');
|
|
26
37
|
},
|
|
27
38
|
});
|
|
28
39
|
|
|
29
40
|
registry.register({
|
|
30
41
|
name: 'handoff',
|
|
31
42
|
description: 'Dedicated front-door for reviewable memory handoff bundles',
|
|
32
|
-
usage: '[export <path> [scope] | inspect <path> | import <path>]',
|
|
43
|
+
usage: '[export <path> [scope] --yes | inspect <path> | import <path> --yes]',
|
|
33
44
|
async handler(args, ctx) {
|
|
34
|
-
const
|
|
45
|
+
const parsed = stripYesFlag(args);
|
|
46
|
+
const commandArgs = [...parsed.rest];
|
|
47
|
+
const sub = (commandArgs[0] ?? '').toLowerCase();
|
|
35
48
|
if (!ctx.executeCommand) {
|
|
36
49
|
ctx.print('Handoff controls are not available in this runtime.');
|
|
37
50
|
return;
|
|
38
51
|
}
|
|
39
|
-
if (sub === 'export' &&
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
if (sub === 'export' && commandArgs[1]) {
|
|
53
|
+
if (!parsed.yes) {
|
|
54
|
+
requireYesFlag(ctx, `export memory handoff bundle to ${commandArgs[1]}`, '/handoff export <path> [scope] --yes');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const scope = commandArgs[2];
|
|
58
|
+
await ctx.executeCommand('recall', ['handoff-export', commandArgs[1], ...(scope ? ['--scope', scope] : []), '--yes']);
|
|
42
59
|
return;
|
|
43
60
|
}
|
|
44
|
-
if (sub === 'inspect' &&
|
|
45
|
-
await ctx.executeCommand('recall', ['handoff-inspect',
|
|
61
|
+
if (sub === 'inspect' && commandArgs[1]) {
|
|
62
|
+
await ctx.executeCommand('recall', ['handoff-inspect', commandArgs[1]]);
|
|
46
63
|
return;
|
|
47
64
|
}
|
|
48
|
-
if (sub === 'import' &&
|
|
49
|
-
|
|
65
|
+
if (sub === 'import' && commandArgs[1]) {
|
|
66
|
+
if (!parsed.yes) {
|
|
67
|
+
requireYesFlag(ctx, `import memory handoff bundle from ${commandArgs[1]}`, '/handoff import <path> --yes');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
await ctx.executeCommand('recall', ['handoff-import', commandArgs[1], '--yes']);
|
|
50
71
|
return;
|
|
51
72
|
}
|
|
52
|
-
ctx.print('Usage: /handoff [export <path> [scope] | inspect <path> | import <path>]');
|
|
73
|
+
ctx.print('Usage: /handoff [export <path> [scope] --yes | inspect <path> | import <path> --yes]');
|
|
53
74
|
},
|
|
54
75
|
});
|
|
55
76
|
|
|
56
77
|
registry.register({
|
|
57
78
|
name: 'session-memory',
|
|
58
79
|
description: 'Dedicated front-door for session-scoped memory capture and review',
|
|
59
|
-
usage: '[queue [limit] | export <path> | add <class> <summary...>]',
|
|
80
|
+
usage: '[queue [limit] | export <path> --yes | add <class> <summary...>]',
|
|
60
81
|
async handler(args, ctx) {
|
|
61
|
-
const
|
|
82
|
+
const parsed = stripYesFlag(args);
|
|
83
|
+
const commandArgs = [...parsed.rest];
|
|
84
|
+
const sub = (commandArgs[0] ?? 'queue').toLowerCase();
|
|
62
85
|
if (!ctx.executeCommand) {
|
|
63
86
|
ctx.print('Session memory controls are not available in this runtime.');
|
|
64
87
|
return;
|
|
65
88
|
}
|
|
66
89
|
if (sub === 'queue') {
|
|
67
|
-
await ctx.executeCommand('recall', ['queue', ...(
|
|
90
|
+
await ctx.executeCommand('recall', ['queue', ...(commandArgs[1] ? [commandArgs[1]] : [])]);
|
|
68
91
|
return;
|
|
69
92
|
}
|
|
70
|
-
if (sub === 'export' &&
|
|
71
|
-
|
|
93
|
+
if (sub === 'export' && commandArgs[1]) {
|
|
94
|
+
if (!parsed.yes) {
|
|
95
|
+
requireYesFlag(ctx, `export session memory bundle to ${commandArgs[1]}`, '/session-memory export <path> --yes');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
await ctx.executeCommand('recall', ['export', commandArgs[1], '--scope', 'session', '--yes']);
|
|
72
99
|
return;
|
|
73
100
|
}
|
|
74
|
-
if (sub === 'add' &&
|
|
75
|
-
await ctx.executeCommand('recall', ['add',
|
|
101
|
+
if (sub === 'add' && commandArgs.length >= 3) {
|
|
102
|
+
await ctx.executeCommand('recall', ['add', commandArgs[1], ...commandArgs.slice(2), '--scope', 'session']);
|
|
76
103
|
return;
|
|
77
104
|
}
|
|
78
|
-
ctx.print('Usage: /session-memory [queue [limit] | export <path> | add <class> <summary...>]');
|
|
105
|
+
ctx.print('Usage: /session-memory [queue [limit] | export <path> --yes | add <class> <summary...>]');
|
|
79
106
|
},
|
|
80
107
|
});
|
|
81
108
|
|
|
82
109
|
registry.register({
|
|
83
110
|
name: 'team-memory',
|
|
84
111
|
description: 'Dedicated front-door for team/shared memory review and exchange',
|
|
85
|
-
usage: '[queue [limit] | export <path> | import <path> | capture policy]',
|
|
112
|
+
usage: '[queue [limit] | export <path> --yes | import <path> --yes | capture policy]',
|
|
86
113
|
async handler(args, ctx) {
|
|
87
|
-
const
|
|
114
|
+
const parsed = stripYesFlag(args);
|
|
115
|
+
const commandArgs = [...parsed.rest];
|
|
116
|
+
const sub = (commandArgs[0] ?? 'queue').toLowerCase();
|
|
88
117
|
if (!ctx.executeCommand) {
|
|
89
118
|
ctx.print('Team memory controls are not available in this runtime.');
|
|
90
119
|
return;
|
|
91
120
|
}
|
|
92
121
|
if (sub === 'queue') {
|
|
93
|
-
await ctx.executeCommand('recall', ['queue', ...(
|
|
122
|
+
await ctx.executeCommand('recall', ['queue', ...(commandArgs[1] ? [commandArgs[1]] : [])]);
|
|
94
123
|
return;
|
|
95
124
|
}
|
|
96
|
-
if (sub === 'export' &&
|
|
97
|
-
|
|
125
|
+
if (sub === 'export' && commandArgs[1]) {
|
|
126
|
+
if (!parsed.yes) {
|
|
127
|
+
requireYesFlag(ctx, `export team memory handoff bundle to ${commandArgs[1]}`, '/team-memory export <path> --yes');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
await ctx.executeCommand('recall', ['handoff-export', commandArgs[1], '--scope', 'team', '--yes']);
|
|
98
131
|
return;
|
|
99
132
|
}
|
|
100
|
-
if (sub === 'import' &&
|
|
101
|
-
|
|
133
|
+
if (sub === 'import' && commandArgs[1]) {
|
|
134
|
+
if (!parsed.yes) {
|
|
135
|
+
requireYesFlag(ctx, `import team memory handoff bundle from ${commandArgs[1]}`, '/team-memory import <path> --yes');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
await ctx.executeCommand('recall', ['handoff-import', commandArgs[1], '--yes']);
|
|
102
139
|
return;
|
|
103
140
|
}
|
|
104
|
-
if (sub === 'capture' &&
|
|
141
|
+
if (sub === 'capture' && commandArgs[1]?.toLowerCase() === 'policy') {
|
|
105
142
|
await ctx.executeCommand('recall', ['capture', 'policy']);
|
|
106
143
|
return;
|
|
107
144
|
}
|
|
108
|
-
ctx.print('Usage: /team-memory [queue [limit] | export <path> | import <path> | capture policy]');
|
|
145
|
+
ctx.print('Usage: /team-memory [queue [limit] | export <path> --yes | import <path> --yes | capture policy]');
|
|
109
146
|
},
|
|
110
147
|
});
|
|
111
148
|
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* /recall add <class> <summary> --detail <text> --tags <tag,tag>
|
|
8
8
|
* /recall search [query] — Search memory records
|
|
9
9
|
* /recall search --cls <class> — Filter by class
|
|
10
|
-
* /recall link <fromId> <toId> <relation> — Link two records
|
|
10
|
+
* /recall link <fromId> <toId> <relation> --yes — Link two records
|
|
11
11
|
* /recall get <id> — Show a single record with provenance
|
|
12
12
|
* /recall list [class] — List all records (optionally by class)
|
|
13
|
-
* /recall remove <id>
|
|
13
|
+
* /recall remove <id> --yes — Delete a record
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { SlashCommand, CommandContext } from '../command-registry.ts';
|
|
@@ -128,20 +128,20 @@ export const recallCommand: SlashCommand = {
|
|
|
128
128
|
' search [query] [--semantic] [--cls <class>] [--scope <scope>] [--limit <n>] — Full-text or sqlite-vec semantic search',
|
|
129
129
|
' vector [status|doctor|rebuild] — Inspect or rebuild the sqlite-vec memory index',
|
|
130
130
|
' get <id> — Show record with provenance + links',
|
|
131
|
-
' link <fromId> <toId> <relation>
|
|
131
|
+
' link <fromId> <toId> <relation> --yes — Create a directed relation between records',
|
|
132
132
|
' queue [limit] — Show the operator review queue',
|
|
133
133
|
' review <id> <state> [--confidence <n>] [--by <name>] [--reason <text>]',
|
|
134
134
|
' stale <id> [reason...] — Mark a record stale with an operator reason',
|
|
135
135
|
' contradict <id> [reason...] — Mark a record contradicted with an operator reason',
|
|
136
136
|
' explain <task...> [--scope <path> ...] — Show the knowledge records that would be injected for a task',
|
|
137
|
-
' promote <id> <scope>
|
|
138
|
-
' export <path> [--scope <scope>] [--cls <class>] — Export a durable knowledge bundle',
|
|
139
|
-
' import <path>
|
|
140
|
-
' handoff-export <path> [--scope <scope>]
|
|
137
|
+
' promote <id> <scope> --yes — Promote a memory record into session|project|team scope',
|
|
138
|
+
' export <path> [--scope <scope>] [--cls <class>] --yes — Export a durable knowledge bundle',
|
|
139
|
+
' import <path> --yes — Import a durable knowledge bundle',
|
|
140
|
+
' handoff-export <path> [--scope <scope>] --yes — Export a reviewable handoff bundle for team/shared use',
|
|
141
141
|
' handoff-inspect <path> — Inspect a handoff bundle before import',
|
|
142
|
-
' handoff-import <path>
|
|
142
|
+
' handoff-import <path> --yes — Import a handoff bundle into durable memory',
|
|
143
143
|
' list [class] [--scope <scope>] — List all records grouped by class',
|
|
144
|
-
' remove <id>
|
|
144
|
+
' remove <id> --yes — Delete a record',
|
|
145
145
|
].join('\n');
|
|
146
146
|
context.print(usage);
|
|
147
147
|
break;
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
2
|
import { requireWebhookNotifier } from './runtime-services.ts';
|
|
3
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
3
4
|
|
|
4
5
|
export function registerNotifyRuntimeCommands(registry: CommandRegistry): void {
|
|
5
6
|
registry.register({
|
|
6
7
|
name: 'notify',
|
|
7
8
|
aliases: [],
|
|
8
9
|
description: 'Manage webhook notification URLs (ntfy.sh format)',
|
|
9
|
-
usage: 'add <url> | remove <url> | list | clear | test',
|
|
10
|
-
argsHint: 'add|remove|
|
|
10
|
+
usage: 'add <url> --yes | remove <url> --yes | list | clear --yes | test --yes',
|
|
11
|
+
argsHint: 'list|add --yes|remove --yes|test --yes',
|
|
11
12
|
async handler(args, ctx) {
|
|
13
|
+
const parsed = stripYesFlag(args);
|
|
14
|
+
const commandArgs = [...parsed.rest];
|
|
12
15
|
const notifications = ctx.platform.configManager.getCategory('notifications');
|
|
13
16
|
const urls: string[] = Array.isArray(notifications.webhookUrls) ? [...notifications.webhookUrls] : [];
|
|
14
17
|
const notifier = requireWebhookNotifier(ctx);
|
|
15
|
-
const sub =
|
|
18
|
+
const sub = commandArgs[0];
|
|
16
19
|
|
|
17
20
|
if (!sub || sub === 'list') {
|
|
18
21
|
if (urls.length === 0) ctx.print('No webhook URLs configured.\nUse: /notify add <url>');
|
|
@@ -21,9 +24,13 @@ export function registerNotifyRuntimeCommands(registry: CommandRegistry): void {
|
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
if (sub === 'add') {
|
|
24
|
-
const url =
|
|
27
|
+
const url = commandArgs[1];
|
|
25
28
|
if (!url) {
|
|
26
|
-
ctx.print('Usage: /notify add <url
|
|
29
|
+
ctx.print('Usage: /notify add <url> --yes\nExample: /notify add https://ntfy.sh/my-topic --yes');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!parsed.yes) {
|
|
33
|
+
requireYesFlag(ctx, `add webhook notification URL ${url}`, '/notify add <url> --yes');
|
|
27
34
|
return;
|
|
28
35
|
}
|
|
29
36
|
try { new URL(url); } catch {
|
|
@@ -42,9 +49,13 @@ export function registerNotifyRuntimeCommands(registry: CommandRegistry): void {
|
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
if (sub === 'remove') {
|
|
45
|
-
const url =
|
|
52
|
+
const url = commandArgs[1];
|
|
46
53
|
if (!url) {
|
|
47
|
-
ctx.print('Usage: /notify remove <url>');
|
|
54
|
+
ctx.print('Usage: /notify remove <url> --yes');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!parsed.yes) {
|
|
58
|
+
requireYesFlag(ctx, `remove webhook notification URL ${url}`, '/notify remove <url> --yes');
|
|
48
59
|
return;
|
|
49
60
|
}
|
|
50
61
|
const next = urls.filter((u) => u !== url);
|
|
@@ -59,6 +70,10 @@ export function registerNotifyRuntimeCommands(registry: CommandRegistry): void {
|
|
|
59
70
|
}
|
|
60
71
|
|
|
61
72
|
if (sub === 'clear') {
|
|
73
|
+
if (!parsed.yes) {
|
|
74
|
+
requireYesFlag(ctx, 'clear all webhook notification URLs', '/notify clear --yes');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
62
77
|
ctx.platform.configManager.mergeCategory('notifications', { webhookUrls: [] });
|
|
63
78
|
notifier.setUrls([]);
|
|
64
79
|
ctx.print('All webhook URLs cleared.');
|
|
@@ -66,6 +81,10 @@ export function registerNotifyRuntimeCommands(registry: CommandRegistry): void {
|
|
|
66
81
|
}
|
|
67
82
|
|
|
68
83
|
if (sub === 'test') {
|
|
84
|
+
if (!parsed.yes) {
|
|
85
|
+
requireYesFlag(ctx, 'send webhook notification test requests', '/notify test --yes');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
69
88
|
if (urls.length === 0) {
|
|
70
89
|
ctx.print('No webhook URLs configured. Use: /notify add <url>');
|
|
71
90
|
return;
|
|
@@ -77,7 +96,7 @@ export function registerNotifyRuntimeCommands(registry: CommandRegistry): void {
|
|
|
77
96
|
return;
|
|
78
97
|
}
|
|
79
98
|
|
|
80
|
-
ctx.print('Usage: /notify add <url> | remove <url> | list | clear | test');
|
|
99
|
+
ctx.print('Usage: /notify add <url> --yes | remove <url> --yes | list | clear --yes | test --yes');
|
|
81
100
|
},
|
|
82
101
|
});
|
|
83
102
|
}
|