@pellux/goodvibes-tui 1.13.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/README.md +6 -6
- package/bin/goodvibes +6 -0
- package/bin/goodvibes-daemon +6 -0
- package/bin/launcher-support.js +34 -1
- package/docs/foundation-artifacts/operator-contract.json +8076 -2047
- package/package.json +15 -11
- package/scripts/postinstall.js +73 -48
- package/src/cli/bundle-command.ts +5 -5
- package/src/cli/completion.ts +1 -1
- package/src/cli/completions/generate.ts +15 -3
- package/src/cli/doctor.ts +385 -0
- package/src/cli/entrypoint.ts +72 -1
- package/src/cli/help.ts +29 -9
- package/src/cli/hooks-command.ts +74 -0
- package/src/cli/hooks-report.ts +167 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/management.ts +13 -1
- package/src/cli/parser.ts +10 -2
- package/src/cli/plugin-bundles-command.ts +228 -0
- package/src/cli/plugin-command.ts +179 -0
- package/src/cli/service-posture.ts +10 -0
- package/src/cli/status.ts +275 -2
- package/src/cli/tui-startup.ts +119 -1
- package/src/cli/types.ts +3 -1
- package/src/config/index.ts +16 -0
- package/src/config/tui-extension-settings.ts +169 -0
- package/src/core/approval-alert.ts +15 -2
- package/src/core/compaction-receipt.ts +127 -0
- package/src/core/model-routing-chip.ts +82 -0
- package/src/core/permission-mode.ts +89 -0
- package/src/core/rewind-receipt.ts +83 -0
- package/src/core/rewind-turn-anchors.ts +160 -0
- package/src/core/scriptable-statusline.ts +141 -0
- package/src/core/session-auto-titler.ts +110 -0
- package/src/core/stream-event-wiring.ts +20 -0
- package/src/core/system-message-router.ts +1 -1
- package/src/core/terminal-notifier.ts +102 -0
- package/src/core/turn-event-wiring.ts +80 -1
- package/src/input/command-reference.ts +96 -0
- package/src/input/command-registry.ts +25 -0
- package/src/input/commands/channel-pairing.ts +245 -0
- package/src/input/commands/channel-runtime.ts +152 -6
- package/src/input/commands/checkin-runtime.ts +89 -0
- package/src/input/commands/checkpoint-runtime.ts +7 -179
- package/src/input/commands/ci-runtime.ts +207 -0
- package/src/input/commands/cost-attribution-format.ts +52 -0
- package/src/input/commands/cost-runtime.ts +44 -4
- package/src/input/commands/editor-runtime.ts +22 -0
- package/src/input/commands/flags-runtime.ts +328 -0
- package/src/input/commands/health-metrics-format.ts +67 -0
- package/src/input/commands/health-runtime.ts +55 -1
- package/src/input/commands/memory.ts +23 -0
- package/src/input/commands/operator-rpc.ts +95 -0
- package/src/input/commands/palette-runtime.ts +22 -0
- package/src/input/commands/permissions-provenance.ts +264 -0
- package/src/input/commands/permissions-runtime.ts +21 -0
- package/src/input/commands/planning-runtime.ts +28 -1
- package/src/input/commands/platform-access-runtime.ts +2 -97
- package/src/input/commands/principals-runtime.ts +212 -0
- package/src/input/commands/product-runtime.ts +43 -3
- package/src/input/commands/recall-files-config.ts +54 -0
- package/src/input/commands/recall-files-git.ts +57 -0
- package/src/input/commands/recall-files.ts +185 -0
- package/src/input/commands/recall-query.ts +8 -3
- package/src/input/commands/recall-review.ts +2 -2
- package/src/input/commands/recall-shared.ts +13 -0
- package/src/input/commands/relay-runtime.ts +86 -0
- package/src/input/commands/review-runtime.ts +160 -0
- package/src/input/commands/rewind-runtime.ts +362 -0
- package/src/input/commands/schedule-nl.ts +147 -0
- package/src/input/commands/schedule-runtime.ts +29 -10
- package/src/input/commands/secret-runtime.ts +55 -0
- package/src/input/commands/session-content.ts +19 -8
- package/src/input/commands/session-workflow.ts +10 -0
- package/src/input/commands/update-runtime.ts +363 -0
- package/src/input/commands/workstream-attempt-dependents.ts +103 -0
- package/src/input/commands/workstream-attempts.ts +244 -0
- package/src/input/commands/workstream-runtime.ts +164 -26
- package/src/input/commands/worktree-runtime.ts +56 -2
- package/src/input/commands.ts +135 -80
- package/src/input/composer-capture.ts +67 -0
- package/src/input/composer-editor.ts +134 -0
- package/src/input/concealed-input.ts +94 -0
- package/src/input/exec-env-scrub-config.ts +84 -0
- package/src/input/feature-unit-layout.ts +142 -0
- package/src/input/feed-context-factory.ts +2 -0
- package/src/input/handler-feed-routes.ts +17 -0
- package/src/input/handler-feed.ts +3 -0
- package/src/input/handler-modal-routes.ts +5 -2
- package/src/input/handler-shortcuts.ts +37 -0
- package/src/input/handler.ts +24 -2
- package/src/input/keybindings.ts +11 -2
- package/src/input/onboarding/onboarding-feature-units.ts +536 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +40 -6
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +324 -629
- package/src/input/onboarding/onboarding-wizard-steps.ts +5 -5
- package/src/input/onboarding/onboarding-wizard-types.ts +6 -0
- package/src/input/relay-settings-descriptions.ts +35 -0
- package/src/input/sandbox-exec-config.ts +107 -0
- package/src/input/settings-modal-adjustment.ts +4 -7
- package/src/input/settings-modal-data.ts +178 -8
- package/src/input/settings-modal-mutations.ts +41 -13
- package/src/input/settings-modal-types.ts +42 -5
- package/src/input/settings-modal.ts +67 -15
- package/src/input/submission-router.ts +4 -1
- package/src/input/worktree-setup-config.ts +93 -0
- package/src/main.ts +43 -43
- package/src/panels/builtin/development.ts +14 -0
- package/src/panels/diff-review-model.ts +189 -0
- package/src/panels/diff-review-panel.ts +395 -0
- package/src/panels/fleet-panel-format.ts +114 -2
- package/src/panels/fleet-panel.ts +65 -78
- package/src/panels/fleet-read-model.ts +91 -11
- package/src/panels/fleet-stop.ts +19 -4
- package/src/panels/modals/memory-modal.ts +11 -2
- package/src/permissions/hunk-selection.ts +86 -5
- package/src/permissions/prompt.ts +108 -7
- package/src/permissions/sandbox-exec-gate.ts +162 -0
- package/src/renderer/settings-modal-helpers.ts +21 -1
- package/src/renderer/settings-modal.ts +70 -44
- package/src/renderer/shell-surface.ts +23 -1
- package/src/renderer/system-message.ts +4 -0
- package/src/renderer/tool-result-summary.ts +95 -5
- package/src/renderer/ui-factory.ts +22 -0
- package/src/runtime/bootstrap-command-context.ts +15 -1
- package/src/runtime/bootstrap-command-parts.ts +14 -2
- package/src/runtime/bootstrap-core.ts +38 -38
- package/src/runtime/bootstrap-notifier-sync.ts +41 -0
- package/src/runtime/bootstrap-shell.ts +8 -0
- package/src/runtime/bootstrap.ts +21 -23
- package/src/runtime/context-accounting-source.ts +103 -0
- package/src/runtime/conversation-rewind-port.ts +130 -0
- package/src/runtime/fleet-needs-input-push.ts +70 -0
- package/src/runtime/install-self-check-startup.ts +36 -0
- package/src/runtime/install-self-check.ts +149 -0
- package/src/runtime/onboarding/fast-path.ts +93 -0
- package/src/runtime/process-lifecycle.ts +32 -24
- package/src/runtime/relay-reachability-bridge.ts +53 -0
- package/src/runtime/release-artifacts.ts +79 -0
- package/src/runtime/services.ts +33 -33
- package/src/runtime/trust/workspace-registration.ts +184 -0
- package/src/runtime/trust/workspace-trust.ts +202 -0
- package/src/runtime/ui-services.ts +2 -1
- package/src/runtime/update-check.ts +113 -0
- package/src/runtime/workspace-checkpointing.ts +32 -0
- package/src/runtime/workstream-attempts-validation.ts +77 -0
- package/src/runtime/workstream-draft-edits.ts +95 -0
- package/src/runtime/workstream-draft-store.ts +101 -0
- package/src/runtime/workstream-draft-types.ts +42 -0
- package/src/runtime/workstream-services.ts +70 -39
- package/src/shell/blocking-input.ts +29 -0
- package/src/shell/ui-openers.ts +65 -0
- package/src/verification/verification-ledger.ts +56 -2
- package/src/version.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +0 -130
- package/src/runtime/gateway-verbs.ts +0 -47
- package/src/runtime/render-scheduler.ts +0 -90
package/src/input/commands.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { CommandRegistry } from './command-registry.ts';
|
|
2
|
+
import type { SlashCommand } from './command-registry.ts';
|
|
2
3
|
import { policyCommand } from './commands/policy.ts';
|
|
3
4
|
import { providerCommand } from './commands/provider.ts';
|
|
4
5
|
import { evalCommand } from './commands/eval.ts';
|
|
@@ -9,6 +10,7 @@ import { registerShellCoreCommands } from './commands/shell-core.ts';
|
|
|
9
10
|
import { registerConfigCommand } from './commands/config.ts';
|
|
10
11
|
import { registerDiscoveryRuntimeCommands } from './commands/discovery-runtime.ts';
|
|
11
12
|
import { registerPlanningRuntimeCommands } from './commands/planning-runtime.ts';
|
|
13
|
+
import { registerSecretRuntimeCommands } from './commands/secret-runtime.ts';
|
|
12
14
|
import { registerScheduleRuntimeCommands } from './commands/schedule-runtime.ts';
|
|
13
15
|
import { registerBranchRuntimeCommands } from './commands/branch-runtime.ts';
|
|
14
16
|
import { registerOperatorRuntimeCommands } from './commands/operator-runtime.ts';
|
|
@@ -34,10 +36,13 @@ import { registerRemoteRuntimeCommands } from './commands/remote-runtime.ts';
|
|
|
34
36
|
import { registerTeleportRuntimeCommands } from './commands/teleport-runtime.ts';
|
|
35
37
|
import { registerSubscriptionRuntimeCommands } from './commands/subscription-runtime.ts';
|
|
36
38
|
import { registerHooksRuntimeCommands } from './commands/hooks-runtime.ts';
|
|
39
|
+
import { registerFlagsRuntimeCommands } from './commands/flags-runtime.ts';
|
|
40
|
+
import { registerEditorRuntimeCommands } from './commands/editor-runtime.ts';
|
|
37
41
|
import { registerControlRoomRuntimeCommands } from './commands/control-room-runtime.ts';
|
|
38
42
|
import { registerMcpRuntimeCommands } from './commands/mcp-runtime.ts';
|
|
39
43
|
import { registerSessionContentCommands } from './commands/session-content.ts';
|
|
40
44
|
import { registerCheckpointRuntimeCommands } from './commands/checkpoint-runtime.ts';
|
|
45
|
+
import { registerRewindRuntimeCommands } from './commands/rewind-runtime.ts';
|
|
41
46
|
import { registerWorkstreamRuntimeCommands } from './commands/workstream-runtime.ts';
|
|
42
47
|
import { registerCodebaseRuntimeCommands } from './commands/codebase-runtime.ts';
|
|
43
48
|
import { registerLocalRuntimeCommands } from './commands/local-runtime.ts';
|
|
@@ -56,6 +61,7 @@ import { registerLocalAuthRuntimeCommands } from './commands/local-auth-runtime.
|
|
|
56
61
|
import { registerIntelligenceRuntimeCommands } from './commands/intelligence-runtime.ts';
|
|
57
62
|
import { registerConversationRuntimeCommands } from './commands/conversation-runtime.ts';
|
|
58
63
|
import { registerQrcodeRuntimeCommands } from './commands/qrcode-runtime.ts';
|
|
64
|
+
import { registerRelayRuntimeCommands } from './commands/relay-runtime.ts';
|
|
59
65
|
import { registerOnboardingRuntimeCommands } from './commands/onboarding-runtime.ts';
|
|
60
66
|
import { registerTtsRuntimeCommands } from './commands/tts-runtime.ts';
|
|
61
67
|
import { registerCloudflareRuntimeCommands } from './commands/cloudflare-runtime.ts';
|
|
@@ -63,90 +69,139 @@ import { registerWorkPlanRuntimeCommands } from './commands/work-plan-runtime.ts
|
|
|
63
69
|
import { registerCostRuntimeCommands } from './commands/cost-runtime.ts';
|
|
64
70
|
import { registerWebSearchRuntimeCommands } from './commands/websearch-runtime.ts';
|
|
65
71
|
import { registerImageRuntimeCommands } from './commands/image-runtime.ts';
|
|
72
|
+
import { registerPermissionsRuntimeCommands } from './commands/permissions-runtime.ts';
|
|
73
|
+
import { registerReviewRuntimeCommands } from './commands/review-runtime.ts';
|
|
74
|
+
import { registerPaletteRuntimeCommands } from './commands/palette-runtime.ts';
|
|
75
|
+
import { registerCiRuntimeCommands } from './commands/ci-runtime.ts';
|
|
76
|
+
import { registerCheckinRuntimeCommands } from './commands/checkin-runtime.ts';
|
|
77
|
+
import { registerPrincipalsRuntimeCommands } from './commands/principals-runtime.ts';
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A named group of built-in commands. The `category` label is the single
|
|
81
|
+
* source of truth for command categorization: it drives both the runtime
|
|
82
|
+
* registration order (via {@link registerBuiltinCommands}) and the generated
|
|
83
|
+
* command-reference docs / command palette grouping (via
|
|
84
|
+
* {@link categorizeBuiltinCommands}). Adding a new command group here is the
|
|
85
|
+
* only edit needed for it to appear, categorized, everywhere.
|
|
86
|
+
*/
|
|
87
|
+
export interface BuiltinCommandGroup {
|
|
88
|
+
readonly category: string;
|
|
89
|
+
readonly register: (registry: CommandRegistry) => void;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Every built-in command group, in registration order. Keeping runtime
|
|
94
|
+
* registration and doc/palette categorization on one array means a category
|
|
95
|
+
* label can never drift between the two.
|
|
96
|
+
*/
|
|
97
|
+
export const BUILTIN_COMMAND_GROUPS: readonly BuiltinCommandGroup[] = [
|
|
98
|
+
{ category: 'Shell & Session', register: registerShellCoreCommands },
|
|
99
|
+
{ category: 'Shell & Session', register: registerPaletteRuntimeCommands },
|
|
100
|
+
{ category: 'Configuration', register: registerConfigCommand },
|
|
101
|
+
{ category: 'Operator', register: registerOperatorRuntimeCommands },
|
|
102
|
+
{ category: 'Plugins', register: registerPluginRuntimeCommands },
|
|
103
|
+
{ category: 'Diff & Review', register: registerDiffRuntimeCommands },
|
|
104
|
+
{ category: 'Git', register: registerGitRuntimeCommands },
|
|
105
|
+
{ category: 'Testing', register: registerTestRuntimeCommands },
|
|
106
|
+
{ category: 'Notifications', register: registerNotifyRuntimeCommands },
|
|
107
|
+
{ category: 'Sessions & Replay', register: registerReplayRuntimeCommands },
|
|
108
|
+
{ category: 'Sharing', register: registerShareRuntimeCommands },
|
|
109
|
+
{ category: 'Channels', register: registerChannelRuntimeCommands },
|
|
110
|
+
{ category: 'Local Setup', register: registerLocalSetupCommands },
|
|
111
|
+
{ category: 'Product', register: registerProductRuntimeCommands },
|
|
112
|
+
{ category: 'Platform', register: registerPlatformRuntimeCommands },
|
|
113
|
+
{ category: 'Profiles', register: registerProfileSyncRuntimeCommands },
|
|
114
|
+
{ category: 'Managed Runtime', register: registerManagedRuntimeCommands },
|
|
115
|
+
{ category: 'Platform Access', register: registerPlatformAccessRuntimeCommands },
|
|
116
|
+
{ category: 'Platform Services', register: registerPlatformServicesRuntimeCommands },
|
|
117
|
+
{ category: 'Teamwork', register: registerTeamworkRuntimeCommands },
|
|
118
|
+
{ category: 'Marketplace', register: registerMarketplaceRuntimeCommands },
|
|
119
|
+
{ category: 'Guidance', register: registerGuidanceRuntimeCommands },
|
|
120
|
+
{ category: 'Remote', register: registerRemoteRuntimeCommands },
|
|
121
|
+
{ category: 'Remote', register: registerTeleportRuntimeCommands },
|
|
122
|
+
{ category: 'Subscriptions', register: registerSubscriptionRuntimeCommands },
|
|
123
|
+
{ category: 'Hooks', register: registerHooksRuntimeCommands },
|
|
124
|
+
{ category: 'Feature Flags', register: registerFlagsRuntimeCommands },
|
|
125
|
+
{ category: 'Editor', register: registerEditorRuntimeCommands },
|
|
126
|
+
{ category: 'Control Room', register: registerControlRoomRuntimeCommands },
|
|
127
|
+
{ category: 'MCP', register: registerMcpRuntimeCommands },
|
|
128
|
+
{ category: 'Incidents', register: registerIncidentRuntimeCommands },
|
|
129
|
+
{ category: 'Memory', register: registerMemoryProductRuntimeCommands },
|
|
130
|
+
{ category: 'Skills', register: registerSkillsRuntimeCommands },
|
|
131
|
+
{ category: 'Experience', register: registerExperienceRuntimeCommands },
|
|
132
|
+
{ category: 'Services', register: registerServicesRuntimeCommands },
|
|
133
|
+
{ category: 'Tasks', register: registerTasksRuntimeCommands },
|
|
134
|
+
{ category: 'Local Providers', register: registerLocalProviderRuntimeCommands },
|
|
135
|
+
{ category: 'Health', register: registerHealthRuntimeCommands },
|
|
136
|
+
{ category: 'Settings Sync', register: registerSettingsSyncRuntimeCommands },
|
|
137
|
+
{ category: 'Worktrees', register: registerWorktreeRuntimeCommands },
|
|
138
|
+
{ category: 'Provider Accounts', register: registerProviderAccountsRuntimeCommands },
|
|
139
|
+
{ category: 'Local Auth', register: registerLocalAuthRuntimeCommands },
|
|
140
|
+
{ category: 'Intelligence', register: registerIntelligenceRuntimeCommands },
|
|
141
|
+
{ category: 'Conversation', register: registerConversationRuntimeCommands },
|
|
142
|
+
{ category: 'QR Codes', register: registerQrcodeRuntimeCommands },
|
|
143
|
+
{ category: 'Relay', register: registerRelayRuntimeCommands },
|
|
144
|
+
{ category: 'Onboarding', register: registerOnboardingRuntimeCommands },
|
|
145
|
+
{ category: 'Voice & TTS', register: registerTtsRuntimeCommands },
|
|
146
|
+
{ category: 'Cloudflare', register: registerCloudflareRuntimeCommands },
|
|
147
|
+
{ category: 'Work Plans', register: registerWorkPlanRuntimeCommands },
|
|
148
|
+
{ category: 'Cost', register: registerCostRuntimeCommands },
|
|
149
|
+
{ category: 'Local Runtime', register: registerLocalRuntimeCommands },
|
|
150
|
+
{ category: 'Discovery', register: registerDiscoveryRuntimeCommands },
|
|
151
|
+
{ category: 'Planning', register: registerPlanningRuntimeCommands },
|
|
152
|
+
{ category: 'Secrets', register: registerSecretRuntimeCommands },
|
|
153
|
+
{ category: 'Scheduling', register: registerScheduleRuntimeCommands },
|
|
154
|
+
{ category: 'Branches', register: registerBranchRuntimeCommands },
|
|
155
|
+
{ category: 'Session Content', register: registerSessionContentCommands },
|
|
156
|
+
{ category: 'Checkpoints', register: registerCheckpointRuntimeCommands },
|
|
157
|
+
{ category: 'Checkpoints', register: registerRewindRuntimeCommands },
|
|
158
|
+
{ category: 'Workstreams', register: registerWorkstreamRuntimeCommands },
|
|
159
|
+
{ category: 'Codebase', register: registerCodebaseRuntimeCommands },
|
|
160
|
+
{ category: 'Web Search', register: registerWebSearchRuntimeCommands },
|
|
161
|
+
{ category: 'Image', register: registerImageRuntimeCommands },
|
|
162
|
+
{ category: 'Permissions', register: registerPermissionsRuntimeCommands },
|
|
163
|
+
{ category: 'Diff & Review', register: registerReviewRuntimeCommands },
|
|
164
|
+
{ category: 'CI', register: registerCiRuntimeCommands },
|
|
165
|
+
{ category: 'Check-in', register: registerCheckinRuntimeCommands },
|
|
166
|
+
{ category: 'Principals', register: registerPrincipalsRuntimeCommands },
|
|
167
|
+
{ category: 'Policy', register: (registry) => registry.register(policyCommand) },
|
|
168
|
+
{ category: 'Providers', register: (registry) => registry.register(providerCommand) },
|
|
169
|
+
{ category: 'Eval', register: (registry) => registry.register(evalCommand) },
|
|
170
|
+
{ category: 'Sessions & Replay', register: (registry) => registry.register(sessionCommand) },
|
|
171
|
+
{ category: 'Sessions & Replay', register: (registry) => registry.register(resumeCommand) },
|
|
172
|
+
{ category: 'Memory', register: (registry) => registry.register(recallCommand) },
|
|
173
|
+
{ category: 'Knowledge', register: (registry) => registry.register(knowledgeCommand) },
|
|
174
|
+
];
|
|
66
175
|
|
|
67
176
|
/**
|
|
68
177
|
* registerBuiltinCommands - Register all built-in slash commands into the registry.
|
|
69
178
|
* Call once during application startup.
|
|
70
179
|
*/
|
|
71
180
|
export function registerBuiltinCommands(registry: CommandRegistry): void {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
registerDiffRuntimeCommands(registry);
|
|
77
|
-
registerGitRuntimeCommands(registry);
|
|
78
|
-
registerTestRuntimeCommands(registry);
|
|
79
|
-
registerNotifyRuntimeCommands(registry);
|
|
80
|
-
registerReplayRuntimeCommands(registry);
|
|
81
|
-
registerShareRuntimeCommands(registry);
|
|
82
|
-
registerChannelRuntimeCommands(registry);
|
|
83
|
-
registerLocalSetupCommands(registry);
|
|
84
|
-
registerProductRuntimeCommands(registry);
|
|
85
|
-
registerPlatformRuntimeCommands(registry);
|
|
86
|
-
registerProfileSyncRuntimeCommands(registry);
|
|
87
|
-
registerManagedRuntimeCommands(registry);
|
|
88
|
-
registerPlatformAccessRuntimeCommands(registry);
|
|
89
|
-
registerPlatformServicesRuntimeCommands(registry);
|
|
90
|
-
registerTeamworkRuntimeCommands(registry);
|
|
91
|
-
registerMarketplaceRuntimeCommands(registry);
|
|
92
|
-
registerGuidanceRuntimeCommands(registry);
|
|
93
|
-
registerRemoteRuntimeCommands(registry);
|
|
94
|
-
registerTeleportRuntimeCommands(registry);
|
|
95
|
-
registerSubscriptionRuntimeCommands(registry);
|
|
96
|
-
registerHooksRuntimeCommands(registry);
|
|
97
|
-
registerControlRoomRuntimeCommands(registry);
|
|
98
|
-
registerMcpRuntimeCommands(registry);
|
|
99
|
-
registerIncidentRuntimeCommands(registry);
|
|
100
|
-
registerMemoryProductRuntimeCommands(registry);
|
|
101
|
-
registerSkillsRuntimeCommands(registry);
|
|
102
|
-
registerExperienceRuntimeCommands(registry);
|
|
103
|
-
registerServicesRuntimeCommands(registry);
|
|
104
|
-
registerTasksRuntimeCommands(registry);
|
|
105
|
-
registerLocalProviderRuntimeCommands(registry);
|
|
106
|
-
registerHealthRuntimeCommands(registry);
|
|
107
|
-
registerSettingsSyncRuntimeCommands(registry);
|
|
108
|
-
registerWorktreeRuntimeCommands(registry);
|
|
109
|
-
registerProviderAccountsRuntimeCommands(registry);
|
|
110
|
-
registerLocalAuthRuntimeCommands(registry);
|
|
111
|
-
registerIntelligenceRuntimeCommands(registry);
|
|
112
|
-
registerConversationRuntimeCommands(registry);
|
|
113
|
-
registerQrcodeRuntimeCommands(registry);
|
|
114
|
-
registerOnboardingRuntimeCommands(registry);
|
|
115
|
-
registerTtsRuntimeCommands(registry);
|
|
116
|
-
registerCloudflareRuntimeCommands(registry);
|
|
117
|
-
registerWorkPlanRuntimeCommands(registry);
|
|
118
|
-
registerCostRuntimeCommands(registry);
|
|
119
|
-
registerLocalRuntimeCommands(registry);
|
|
120
|
-
registerDiscoveryRuntimeCommands(registry);
|
|
121
|
-
registerPlanningRuntimeCommands(registry);
|
|
122
|
-
registerScheduleRuntimeCommands(registry);
|
|
123
|
-
registerBranchRuntimeCommands(registry);
|
|
124
|
-
registerSessionContentCommands(registry);
|
|
125
|
-
registerCheckpointRuntimeCommands(registry);
|
|
126
|
-
registerWorkstreamRuntimeCommands(registry);
|
|
127
|
-
registerCodebaseRuntimeCommands(registry);
|
|
128
|
-
registerWebSearchRuntimeCommands(registry);
|
|
129
|
-
registerImageRuntimeCommands(registry);
|
|
130
|
-
|
|
131
|
-
// ── /policy ───────────────────────────────────────────────────────────────
|
|
132
|
-
registry.register(policyCommand);
|
|
133
|
-
|
|
134
|
-
// ── /provider ─────────────────────────────────────────────────────────────
|
|
135
|
-
registry.register(providerCommand);
|
|
136
|
-
|
|
137
|
-
// ── /eval ─────────────────────────────────────────────────────────────────
|
|
138
|
-
registry.register(evalCommand);
|
|
139
|
-
|
|
140
|
-
// ── /session ─────────────────────────────────────────────────────────────
|
|
141
|
-
registry.register(sessionCommand);
|
|
142
|
-
|
|
143
|
-
// ── /resume — the discoverable front door to /session resume ─────────────
|
|
144
|
-
registry.register(resumeCommand);
|
|
145
|
-
|
|
146
|
-
// ── /recall ──────────────────────────────────────────────────────────────
|
|
147
|
-
registry.register(recallCommand);
|
|
148
|
-
|
|
149
|
-
// ── /knowledge ───────────────────────────────────────────────────────────
|
|
150
|
-
registry.register(knowledgeCommand);
|
|
181
|
+
for (const group of BUILTIN_COMMAND_GROUPS) {
|
|
182
|
+
group.register(registry);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
151
185
|
|
|
186
|
+
/**
|
|
187
|
+
* categorizeBuiltinCommands - Build every built-in command paired with its
|
|
188
|
+
* category label, by replaying the same {@link BUILTIN_COMMAND_GROUPS} into a
|
|
189
|
+
* throwaway registry and attributing each newly-registered command to the
|
|
190
|
+
* group that added it. Handlers are never invoked, so this is side-effect free
|
|
191
|
+
* and safe to call from build scripts and tests. Used by the generated command
|
|
192
|
+
* reference (docs) and the command palette so both stay registry-driven.
|
|
193
|
+
*/
|
|
194
|
+
export function categorizeBuiltinCommands(): Array<{ command: SlashCommand; category: string }> {
|
|
195
|
+
const registry = new CommandRegistry();
|
|
196
|
+
const out: Array<{ command: SlashCommand; category: string }> = [];
|
|
197
|
+
const seen = new Set<string>();
|
|
198
|
+
for (const group of BUILTIN_COMMAND_GROUPS) {
|
|
199
|
+
group.register(registry);
|
|
200
|
+
for (const command of registry.getAll()) {
|
|
201
|
+
if (seen.has(command.name)) continue;
|
|
202
|
+
seen.add(command.name);
|
|
203
|
+
out.push({ command, category: group.category });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
152
207
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composer capture intents — leading markers in the composer that write to
|
|
3
|
+
* memory instead of (or in addition to) sending a turn.
|
|
4
|
+
*
|
|
5
|
+
* `!# <text>` pins <text> to session memory AND sends it as a normal prompt
|
|
6
|
+
* (pre-existing behavior).
|
|
7
|
+
* `# <text>` captures <text> as a session-memory note and does NOT send a
|
|
8
|
+
* turn — a pure "jot this down" gesture. A single leading `#`
|
|
9
|
+
* only; `##...` (markdown headings) is left alone so it can be
|
|
10
|
+
* sent as an ordinary prompt.
|
|
11
|
+
*
|
|
12
|
+
* Both write through the existing session memory machinery
|
|
13
|
+
* (SessionMemoryStore.add) and surface a visible confirmation naming what was
|
|
14
|
+
* written and where.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface ComposerCaptureDeps {
|
|
18
|
+
readonly sessionMemoryStore: { add(text: string): string };
|
|
19
|
+
/** Surfaces the confirmation / usage message (a system message the shell renders). */
|
|
20
|
+
readonly notify: (message: string) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ComposerCaptureResult {
|
|
24
|
+
/** Text the caller should continue submission with (empty when nothing should be sent). */
|
|
25
|
+
readonly text: string;
|
|
26
|
+
/** Whether a capture marker was recognized and handled. */
|
|
27
|
+
readonly captured: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function truncateForChip(text: string): string {
|
|
31
|
+
return text.length > 60 ? `${text.slice(0, 57)}...` : text;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Interpret composer capture markers. Returns the (possibly emptied) text to
|
|
36
|
+
* continue with and whether a marker was handled. When `captured` is true and
|
|
37
|
+
* the returned `text` is empty, the caller should not send a turn.
|
|
38
|
+
*/
|
|
39
|
+
export function applyComposerCapture(input: string, deps: ComposerCaptureDeps): ComposerCaptureResult {
|
|
40
|
+
// `!#` — pin to session memory and continue to send as a prompt.
|
|
41
|
+
if (input.startsWith('!#')) {
|
|
42
|
+
const memoryText = input.slice(2).trim();
|
|
43
|
+
if (!memoryText) {
|
|
44
|
+
deps.notify('[Memory] Usage: !# <text to pin as session memory>');
|
|
45
|
+
return { text: '', captured: true };
|
|
46
|
+
}
|
|
47
|
+
const id = deps.sessionMemoryStore.add(memoryText);
|
|
48
|
+
deps.notify(`[Memory] Pinned: "${memoryText}" (${id})`);
|
|
49
|
+
return { text: memoryText, captured: true };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// `#` (single, not `##`) — capture a note to session memory; do not send.
|
|
53
|
+
const leading = input.replace(/^\s+/, '');
|
|
54
|
+
if (leading.startsWith('#') && !leading.startsWith('##')) {
|
|
55
|
+
const noteText = leading.slice(1).trim();
|
|
56
|
+
if (!noteText) {
|
|
57
|
+
deps.notify('[Note] Usage: # <text to save as a session-memory note>');
|
|
58
|
+
return { text: '', captured: true };
|
|
59
|
+
}
|
|
60
|
+
const id = deps.sessionMemoryStore.add(noteText);
|
|
61
|
+
// Confirmation chip: names WHAT was written and WHERE it went.
|
|
62
|
+
deps.notify(`✓ Note saved → session memory: "${truncateForChip(noteText)}" (${id})`);
|
|
63
|
+
return { text: '', captured: true };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { text: input, captured: false };
|
|
67
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External-editor composition for the composer.
|
|
3
|
+
*
|
|
4
|
+
* Opens the user's $VISUAL/$EDITOR on the current composer draft, then loads
|
|
5
|
+
* the edited text back into the composer. While the editor runs the TUI is
|
|
6
|
+
* suspended: raw mode is dropped and the alt screen / mouse / paste / focus
|
|
7
|
+
* modes are disabled so the editor owns a normal terminal; on return the TUI
|
|
8
|
+
* modes are restored and the screen is repainted from scratch.
|
|
9
|
+
*
|
|
10
|
+
* The subprocess is run through the existing exec pattern (Bun/Node
|
|
11
|
+
* `spawnSync` with inherited stdio) so the editor shares this process's tty.
|
|
12
|
+
* The spawn is injectable for testing.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { spawnSync } from 'node:child_process';
|
|
16
|
+
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
|
|
17
|
+
import { tmpdir } from 'node:os';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import {
|
|
20
|
+
ALT_SCREEN_ENTER, ALT_SCREEN_EXIT, CLEAR_SCREEN, CURSOR_HIDE, CURSOR_SHOW,
|
|
21
|
+
MOUSE_ENABLE, MOUSE_DISABLE, KEYBOARD_EXT_ENABLE, KEYBOARD_EXT_DISABLE,
|
|
22
|
+
PASTE_ENABLE, PASTE_DISABLE, FOCUS_ENABLE, FOCUS_DISABLE,
|
|
23
|
+
} from '../renderer/terminal-escapes.ts';
|
|
24
|
+
|
|
25
|
+
export interface EditorSpawnResult {
|
|
26
|
+
readonly status: number | null;
|
|
27
|
+
readonly error?: Error | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Runs the editor synchronously with inherited stdio. Injectable for tests. */
|
|
31
|
+
export type EditorSpawn = (cmd: string, args: string[], opts: { cwd: string; env: NodeJS.ProcessEnv }) => EditorSpawnResult;
|
|
32
|
+
|
|
33
|
+
export interface ComposerEditorDeps {
|
|
34
|
+
readonly readDraft: () => string;
|
|
35
|
+
readonly writeDraft: (text: string) => void;
|
|
36
|
+
readonly cwd: string;
|
|
37
|
+
readonly env: NodeJS.ProcessEnv;
|
|
38
|
+
/** Drop the TUI's raw/alt-screen modes so the editor owns the terminal. */
|
|
39
|
+
readonly suspend: () => void;
|
|
40
|
+
/** Restore the TUI's modes and repaint. */
|
|
41
|
+
readonly resume: () => void;
|
|
42
|
+
readonly notify: (message: string) => void;
|
|
43
|
+
readonly spawn?: EditorSpawn;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Resolve $VISUAL/$EDITOR into a command + fixed args, or null when unset. */
|
|
47
|
+
export function resolveEditorCommand(env: NodeJS.ProcessEnv): { cmd: string; args: string[] } | null {
|
|
48
|
+
const raw = (env.VISUAL || env.EDITOR || '').trim();
|
|
49
|
+
if (!raw) return null;
|
|
50
|
+
const parts = raw.split(/\s+/);
|
|
51
|
+
const cmd = parts[0];
|
|
52
|
+
if (!cmd) return null;
|
|
53
|
+
return { cmd, args: parts.slice(1) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const defaultSpawn: EditorSpawn = (cmd, args, opts) => {
|
|
57
|
+
const res = spawnSync(cmd, args, { stdio: 'inherit', cwd: opts.cwd, env: opts.env });
|
|
58
|
+
return { status: res.status, error: res.error };
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Core flow: temp file round-trip around a suspended-terminal editor launch. */
|
|
62
|
+
export function openComposerInEditor(deps: ComposerEditorDeps): void {
|
|
63
|
+
const editor = resolveEditorCommand(deps.env);
|
|
64
|
+
if (!editor) {
|
|
65
|
+
deps.notify('[Editor] Set $EDITOR or $VISUAL to edit the composer in an external editor.');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const draft = deps.readDraft();
|
|
69
|
+
const dir = mkdtempSync(join(tmpdir(), 'goodvibes-composer-'));
|
|
70
|
+
const file = join(dir, 'COMPOSER.md');
|
|
71
|
+
const spawn = deps.spawn ?? defaultSpawn;
|
|
72
|
+
let result: EditorSpawnResult;
|
|
73
|
+
try {
|
|
74
|
+
writeFileSync(file, draft, 'utf8');
|
|
75
|
+
deps.suspend();
|
|
76
|
+
try {
|
|
77
|
+
result = spawn(editor.cmd, [...editor.args, file], { cwd: deps.cwd, env: deps.env });
|
|
78
|
+
} finally {
|
|
79
|
+
deps.resume();
|
|
80
|
+
}
|
|
81
|
+
if (result.error) {
|
|
82
|
+
deps.notify(`[Editor] Failed to launch ${editor.cmd}: ${result.error.message}`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (result.status !== 0 && result.status !== null) {
|
|
86
|
+
deps.notify(`[Editor] ${editor.cmd} exited with code ${result.status}; composer left unchanged.`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
let edited = draft;
|
|
90
|
+
try { edited = readFileSync(file, 'utf8'); } catch { /* keep the original draft */ }
|
|
91
|
+
deps.writeDraft(edited.replace(/\n+$/, '')); // editors append a trailing newline
|
|
92
|
+
deps.notify('[Editor] Composer updated from external editor.');
|
|
93
|
+
} finally {
|
|
94
|
+
rmSync(dir, { recursive: true, force: true });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const LEAVE_TUI = PASTE_DISABLE + KEYBOARD_EXT_DISABLE + MOUSE_DISABLE + FOCUS_DISABLE + ALT_SCREEN_EXIT + CURSOR_SHOW;
|
|
99
|
+
const ENTER_TUI = ALT_SCREEN_ENTER + CLEAR_SCREEN + CURSOR_HIDE + MOUSE_ENABLE + KEYBOARD_EXT_ENABLE + PASTE_ENABLE + FOCUS_ENABLE;
|
|
100
|
+
|
|
101
|
+
export interface ComposerEditorOpenerDeps {
|
|
102
|
+
/** The live composer buffer (InputHandler exposes public `prompt`/`cursorPos`). */
|
|
103
|
+
readonly buffer: { prompt: string; cursorPos: number };
|
|
104
|
+
readonly stdin: { setRawMode(on: boolean): unknown };
|
|
105
|
+
readonly stdout: { write(seq: string): unknown };
|
|
106
|
+
/** Terminal-output guard wrapper (allowTerminalWrite). */
|
|
107
|
+
readonly writeGuard: (fn: () => void) => void;
|
|
108
|
+
readonly repaint: () => void;
|
|
109
|
+
readonly cwd: string;
|
|
110
|
+
readonly env: NodeJS.ProcessEnv;
|
|
111
|
+
readonly notify: (message: string) => void;
|
|
112
|
+
readonly spawn?: EditorSpawn;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Build the `openComposerEditor` command action, wiring terminal suspend/resume. */
|
|
116
|
+
export function makeComposerEditorOpener(deps: ComposerEditorOpenerDeps): () => void {
|
|
117
|
+
return () => openComposerInEditor({
|
|
118
|
+
readDraft: () => deps.buffer.prompt,
|
|
119
|
+
writeDraft: (text) => { deps.buffer.prompt = text; deps.buffer.cursorPos = text.length; },
|
|
120
|
+
cwd: deps.cwd,
|
|
121
|
+
env: deps.env,
|
|
122
|
+
notify: deps.notify,
|
|
123
|
+
spawn: deps.spawn,
|
|
124
|
+
suspend: () => {
|
|
125
|
+
try { deps.stdin.setRawMode(false); } catch { /* stdin may not be a TTY */ }
|
|
126
|
+
deps.writeGuard(() => deps.stdout.write(LEAVE_TUI));
|
|
127
|
+
},
|
|
128
|
+
resume: () => {
|
|
129
|
+
deps.writeGuard(() => deps.stdout.write(ENTER_TUI));
|
|
130
|
+
try { deps.stdin.setRawMode(true); } catch { /* stdin may not be a TTY */ }
|
|
131
|
+
deps.repaint();
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composer concealed-input mode.
|
|
3
|
+
*
|
|
4
|
+
* Generalizes the masking that previously lived only on the two purpose-built
|
|
5
|
+
* auth surfaces (local-auth panel, onboarding wizard) into the MAIN composer,
|
|
6
|
+
* so any password-like prompt can request concealed entry: the typed text is
|
|
7
|
+
* masked in the composer AND never reaches input history or the transcript
|
|
8
|
+
* plaintext. The plaintext is delivered exactly once, to the requester's
|
|
9
|
+
* onSubmit callback.
|
|
10
|
+
*
|
|
11
|
+
* The real value lives in the composer's normal prompt buffer (so all the usual
|
|
12
|
+
* editing works); only the RENDER path is masked (see maskConcealedText) and
|
|
13
|
+
* the SUBMIT path is diverted (see InputHandler.submitConcealedInput).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** A pending request for one line of concealed input from the composer. */
|
|
17
|
+
export interface ConcealedInputRequest {
|
|
18
|
+
/**
|
|
19
|
+
* Short label describing what is being asked for, e.g. 'Password' or
|
|
20
|
+
* 'API key'. Surfaced by the caller (e.g. as a system message) — the composer
|
|
21
|
+
* itself only masks; it does not print the label.
|
|
22
|
+
*/
|
|
23
|
+
readonly label?: string;
|
|
24
|
+
/** Receives the entered plaintext exactly once, when the user submits. */
|
|
25
|
+
readonly onSubmit: (value: string) => void;
|
|
26
|
+
/** Invoked if the user cancels (Escape) instead of submitting. */
|
|
27
|
+
readonly onCancel?: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Mask a composer buffer for display. Every UTF-16 code unit except a newline
|
|
32
|
+
* becomes a bullet, so the masked string has the EXACT same length and line
|
|
33
|
+
* structure as the plaintext — word-wrap and cursor-position math (which index
|
|
34
|
+
* the string by code unit) stay correct while no plaintext character ever
|
|
35
|
+
* reaches the screen buffer.
|
|
36
|
+
*/
|
|
37
|
+
export function maskConcealedText(text: string): string {
|
|
38
|
+
return text.replace(/[^\n]/g, '•');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Minimal composer surface the concealed-input helpers mutate. Kept structural
|
|
43
|
+
* so the logic lives here (out of the 800-line handler.ts) while the InputHandler
|
|
44
|
+
* only holds thin delegating methods.
|
|
45
|
+
*/
|
|
46
|
+
export interface ConcealedInputHost {
|
|
47
|
+
prompt: string;
|
|
48
|
+
cursorPos: number;
|
|
49
|
+
concealedInput: ConcealedInputRequest | null;
|
|
50
|
+
requestRender: () => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Begin one line of concealed input. The composer starts empty and masked; the
|
|
55
|
+
* next submit delivers the plaintext to request.onSubmit and auto-clears
|
|
56
|
+
* concealed mode. A second call replaces any in-flight request (its onCancel
|
|
57
|
+
* fires so no requester is left dangling).
|
|
58
|
+
*/
|
|
59
|
+
export function beginConcealedInputFor(host: ConcealedInputHost, request: ConcealedInputRequest): void {
|
|
60
|
+
if (host.concealedInput) host.concealedInput.onCancel?.();
|
|
61
|
+
host.concealedInput = request;
|
|
62
|
+
host.prompt = '';
|
|
63
|
+
host.cursorPos = 0;
|
|
64
|
+
host.requestRender();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Deliver a concealed submission. Returns true when concealed mode was active
|
|
69
|
+
* and consumed the value; false when not concealed (caller uses the normal
|
|
70
|
+
* submit path). The value is passed by the caller (the live feed snapshot),
|
|
71
|
+
* never read back from host.prompt, to avoid mid-feed staleness. Concealed
|
|
72
|
+
* state is cleared BEFORE onSubmit runs so the secret does not linger.
|
|
73
|
+
*/
|
|
74
|
+
export function submitConcealedInputFor(host: ConcealedInputHost, value: string): boolean {
|
|
75
|
+
const request = host.concealedInput;
|
|
76
|
+
if (!request) return false;
|
|
77
|
+
host.concealedInput = null;
|
|
78
|
+
host.prompt = '';
|
|
79
|
+
host.cursorPos = 0;
|
|
80
|
+
request.onSubmit(value);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Cancel an active concealed-input request without submitting (Escape). */
|
|
85
|
+
export function cancelConcealedInputFor(host: ConcealedInputHost): boolean {
|
|
86
|
+
const request = host.concealedInput;
|
|
87
|
+
if (!request) return false;
|
|
88
|
+
host.concealedInput = null;
|
|
89
|
+
host.prompt = '';
|
|
90
|
+
host.cursorPos = 0;
|
|
91
|
+
request.onCancel?.();
|
|
92
|
+
host.requestRender();
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* exec-env-scrub-config.ts — TUI-local synthetic settings entry for
|
|
3
|
+
* permissions.execEnvScrubAllowlist.
|
|
4
|
+
*
|
|
5
|
+
* The SDK's credential-bearing env-var scrub (platform/tools/exec/credential-env.ts)
|
|
6
|
+
* removes well-known credential-bearing variable NAMES (AWS_SECRET_ACCESS_KEY,
|
|
7
|
+
* GITHUB_TOKEN, OPENAI_API_KEY, …) from the environment handed to every spawned
|
|
8
|
+
* exec command, and is threaded into `registerAllTools`'s `credentialEnvScrub`
|
|
9
|
+
* option (bootstrap-core.ts) so a consumer can wire an operator-configured
|
|
10
|
+
* allowlist instead of the scrub always resolving to its built-in default. The
|
|
11
|
+
* scrub's master switch stays on: this entry configures ONLY the allowlist —
|
|
12
|
+
* variable NAMES always kept even though their name looks credential-bearing
|
|
13
|
+
* (case-insensitive), for a command that genuinely needs one (e.g. a CI runner
|
|
14
|
+
* re-reading its own signing key). NEVER a value; the scrub only ever inspects
|
|
15
|
+
* and reports NAMES.
|
|
16
|
+
*
|
|
17
|
+
* `permissions.execEnvScrubAllowlist` is not in CONFIG_SCHEMA — there is no SDK
|
|
18
|
+
* config domain for the exec env scrub at all (it is consumer-wired, not
|
|
19
|
+
* schema-registered), so this mirrors the other TUI-local synthetic settings
|
|
20
|
+
* (worktree.setup.*, sandbox.egressAllowlist, …). No defensive try/catch is
|
|
21
|
+
* needed: 'permissions' already exists as a DEFAULT_CONFIG section (it backs
|
|
22
|
+
* permissions.mode/tools.*), so a plain get()/set() resolves the path without
|
|
23
|
+
* throwing — only the LEAF field is new, and ConfigManager.resolvePath never
|
|
24
|
+
* validates leaf existence.
|
|
25
|
+
*
|
|
26
|
+
* Stored as a JSON array of strings on disk; the settings modal's inline
|
|
27
|
+
* editor is a single-line text field, so — matching worktree.setup.* and
|
|
28
|
+
* sandbox.egressAllowlist/workspaceWritable — it is displayed and edited as a
|
|
29
|
+
* comma-separated list and parsed back into an array on commit (see
|
|
30
|
+
* isExecEnvScrubAllowlistConfigKey's use in settings-modal.ts#commitEdit).
|
|
31
|
+
*/
|
|
32
|
+
import type { ConfigKey, ConfigManager, ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
|
|
33
|
+
import type { SettingEntry, SettingsCategory } from './settings-modal-types.ts';
|
|
34
|
+
|
|
35
|
+
export const EXEC_ENV_SCRUB_ALLOWLIST_CONFIG_KEY = 'permissions.execEnvScrubAllowlist' as ConfigKey;
|
|
36
|
+
|
|
37
|
+
/** True for the exec env-scrub allowlist key, whose stored value is a string array edited as a comma-separated list. */
|
|
38
|
+
export function isExecEnvScrubAllowlistConfigKey(key: ConfigKey): boolean {
|
|
39
|
+
return key === EXEC_ENV_SCRUB_ALLOWLIST_CONFIG_KEY;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Read the allowlist as a string array (non-array/malformed/unset degrades to empty). */
|
|
43
|
+
export function readExecEnvScrubAllowlist(configManager: Pick<ConfigManager, 'get'>): string[] {
|
|
44
|
+
const raw = configManager.get(EXEC_ENV_SCRUB_ALLOWLIST_CONFIG_KEY);
|
|
45
|
+
if (!Array.isArray(raw)) return [];
|
|
46
|
+
return raw.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Parse a comma-separated edit-buffer submission back into a string array (trimmed, empties dropped). Same convention as worktree.setup.* / sandbox.egressAllowlist. */
|
|
50
|
+
export function parseExecEnvScrubAllowlistInput(text: string): string[] {
|
|
51
|
+
return text.split(',').map((part) => part.trim()).filter((part) => part.length > 0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const EXEC_ENV_SCRUB_ALLOWLIST_SYNTHETIC_SETTING: ConfigSetting = {
|
|
55
|
+
key: EXEC_ENV_SCRUB_ALLOWLIST_CONFIG_KEY,
|
|
56
|
+
type: 'string',
|
|
57
|
+
default: [],
|
|
58
|
+
description: 'Comma-separated environment variable NAMES always kept in a spawned exec command\'s environment even when the name looks credential-bearing (case-insensitive). Names only — this never inspects or reports values. The scrub itself stays on; this only widens what it lets through. Empty = the built-in credential-name scrub applies with no extra exceptions.',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Build the synthetic SettingEntry for permissions.execEnvScrubAllowlist. */
|
|
62
|
+
export function buildExecEnvScrubAllowlistSyntheticEntry(configManager: Pick<ConfigManager, 'get'>): SettingEntry {
|
|
63
|
+
const currentValue = readExecEnvScrubAllowlist(configManager);
|
|
64
|
+
return {
|
|
65
|
+
setting: EXEC_ENV_SCRUB_ALLOWLIST_SYNTHETIC_SETTING,
|
|
66
|
+
currentValue,
|
|
67
|
+
isDefault: currentValue.length === 0,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Inject the synthetic entry into the permissions category's SettingEntry
|
|
73
|
+
* list, idempotently. No-op when the category isn't present in `groups`.
|
|
74
|
+
*/
|
|
75
|
+
export function injectExecEnvScrubSyntheticEntry(
|
|
76
|
+
groups: Map<SettingsCategory, SettingEntry[]>,
|
|
77
|
+
configManager: Pick<ConfigManager, 'get'>,
|
|
78
|
+
): void {
|
|
79
|
+
const permissionsEntries = groups.get('permissions');
|
|
80
|
+
if (!permissionsEntries) return;
|
|
81
|
+
if (!permissionsEntries.some((e) => e.setting.key === EXEC_ENV_SCRUB_ALLOWLIST_CONFIG_KEY)) {
|
|
82
|
+
permissionsEntries.push(buildExecEnvScrubAllowlistSyntheticEntry(configManager));
|
|
83
|
+
}
|
|
84
|
+
}
|