@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,9 +4,9 @@
|
|
|
4
4
|
* Implements the Evaluation Harness commands:
|
|
5
5
|
*
|
|
6
6
|
* /eval list — List all available eval suites
|
|
7
|
-
* /eval run <suite>
|
|
7
|
+
* /eval run <suite> --yes — Run a named suite (or 'all')
|
|
8
8
|
* /eval compare <baseline-file> — Compare last run against a baseline file
|
|
9
|
-
* /eval gate <suite>
|
|
9
|
+
* /eval gate <suite> --yes — Run suite and apply CI gate (exits 1 on regression)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { SlashCommand, CommandContext } from '../command-registry.ts';
|
|
@@ -18,6 +18,7 @@ import type { EvalRegistry } from '../../panels/eval-panel.ts';
|
|
|
18
18
|
import { formatSuiteResult, formatGateResult } from '@/runtime/index.ts';
|
|
19
19
|
import { requireShellPaths } from './runtime-services.ts';
|
|
20
20
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
21
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
21
22
|
|
|
22
23
|
// ── Subcommand helpers ────────────────────────────────────────────────────────
|
|
23
24
|
|
|
@@ -29,7 +30,7 @@ function printSuiteList(context: CommandContext): void {
|
|
|
29
30
|
context.print(` - ${s.id}: ${s.name}`);
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
|
-
context.print('[eval] Usage: /eval run <suite> or /eval run all');
|
|
33
|
+
context.print('[eval] Usage: /eval run <suite> --yes or /eval run all --yes');
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
function getRegistry(context: CommandContext): EvalRegistry | undefined {
|
|
@@ -45,7 +46,8 @@ function handleList(_args: string[], context: CommandContext): void {
|
|
|
45
46
|
// ── /eval run ────────────────────────────────────────────────────────────────
|
|
46
47
|
|
|
47
48
|
async function handleRun(args: string[], context: CommandContext): Promise<void> {
|
|
48
|
-
const
|
|
49
|
+
const { rest, yes } = stripYesFlag(args);
|
|
50
|
+
const suiteName = rest[0] ?? 'all';
|
|
49
51
|
const registry = getRegistry(context);
|
|
50
52
|
|
|
51
53
|
const suitesToRun =
|
|
@@ -59,6 +61,10 @@ async function handleRun(args: string[], context: CommandContext): Promise<void>
|
|
|
59
61
|
context.print(`[eval] Unknown suite: "${suiteName}". Run /eval list to see available suites.`);
|
|
60
62
|
return;
|
|
61
63
|
}
|
|
64
|
+
if (!yes) {
|
|
65
|
+
requireYesFlag(context, `run eval suite ${suiteName}`, '/eval run <suite|all> --yes');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
62
68
|
|
|
63
69
|
const runner = new EvalRunner();
|
|
64
70
|
registry?.setRunning(true);
|
|
@@ -90,14 +96,14 @@ async function handleCompare(args: string[], context: CommandContext): Promise<v
|
|
|
90
96
|
const suiteResults = registry?.getSuiteResults() ?? [];
|
|
91
97
|
|
|
92
98
|
if (suiteResults.length === 0) {
|
|
93
|
-
context.print('[eval] No suite results to compare. Run /eval run <suite> first.');
|
|
99
|
+
context.print('[eval] No suite results to compare. Run /eval run <suite> --yes first.');
|
|
94
100
|
return;
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
const baseline = await loadBaseline(baselineFile, projectRoot);
|
|
98
104
|
if (!baseline) {
|
|
99
105
|
context.print(`[eval] Baseline file not found: ${baselineFile}`);
|
|
100
|
-
context.print('[eval] Tip: run /eval gate <suite> to create a baseline.');
|
|
106
|
+
context.print('[eval] Tip: run /eval gate <suite> [baseline-file] --save-baseline --yes to create a baseline.');
|
|
101
107
|
return;
|
|
102
108
|
}
|
|
103
109
|
|
|
@@ -109,13 +115,15 @@ async function handleCompare(args: string[], context: CommandContext): Promise<v
|
|
|
109
115
|
// ── /eval gate ────────────────────────────────────────────────────────────────
|
|
110
116
|
|
|
111
117
|
async function handleGate(args: string[], context: CommandContext): Promise<void> {
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
const
|
|
118
|
+
const { rest, yes } = stripYesFlag(args);
|
|
119
|
+
const positional = rest.filter((arg) => arg !== '--save-baseline');
|
|
120
|
+
const suiteName = positional[0];
|
|
121
|
+
const baselineFile = positional[1] ?? '.goodvibes/eval/baseline.json';
|
|
122
|
+
const saveFlag = rest.includes('--save-baseline');
|
|
115
123
|
const projectRoot = requireShellPaths(context).workingDirectory;
|
|
116
124
|
|
|
117
125
|
if (!suiteName) {
|
|
118
|
-
context.print('[eval] Usage: /eval gate <suite> [baseline-file] [--save-baseline]');
|
|
126
|
+
context.print('[eval] Usage: /eval gate <suite> [baseline-file] [--save-baseline] --yes');
|
|
119
127
|
return;
|
|
120
128
|
}
|
|
121
129
|
|
|
@@ -124,6 +132,10 @@ async function handleGate(args: string[], context: CommandContext): Promise<void
|
|
|
124
132
|
context.print(`[eval] Unknown suite: "${suiteName}". Run /eval list to see available suites.`);
|
|
125
133
|
return;
|
|
126
134
|
}
|
|
135
|
+
if (!yes) {
|
|
136
|
+
requireYesFlag(context, `run eval gate ${suiteName}`, '/eval gate <suite> [baseline-file] [--save-baseline] --yes');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
127
139
|
|
|
128
140
|
const registry = getRegistry(context);
|
|
129
141
|
const runner = new EvalRunner();
|
|
@@ -141,7 +153,7 @@ async function handleGate(args: string[], context: CommandContext): Promise<void
|
|
|
141
153
|
context.print(formatGateResult(gate));
|
|
142
154
|
|
|
143
155
|
if (saveFlag || !baseline) {
|
|
144
|
-
const label =
|
|
156
|
+
const label = suiteName ?? 'latest';
|
|
145
157
|
const newBaseline = captureBaseline(label, [fresh]);
|
|
146
158
|
try {
|
|
147
159
|
await writeBaseline(baselineFile, newBaseline, projectRoot);
|
|
@@ -164,7 +176,7 @@ export const evalCommand: SlashCommand = {
|
|
|
164
176
|
name: 'eval',
|
|
165
177
|
description: 'Evaluation harness: run benchmark suites, compare baselines, and gate regressions.',
|
|
166
178
|
usage: '<subcommand> [args]',
|
|
167
|
-
argsHint: 'list|run <suite
|
|
179
|
+
argsHint: 'list|run <suite> --yes|compare <baseline>|gate <suite> --yes',
|
|
168
180
|
handler: async (args: string[], context: CommandContext): Promise<void> => {
|
|
169
181
|
const [sub, ...rest] = args;
|
|
170
182
|
|
|
@@ -191,9 +203,10 @@ export const evalCommand: SlashCommand = {
|
|
|
191
203
|
const usage = [
|
|
192
204
|
'Usage: /eval <subcommand>',
|
|
193
205
|
' list — List all available eval suites',
|
|
194
|
-
' run <suite|all>
|
|
206
|
+
' run <suite|all> --yes — Run a named suite (or all suites)',
|
|
195
207
|
' compare [baseline-file] — Compare last results against baseline',
|
|
196
|
-
' gate <suite> [baseline-file]
|
|
208
|
+
' gate <suite> [baseline-file] --yes',
|
|
209
|
+
' — Run suite and apply regression gate',
|
|
197
210
|
' --save-baseline — Save fresh run as new baseline',
|
|
198
211
|
].join('\n');
|
|
199
212
|
context.print(usage);
|
|
@@ -2,6 +2,7 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { dirname, resolve } from 'node:path';
|
|
3
3
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
4
4
|
import { requirePanelManager, requireShellPaths } from './runtime-services.ts';
|
|
5
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
5
6
|
|
|
6
7
|
interface VoiceBundle {
|
|
7
8
|
readonly version: 1;
|
|
@@ -23,21 +24,27 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
23
24
|
registry.register({
|
|
24
25
|
name: 'remote-setup',
|
|
25
26
|
description: 'Dedicated front-door for remote setup review and portable setup bundles',
|
|
26
|
-
usage: '[review|export <path>]',
|
|
27
|
+
usage: '[review|export <path> --yes]',
|
|
27
28
|
async handler(args, ctx) {
|
|
28
|
-
const
|
|
29
|
+
const parsed = stripYesFlag(args);
|
|
30
|
+
const commandArgs = [...parsed.rest];
|
|
31
|
+
const sub = (commandArgs[0] ?? 'review').toLowerCase();
|
|
29
32
|
if (ctx.executeCommand) {
|
|
30
33
|
if (sub === 'review') {
|
|
31
34
|
await ctx.executeCommand('remote', ['setup']);
|
|
32
35
|
return;
|
|
33
36
|
}
|
|
34
37
|
if (sub === 'export') {
|
|
35
|
-
const pathArg =
|
|
38
|
+
const pathArg = commandArgs[1];
|
|
36
39
|
if (!pathArg) {
|
|
37
|
-
ctx.print('Usage: /remote-setup export <path>');
|
|
40
|
+
ctx.print('Usage: /remote-setup export <path> --yes');
|
|
38
41
|
return;
|
|
39
42
|
}
|
|
40
|
-
|
|
43
|
+
if (!parsed.yes) {
|
|
44
|
+
requireYesFlag(ctx, `export remote setup bundle to ${pathArg}`, '/remote-setup export <path> --yes');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
await ctx.executeCommand('remote', ['setup', 'export', pathArg, '--yes']);
|
|
41
48
|
return;
|
|
42
49
|
}
|
|
43
50
|
}
|
|
@@ -48,21 +55,27 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
48
55
|
registry.register({
|
|
49
56
|
name: 'remote-env',
|
|
50
57
|
description: 'Dedicated front-door for remote environment snippets and portable env exports',
|
|
51
|
-
usage: '[review|export <path>]',
|
|
58
|
+
usage: '[review|export <path> --yes]',
|
|
52
59
|
async handler(args, ctx) {
|
|
53
|
-
const
|
|
60
|
+
const parsed = stripYesFlag(args);
|
|
61
|
+
const commandArgs = [...parsed.rest];
|
|
62
|
+
const sub = (commandArgs[0] ?? 'review').toLowerCase();
|
|
54
63
|
if (ctx.executeCommand) {
|
|
55
64
|
if (sub === 'review') {
|
|
56
65
|
await ctx.executeCommand('remote', ['env']);
|
|
57
66
|
return;
|
|
58
67
|
}
|
|
59
68
|
if (sub === 'export') {
|
|
60
|
-
const pathArg =
|
|
69
|
+
const pathArg = commandArgs[1];
|
|
61
70
|
if (!pathArg) {
|
|
62
|
-
ctx.print('Usage: /remote-env export <path>');
|
|
71
|
+
ctx.print('Usage: /remote-env export <path> --yes');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!parsed.yes) {
|
|
75
|
+
requireYesFlag(ctx, `export remote environment snippet to ${pathArg}`, '/remote-env export <path> --yes');
|
|
63
76
|
return;
|
|
64
77
|
}
|
|
65
|
-
await ctx.executeCommand('remote', ['env', 'export', pathArg]);
|
|
78
|
+
await ctx.executeCommand('remote', ['env', 'export', pathArg, '--yes']);
|
|
66
79
|
return;
|
|
67
80
|
}
|
|
68
81
|
}
|
|
@@ -73,21 +86,27 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
73
86
|
registry.register({
|
|
74
87
|
name: 'tunnel',
|
|
75
88
|
description: 'Dedicated front-door for remote tunnel review and export flows',
|
|
76
|
-
usage: '[review|export <path>]',
|
|
89
|
+
usage: '[review|export <path> --yes]',
|
|
77
90
|
async handler(args, ctx) {
|
|
78
|
-
const
|
|
91
|
+
const parsed = stripYesFlag(args);
|
|
92
|
+
const commandArgs = [...parsed.rest];
|
|
93
|
+
const sub = (commandArgs[0] ?? 'review').toLowerCase();
|
|
79
94
|
if (ctx.executeCommand) {
|
|
80
95
|
if (sub === 'review') {
|
|
81
96
|
await ctx.executeCommand('remote', ['tunnel', 'review']);
|
|
82
97
|
return;
|
|
83
98
|
}
|
|
84
99
|
if (sub === 'export') {
|
|
85
|
-
const pathArg =
|
|
100
|
+
const pathArg = commandArgs[1];
|
|
86
101
|
if (!pathArg) {
|
|
87
|
-
ctx.print('Usage: /tunnel export <path>');
|
|
102
|
+
ctx.print('Usage: /tunnel export <path> --yes');
|
|
88
103
|
return;
|
|
89
104
|
}
|
|
90
|
-
|
|
105
|
+
if (!parsed.yes) {
|
|
106
|
+
requireYesFlag(ctx, `export remote tunnel review to ${pathArg}`, '/tunnel export <path> --yes');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
await ctx.executeCommand('remote', ['tunnel', 'export', pathArg, '--yes']);
|
|
91
110
|
return;
|
|
92
111
|
}
|
|
93
112
|
}
|
|
@@ -98,19 +117,29 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
98
117
|
registry.register({
|
|
99
118
|
name: 'bootstrap',
|
|
100
119
|
description: 'Dedicated front-door for remote bootstrap bundle export and inspection',
|
|
101
|
-
usage: '[export <path
|
|
120
|
+
usage: '[export <path> --yes|inspect <path>]',
|
|
102
121
|
async handler(args, ctx) {
|
|
103
|
-
const
|
|
104
|
-
const
|
|
122
|
+
const parsed = stripYesFlag(args);
|
|
123
|
+
const commandArgs = [...parsed.rest];
|
|
124
|
+
const sub = (commandArgs[0] ?? '').toLowerCase();
|
|
125
|
+
const pathArg = commandArgs[1];
|
|
105
126
|
if (!ctx.executeCommand) {
|
|
106
127
|
ctx.print('Bootstrap controls are not available in this runtime.');
|
|
107
128
|
return;
|
|
108
129
|
}
|
|
109
|
-
if (
|
|
130
|
+
if (sub === 'export' && pathArg) {
|
|
131
|
+
if (!parsed.yes) {
|
|
132
|
+
requireYesFlag(ctx, `export remote bootstrap bundle to ${pathArg}`, '/bootstrap export <path> --yes');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
await ctx.executeCommand('remote', ['bootstrap', sub, pathArg, '--yes']);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (sub === 'inspect' && pathArg) {
|
|
110
139
|
await ctx.executeCommand('remote', ['bootstrap', sub, pathArg]);
|
|
111
140
|
return;
|
|
112
141
|
}
|
|
113
|
-
ctx.print('Usage: /bootstrap [export <path
|
|
142
|
+
ctx.print('Usage: /bootstrap [export <path> --yes|inspect <path>]');
|
|
114
143
|
},
|
|
115
144
|
});
|
|
116
145
|
|
|
@@ -165,7 +194,7 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
165
194
|
['shell', 'Shell execution approval with side-effect and credential review.'],
|
|
166
195
|
['file', 'File mutation approval with config/notebook differentiation.'],
|
|
167
196
|
['network', 'Network access approval with host/scope review.'],
|
|
168
|
-
['delegate', '
|
|
197
|
+
['delegate', 'Explicit GoodVibes TUI build delegation approval; local Agent spawn is blocked.'],
|
|
169
198
|
['mcp', 'MCP trust escalation approval with host/path review.'],
|
|
170
199
|
['remote', 'Remote dispatch approval with trust/artifact review.'],
|
|
171
200
|
['hook', 'Hook execution approval with deny/mutate authority review.'],
|
|
@@ -223,10 +252,12 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
223
252
|
registry.register({
|
|
224
253
|
name: 'voice',
|
|
225
254
|
description: 'Review voice posture and package portable voice-surface metadata',
|
|
226
|
-
usage: '[review|enable|disable|bundle export <path
|
|
255
|
+
usage: '[review|enable --yes|disable --yes|bundle export <path> --yes|bundle inspect <path>]',
|
|
227
256
|
handler(args, ctx) {
|
|
257
|
+
const parsed = stripYesFlag(args);
|
|
258
|
+
const commandArgs = [...parsed.rest];
|
|
228
259
|
const shellPaths = requireShellPaths(ctx);
|
|
229
|
-
const sub = (
|
|
260
|
+
const sub = (commandArgs[0] ?? 'review').toLowerCase();
|
|
230
261
|
if (sub === 'review') {
|
|
231
262
|
const enabled = Boolean(ctx.platform.configManager.get('ui.voiceEnabled') ?? false);
|
|
232
263
|
ctx.print([
|
|
@@ -238,20 +269,28 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
238
269
|
return;
|
|
239
270
|
}
|
|
240
271
|
if (sub === 'enable' || sub === 'disable') {
|
|
272
|
+
if (!parsed.yes) {
|
|
273
|
+
requireYesFlag(ctx, `${sub} voice surface`, `/voice ${sub} --yes`);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
241
276
|
const next = sub === 'enable';
|
|
242
277
|
ctx.platform.configManager.setDynamic('ui.voiceEnabled', next);
|
|
243
278
|
ctx.print(`Voice surface ${next ? 'enabled' : 'disabled'} for this runtime.`);
|
|
244
279
|
return;
|
|
245
280
|
}
|
|
246
281
|
if (sub === 'bundle') {
|
|
247
|
-
const mode =
|
|
248
|
-
const pathArg =
|
|
282
|
+
const mode = commandArgs[1];
|
|
283
|
+
const pathArg = commandArgs[2];
|
|
249
284
|
if ((mode === 'export' || mode === 'inspect') && !pathArg) {
|
|
250
|
-
ctx.print(`Usage: /voice bundle ${mode} <path
|
|
285
|
+
ctx.print(`Usage: /voice bundle ${mode} <path>${mode === 'export' ? ' --yes' : ''}`);
|
|
251
286
|
return;
|
|
252
287
|
}
|
|
253
288
|
const targetPath = shellPaths.resolveWorkspacePath(pathArg!);
|
|
254
289
|
if (mode === 'export') {
|
|
290
|
+
if (!parsed.yes) {
|
|
291
|
+
requireYesFlag(ctx, `export voice bundle to ${pathArg}`, '/voice bundle export <path> --yes');
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
255
294
|
const bundle: VoiceBundle = {
|
|
256
295
|
version: 1,
|
|
257
296
|
exportedAt: Date.now(),
|
|
@@ -272,7 +311,7 @@ export function registerExperienceRuntimeCommands(registry: CommandRegistry): vo
|
|
|
272
311
|
return;
|
|
273
312
|
}
|
|
274
313
|
}
|
|
275
|
-
ctx.print('Usage: /voice [review|enable|disable|bundle export <path
|
|
314
|
+
ctx.print('Usage: /voice [review|enable --yes|disable --yes|bundle export <path> --yes|bundle inspect <path>]');
|
|
276
315
|
},
|
|
277
316
|
});
|
|
278
317
|
}
|
|
@@ -299,7 +299,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
299
299
|
lines.push(' domain: auth');
|
|
300
300
|
lines.push(...(
|
|
301
301
|
auth.bootstrapCredentialPresent
|
|
302
|
-
? [' /auth local review', ' /auth local rotate-password admin <password>', ' /auth local clear-bootstrap-file']
|
|
302
|
+
? [' /auth local review', ' /auth local rotate-password admin <password> --yes', ' /auth local clear-bootstrap-file --yes']
|
|
303
303
|
: [' /auth local review']
|
|
304
304
|
));
|
|
305
305
|
lines.push(' verify: /health auth');
|
|
@@ -1,34 +1,69 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
1
2
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
3
|
import { requireHookApi } from './runtime-services.ts';
|
|
4
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
5
|
+
|
|
6
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function containsAgentHookType(value: unknown): boolean {
|
|
11
|
+
if (Array.isArray(value)) return value.some((entry) => containsAgentHookType(entry));
|
|
12
|
+
if (!isRecord(value)) return false;
|
|
13
|
+
if (value.type === 'agent') return true;
|
|
14
|
+
return Object.values(value).some((entry) => containsAgentHookType(entry));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function fileContainsAgentHookType(path: string): boolean {
|
|
18
|
+
try {
|
|
19
|
+
return containsAgentHookType(JSON.parse(readFileSync(path, 'utf-8')) as unknown);
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
3
24
|
|
|
4
25
|
export function registerHooksRuntimeCommands(registry: CommandRegistry): void {
|
|
5
26
|
registry.register({
|
|
6
27
|
name: 'hooks',
|
|
7
28
|
aliases: [],
|
|
8
29
|
description: 'Inspect, author, simulate, and reload managed hook workflows',
|
|
9
|
-
usage: '[contracts [filter] | reload | scaffold <name> <match> <type> | chain <name> <event1,event2,...> | remove <name> | enable <name> | disable <name> | simulate <eventPath> | inspect <path> | import <path> [merge|replace] | export [path]]',
|
|
30
|
+
usage: '[contracts [filter] | reload --yes | scaffold <name> <match> <type> --yes | chain <name> <event1,event2,...> --yes | remove <name> --yes | enable <name> --yes | disable <name> --yes | simulate <eventPath> | inspect <path> | import <path> [merge|replace] --yes | export [path] --yes]',
|
|
10
31
|
argsHint: '[subcommand]',
|
|
11
32
|
async handler(args, ctx) {
|
|
33
|
+
const parsed = stripYesFlag(args);
|
|
34
|
+
const commandArgs = [...parsed.rest];
|
|
12
35
|
const hookApi = requireHookApi(ctx);
|
|
13
36
|
const workbench = hookApi.workbench;
|
|
14
|
-
if (
|
|
37
|
+
if (commandArgs.length === 0 && ctx.openHooksPanel) {
|
|
15
38
|
ctx.openHooksPanel();
|
|
16
39
|
return;
|
|
17
40
|
}
|
|
18
41
|
|
|
19
|
-
const subcommand = (
|
|
42
|
+
const subcommand = (commandArgs[0] ?? 'contracts').toLowerCase();
|
|
20
43
|
if (subcommand === 'reload') {
|
|
44
|
+
if (!parsed.yes) {
|
|
45
|
+
requireYesFlag(ctx, 'reload managed hook workflows', '/hooks reload --yes');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
21
48
|
await workbench.reload();
|
|
22
49
|
ctx.print(`Reloaded managed hooks from ${workbench.getFilePath()}`);
|
|
23
50
|
return;
|
|
24
51
|
}
|
|
25
52
|
if (subcommand === 'scaffold') {
|
|
26
|
-
const [name, match, type] =
|
|
53
|
+
const [name, match, type] = commandArgs.slice(1);
|
|
27
54
|
if (!name || !match || !type) {
|
|
28
|
-
ctx.print('Usage: /hooks scaffold <name> <match> <command|prompt|
|
|
55
|
+
ctx.print('Usage: /hooks scaffold <name> <match> <command|prompt|http|ts> --yes');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!parsed.yes) {
|
|
59
|
+
requireYesFlag(ctx, `scaffold managed hook ${name}`, '/hooks scaffold <name> <match> <command|prompt|http|ts> --yes');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (type === 'agent') {
|
|
63
|
+
ctx.print('Blocked: GoodVibes Agent does not author local agent-spawning hooks. Use command, prompt, http, or ts hooks, or delegate explicit build work to GoodVibes TUI.');
|
|
29
64
|
return;
|
|
30
65
|
}
|
|
31
|
-
if (!['command', 'prompt', '
|
|
66
|
+
if (!['command', 'prompt', 'http', 'ts'].includes(type)) {
|
|
32
67
|
ctx.print(`Unknown hook type: ${type}`);
|
|
33
68
|
return;
|
|
34
69
|
}
|
|
@@ -37,10 +72,14 @@ export function registerHooksRuntimeCommands(registry: CommandRegistry): void {
|
|
|
37
72
|
return;
|
|
38
73
|
}
|
|
39
74
|
if (subcommand === 'chain') {
|
|
40
|
-
const name =
|
|
41
|
-
const matches =
|
|
75
|
+
const name = commandArgs[1];
|
|
76
|
+
const matches = commandArgs[2]?.split(',').map((entry) => entry.trim()).filter(Boolean) ?? [];
|
|
42
77
|
if (!name || matches.length === 0) {
|
|
43
|
-
ctx.print('Usage: /hooks chain <name> <event1,event2,...>');
|
|
78
|
+
ctx.print('Usage: /hooks chain <name> <event1,event2,...> --yes');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!parsed.yes) {
|
|
82
|
+
requireYesFlag(ctx, `scaffold managed hook chain ${name}`, '/hooks chain <name> <event1,event2,...> --yes');
|
|
44
83
|
return;
|
|
45
84
|
}
|
|
46
85
|
const chain = await workbench.scaffoldChain(name, matches);
|
|
@@ -48,9 +87,13 @@ export function registerHooksRuntimeCommands(registry: CommandRegistry): void {
|
|
|
48
87
|
return;
|
|
49
88
|
}
|
|
50
89
|
if (subcommand === 'remove') {
|
|
51
|
-
const name =
|
|
90
|
+
const name = commandArgs[1];
|
|
52
91
|
if (!name) {
|
|
53
|
-
ctx.print('Usage: /hooks remove <name>');
|
|
92
|
+
ctx.print('Usage: /hooks remove <name> --yes');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (!parsed.yes) {
|
|
96
|
+
requireYesFlag(ctx, `remove managed hook workflow ${name}`, '/hooks remove <name> --yes');
|
|
54
97
|
return;
|
|
55
98
|
}
|
|
56
99
|
const removed = await workbench.remove(name);
|
|
@@ -62,9 +105,13 @@ export function registerHooksRuntimeCommands(registry: CommandRegistry): void {
|
|
|
62
105
|
return;
|
|
63
106
|
}
|
|
64
107
|
if (subcommand === 'enable' || subcommand === 'disable') {
|
|
65
|
-
const name =
|
|
108
|
+
const name = commandArgs[1];
|
|
66
109
|
if (!name) {
|
|
67
|
-
ctx.print(`Usage: /hooks ${subcommand} <name
|
|
110
|
+
ctx.print(`Usage: /hooks ${subcommand} <name> --yes`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (!parsed.yes) {
|
|
114
|
+
requireYesFlag(ctx, `${subcommand} managed hook ${name}`, `/hooks ${subcommand} <name> --yes`);
|
|
68
115
|
return;
|
|
69
116
|
}
|
|
70
117
|
const changed = await workbench.toggle(name, subcommand === 'enable');
|
|
@@ -76,7 +123,7 @@ export function registerHooksRuntimeCommands(registry: CommandRegistry): void {
|
|
|
76
123
|
return;
|
|
77
124
|
}
|
|
78
125
|
if (subcommand === 'simulate') {
|
|
79
|
-
const eventPath =
|
|
126
|
+
const eventPath = commandArgs[1];
|
|
80
127
|
if (!eventPath) {
|
|
81
128
|
ctx.print('Usage: /hooks simulate <eventPath>');
|
|
82
129
|
return;
|
|
@@ -92,12 +139,16 @@ export function registerHooksRuntimeCommands(registry: CommandRegistry): void {
|
|
|
92
139
|
return;
|
|
93
140
|
}
|
|
94
141
|
if (subcommand === 'export') {
|
|
95
|
-
|
|
142
|
+
if (!parsed.yes) {
|
|
143
|
+
requireYesFlag(ctx, 'export managed hook workflows', '/hooks export [path] --yes');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const path = await workbench.export(commandArgs[1] ?? workbench.getFilePath());
|
|
96
147
|
ctx.print(`Exported managed hooks to ${path}`);
|
|
97
148
|
return;
|
|
98
149
|
}
|
|
99
150
|
if (subcommand === 'inspect') {
|
|
100
|
-
const path =
|
|
151
|
+
const path = commandArgs[1];
|
|
101
152
|
if (!path) {
|
|
102
153
|
ctx.print('Usage: /hooks inspect <path>');
|
|
103
154
|
return;
|
|
@@ -112,10 +163,18 @@ export function registerHooksRuntimeCommands(registry: CommandRegistry): void {
|
|
|
112
163
|
return;
|
|
113
164
|
}
|
|
114
165
|
if (subcommand === 'import') {
|
|
115
|
-
const path =
|
|
116
|
-
const strategy =
|
|
166
|
+
const path = commandArgs[1];
|
|
167
|
+
const strategy = commandArgs[2] === 'replace' ? 'replace' : 'merge';
|
|
117
168
|
if (!path) {
|
|
118
|
-
ctx.print('Usage: /hooks import <path> [merge|replace]');
|
|
169
|
+
ctx.print('Usage: /hooks import <path> [merge|replace] --yes');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (!parsed.yes) {
|
|
173
|
+
requireYesFlag(ctx, `import managed hook workflows from ${path}`, '/hooks import <path> [merge|replace] --yes');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (fileContainsAgentHookType(path)) {
|
|
177
|
+
ctx.print('Blocked: hook bundle contains type=agent entries. GoodVibes Agent does not import local agent-spawning hooks.');
|
|
119
178
|
return;
|
|
120
179
|
}
|
|
121
180
|
await workbench.import(path, strategy);
|
|
@@ -123,7 +182,7 @@ export function registerHooksRuntimeCommands(registry: CommandRegistry): void {
|
|
|
123
182
|
return;
|
|
124
183
|
}
|
|
125
184
|
|
|
126
|
-
const filter = (subcommand === 'contracts' ?
|
|
185
|
+
const filter = (subcommand === 'contracts' ? commandArgs.slice(1) : commandArgs).join(' ').trim().toLowerCase();
|
|
127
186
|
const contracts = hookApi.contracts(filter);
|
|
128
187
|
|
|
129
188
|
if (contracts.length === 0) {
|
|
@@ -4,16 +4,19 @@ import type { CommandRegistry } from '../command-registry.ts';
|
|
|
4
4
|
import { buildIncidentMemoryAddOptions } from '@pellux/goodvibes-sdk/platform/state';
|
|
5
5
|
import { requireShellPaths } from './runtime-services.ts';
|
|
6
6
|
import { getMemoryApi } from './recall-query.ts';
|
|
7
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
7
8
|
|
|
8
9
|
export function registerIncidentRuntimeCommands(registry: CommandRegistry): void {
|
|
9
10
|
registry.register({
|
|
10
11
|
name: 'incident',
|
|
11
12
|
aliases: [],
|
|
12
13
|
description: 'Open, export, and capture incident review bundles',
|
|
13
|
-
usage: '[open | latest | show <id|latest> | export <id|latest> <path> | capture <id|latest>]',
|
|
14
|
+
usage: '[open | latest | show <id|latest> | export <id|latest> <path> --yes | capture <id|latest> --yes]',
|
|
14
15
|
async handler(args, ctx) {
|
|
16
|
+
const parsed = stripYesFlag(args);
|
|
17
|
+
const commandArgs = [...parsed.rest];
|
|
15
18
|
const shellPaths = requireShellPaths(ctx);
|
|
16
|
-
const subcommand = (
|
|
19
|
+
const subcommand = (commandArgs[0] ?? 'open').toLowerCase();
|
|
17
20
|
const forensicRegistry = ctx.extensions.forensicsRegistry;
|
|
18
21
|
if (subcommand === 'open') {
|
|
19
22
|
if (ctx.openIncidentPanel) {
|
|
@@ -27,7 +30,7 @@ export function registerIncidentRuntimeCommands(registry: CommandRegistry): void
|
|
|
27
30
|
ctx.print('Forensics registry is not available in this runtime.');
|
|
28
31
|
return;
|
|
29
32
|
}
|
|
30
|
-
const requestedId =
|
|
33
|
+
const requestedId = commandArgs[1];
|
|
31
34
|
const report = !requestedId || requestedId === 'latest'
|
|
32
35
|
? forensicRegistry.latest()
|
|
33
36
|
: forensicRegistry.getById(requestedId);
|
|
@@ -53,9 +56,13 @@ export function registerIncidentRuntimeCommands(registry: CommandRegistry): void
|
|
|
53
56
|
return;
|
|
54
57
|
}
|
|
55
58
|
if (subcommand === 'export') {
|
|
56
|
-
const pathArg =
|
|
59
|
+
const pathArg = commandArgs[2];
|
|
57
60
|
if (!requestedId || !pathArg) {
|
|
58
|
-
ctx.print('Usage: /incident export <id|latest> <path>');
|
|
61
|
+
ctx.print('Usage: /incident export <id|latest> <path> --yes');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (!parsed.yes) {
|
|
65
|
+
requireYesFlag(ctx, `export incident bundle ${requestedId}`, '/incident export <id|latest> <path> --yes');
|
|
59
66
|
return;
|
|
60
67
|
}
|
|
61
68
|
if (!report) {
|
|
@@ -74,6 +81,10 @@ export function registerIncidentRuntimeCommands(registry: CommandRegistry): void
|
|
|
74
81
|
return;
|
|
75
82
|
}
|
|
76
83
|
if (subcommand === 'capture') {
|
|
84
|
+
if (!parsed.yes) {
|
|
85
|
+
requireYesFlag(ctx, `capture incident ${requestedId ?? 'latest'} into durable memory`, '/incident capture <id|latest> --yes');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
77
88
|
const memory = getMemoryApi(ctx);
|
|
78
89
|
if (!memory) return;
|
|
79
90
|
if (!report) {
|
|
@@ -89,7 +100,7 @@ export function registerIncidentRuntimeCommands(registry: CommandRegistry): void
|
|
|
89
100
|
ctx.print(`Captured incident ${report.id} into durable memory as ${record.id}`);
|
|
90
101
|
return;
|
|
91
102
|
}
|
|
92
|
-
ctx.print('Usage: /incident [open | latest | show <id|latest> | export <id|latest> <path> | capture <id|latest>]');
|
|
103
|
+
ctx.print('Usage: /incident [open | latest | show <id|latest> | export <id|latest> <path> --yes | capture <id|latest> --yes]');
|
|
93
104
|
},
|
|
94
105
|
});
|
|
95
106
|
}
|