praxis-agent 0.7.0 → 0.9.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/README.md +8 -0
- package/dist/application/background-agent-manager.d.ts +5 -0
- package/dist/application/background-agent-manager.js +21 -0
- package/dist/application/background-bash-manager.d.ts +14 -0
- package/dist/application/background-bash-manager.js +42 -4
- package/dist/application/background-task-runtime.d.ts +34 -0
- package/dist/application/background-task-runtime.js +59 -0
- package/dist/application/session-service.d.ts +21 -1
- package/dist/application/session-service.js +188 -17
- package/dist/application/session-worktree.d.ts +1 -0
- package/dist/application/session-worktree.js +13 -2
- package/dist/application/subagent-service.d.ts +3 -0
- package/dist/application/subagent-service.js +6 -0
- package/dist/application/workflow-manager.d.ts +70 -2
- package/dist/application/workflow-manager.js +21 -4
- package/dist/cli/agents-dashboard.d.ts +9 -0
- package/dist/cli/agents-dashboard.js +16 -5
- package/dist/cli/interactive.d.ts +41 -3
- package/dist/cli/interactive.js +1281 -183
- package/dist/cli/tui/claude-style.d.ts +31 -3
- package/dist/cli/tui/claude-style.js +171 -54
- package/dist/cli/tui/config-dashboard.d.ts +63 -0
- package/dist/cli/tui/config-dashboard.js +150 -0
- package/dist/cli/tui/config-settings.d.ts +32 -0
- package/dist/cli/tui/config-settings.js +276 -0
- package/dist/cli/tui/custom-themes.d.ts +21 -0
- package/dist/cli/tui/custom-themes.js +284 -0
- package/dist/cli/tui/file-picker.d.ts +3 -1
- package/dist/cli/tui/file-picker.js +8 -2
- package/dist/cli/tui/keybindings.js +0 -1
- package/dist/cli/tui/mcp-panel-controller.d.ts +32 -0
- package/dist/cli/tui/mcp-panel-controller.js +80 -0
- package/dist/cli/tui/mcp-panel-projector.d.ts +79 -0
- package/dist/cli/tui/mcp-panel-projector.js +181 -0
- package/dist/cli/tui/mcp-panel.d.ts +8 -0
- package/dist/cli/tui/mcp-panel.js +99 -0
- package/dist/cli/tui/recently-denied.d.ts +15 -0
- package/dist/cli/tui/recently-denied.js +19 -0
- package/dist/cli/tui/runtime-interactions.d.ts +17 -0
- package/dist/cli/tui/runtime-interactions.js +100 -0
- package/dist/cli/tui/runtime-settings.d.ts +40 -0
- package/dist/cli/tui/runtime-settings.js +84 -0
- package/dist/cli/tui/slash-commands.js +10 -0
- package/dist/cli/tui/task-panel.d.ts +53 -0
- package/dist/cli/tui/task-panel.js +204 -0
- package/dist/cli/tui/terminal-notifications.d.ts +10 -0
- package/dist/cli/tui/terminal-notifications.js +48 -0
- package/dist/cli/tui/terminal-setup.d.ts +24 -0
- package/dist/cli/tui/terminal-setup.js +618 -0
- package/dist/cli/tui/theme-settings.d.ts +8 -0
- package/dist/cli/tui/theme-settings.js +132 -0
- package/dist/cli/tui/theme.d.ts +53 -0
- package/dist/cli/tui/theme.js +265 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +97 -17
- package/dist/compatibility/claude/schema.js +2 -0
- package/dist/core/runtime.d.ts +12 -0
- package/dist/core/runtime.js +24 -0
- package/dist/maintenance/self-update.js +1 -1
- package/dist/mcp/claude-mcp-management.d.ts +6 -1
- package/dist/mcp/claude-mcp-management.js +191 -44
- package/dist/mcp/claude-mcp-oauth.js +2 -0
- package/dist/mcp/claude-mcp-tools.d.ts +31 -3
- package/dist/mcp/claude-mcp-tools.js +116 -34
- package/dist/permissions/claude-permission-resolver.d.ts +3 -0
- package/dist/permissions/claude-permission-resolver.js +42 -20
- package/dist/platform/claude-version.js +2 -1
- package/dist/plugins/claude-plugin-runtime.js +11 -3
- package/dist/tools/claude-interactive-tools.d.ts +7 -0
- package/dist/tools/claude-interactive-tools.js +21 -8
- package/dist/tools/claude-task-tools.d.ts +4 -0
- package/dist/tools/claude-task-tools.js +7 -0
- package/package.json +2 -1
- package/dist/application/background-agent-manager.d.ts.map +0 -1
- package/dist/application/background-agent-manager.js.map +0 -1
- package/dist/application/background-bash-manager.d.ts.map +0 -1
- package/dist/application/background-bash-manager.js.map +0 -1
- package/dist/application/background-task-id.d.ts.map +0 -1
- package/dist/application/background-task-id.js.map +0 -1
- package/dist/application/claude-project-purge.d.ts.map +0 -1
- package/dist/application/claude-project-purge.js.map +0 -1
- package/dist/application/managed-worktree.d.ts.map +0 -1
- package/dist/application/managed-worktree.js.map +0 -1
- package/dist/application/model-compactor.d.ts.map +0 -1
- package/dist/application/model-compactor.js.map +0 -1
- package/dist/application/scheduled-prompt-manager.d.ts.map +0 -1
- package/dist/application/scheduled-prompt-manager.js.map +0 -1
- package/dist/application/session-service.d.ts.map +0 -1
- package/dist/application/session-service.js.map +0 -1
- package/dist/application/session-worktree.d.ts.map +0 -1
- package/dist/application/session-worktree.js.map +0 -1
- package/dist/application/subagent-service.d.ts.map +0 -1
- package/dist/application/subagent-service.js.map +0 -1
- package/dist/application/tool-use-summary.d.ts.map +0 -1
- package/dist/application/tool-use-summary.js.map +0 -1
- package/dist/application/top-level-agent-manager.d.ts.map +0 -1
- package/dist/application/top-level-agent-manager.js.map +0 -1
- package/dist/application/workflow-manager.d.ts.map +0 -1
- package/dist/application/workflow-manager.js.map +0 -1
- package/dist/application/workflow-meta.d.ts.map +0 -1
- package/dist/application/workflow-meta.js.map +0 -1
- package/dist/application/workflow-runtime.d.ts.map +0 -1
- package/dist/application/workflow-runtime.js.map +0 -1
- package/dist/application/workflow-worktree.d.ts.map +0 -1
- package/dist/application/workflow-worktree.js.map +0 -1
- package/dist/cli/agents-dashboard.d.ts.map +0 -1
- package/dist/cli/agents-dashboard.js.map +0 -1
- package/dist/cli/controls.d.ts.map +0 -1
- package/dist/cli/controls.js.map +0 -1
- package/dist/cli/debug.d.ts.map +0 -1
- package/dist/cli/debug.js.map +0 -1
- package/dist/cli/interactive.d.ts.map +0 -1
- package/dist/cli/interactive.js.map +0 -1
- package/dist/cli/protocol.d.ts.map +0 -1
- package/dist/cli/protocol.js.map +0 -1
- package/dist/cli/tui/claude-style.d.ts.map +0 -1
- package/dist/cli/tui/claude-style.js.map +0 -1
- package/dist/cli/tui/clipboard.d.ts.map +0 -1
- package/dist/cli/tui/clipboard.js.map +0 -1
- package/dist/cli/tui/composer-editor.d.ts.map +0 -1
- package/dist/cli/tui/composer-editor.js.map +0 -1
- package/dist/cli/tui/composer-images.d.ts.map +0 -1
- package/dist/cli/tui/composer-images.js.map +0 -1
- package/dist/cli/tui/conversation-export.d.ts.map +0 -1
- package/dist/cli/tui/conversation-export.js.map +0 -1
- package/dist/cli/tui/external-editor.d.ts.map +0 -1
- package/dist/cli/tui/external-editor.js.map +0 -1
- package/dist/cli/tui/file-picker.d.ts.map +0 -1
- package/dist/cli/tui/file-picker.js.map +0 -1
- package/dist/cli/tui/git-diff.d.ts.map +0 -1
- package/dist/cli/tui/git-diff.js.map +0 -1
- package/dist/cli/tui/hook-settings.d.ts.map +0 -1
- package/dist/cli/tui/hook-settings.js.map +0 -1
- package/dist/cli/tui/keybindings.d.ts.map +0 -1
- package/dist/cli/tui/keybindings.js.map +0 -1
- package/dist/cli/tui/memory-files.d.ts.map +0 -1
- package/dist/cli/tui/memory-files.js.map +0 -1
- package/dist/cli/tui/permission-settings.d.ts.map +0 -1
- package/dist/cli/tui/permission-settings.js.map +0 -1
- package/dist/cli/tui/slash-commands.d.ts.map +0 -1
- package/dist/cli/tui/slash-commands.js.map +0 -1
- package/dist/cli/tui/terminal-suspend.d.ts.map +0 -1
- package/dist/cli/tui/terminal-suspend.js.map +0 -1
- package/dist/cli/tui/workspace-directories.d.ts.map +0 -1
- package/dist/cli/tui/workspace-directories.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/compatibility/claude/compaction.d.ts.map +0 -1
- package/dist/compatibility/claude/compaction.js.map +0 -1
- package/dist/compatibility/claude/context.d.ts.map +0 -1
- package/dist/compatibility/claude/context.js.map +0 -1
- package/dist/compatibility/claude/dynamic-context.d.ts.map +0 -1
- package/dist/compatibility/claude/dynamic-context.js.map +0 -1
- package/dist/compatibility/claude/file-history.d.ts.map +0 -1
- package/dist/compatibility/claude/file-history.js.map +0 -1
- package/dist/compatibility/claude/file-resources.d.ts.map +0 -1
- package/dist/compatibility/claude/file-resources.js.map +0 -1
- package/dist/compatibility/claude/fork.d.ts.map +0 -1
- package/dist/compatibility/claude/fork.js.map +0 -1
- package/dist/compatibility/claude/history.d.ts.map +0 -1
- package/dist/compatibility/claude/history.js.map +0 -1
- package/dist/compatibility/claude/ownership.d.ts.map +0 -1
- package/dist/compatibility/claude/ownership.js.map +0 -1
- package/dist/compatibility/claude/paths.d.ts.map +0 -1
- package/dist/compatibility/claude/paths.js.map +0 -1
- package/dist/compatibility/claude/pr-links.d.ts.map +0 -1
- package/dist/compatibility/claude/pr-links.js.map +0 -1
- package/dist/compatibility/claude/projection.d.ts.map +0 -1
- package/dist/compatibility/claude/projection.js.map +0 -1
- package/dist/compatibility/claude/schema.d.ts.map +0 -1
- package/dist/compatibility/claude/schema.js.map +0 -1
- package/dist/compatibility/claude/shared-resources.d.ts.map +0 -1
- package/dist/compatibility/claude/shared-resources.js.map +0 -1
- package/dist/compatibility/claude/sidechain.d.ts.map +0 -1
- package/dist/compatibility/claude/sidechain.js.map +0 -1
- package/dist/compatibility/claude/tool-links.d.ts.map +0 -1
- package/dist/compatibility/claude/tool-links.js.map +0 -1
- package/dist/compatibility/claude/translation.d.ts.map +0 -1
- package/dist/compatibility/claude/translation.js.map +0 -1
- package/dist/compatibility/claude/workflow-replay.d.ts.map +0 -1
- package/dist/compatibility/claude/workflow-replay.js.map +0 -1
- package/dist/compatibility/claude/workflow.d.ts.map +0 -1
- package/dist/compatibility/claude/workflow.js.map +0 -1
- package/dist/core/compaction.d.ts.map +0 -1
- package/dist/core/compaction.js.map +0 -1
- package/dist/core/context-budget.d.ts.map +0 -1
- package/dist/core/context-budget.js.map +0 -1
- package/dist/core/context.d.ts.map +0 -1
- package/dist/core/context.js.map +0 -1
- package/dist/core/runtime.d.ts.map +0 -1
- package/dist/core/runtime.js.map +0 -1
- package/dist/core/usage.d.ts.map +0 -1
- package/dist/core/usage.js.map +0 -1
- package/dist/extensions/claude-extension-tools.d.ts.map +0 -1
- package/dist/extensions/claude-extension-tools.js.map +0 -1
- package/dist/extensions/claude-extensions.d.ts.map +0 -1
- package/dist/extensions/claude-extensions.js.map +0 -1
- package/dist/hooks/claude-hook-tools.d.ts.map +0 -1
- package/dist/hooks/claude-hook-tools.js.map +0 -1
- package/dist/hooks/claude-hooks.d.ts.map +0 -1
- package/dist/hooks/claude-hooks.js.map +0 -1
- package/dist/maintenance/doctor.d.ts.map +0 -1
- package/dist/maintenance/doctor.js.map +0 -1
- package/dist/maintenance/self-update.d.ts.map +0 -1
- package/dist/maintenance/self-update.js.map +0 -1
- package/dist/mcp/claude-mcp-management.d.ts.map +0 -1
- package/dist/mcp/claude-mcp-management.js.map +0 -1
- package/dist/mcp/claude-mcp-oauth.d.ts.map +0 -1
- package/dist/mcp/claude-mcp-oauth.js.map +0 -1
- package/dist/mcp/claude-mcp-tools.d.ts.map +0 -1
- package/dist/mcp/claude-mcp-tools.js.map +0 -1
- package/dist/mcp/praxis-mcp-server.d.ts.map +0 -1
- package/dist/mcp/praxis-mcp-server.js.map +0 -1
- package/dist/permissions/claude-auto-classifier.d.ts.map +0 -1
- package/dist/permissions/claude-auto-classifier.js.map +0 -1
- package/dist/permissions/claude-permission-resolver.d.ts.map +0 -1
- package/dist/permissions/claude-permission-resolver.js.map +0 -1
- package/dist/persistence/claude-job-store.d.ts.map +0 -1
- package/dist/persistence/claude-job-store.js.map +0 -1
- package/dist/persistence/claude-scheduled-task-store.d.ts.map +0 -1
- package/dist/persistence/claude-scheduled-task-store.js.map +0 -1
- package/dist/persistence/claude-sidechain-store.d.ts.map +0 -1
- package/dist/persistence/claude-sidechain-store.js.map +0 -1
- package/dist/persistence/claude-task-store.d.ts.map +0 -1
- package/dist/persistence/claude-task-store.js.map +0 -1
- package/dist/persistence/claude-transcript-store.d.ts.map +0 -1
- package/dist/persistence/claude-transcript-store.js.map +0 -1
- package/dist/persistence/claude-workflow-store.d.ts.map +0 -1
- package/dist/persistence/claude-workflow-store.js.map +0 -1
- package/dist/persistence/in-memory-sidechain-store.d.ts.map +0 -1
- package/dist/persistence/in-memory-sidechain-store.js.map +0 -1
- package/dist/persistence/in-memory-transcript-store.d.ts.map +0 -1
- package/dist/persistence/in-memory-transcript-store.js.map +0 -1
- package/dist/platform/atomic-write.d.ts.map +0 -1
- package/dist/platform/atomic-write.js.map +0 -1
- package/dist/platform/bounded-process-runner.d.ts.map +0 -1
- package/dist/platform/bounded-process-runner.js.map +0 -1
- package/dist/platform/claude-version.d.ts.map +0 -1
- package/dist/platform/claude-version.js.map +0 -1
- package/dist/platform/command-shell.d.ts.map +0 -1
- package/dist/platform/command-shell.js.map +0 -1
- package/dist/platform/exclusive-file-lease.d.ts.map +0 -1
- package/dist/platform/exclusive-file-lease.js.map +0 -1
- package/dist/platform/sensitive-data.d.ts.map +0 -1
- package/dist/platform/sensitive-data.js.map +0 -1
- package/dist/platform/tmux-worktree.d.ts.map +0 -1
- package/dist/platform/tmux-worktree.js.map +0 -1
- package/dist/plugins/claude-plugin-eval-discovery.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-eval-discovery.js.map +0 -1
- package/dist/plugins/claude-plugin-eval-graders.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-eval-graders.js.map +0 -1
- package/dist/plugins/claude-plugin-eval-runner.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-eval-runner.js.map +0 -1
- package/dist/plugins/claude-plugin-eval-schema.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-eval-schema.js.map +0 -1
- package/dist/plugins/claude-plugin-eval.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-eval.js.map +0 -1
- package/dist/plugins/claude-plugin-maintenance.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-maintenance.js.map +0 -1
- package/dist/plugins/claude-plugin-marketplace.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-marketplace.js.map +0 -1
- package/dist/plugins/claude-plugin-mcpb.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-mcpb.js.map +0 -1
- package/dist/plugins/claude-plugin-runtime.d.ts.map +0 -1
- package/dist/plugins/claude-plugin-runtime.js.map +0 -1
- package/dist/providers/anthropic-compatible.d.ts.map +0 -1
- package/dist/providers/anthropic-compatible.js.map +0 -1
- package/dist/providers/environment.d.ts.map +0 -1
- package/dist/providers/environment.js.map +0 -1
- package/dist/providers/fallback-provider.d.ts.map +0 -1
- package/dist/providers/fallback-provider.js.map +0 -1
- package/dist/providers/openai-compatible.d.ts.map +0 -1
- package/dist/providers/openai-compatible.js.map +0 -1
- package/dist/tools/claude-interactive-tools.d.ts.map +0 -1
- package/dist/tools/claude-interactive-tools.js.map +0 -1
- package/dist/tools/claude-lsp-formatters.d.ts.map +0 -1
- package/dist/tools/claude-lsp-formatters.js.map +0 -1
- package/dist/tools/claude-lsp-tool.d.ts.map +0 -1
- package/dist/tools/claude-lsp-tool.js.map +0 -1
- package/dist/tools/claude-scheduled-tools.d.ts.map +0 -1
- package/dist/tools/claude-scheduled-tools.js.map +0 -1
- package/dist/tools/claude-task-tools.d.ts.map +0 -1
- package/dist/tools/claude-task-tools.js.map +0 -1
- package/dist/tools/claude-user-message.d.ts.map +0 -1
- package/dist/tools/claude-user-message.js.map +0 -1
- package/dist/tools/claude-workflow-tools.d.ts.map +0 -1
- package/dist/tools/claude-workflow-tools.js.map +0 -1
- package/dist/tools/claude-worktree-tools.d.ts.map +0 -1
- package/dist/tools/claude-worktree-tools.js.map +0 -1
- package/dist/tools/filtered-tool-registry.d.ts.map +0 -1
- package/dist/tools/filtered-tool-registry.js.map +0 -1
- package/dist/tools/glob.d.ts.map +0 -1
- package/dist/tools/glob.js.map +0 -1
- package/dist/tools/local-tools.d.ts.map +0 -1
- package/dist/tools/local-tools.js.map +0 -1
- package/dist/tools/notebook.d.ts.map +0 -1
- package/dist/tools/notebook.js.map +0 -1
- package/dist/tools/pdf.d.ts.map +0 -1
- package/dist/tools/pdf.js.map +0 -1
- package/dist/tools/web.d.ts.map +0 -1
- package/dist/tools/web.js.map +0 -1
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { copyFile, lstat, mkdir, readFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir, platform as hostPlatform } from 'node:os';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
import { writeFileAtomically } from '../../platform/atomic-write.js';
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const NATIVE_SHIFT_ENTER_TERMINALS = {
|
|
10
|
+
ghostty: 'Ghostty',
|
|
11
|
+
kitty: 'Kitty',
|
|
12
|
+
'iTerm.app': 'iTerm2',
|
|
13
|
+
WezTerm: 'WezTerm',
|
|
14
|
+
WarpTerminal: 'Warp',
|
|
15
|
+
};
|
|
16
|
+
const SETUP_SUPPORTED_TERMINALS = new Set([
|
|
17
|
+
'vscode',
|
|
18
|
+
'cursor',
|
|
19
|
+
'windsurf',
|
|
20
|
+
'alacritty',
|
|
21
|
+
'zed',
|
|
22
|
+
]);
|
|
23
|
+
const MAX_CONFIGURATION_BYTES = 4 * 1024 * 1024;
|
|
24
|
+
function ownHome(options) {
|
|
25
|
+
return options.homeDirectory ?? homedir();
|
|
26
|
+
}
|
|
27
|
+
function ownEnvironment(options) {
|
|
28
|
+
return options.environment ?? process.env;
|
|
29
|
+
}
|
|
30
|
+
function ownPlatform(options) {
|
|
31
|
+
return options.platform ?? hostPlatform();
|
|
32
|
+
}
|
|
33
|
+
async function runTerminalCommand(command, arguments_) {
|
|
34
|
+
try {
|
|
35
|
+
const result = await execFileAsync(command, [...arguments_], {
|
|
36
|
+
encoding: 'utf8',
|
|
37
|
+
maxBuffer: MAX_CONFIGURATION_BYTES,
|
|
38
|
+
});
|
|
39
|
+
return { exitCode: 0, stdout: result.stdout, stderr: result.stderr };
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const failure = error;
|
|
43
|
+
return {
|
|
44
|
+
exitCode: typeof failure.code === 'number' ? failure.code : 1,
|
|
45
|
+
stdout: failure.stdout ?? '',
|
|
46
|
+
stderr: failure.stderr ?? failure.message,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function detectTuiTerminal(environment = process.env) {
|
|
51
|
+
if (environment.CURSOR_TRACE_ID)
|
|
52
|
+
return 'cursor';
|
|
53
|
+
const askpass = environment.VSCODE_GIT_ASKPASS_MAIN ?? '';
|
|
54
|
+
if (askpass.includes('cursor'))
|
|
55
|
+
return 'cursor';
|
|
56
|
+
if (askpass.includes('windsurf'))
|
|
57
|
+
return 'windsurf';
|
|
58
|
+
const bundleId = environment.__CFBundleIdentifier?.toLowerCase();
|
|
59
|
+
if (bundleId?.includes('windsurf'))
|
|
60
|
+
return 'windsurf';
|
|
61
|
+
if (bundleId?.includes('vscode') || bundleId?.includes('vscodium'))
|
|
62
|
+
return 'vscode';
|
|
63
|
+
if (environment.TERM === 'xterm-ghostty')
|
|
64
|
+
return 'ghostty';
|
|
65
|
+
if (environment.TERM?.includes('kitty'))
|
|
66
|
+
return 'kitty';
|
|
67
|
+
if (environment.TERM_PROGRAM)
|
|
68
|
+
return environment.TERM_PROGRAM;
|
|
69
|
+
if (environment.TMUX)
|
|
70
|
+
return 'tmux';
|
|
71
|
+
if (environment.STY)
|
|
72
|
+
return 'screen';
|
|
73
|
+
if (environment.KITTY_WINDOW_ID)
|
|
74
|
+
return 'kitty';
|
|
75
|
+
if (environment.ALACRITTY_LOG || environment.TERM?.includes('alacritty'))
|
|
76
|
+
return 'alacritty';
|
|
77
|
+
if (environment.ZED_TERM)
|
|
78
|
+
return 'zed';
|
|
79
|
+
if (environment.TERM)
|
|
80
|
+
return environment.TERM;
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
export function nativeShiftEnterTerminalName(terminal) {
|
|
84
|
+
if (!terminal)
|
|
85
|
+
return null;
|
|
86
|
+
return NATIVE_SHIFT_ENTER_TERMINALS[terminal] ?? null;
|
|
87
|
+
}
|
|
88
|
+
export function terminalSetupTuiSlashCommand(environment = process.env, platform = hostPlatform()) {
|
|
89
|
+
const terminal = detectTuiTerminal(environment);
|
|
90
|
+
if (nativeShiftEnterTerminalName(terminal))
|
|
91
|
+
return null;
|
|
92
|
+
return {
|
|
93
|
+
name: 'terminal-setup',
|
|
94
|
+
description: terminal === 'Apple_Terminal' && platform === 'darwin'
|
|
95
|
+
? 'Enable Option+Enter key binding for newlines and visual bell'
|
|
96
|
+
: 'Install Shift+Enter key binding for newlines',
|
|
97
|
+
source: 'builtin',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function isVsCodeRemoteSsh(environment) {
|
|
101
|
+
const askpass = environment.VSCODE_GIT_ASKPASS_MAIN ?? '';
|
|
102
|
+
const path = environment.PATH ?? '';
|
|
103
|
+
return (askpass.includes('.vscode-server') ||
|
|
104
|
+
askpass.includes('.cursor-server') ||
|
|
105
|
+
askpass.includes('.windsurf-server') ||
|
|
106
|
+
path.includes('.vscode-server') ||
|
|
107
|
+
path.includes('.cursor-server') ||
|
|
108
|
+
path.includes('.windsurf-server'));
|
|
109
|
+
}
|
|
110
|
+
function terminalUnavailableMessage(terminal, platform) {
|
|
111
|
+
const platformTerminals = platform === 'darwin'
|
|
112
|
+
? ' • macOS: Apple Terminal\n'
|
|
113
|
+
: platform === 'win32'
|
|
114
|
+
? ' • Windows: Windows Terminal\n'
|
|
115
|
+
: '';
|
|
116
|
+
return `Terminal setup cannot be run from ${terminal ?? 'your current terminal'}.
|
|
117
|
+
|
|
118
|
+
This command configures a convenient Shift+Enter shortcut for multi-line prompts.
|
|
119
|
+
Note: You can already use backslash (\\) + return to add newlines.
|
|
120
|
+
|
|
121
|
+
To set up the shortcut (optional):
|
|
122
|
+
1. Exit tmux/screen temporarily
|
|
123
|
+
2. Run /terminal-setup directly in one of these terminals:
|
|
124
|
+
${platformTerminals} • IDE: VSCode, Cursor, Windsurf, Zed
|
|
125
|
+
• Other: Alacritty
|
|
126
|
+
3. Return to tmux/screen - settings will persist
|
|
127
|
+
|
|
128
|
+
Note: iTerm2, WezTerm, Ghostty, Kitty, and Warp support Shift+Enter natively.`;
|
|
129
|
+
}
|
|
130
|
+
function manualVsCodeRemoteMessage(editor) {
|
|
131
|
+
return `Cannot install keybindings from a remote ${editor} session.
|
|
132
|
+
|
|
133
|
+
${editor} keybindings must be installed on your local machine, not the remote server.
|
|
134
|
+
|
|
135
|
+
To install the Shift+Enter keybinding:
|
|
136
|
+
1. Open ${editor} on your local machine (not connected to remote)
|
|
137
|
+
2. Open the Command Palette (Cmd/Ctrl+Shift+P) → "Preferences: Open Keyboard Shortcuts (JSON)"
|
|
138
|
+
3. Add this keybinding (the file must be a JSON array):
|
|
139
|
+
|
|
140
|
+
[
|
|
141
|
+
{
|
|
142
|
+
"key": "shift+enter",
|
|
143
|
+
"command": "workbench.action.terminal.sendSequence",
|
|
144
|
+
"args": { "text": "\\u001b\\r" },
|
|
145
|
+
"when": "terminalFocus"
|
|
146
|
+
}
|
|
147
|
+
]`;
|
|
148
|
+
}
|
|
149
|
+
async function regularFileOrMissing(path) {
|
|
150
|
+
try {
|
|
151
|
+
const status = await lstat(path);
|
|
152
|
+
if (status.isSymbolicLink() || !status.isFile())
|
|
153
|
+
throw new Error(`Configuration path must be a regular file: ${path}`);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (error.code === 'ENOENT')
|
|
158
|
+
return false;
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function readConfiguration(path) {
|
|
163
|
+
const exists = await regularFileOrMissing(path);
|
|
164
|
+
if (!exists)
|
|
165
|
+
return { source: '', exists: false };
|
|
166
|
+
const source = await readFile(path, 'utf8');
|
|
167
|
+
if (Buffer.byteLength(source) > MAX_CONFIGURATION_BYTES)
|
|
168
|
+
throw new Error(`Configuration file is too large: ${path}`);
|
|
169
|
+
return { source, exists: true };
|
|
170
|
+
}
|
|
171
|
+
function withoutJsoncComments(source) {
|
|
172
|
+
let result = '';
|
|
173
|
+
let quote = null;
|
|
174
|
+
let escaped = false;
|
|
175
|
+
let lineComment = false;
|
|
176
|
+
let blockComment = false;
|
|
177
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
178
|
+
const character = source[index] ?? '';
|
|
179
|
+
const next = source[index + 1] ?? '';
|
|
180
|
+
if (lineComment) {
|
|
181
|
+
if (character === '\n' || character === '\r') {
|
|
182
|
+
lineComment = false;
|
|
183
|
+
result += character;
|
|
184
|
+
}
|
|
185
|
+
else
|
|
186
|
+
result += ' ';
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (blockComment) {
|
|
190
|
+
if (character === '*' && next === '/') {
|
|
191
|
+
blockComment = false;
|
|
192
|
+
result += ' ';
|
|
193
|
+
index += 1;
|
|
194
|
+
}
|
|
195
|
+
else
|
|
196
|
+
result += character === '\n' || character === '\r' ? character : ' ';
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (quote) {
|
|
200
|
+
result += character;
|
|
201
|
+
if (escaped)
|
|
202
|
+
escaped = false;
|
|
203
|
+
else if (character === '\\')
|
|
204
|
+
escaped = true;
|
|
205
|
+
else if (character === quote)
|
|
206
|
+
quote = null;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (character === '"' || character === "'") {
|
|
210
|
+
quote = character;
|
|
211
|
+
result += character;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (character === '/' && next === '/') {
|
|
215
|
+
lineComment = true;
|
|
216
|
+
result += ' ';
|
|
217
|
+
index += 1;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (character === '/' && next === '*') {
|
|
221
|
+
blockComment = true;
|
|
222
|
+
result += ' ';
|
|
223
|
+
index += 1;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
result += character;
|
|
227
|
+
}
|
|
228
|
+
if (quote || blockComment)
|
|
229
|
+
throw new Error('Invalid JSONC configuration');
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
function parseJsoncArray(source, path) {
|
|
233
|
+
const normalized = withoutJsoncComments(source).replace(/,(\s*[}\]])/gu, '$1');
|
|
234
|
+
let value;
|
|
235
|
+
try {
|
|
236
|
+
value = JSON.parse(normalized);
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
throw new Error(`Invalid JSONC configuration: ${path}`, { cause: error });
|
|
240
|
+
}
|
|
241
|
+
if (!Array.isArray(value))
|
|
242
|
+
throw new Error(`Configuration must be a JSON array: ${path}`);
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
function findJsonArrayClosingBracket(source) {
|
|
246
|
+
const normalized = withoutJsoncComments(source);
|
|
247
|
+
const start = normalized.search(/\S/u);
|
|
248
|
+
if (start < 0 || normalized[start] !== '[')
|
|
249
|
+
throw new Error('Configuration must be a JSON array');
|
|
250
|
+
let quote = null;
|
|
251
|
+
let escaped = false;
|
|
252
|
+
let depth = 0;
|
|
253
|
+
for (let index = start; index < normalized.length; index += 1) {
|
|
254
|
+
const character = normalized[index] ?? '';
|
|
255
|
+
if (quote) {
|
|
256
|
+
if (escaped)
|
|
257
|
+
escaped = false;
|
|
258
|
+
else if (character === '\\')
|
|
259
|
+
escaped = true;
|
|
260
|
+
else if (character === quote)
|
|
261
|
+
quote = null;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (character === '"' || character === "'") {
|
|
265
|
+
quote = character;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (character === '[')
|
|
269
|
+
depth += 1;
|
|
270
|
+
if (character !== ']')
|
|
271
|
+
continue;
|
|
272
|
+
depth -= 1;
|
|
273
|
+
if (depth === 0)
|
|
274
|
+
return index;
|
|
275
|
+
}
|
|
276
|
+
throw new Error('Configuration must be a complete JSON array');
|
|
277
|
+
}
|
|
278
|
+
function appendJsoncArrayItem(source, value) {
|
|
279
|
+
const closing = findJsonArrayClosingBracket(source);
|
|
280
|
+
const before = source.slice(0, closing);
|
|
281
|
+
const after = source.slice(closing);
|
|
282
|
+
const normalizedBefore = withoutJsoncComments(before);
|
|
283
|
+
const opening = normalizedBefore.indexOf('[');
|
|
284
|
+
const hasExistingItem = opening >= 0 &&
|
|
285
|
+
normalizedBefore.slice(opening + 1).replace(/[\s,]/gu, '').length > 0;
|
|
286
|
+
const item = JSON.stringify(value, null, 2)
|
|
287
|
+
.split('\n')
|
|
288
|
+
.map((line) => ` ${line}`)
|
|
289
|
+
.join('\n');
|
|
290
|
+
const separator = hasExistingItem && !normalizedBefore.trimEnd().endsWith(',') ? ',' : '';
|
|
291
|
+
return `${before}${separator}\n${item}\n${after}`;
|
|
292
|
+
}
|
|
293
|
+
async function backupBeforeMutation(path) {
|
|
294
|
+
const exists = await regularFileOrMissing(path);
|
|
295
|
+
if (!exists)
|
|
296
|
+
return null;
|
|
297
|
+
const backupPath = `${path}.${randomUUID().slice(0, 8)}.bak`;
|
|
298
|
+
await copyFile(path, backupPath);
|
|
299
|
+
return backupPath;
|
|
300
|
+
}
|
|
301
|
+
async function writeConfiguration(path, source) {
|
|
302
|
+
await mkdir(dirname(path), { recursive: true });
|
|
303
|
+
const committed = await writeFileAtomically(path, source, { mode: 0o600 });
|
|
304
|
+
if (!committed)
|
|
305
|
+
throw new Error(`Configuration changed concurrently: ${path}`);
|
|
306
|
+
}
|
|
307
|
+
function hasVsCodeShiftEnterBinding(bindings) {
|
|
308
|
+
return bindings.some((entry) => typeof entry === 'object' &&
|
|
309
|
+
entry !== null &&
|
|
310
|
+
entry.key === 'shift+enter' &&
|
|
311
|
+
entry.command ===
|
|
312
|
+
'workbench.action.terminal.sendSequence' &&
|
|
313
|
+
entry.when === 'terminalFocus');
|
|
314
|
+
}
|
|
315
|
+
function vsCodeKeybindingsPath(editor, options) {
|
|
316
|
+
const platform = ownPlatform(options);
|
|
317
|
+
const home = ownHome(options);
|
|
318
|
+
const directory = editor === 'VSCode' ? 'Code' : editor;
|
|
319
|
+
const userDirectory = platform === 'win32'
|
|
320
|
+
? join(ownEnvironment(options).APPDATA ?? join(home, 'AppData', 'Roaming'), directory, 'User')
|
|
321
|
+
: platform === 'darwin'
|
|
322
|
+
? join(home, 'Library', 'Application Support', directory, 'User')
|
|
323
|
+
: join(home, '.config', directory, 'User');
|
|
324
|
+
return join(userDirectory, 'keybindings.json');
|
|
325
|
+
}
|
|
326
|
+
async function installVsCodeBinding(editor, options) {
|
|
327
|
+
const environment = ownEnvironment(options);
|
|
328
|
+
if (isVsCodeRemoteSsh(environment))
|
|
329
|
+
return manualVsCodeRemoteMessage(editor);
|
|
330
|
+
const path = vsCodeKeybindingsPath(editor, options);
|
|
331
|
+
try {
|
|
332
|
+
const current = await readConfiguration(path);
|
|
333
|
+
const bindings = current.exists ? parseJsoncArray(current.source, path) : [];
|
|
334
|
+
if (hasVsCodeShiftEnterBinding(bindings))
|
|
335
|
+
return `Found existing ${editor} terminal Shift+Enter key binding. Remove it to continue.\nSee ${path}`;
|
|
336
|
+
await backupBeforeMutation(path);
|
|
337
|
+
const binding = {
|
|
338
|
+
key: 'shift+enter',
|
|
339
|
+
command: 'workbench.action.terminal.sendSequence',
|
|
340
|
+
args: { text: '\u001b\r' },
|
|
341
|
+
when: 'terminalFocus',
|
|
342
|
+
};
|
|
343
|
+
const next = current.exists
|
|
344
|
+
? appendJsoncArrayItem(current.source, binding)
|
|
345
|
+
: `${JSON.stringify([binding], null, 2)}\n`;
|
|
346
|
+
await writeConfiguration(path, next);
|
|
347
|
+
return `Installed ${editor} terminal Shift+Enter key binding\nSee ${path}`;
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
return `Failed to install ${editor} terminal Shift+Enter key binding: ${error instanceof Error ? error.message : String(error)}`;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function alacrittyConfigurationPaths(options) {
|
|
354
|
+
const environment = ownEnvironment(options);
|
|
355
|
+
const home = ownHome(options);
|
|
356
|
+
const paths = [
|
|
357
|
+
join(environment.XDG_CONFIG_HOME ?? join(home, '.config'), 'alacritty', 'alacritty.toml'),
|
|
358
|
+
];
|
|
359
|
+
if (ownPlatform(options) === 'win32' && environment.APPDATA)
|
|
360
|
+
paths.push(join(environment.APPDATA, 'alacritty', 'alacritty.toml'));
|
|
361
|
+
return paths;
|
|
362
|
+
}
|
|
363
|
+
function hasAlacrittyShiftEnterBinding(source) {
|
|
364
|
+
return source
|
|
365
|
+
.split(/^\s*\[\[keyboard\.bindings\]\]\s*$/mu)
|
|
366
|
+
.slice(1)
|
|
367
|
+
.some((block) => /^\s*key\s*=\s*["']Return["']/mu.test(block) &&
|
|
368
|
+
/^\s*mods\s*=\s*["']Shift["']/mu.test(block));
|
|
369
|
+
}
|
|
370
|
+
async function alacrittyConfigPath(options) {
|
|
371
|
+
const paths = alacrittyConfigurationPaths(options);
|
|
372
|
+
for (const path of paths) {
|
|
373
|
+
const current = await readConfiguration(path);
|
|
374
|
+
if (current.exists)
|
|
375
|
+
return { path, ...current };
|
|
376
|
+
}
|
|
377
|
+
const path = paths[0];
|
|
378
|
+
if (!path)
|
|
379
|
+
throw new Error('No valid Alacritty configuration path');
|
|
380
|
+
return { path, source: '', exists: false };
|
|
381
|
+
}
|
|
382
|
+
async function installAlacrittyBinding(options) {
|
|
383
|
+
try {
|
|
384
|
+
const current = await alacrittyConfigPath(options);
|
|
385
|
+
if (current.exists && hasAlacrittyShiftEnterBinding(current.source))
|
|
386
|
+
return `Found existing Alacritty Shift+Enter key binding. Remove it to continue.\nSee ${current.path}`;
|
|
387
|
+
await backupBeforeMutation(current.path);
|
|
388
|
+
const prefix = current.source.length === 0 || current.source.endsWith('\n')
|
|
389
|
+
? current.source
|
|
390
|
+
: `${current.source}\n`;
|
|
391
|
+
await writeConfiguration(current.path, `${prefix}\n[[keyboard.bindings]]\nkey = "Return"\nmods = "Shift"\nchars = "\\u001B\\r"\n`);
|
|
392
|
+
return `Installed Alacritty Shift+Enter key binding\nYou may need to restart Alacritty for changes to take effect\nSee ${current.path}`;
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
return `Failed to install Alacritty Shift+Enter key binding: ${error instanceof Error ? error.message : String(error)}`;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function zedKeymapPath(options) {
|
|
399
|
+
return join(ownHome(options), '.config', 'zed', 'keymap.json');
|
|
400
|
+
}
|
|
401
|
+
function hasZedShiftEnterBinding(keymap) {
|
|
402
|
+
return keymap.some((entry) => {
|
|
403
|
+
if (typeof entry !== 'object' || entry === null)
|
|
404
|
+
return false;
|
|
405
|
+
const record = entry;
|
|
406
|
+
if (typeof record.bindings !== 'object' || record.bindings === null)
|
|
407
|
+
return false;
|
|
408
|
+
return Object.hasOwn(record.bindings, 'shift-enter');
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
async function installZedBinding(options) {
|
|
412
|
+
const path = zedKeymapPath(options);
|
|
413
|
+
try {
|
|
414
|
+
const current = await readConfiguration(path);
|
|
415
|
+
const keymap = current.exists ? parseJsoncArray(current.source, path) : [];
|
|
416
|
+
if (hasZedShiftEnterBinding(keymap))
|
|
417
|
+
return `Found existing Zed Shift+Enter key binding. Remove it to continue.\nSee ${path}`;
|
|
418
|
+
await backupBeforeMutation(path);
|
|
419
|
+
const next = [
|
|
420
|
+
...keymap,
|
|
421
|
+
{
|
|
422
|
+
context: 'Terminal',
|
|
423
|
+
bindings: { 'shift-enter': ['terminal::SendText', '\u001b\r'] },
|
|
424
|
+
},
|
|
425
|
+
];
|
|
426
|
+
await writeConfiguration(path, `${JSON.stringify(next, null, 2)}\n`);
|
|
427
|
+
return `Installed Zed Shift+Enter key binding\nSee ${path}`;
|
|
428
|
+
}
|
|
429
|
+
catch (error) {
|
|
430
|
+
return `Failed to install Zed Shift+Enter key binding: ${error instanceof Error ? error.message : String(error)}`;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function appleTerminalPlistPath(options) {
|
|
434
|
+
return join(ownHome(options), 'Library', 'Preferences', 'com.apple.Terminal.plist');
|
|
435
|
+
}
|
|
436
|
+
function appleTerminalStatePath(options) {
|
|
437
|
+
const root = ownEnvironment(options).CLAUDE_CONFIG_DIR ??
|
|
438
|
+
join(ownHome(options), '.claude');
|
|
439
|
+
return join(root, 'praxis', 'terminal-setup.json');
|
|
440
|
+
}
|
|
441
|
+
function validAppleTerminalProfile(profile) {
|
|
442
|
+
return /^[A-Za-z0-9 _.-]{1,128}$/u.test(profile);
|
|
443
|
+
}
|
|
444
|
+
async function writeAppleTerminalState(options, state) {
|
|
445
|
+
const path = appleTerminalStatePath(options);
|
|
446
|
+
await mkdir(dirname(path), { recursive: true });
|
|
447
|
+
const committed = await writeFileAtomically(path, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
448
|
+
if (!committed)
|
|
449
|
+
throw new Error(`Terminal setup state changed concurrently: ${path}`);
|
|
450
|
+
}
|
|
451
|
+
async function readAppleTerminalState(options) {
|
|
452
|
+
const path = appleTerminalStatePath(options);
|
|
453
|
+
const source = await readConfiguration(path);
|
|
454
|
+
if (!source.exists)
|
|
455
|
+
return null;
|
|
456
|
+
let value;
|
|
457
|
+
try {
|
|
458
|
+
value = JSON.parse(source.source);
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
if (typeof value !== 'object' || value === null)
|
|
464
|
+
return null;
|
|
465
|
+
const record = value;
|
|
466
|
+
if ((record.status !== 'in-progress' && record.status !== 'complete') ||
|
|
467
|
+
typeof record.backupPath !== 'string' ||
|
|
468
|
+
!record.backupPath.startsWith(`${dirname(appleTerminalPlistPath(options))}/`))
|
|
469
|
+
return null;
|
|
470
|
+
return { status: record.status, backupPath: record.backupPath };
|
|
471
|
+
}
|
|
472
|
+
async function restoreAppleTerminalBackup(backupPath, run) {
|
|
473
|
+
const restored = await run('defaults', [
|
|
474
|
+
'import',
|
|
475
|
+
'com.apple.Terminal',
|
|
476
|
+
backupPath,
|
|
477
|
+
]);
|
|
478
|
+
if (restored.exitCode !== 0)
|
|
479
|
+
return false;
|
|
480
|
+
await run('killall', ['cfprefsd']);
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
async function recoverIncompleteAppleTerminalSetup(options, run) {
|
|
484
|
+
const state = await readAppleTerminalState(options);
|
|
485
|
+
if (!state || state.status !== 'in-progress')
|
|
486
|
+
return null;
|
|
487
|
+
if (!(await regularFileOrMissing(state.backupPath)))
|
|
488
|
+
return null;
|
|
489
|
+
if (await restoreAppleTerminalBackup(state.backupPath, run)) {
|
|
490
|
+
await writeAppleTerminalState(options, {
|
|
491
|
+
status: 'complete',
|
|
492
|
+
backupPath: state.backupPath,
|
|
493
|
+
});
|
|
494
|
+
return 'Recovered incomplete Terminal.app setup from its backup. Run /terminal-setup again to apply it.';
|
|
495
|
+
}
|
|
496
|
+
return `A previous Terminal.app setup may be incomplete. Restore it manually with: defaults import com.apple.Terminal ${state.backupPath}`;
|
|
497
|
+
}
|
|
498
|
+
async function updateAppleTerminalProfile(profile, plistPath, run) {
|
|
499
|
+
if (!validAppleTerminalProfile(profile))
|
|
500
|
+
throw new Error('Terminal.app returned an unsafe profile name');
|
|
501
|
+
const path = `:'Window Settings':'${profile}'`;
|
|
502
|
+
const option = await run('/usr/libexec/PlistBuddy', [
|
|
503
|
+
'-c',
|
|
504
|
+
`Add ${path}:useOptionAsMetaKey bool true`,
|
|
505
|
+
plistPath,
|
|
506
|
+
]);
|
|
507
|
+
const optionEnabled = option.exitCode === 0 ||
|
|
508
|
+
(await run('/usr/libexec/PlistBuddy', [
|
|
509
|
+
'-c',
|
|
510
|
+
`Set ${path}:useOptionAsMetaKey true`,
|
|
511
|
+
plistPath,
|
|
512
|
+
])).exitCode === 0;
|
|
513
|
+
const bell = await run('/usr/libexec/PlistBuddy', [
|
|
514
|
+
'-c',
|
|
515
|
+
`Add ${path}:Bell bool false`,
|
|
516
|
+
plistPath,
|
|
517
|
+
]);
|
|
518
|
+
const bellDisabled = bell.exitCode === 0 ||
|
|
519
|
+
(await run('/usr/libexec/PlistBuddy', [
|
|
520
|
+
'-c',
|
|
521
|
+
`Set ${path}:Bell false`,
|
|
522
|
+
plistPath,
|
|
523
|
+
])).exitCode === 0;
|
|
524
|
+
return optionEnabled || bellDisabled;
|
|
525
|
+
}
|
|
526
|
+
async function installAppleTerminalSetup(options) {
|
|
527
|
+
const run = options.run ?? runTerminalCommand;
|
|
528
|
+
const recovery = await recoverIncompleteAppleTerminalSetup(options, run);
|
|
529
|
+
if (recovery)
|
|
530
|
+
return recovery;
|
|
531
|
+
const plistPath = appleTerminalPlistPath(options);
|
|
532
|
+
const backupPath = `${plistPath}.bak`;
|
|
533
|
+
try {
|
|
534
|
+
await mkdir(dirname(plistPath), { recursive: true });
|
|
535
|
+
const exported = await run('defaults', [
|
|
536
|
+
'export',
|
|
537
|
+
'com.apple.Terminal',
|
|
538
|
+
backupPath,
|
|
539
|
+
]);
|
|
540
|
+
if (exported.exitCode !== 0 || !(await regularFileOrMissing(backupPath)))
|
|
541
|
+
throw new Error('Failed to create a Terminal.app preferences backup');
|
|
542
|
+
await writeAppleTerminalState(options, {
|
|
543
|
+
status: 'in-progress',
|
|
544
|
+
backupPath,
|
|
545
|
+
});
|
|
546
|
+
const [defaultProfile, startupProfile] = await Promise.all([
|
|
547
|
+
run('defaults', [
|
|
548
|
+
'read',
|
|
549
|
+
'com.apple.Terminal',
|
|
550
|
+
'Default Window Settings',
|
|
551
|
+
]),
|
|
552
|
+
run('defaults', [
|
|
553
|
+
'read',
|
|
554
|
+
'com.apple.Terminal',
|
|
555
|
+
'Startup Window Settings',
|
|
556
|
+
]),
|
|
557
|
+
]);
|
|
558
|
+
if (defaultProfile.exitCode !== 0 || !defaultProfile.stdout.trim())
|
|
559
|
+
throw new Error('Failed to read the default Terminal.app profile');
|
|
560
|
+
if (startupProfile.exitCode !== 0 || !startupProfile.stdout.trim())
|
|
561
|
+
throw new Error('Failed to read the startup Terminal.app profile');
|
|
562
|
+
const profiles = [
|
|
563
|
+
...new Set([defaultProfile.stdout.trim(), startupProfile.stdout.trim()]),
|
|
564
|
+
];
|
|
565
|
+
const changed = [];
|
|
566
|
+
for (const profile of profiles) {
|
|
567
|
+
changed.push(await updateAppleTerminalProfile(profile, plistPath, run));
|
|
568
|
+
}
|
|
569
|
+
if (!changed.some(Boolean))
|
|
570
|
+
throw new Error('Failed to update Terminal.app profile settings');
|
|
571
|
+
await run('killall', ['cfprefsd']);
|
|
572
|
+
await writeAppleTerminalState(options, { status: 'complete', backupPath });
|
|
573
|
+
return 'Configured Terminal.app settings:\n- Enabled "Use Option as Meta key"\n- Switched to visual bell\nOption+Enter will now enter a newline.\nYou must restart Terminal.app for changes to take effect.';
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
const restored = await restoreAppleTerminalBackup(backupPath, run);
|
|
577
|
+
if (restored) {
|
|
578
|
+
await writeAppleTerminalState(options, {
|
|
579
|
+
status: 'complete',
|
|
580
|
+
backupPath,
|
|
581
|
+
}).catch(() => undefined);
|
|
582
|
+
return 'Failed to enable Option as Meta key for Terminal.app. Your settings have been restored from backup.';
|
|
583
|
+
}
|
|
584
|
+
return `Failed to enable Option as Meta key for Terminal.app: ${error instanceof Error ? error.message : String(error)}`;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Diagnose or install the terminal-local newline binding used by the Claude
|
|
589
|
+
* compatible composer. This intentionally operates outside shared transcripts:
|
|
590
|
+
* terminal preferences belong to the local single-user machine.
|
|
591
|
+
*/
|
|
592
|
+
export async function setupTuiTerminal(options = {}) {
|
|
593
|
+
const terminal = detectTuiTerminal(ownEnvironment(options));
|
|
594
|
+
const nativeName = nativeShiftEnterTerminalName(terminal);
|
|
595
|
+
if (nativeName)
|
|
596
|
+
return `Shift+Enter is natively supported in ${nativeName}.
|
|
597
|
+
|
|
598
|
+
No configuration needed. Just use Shift+Enter to add newlines.`;
|
|
599
|
+
if (terminal === 'Apple_Terminal' && ownPlatform(options) === 'darwin')
|
|
600
|
+
return installAppleTerminalSetup(options);
|
|
601
|
+
if (!terminal || !SETUP_SUPPORTED_TERMINALS.has(terminal))
|
|
602
|
+
return terminalUnavailableMessage(terminal, ownPlatform(options));
|
|
603
|
+
switch (terminal) {
|
|
604
|
+
case 'vscode':
|
|
605
|
+
return installVsCodeBinding('VSCode', options);
|
|
606
|
+
case 'cursor':
|
|
607
|
+
return installVsCodeBinding('Cursor', options);
|
|
608
|
+
case 'windsurf':
|
|
609
|
+
return installVsCodeBinding('Windsurf', options);
|
|
610
|
+
case 'alacritty':
|
|
611
|
+
return installAlacrittyBinding(options);
|
|
612
|
+
case 'zed':
|
|
613
|
+
return installZedBinding(options);
|
|
614
|
+
default:
|
|
615
|
+
return terminalUnavailableMessage(terminal, ownPlatform(options));
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
//# sourceMappingURL=terminal-setup.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type TuiThemeSettings } from './theme.js';
|
|
2
|
+
import { type TuiCustomTheme } from './custom-themes.js';
|
|
3
|
+
export declare function themeSettingsWithCustomTheme(settings: TuiThemeSettings, customThemes: readonly TuiCustomTheme[]): TuiThemeSettings;
|
|
4
|
+
export declare function loadTuiThemeSettings(configRoot?: string): Promise<TuiThemeSettings>;
|
|
5
|
+
export declare function saveTuiThemeSettings(update: Partial<TuiThemeSettings>, configRoot?: string, hooks?: {
|
|
6
|
+
afterValidation?: () => Promise<void>;
|
|
7
|
+
}): Promise<TuiThemeSettings>;
|
|
8
|
+
//# sourceMappingURL=theme-settings.d.ts.map
|