@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
|
@@ -4,6 +4,7 @@ import { getDefaultAcpAgentCommand } from '@pellux/goodvibes-sdk/platform/acp';
|
|
|
4
4
|
import type { CommandContext, RemoteCommandService } from '../command-registry.ts';
|
|
5
5
|
import type { RemoteSessionBundle } from '@/runtime/index.ts';
|
|
6
6
|
import { requireShellPaths } from './runtime-services.ts';
|
|
7
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
7
8
|
|
|
8
9
|
type RemoteRegistryLike = Pick<RemoteCommandService, 'listContracts' | 'exportSessionBundle' | 'importSessionBundle'>;
|
|
9
10
|
|
|
@@ -33,7 +34,9 @@ export async function handleRemoteSetupCommand(
|
|
|
33
34
|
activeConnections: ActiveConnectionLike[],
|
|
34
35
|
remoteRegistry: RemoteRegistryLike,
|
|
35
36
|
): Promise<boolean> {
|
|
36
|
-
const
|
|
37
|
+
const parsed = stripYesFlag(args);
|
|
38
|
+
const commandArgs = [...parsed.rest];
|
|
39
|
+
const subcommand = commandArgs[0]?.toLowerCase() ?? 'show';
|
|
37
40
|
if (subcommand === 'setup') {
|
|
38
41
|
const command = getDefaultAcpAgentCommand();
|
|
39
42
|
const danger = ctx.platform.configManager.getCategory('danger');
|
|
@@ -50,10 +53,14 @@ export async function handleRemoteSetupCommand(
|
|
|
50
53
|
' - use /remote env to export a reusable shell snippet',
|
|
51
54
|
' - enable danger.daemon / danger.httpListener only when you actually need those remote surfaces',
|
|
52
55
|
];
|
|
53
|
-
if (
|
|
54
|
-
const pathArg =
|
|
56
|
+
if (commandArgs[1]?.toLowerCase() === 'export') {
|
|
57
|
+
const pathArg = commandArgs[2];
|
|
55
58
|
if (!pathArg) {
|
|
56
|
-
ctx.print('Usage: /remote setup export <path>');
|
|
59
|
+
ctx.print('Usage: /remote setup export <path> --yes');
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (!parsed.yes) {
|
|
63
|
+
requireYesFlag(ctx, `export remote setup bundle to ${pathArg}`, '/remote setup export <path> --yes');
|
|
57
64
|
return true;
|
|
58
65
|
}
|
|
59
66
|
const shellPaths = requireShellPaths(ctx);
|
|
@@ -79,10 +86,14 @@ export async function handleRemoteSetupCommand(
|
|
|
79
86
|
`export ACP_AGENT_CMD='${command.join(' ')}'`,
|
|
80
87
|
`export GOODVIBES_REMOTE_SESSION='${ctx.session.runtime.sessionId}'`,
|
|
81
88
|
].join('\n');
|
|
82
|
-
if (
|
|
83
|
-
const pathArg =
|
|
89
|
+
if (commandArgs[1]?.toLowerCase() === 'export') {
|
|
90
|
+
const pathArg = commandArgs[2];
|
|
84
91
|
if (!pathArg) {
|
|
85
|
-
ctx.print('Usage: /remote env export <path>');
|
|
92
|
+
ctx.print('Usage: /remote env export <path> --yes');
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
if (!parsed.yes) {
|
|
96
|
+
requireYesFlag(ctx, `export remote environment snippet to ${pathArg}`, '/remote env export <path> --yes');
|
|
86
97
|
return true;
|
|
87
98
|
}
|
|
88
99
|
const shellPaths = requireShellPaths(ctx);
|
|
@@ -97,7 +108,7 @@ export async function handleRemoteSetupCommand(
|
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
if (subcommand === 'tunnel') {
|
|
100
|
-
const mode =
|
|
111
|
+
const mode = commandArgs[1]?.toLowerCase() ?? 'review';
|
|
101
112
|
const lines = [
|
|
102
113
|
'Remote Tunnel Review',
|
|
103
114
|
' transport: self-hosted ACP / daemon relay',
|
|
@@ -106,9 +117,13 @@ export async function handleRemoteSetupCommand(
|
|
|
106
117
|
' guidance: forward ACP agent traffic through your chosen self-hosted tunnel or SSH transport',
|
|
107
118
|
];
|
|
108
119
|
if (mode === 'export') {
|
|
109
|
-
const pathArg =
|
|
120
|
+
const pathArg = commandArgs[2];
|
|
110
121
|
if (!pathArg) {
|
|
111
|
-
ctx.print('Usage: /remote tunnel export <path>');
|
|
122
|
+
ctx.print('Usage: /remote tunnel export <path> --yes');
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
if (!parsed.yes) {
|
|
126
|
+
requireYesFlag(ctx, `export remote tunnel review to ${pathArg}`, '/remote tunnel export <path> --yes');
|
|
112
127
|
return true;
|
|
113
128
|
}
|
|
114
129
|
const shellPaths = requireShellPaths(ctx);
|
|
@@ -123,7 +138,7 @@ export async function handleRemoteSetupCommand(
|
|
|
123
138
|
}
|
|
124
139
|
|
|
125
140
|
if (subcommand === 'bootstrap') {
|
|
126
|
-
const mode =
|
|
141
|
+
const mode = commandArgs[1]?.toLowerCase() ?? 'export';
|
|
127
142
|
const payload = {
|
|
128
143
|
exportedAt: Date.now(),
|
|
129
144
|
sessionId: ctx.session.runtime.sessionId,
|
|
@@ -138,7 +153,7 @@ export async function handleRemoteSetupCommand(
|
|
|
138
153
|
],
|
|
139
154
|
};
|
|
140
155
|
if (mode === 'inspect') {
|
|
141
|
-
const pathArg =
|
|
156
|
+
const pathArg = commandArgs[2];
|
|
142
157
|
if (!pathArg) {
|
|
143
158
|
ctx.print('Usage: /remote bootstrap inspect <path>');
|
|
144
159
|
return true;
|
|
@@ -154,9 +169,13 @@ export async function handleRemoteSetupCommand(
|
|
|
154
169
|
].join('\n'));
|
|
155
170
|
return true;
|
|
156
171
|
}
|
|
157
|
-
const pathArg =
|
|
172
|
+
const pathArg = commandArgs[2] ?? commandArgs[1];
|
|
158
173
|
if (!pathArg || mode !== 'export') {
|
|
159
|
-
ctx.print('Usage: /remote bootstrap export <path> | /remote bootstrap inspect <path>');
|
|
174
|
+
ctx.print('Usage: /remote bootstrap export <path> --yes | /remote bootstrap inspect <path>');
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
if (!parsed.yes) {
|
|
178
|
+
requireYesFlag(ctx, `export remote bootstrap bundle to ${pathArg}`, '/remote bootstrap export <path> --yes');
|
|
160
179
|
return true;
|
|
161
180
|
}
|
|
162
181
|
const shellPaths = requireShellPaths(ctx);
|
|
@@ -168,15 +187,19 @@ export async function handleRemoteSetupCommand(
|
|
|
168
187
|
}
|
|
169
188
|
|
|
170
189
|
if (subcommand === 'session') {
|
|
171
|
-
const mode =
|
|
172
|
-
const pathArg =
|
|
190
|
+
const mode = commandArgs[1]?.toLowerCase();
|
|
191
|
+
const pathArg = commandArgs[2];
|
|
173
192
|
if (!mode || !pathArg) {
|
|
174
|
-
ctx.print('Usage: /remote session <export|inspect|import> <path>');
|
|
193
|
+
ctx.print('Usage: /remote session <export|inspect|import> <path> [--yes]');
|
|
175
194
|
return true;
|
|
176
195
|
}
|
|
177
196
|
const shellPaths = requireShellPaths(ctx);
|
|
178
197
|
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
179
198
|
if (mode === 'export') {
|
|
199
|
+
if (!parsed.yes) {
|
|
200
|
+
requireYesFlag(ctx, `export remote session bundle to ${pathArg}`, '/remote session export <path> --yes');
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
180
203
|
const exported = await remoteRegistry.exportSessionBundle(targetPath);
|
|
181
204
|
ctx.print(`Exported remote session bundle ${exported.bundle.sessionId} to ${exported.path}`);
|
|
182
205
|
return true;
|
|
@@ -187,11 +210,15 @@ export async function handleRemoteSetupCommand(
|
|
|
187
210
|
return true;
|
|
188
211
|
}
|
|
189
212
|
if (mode === 'import') {
|
|
213
|
+
if (!parsed.yes) {
|
|
214
|
+
requireYesFlag(ctx, `import remote session bundle from ${pathArg}`, '/remote session import <path> --yes');
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
190
217
|
const bundle = await remoteRegistry.importSessionBundle(targetPath);
|
|
191
218
|
ctx.print(`Imported remote session bundle ${bundle.sessionId} with ${bundle.contracts.length} contracts.`);
|
|
192
219
|
return true;
|
|
193
220
|
}
|
|
194
|
-
ctx.print('Usage: /remote session <export|inspect|import> <path>');
|
|
221
|
+
ctx.print('Usage: /remote session <export|inspect|import> <path> [--yes]');
|
|
195
222
|
return true;
|
|
196
223
|
}
|
|
197
224
|
|
|
@@ -4,6 +4,7 @@ import { AGENT_TEMPLATES } from '@pellux/goodvibes-sdk/platform/tools';
|
|
|
4
4
|
import { handleRemoteSetupCommand } from './remote-runtime-setup.ts';
|
|
5
5
|
import { handleRemotePoolCommand } from './remote-runtime-pool.ts';
|
|
6
6
|
import { requirePeerClient } from './runtime-services.ts';
|
|
7
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
7
8
|
|
|
8
9
|
type RemoteConnectionLike = { agentId: string };
|
|
9
10
|
type RemoteCancelContext = Pick<CommandContext, 'print'>;
|
|
@@ -45,7 +46,7 @@ export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
|
|
|
45
46
|
name: 'remote',
|
|
46
47
|
aliases: [],
|
|
47
48
|
description: 'Inspect, dispatch, and review self-hosted remote runners and artifacts',
|
|
48
|
-
usage: '[list | show [agentId] | supervisor [runnerId] | capabilities [runnerId] | recover [runnerId] | setup [export <path>] | env [export <path>] | tunnel [review|export <path>] | bootstrap [export <path
|
|
49
|
+
usage: '[list | show [agentId] | supervisor [runnerId] | capabilities [runnerId] | recover [runnerId] | setup [export <path> --yes] | env [export <path> --yes] | tunnel [review|export <path> --yes] | bootstrap [export <path> --yes|inspect <path>] | session <export|inspect|import> <path> [--yes] | pool <list|show|create|assign|unassign> ... | dispatch [template] <description> | dispatch-pool <pool> [template] <description> | contract [agentId] | cancel <agentId> | export <agentId> [path] --yes | artifact list | artifact show <id> | artifact export <id> [path] --yes | review <id> | rerun-local <id> | import <path> --yes]',
|
|
49
50
|
async handler(args, ctx) {
|
|
50
51
|
if (args.length === 0) {
|
|
51
52
|
if (ctx.openRemotePanel) {
|
|
@@ -310,9 +311,14 @@ export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
|
|
|
310
311
|
}
|
|
311
312
|
|
|
312
313
|
if (subcommand === 'export') {
|
|
313
|
-
const
|
|
314
|
+
const { rest, yes } = stripYesFlag(args);
|
|
315
|
+
const agentId = rest[1];
|
|
314
316
|
if (!agentId) {
|
|
315
|
-
ctx.print('Usage: /remote export <agentId> [path]');
|
|
317
|
+
ctx.print('Usage: /remote export <agentId> [path] --yes');
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (!yes) {
|
|
321
|
+
requireYesFlag(ctx, `export remote review artifact for ${agentId}`, '/remote export <agentId> [path] --yes');
|
|
316
322
|
return;
|
|
317
323
|
}
|
|
318
324
|
const artifact = remoteRunners.captureArtifactForRunner(agentId);
|
|
@@ -320,7 +326,7 @@ export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
|
|
|
320
326
|
ctx.print(`Remote artifact export failed for ${agentId}.`);
|
|
321
327
|
return;
|
|
322
328
|
}
|
|
323
|
-
const exported = await remoteRunners.exportArtifact(artifact.id,
|
|
329
|
+
const exported = await remoteRunners.exportArtifact(artifact.id, rest[2]);
|
|
324
330
|
if (!exported) {
|
|
325
331
|
ctx.print(`Remote artifact export failed for ${agentId}.`);
|
|
326
332
|
return;
|
|
@@ -356,12 +362,17 @@ export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
|
|
|
356
362
|
return;
|
|
357
363
|
}
|
|
358
364
|
if (mode === 'export') {
|
|
359
|
-
const
|
|
365
|
+
const { rest, yes } = stripYesFlag(args);
|
|
366
|
+
const artifactId = rest[2];
|
|
360
367
|
if (!artifactId) {
|
|
361
|
-
ctx.print('Usage: /remote artifact export <artifactId> [path]');
|
|
368
|
+
ctx.print('Usage: /remote artifact export <artifactId> [path] --yes');
|
|
362
369
|
return;
|
|
363
370
|
}
|
|
364
|
-
|
|
371
|
+
if (!yes) {
|
|
372
|
+
requireYesFlag(ctx, `export remote review artifact ${artifactId}`, '/remote artifact export <artifactId> [path] --yes');
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const exported = await remoteRunners.exportArtifact(artifactId, rest[3]);
|
|
365
376
|
if (!exported) {
|
|
366
377
|
ctx.print(`Unknown remote artifact: ${artifactId}`);
|
|
367
378
|
return;
|
|
@@ -400,9 +411,14 @@ export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
|
|
|
400
411
|
}
|
|
401
412
|
|
|
402
413
|
if (subcommand === 'import') {
|
|
403
|
-
const
|
|
414
|
+
const { rest, yes } = stripYesFlag(args);
|
|
415
|
+
const path = rest[1];
|
|
404
416
|
if (!path) {
|
|
405
|
-
ctx.print('Usage: /remote import <path>');
|
|
417
|
+
ctx.print('Usage: /remote import <path> --yes');
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (!yes) {
|
|
421
|
+
requireYesFlag(ctx, `import remote review artifact from ${path}`, '/remote import <path> --yes');
|
|
406
422
|
return;
|
|
407
423
|
}
|
|
408
424
|
const artifact = await remoteRunners.importArtifact(path);
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
2
|
import { handleReplayCommand } from '@pellux/goodvibes-sdk/platform/core';
|
|
3
3
|
import { requireReplayEngine } from './runtime-services.ts';
|
|
4
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
4
5
|
|
|
5
6
|
export function registerReplayRuntimeCommands(registry: CommandRegistry): void {
|
|
6
7
|
registry.register({
|
|
7
8
|
name: 'replay',
|
|
8
9
|
aliases: ['rep'],
|
|
9
10
|
description: 'Deterministic replay: load, step, seek, diff, and export recorded runs',
|
|
10
|
-
usage: '[load [runId] | step [n] | seek <rev> | diff | export <path>]',
|
|
11
|
+
usage: '[load [runId] | step [n] | seek <rev> | diff | export <path> --yes]',
|
|
11
12
|
argsHint: '[load|step|seek|diff|export]',
|
|
12
13
|
handler(args, ctx) {
|
|
14
|
+
const command = args[0] ?? 'help';
|
|
15
|
+
const { rest, yes } = stripYesFlag(args);
|
|
16
|
+
if (command === 'export' && !yes) {
|
|
17
|
+
requireYesFlag(ctx, `export replay run to ${args[1] ?? '<path>'}`, '/replay export <path> --yes');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
13
20
|
const replayEngine = requireReplayEngine(ctx);
|
|
14
|
-
const result = handleReplayCommand({ replayEngine },
|
|
21
|
+
const result = handleReplayCommand({ replayEngine }, command, rest.slice(1));
|
|
15
22
|
ctx.print(result.output);
|
|
16
23
|
},
|
|
17
24
|
});
|
|
@@ -5,15 +5,18 @@ import type { SelectionAction, SelectionItem } from '../selection-modal.ts';
|
|
|
5
5
|
import { openCommandPanel, requireServiceRegistry, requireShellPaths } from './runtime-services.ts';
|
|
6
6
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
7
7
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../../config/surface.ts';
|
|
8
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
8
9
|
|
|
9
10
|
export function registerServicesRuntimeCommands(registry: CommandRegistry): void {
|
|
10
11
|
registry.register({
|
|
11
12
|
name: 'services',
|
|
12
13
|
aliases: ['svc'],
|
|
13
14
|
description: 'Manage API service configurations',
|
|
14
|
-
usage: '[open|list|inspect <name>|test <name>|resolve <name>|auth <name>|auth-review|doctor|export <path
|
|
15
|
+
usage: '[open|list|inspect <name>|test <name>|resolve <name>|auth <name>|auth-review|doctor|export <path> --yes|import <path> --yes]',
|
|
15
16
|
async handler(args, ctx) {
|
|
16
|
-
const
|
|
17
|
+
const parsed = stripYesFlag(args);
|
|
18
|
+
const commandArgs = [...parsed.rest];
|
|
19
|
+
const sub = commandArgs[0] ?? 'open';
|
|
17
20
|
const shellPaths = requireShellPaths(ctx);
|
|
18
21
|
if (sub === 'open' || sub === 'panel') {
|
|
19
22
|
openCommandPanel(ctx, 'services');
|
|
@@ -23,7 +26,7 @@ export function registerServicesRuntimeCommands(registry: CommandRegistry): void
|
|
|
23
26
|
const all = svcRegistry.getAll();
|
|
24
27
|
const keys = Object.keys(all);
|
|
25
28
|
if (sub === 'inspect') {
|
|
26
|
-
const name =
|
|
29
|
+
const name = commandArgs[1];
|
|
27
30
|
if (!name) {
|
|
28
31
|
ctx.print('Usage: /services inspect <name>');
|
|
29
32
|
return;
|
|
@@ -47,7 +50,7 @@ export function registerServicesRuntimeCommands(registry: CommandRegistry): void
|
|
|
47
50
|
return;
|
|
48
51
|
}
|
|
49
52
|
if (sub === 'test') {
|
|
50
|
-
const name =
|
|
53
|
+
const name = commandArgs[1];
|
|
51
54
|
if (!name) {
|
|
52
55
|
ctx.print('Usage: /services test <name>');
|
|
53
56
|
return;
|
|
@@ -63,7 +66,7 @@ export function registerServicesRuntimeCommands(registry: CommandRegistry): void
|
|
|
63
66
|
return;
|
|
64
67
|
}
|
|
65
68
|
if (sub === 'resolve') {
|
|
66
|
-
const name =
|
|
69
|
+
const name = commandArgs[1];
|
|
67
70
|
if (!name) {
|
|
68
71
|
ctx.print('Usage: /services resolve <name>');
|
|
69
72
|
return;
|
|
@@ -80,7 +83,7 @@ export function registerServicesRuntimeCommands(registry: CommandRegistry): void
|
|
|
80
83
|
return;
|
|
81
84
|
}
|
|
82
85
|
if (sub === 'auth') {
|
|
83
|
-
const name =
|
|
86
|
+
const name = commandArgs[1];
|
|
84
87
|
if (!name) {
|
|
85
88
|
ctx.print('Usage: /services auth <name>');
|
|
86
89
|
return;
|
|
@@ -141,9 +144,13 @@ export function registerServicesRuntimeCommands(registry: CommandRegistry): void
|
|
|
141
144
|
return;
|
|
142
145
|
}
|
|
143
146
|
if (sub === 'export') {
|
|
144
|
-
const pathArg =
|
|
147
|
+
const pathArg = commandArgs[1];
|
|
145
148
|
if (!pathArg) {
|
|
146
|
-
ctx.print('Usage: /services export <path>');
|
|
149
|
+
ctx.print('Usage: /services export <path> --yes');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!parsed.yes) {
|
|
153
|
+
requireYesFlag(ctx, `export services config to ${pathArg}`, '/services export <path> --yes');
|
|
147
154
|
return;
|
|
148
155
|
}
|
|
149
156
|
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
@@ -153,9 +160,13 @@ export function registerServicesRuntimeCommands(registry: CommandRegistry): void
|
|
|
153
160
|
return;
|
|
154
161
|
}
|
|
155
162
|
if (sub === 'import') {
|
|
156
|
-
const pathArg =
|
|
163
|
+
const pathArg = commandArgs[1];
|
|
157
164
|
if (!pathArg) {
|
|
158
|
-
ctx.print('Usage: /services import <path>');
|
|
165
|
+
ctx.print('Usage: /services import <path> --yes');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (!parsed.yes) {
|
|
169
|
+
requireYesFlag(ctx, `import services config from ${pathArg}`, '/services import <path> --yes');
|
|
159
170
|
return;
|
|
160
171
|
}
|
|
161
172
|
const sourcePath = shellPaths.resolveWorkspacePath(pathArg);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join, resolve } from 'path';
|
|
2
|
-
import { existsSync, mkdirSync
|
|
2
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
3
|
import { writeFile } from 'node:fs/promises';
|
|
4
4
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
5
5
|
import type { SelectionItem } from '../selection-modal.ts';
|
|
@@ -7,18 +7,20 @@ import { exportToMarkdown } from '@pellux/goodvibes-sdk/platform/export';
|
|
|
7
7
|
import { TemplateManager, parseTemplateArgs } from '@pellux/goodvibes-sdk/platform/templates';
|
|
8
8
|
import { requireSessionManager, requireSessionMemoryStore, requireShellPaths } from './runtime-services.ts';
|
|
9
9
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
10
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
10
11
|
|
|
11
12
|
export function registerSessionContentCommands(registry: CommandRegistry): void {
|
|
12
13
|
registry.register({
|
|
13
14
|
name: 'export',
|
|
14
15
|
description: 'Export conversation to a Markdown file',
|
|
15
|
-
usage: '[format] [path]',
|
|
16
|
+
usage: '[format] [path] --yes',
|
|
16
17
|
argsHint: '[markdown] [path]',
|
|
17
18
|
async handler(args, ctx) {
|
|
18
19
|
const shellPaths = requireShellPaths(ctx);
|
|
20
|
+
const { rest, yes } = stripYesFlag(args);
|
|
19
21
|
let format = 'markdown';
|
|
20
22
|
let outPath: string | undefined;
|
|
21
|
-
for (const arg of
|
|
23
|
+
for (const arg of rest) {
|
|
22
24
|
if (arg === 'markdown' || arg === 'md' || arg === 'text' || arg === 'txt') {
|
|
23
25
|
format = arg === 'md' ? 'markdown' : arg === 'txt' ? 'text' : arg;
|
|
24
26
|
} else {
|
|
@@ -34,6 +36,10 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
|
|
|
34
36
|
ctx.print('Error: Export path must be within the current directory.');
|
|
35
37
|
return;
|
|
36
38
|
}
|
|
39
|
+
if (!yes) {
|
|
40
|
+
requireYesFlag(ctx, `export conversation to ${resolvedPath}`, '/export [format] [path] --yes');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
37
43
|
|
|
38
44
|
try {
|
|
39
45
|
const data = ctx.session.conversationManager.toJSON() as { messages: Array<Record<string, unknown>> };
|
|
@@ -229,34 +235,21 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
|
|
|
229
235
|
const sessionManager = requireSessionManager(ctx);
|
|
230
236
|
const sessions = sessionManager.list();
|
|
231
237
|
if (ctx.openSelection) {
|
|
232
|
-
const deleteAction = new Map([['d', 'delete' as const]]);
|
|
233
238
|
const items: SelectionItem[] = sessions.length === 0
|
|
234
239
|
? [{ id: '_empty', label: 'No saved sessions', detail: 'Use /save [name] to save' }]
|
|
235
|
-
: sessions.map(s => ({ id: s.name, label: s.name, detail: s.title || '(untitled)', actions: '
|
|
236
|
-
ctx.openSelection('Sessions', items, { allowSearch: true
|
|
240
|
+
: sessions.map(s => ({ id: s.name, label: s.name, detail: s.title || '(untitled)', actions: 'Enter to load' }));
|
|
241
|
+
ctx.openSelection('Sessions', items, { allowSearch: true }, (result) => {
|
|
237
242
|
if (!result) return;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
} else {
|
|
249
|
-
try {
|
|
250
|
-
const { meta, messages } = sessionManager.load(result.item.id);
|
|
251
|
-
ctx.session.conversationManager.resetAll();
|
|
252
|
-
ctx.session.conversationManager.fromJSON({ messages: messages as never[] });
|
|
253
|
-
if (meta.title) ctx.session.conversationManager.title = meta.title;
|
|
254
|
-
ctx.session.conversationManager.rebuildHistory();
|
|
255
|
-
ctx.renderRequest();
|
|
256
|
-
ctx.print(`Session loaded: ${result.item.id} (${messages.length} messages)`);
|
|
257
|
-
} catch (e) {
|
|
258
|
-
ctx.print(`Failed to load session: ${summarizeError(e)}`);
|
|
259
|
-
}
|
|
243
|
+
try {
|
|
244
|
+
const { meta, messages } = sessionManager.load(result.item.id);
|
|
245
|
+
ctx.session.conversationManager.resetAll();
|
|
246
|
+
ctx.session.conversationManager.fromJSON({ messages: messages as never[] });
|
|
247
|
+
if (meta.title) ctx.session.conversationManager.title = meta.title;
|
|
248
|
+
ctx.session.conversationManager.rebuildHistory();
|
|
249
|
+
ctx.renderRequest();
|
|
250
|
+
ctx.print(`Session loaded: ${result.item.id} (${messages.length} messages)`);
|
|
251
|
+
} catch (e) {
|
|
252
|
+
ctx.print(`Failed to load session: ${summarizeError(e)}`);
|
|
260
253
|
}
|
|
261
254
|
});
|
|
262
255
|
return;
|
|
@@ -271,7 +264,7 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
|
|
|
271
264
|
name: 'template',
|
|
272
265
|
aliases: ['tmpl'],
|
|
273
266
|
description: 'Manage and use prompt templates',
|
|
274
|
-
usage: 'save <name> | use <name> [args] | list | edit <name> | delete <name>',
|
|
267
|
+
usage: 'save <name> --yes | use <name> [args] | list | edit <name> | delete <name> --yes',
|
|
275
268
|
argsHint: '<save|use|list|edit|delete> [name]',
|
|
276
269
|
handler(args, ctx) {
|
|
277
270
|
const shellPaths = requireShellPaths(ctx);
|
|
@@ -279,28 +272,24 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
|
|
|
279
272
|
projectRoot: shellPaths.workingDirectory,
|
|
280
273
|
homeDirectory: shellPaths.homeDirectory,
|
|
281
274
|
});
|
|
282
|
-
const
|
|
283
|
-
const
|
|
275
|
+
const parsed = stripYesFlag(args);
|
|
276
|
+
const sub = parsed.rest[0];
|
|
277
|
+
const rest = parsed.rest.slice(1);
|
|
284
278
|
if (!sub || sub === 'list') {
|
|
285
279
|
const templates = templateManager.list();
|
|
286
280
|
if (ctx.openSelection) {
|
|
287
|
-
const actions = new Map([['
|
|
281
|
+
const actions = new Map([['e', 'edit' as const]]);
|
|
288
282
|
const items: SelectionItem[] = templates.length === 0
|
|
289
|
-
? [{ id: '_empty', label: 'No templates saved', detail: 'Use /template save <name>' }]
|
|
290
|
-
: templates.map(t => ({ id: t.name, label: t.name, detail: t.preview, category: t.scope === 'project' ? 'project' : 'global', actions: '[
|
|
283
|
+
? [{ id: '_empty', label: 'No templates saved', detail: 'Use /template save <name> --yes' }]
|
|
284
|
+
: templates.map(t => ({ id: t.name, label: t.name, detail: t.preview, category: t.scope === 'project' ? 'project' : 'global', actions: '[e] edit' }));
|
|
291
285
|
ctx.openSelection('Templates', items, { allowSearch: true, customActions: actions }, (result) => {
|
|
292
286
|
if (!result) return;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
ctx.print(
|
|
287
|
+
const content = templateManager.load(result.item.id);
|
|
288
|
+
if (content !== null) {
|
|
289
|
+
if (result.action === 'edit') ctx.print(`Template: ${result.item.id}\n\n${content}`);
|
|
290
|
+
else ctx.submitInput?.(content);
|
|
296
291
|
} else {
|
|
297
|
-
|
|
298
|
-
if (content !== null) {
|
|
299
|
-
if (result.action === 'edit') ctx.print(`Template: ${result.item.id}\n\n${content}`);
|
|
300
|
-
else ctx.submitInput?.(content);
|
|
301
|
-
} else {
|
|
302
|
-
ctx.print(`Template not found: ${result.item.id}`);
|
|
303
|
-
}
|
|
292
|
+
ctx.print(`Template not found: ${result.item.id}`);
|
|
304
293
|
}
|
|
305
294
|
});
|
|
306
295
|
return;
|
|
@@ -311,7 +300,11 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
|
|
|
311
300
|
if (sub === 'save') {
|
|
312
301
|
const name = rest[0];
|
|
313
302
|
if (!name) {
|
|
314
|
-
ctx.print('Usage: /template save <name>');
|
|
303
|
+
ctx.print('Usage: /template save <name> --yes');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (!parsed.yes) {
|
|
307
|
+
requireYesFlag(ctx, `save prompt template ${name}`, '/template save <name> --yes');
|
|
315
308
|
return;
|
|
316
309
|
}
|
|
317
310
|
try {
|
|
@@ -349,20 +342,24 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
|
|
|
349
342
|
if (sub === 'delete') {
|
|
350
343
|
const name = rest[0];
|
|
351
344
|
if (!name) {
|
|
352
|
-
ctx.print('Usage: /template delete <name>');
|
|
345
|
+
ctx.print('Usage: /template delete <name> --yes');
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (!parsed.yes) {
|
|
349
|
+
requireYesFlag(ctx, `delete prompt template ${name}`, '/template delete <name> --yes');
|
|
353
350
|
return;
|
|
354
351
|
}
|
|
355
352
|
ctx.print(templateManager.delete(name) ? `Template deleted: ${name}` : `Template not found: ${name}`);
|
|
356
353
|
return;
|
|
357
354
|
}
|
|
358
|
-
ctx.print(`Unknown subcommand: ${sub}\nUsage: /template save|use|list|edit|delete`);
|
|
355
|
+
ctx.print(`Unknown subcommand: ${sub}\nUsage: /template save <name> --yes | use <name> | list | edit <name> | delete <name> --yes`);
|
|
359
356
|
},
|
|
360
357
|
});
|
|
361
358
|
|
|
362
359
|
registry.register({
|
|
363
360
|
name: 'memory',
|
|
364
361
|
description: 'Manage session memories (pinned across context compaction)',
|
|
365
|
-
usage: '[list|add <text>|remove <id>]',
|
|
362
|
+
usage: '[list|add <text>|remove <id> --yes]',
|
|
366
363
|
argsHint: '[list|add|remove]',
|
|
367
364
|
handler(args, ctx) {
|
|
368
365
|
const sub = args[0] ?? 'list';
|
|
@@ -380,15 +377,20 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
|
|
|
380
377
|
const id = requireSessionMemoryStore(ctx).add(text);
|
|
381
378
|
ctx.print(`Memory added: [${id}] ${text}`);
|
|
382
379
|
} else if (sub === 'remove') {
|
|
383
|
-
const
|
|
380
|
+
const parsed = stripYesFlag(args);
|
|
381
|
+
const id = parsed.rest[1];
|
|
384
382
|
if (!id) {
|
|
385
|
-
ctx.print('Usage: /memory remove <id>');
|
|
383
|
+
ctx.print('Usage: /memory remove <id> --yes');
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (!parsed.yes) {
|
|
387
|
+
requireYesFlag(ctx, `remove session memory ${id}`, '/memory remove <id> --yes');
|
|
386
388
|
return;
|
|
387
389
|
}
|
|
388
390
|
const store = requireSessionMemoryStore(ctx);
|
|
389
391
|
ctx.print(store.remove(id) ? `Memory removed: [${id}]` : `Memory not found: ${id}`);
|
|
390
392
|
} else {
|
|
391
|
-
ctx.print('Usage: /memory [list|add <text>|remove <id>]\n /memory
|
|
393
|
+
ctx.print('Usage: /memory [list|add <text>|remove <id> --yes]\n /memory — list all session memories\n /memory list — list all session memories\n /memory add <text> — add a memory without sending a message\n /memory remove <id> --yes — remove a specific memory');
|
|
392
394
|
}
|
|
393
395
|
},
|
|
394
396
|
});
|
|
@@ -8,6 +8,7 @@ import type { SessionReturnContextSummary } from '@/runtime/index.ts';
|
|
|
8
8
|
import { formatReturnContextForDisplay, getReturnContextMode, maybeAssistReturnContextSummary } from '@/runtime/index.ts';
|
|
9
9
|
import { requirePanelManager, requireProviderApi, requireSessionManager } from './runtime-services.ts';
|
|
10
10
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
11
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
11
12
|
|
|
12
13
|
function parseTranscriptKind(raw: string | undefined): TranscriptEventKind | 'all' {
|
|
13
14
|
const normalized = (raw ?? 'all').toLowerCase().replace(/-/g, '_');
|
|
@@ -414,9 +415,14 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
|
|
|
414
415
|
}
|
|
415
416
|
|
|
416
417
|
if (sub === 'delete') {
|
|
417
|
-
const
|
|
418
|
+
const parsed = stripYesFlag(args);
|
|
419
|
+
const target = parsed.rest[1];
|
|
418
420
|
if (!target) {
|
|
419
|
-
ctx.print('Usage: /session delete <session-id>');
|
|
421
|
+
ctx.print('Usage: /session delete <session-id> --yes');
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
if (!parsed.yes) {
|
|
425
|
+
requireYesFlag(ctx, `delete saved session ${target}`, '/session delete <session-id> --yes');
|
|
420
426
|
return true;
|
|
421
427
|
}
|
|
422
428
|
const sessions = sm.list();
|
|
@@ -446,12 +452,12 @@ export function registerSessionWorkflowCommands(registry: CommandRegistry): void
|
|
|
446
452
|
name: 'session',
|
|
447
453
|
aliases: ['sess'],
|
|
448
454
|
description: 'Manage sessions, resume posture, and transcript structure',
|
|
449
|
-
usage: '[list | rename <name> | resume <id|name> | fork | save | info <id> | events [kind] | groups [kind] | hotspots | export <id> [format] | search <query> | delete <id>]',
|
|
455
|
+
usage: '[list | rename <name> | resume <id|name> | fork | save | info <id> | events [kind] | groups [kind] | hotspots | export <id> [format] | search <query> | delete <id> --yes]',
|
|
450
456
|
argsHint: '<list|rename|resume|fork|save|info|events|groups|hotspots|export|search|delete>',
|
|
451
457
|
async handler(args, ctx) {
|
|
452
458
|
const handled = await handleSessionWorkflowCommand(args, ctx);
|
|
453
459
|
if (!handled) {
|
|
454
|
-
ctx.print('Unknown subcommand: ' + (args[0] ?? '') + '\nUsage: /session [list | rename <name> | resume <id> | fork [name] | save [name] | info [id] | events [kind] | groups [kind] | hotspots | export <id> [format] | search <query> | delete <id>]');
|
|
460
|
+
ctx.print('Unknown subcommand: ' + (args[0] ?? '') + '\nUsage: /session [list | rename <name> | resume <id> | fork [name] | save [name] | info [id] | events [kind] | groups [kind] | hotspots | export <id> [format] | search <query> | delete <id> --yes]');
|
|
455
461
|
}
|
|
456
462
|
},
|
|
457
463
|
});
|
|
@@ -352,7 +352,7 @@ export const sessionCommand: SlashCommand = {
|
|
|
352
352
|
if (!handled) {
|
|
353
353
|
const usage = [
|
|
354
354
|
'Usage: /session <subcommand>',
|
|
355
|
-
' list | rename <name> | resume <id|name> | fork [name] | save [name] | info [id] | export <id> [format] | search <query> | delete <id>',
|
|
355
|
+
' list | rename <name> | resume <id|name> | fork [name] | save [name] | info [id] | export <id> [format] | search <query> | delete <id> --yes',
|
|
356
356
|
' — Session continuity, export, resume, and pruning',
|
|
357
357
|
' graph [--session <sid>] [--format text|json]',
|
|
358
358
|
' — Display the cross-session task dependency graph',
|