@bike4mind/cli 0.2.64-worktree-refactor-extract-search-query-builders.21815 → 0.2.64
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/bin/bike4mind-cli.mjs +6 -6
- package/dist/BubblewrapRuntime-BHbtqvLx.mjs +72 -0
- package/dist/ConfigStore-CllM6jOf.mjs +8614 -0
- package/dist/ImageStore-DaKT_Ew8.mjs +202 -0
- package/dist/ProxyManager-Dl2nFk-A.mjs +259 -0
- package/dist/ProxyManager-kiOD1X8-.mjs +3 -0
- package/dist/SandboxOrchestrator-BEW3rqYi.mjs +159 -0
- package/dist/SandboxOrchestrator-CHZgSR3P.mjs +3 -0
- package/dist/SandboxRuntimeAdapter-C1B4t20N.mjs +57 -0
- package/dist/SandboxRuntimeAdapter-D7UAG13n.mjs +3 -0
- package/dist/SeatbeltRuntime-D4m0VOcD.mjs +116 -0
- package/dist/StderrViolationParser-D0afQ3-1.mjs +70 -0
- package/dist/ViolationLogStore-CZl35HcA.mjs +96 -0
- package/dist/bashExecute-BTkdqlSs-5foM20Lb.mjs +466 -0
- package/dist/commands/doctorCommand.mjs +101 -0
- package/dist/commands/headlessCommand.mjs +319 -0
- package/dist/commands/mcpCommand.mjs +218 -0
- package/dist/commands/updateCommand.mjs +40 -0
- package/dist/createFile-yQfh8uvk-I-yM5DxC.mjs +63 -0
- package/dist/deleteFile-DKHfnyny-G3b1Kj2T.mjs +66 -0
- package/dist/globFiles-D1en6joM-8jekiXdX.mjs +100 -0
- package/dist/grepSearch-aMamoBn_-DCJcY8JS.mjs +173 -0
- package/dist/index.mjs +6722 -0
- package/dist/pathValidation-Cgjh5WQO-DiCZTcq6.mjs +63 -0
- package/dist/store-Dw1nZX2Y.mjs +128 -0
- package/dist/store-nZExNOWX.mjs +3 -0
- package/dist/terminalSetup-rmr1P8KF.mjs +254 -0
- package/dist/tools-C6M5aW8W.mjs +20907 -0
- package/dist/treeSitterEngine-DCSXcm_3.mjs +309 -0
- package/dist/types-DBEjF9YS.mjs +59 -0
- package/dist/types-DK3P88Px.mjs +3 -0
- package/dist/updateChecker-Cu9dkHxV.mjs +120 -0
- package/package.json +10 -10
- package/dist/BubblewrapRuntime-PMIOLWKR.js +0 -71
- package/dist/HydrationEngine-YL2HWJ3V.js +0 -9
- package/dist/ImageStore-MMUOUPI2.js +0 -224
- package/dist/ProxyManager-HEB4TLVX.js +0 -7
- package/dist/SandboxOrchestrator-UIJ5GYBB.js +0 -8
- package/dist/SandboxRuntimeAdapter-FQ56MAB2.js +0 -13
- package/dist/SeatbeltRuntime-EE3TTLEP.js +0 -98
- package/dist/StderrViolationParser-7OYPM2DJ.js +0 -59
- package/dist/ViolationLogStore-RIIUVURH.js +0 -104
- package/dist/artifactExtractor-R7DIP2XO.js +0 -180
- package/dist/bashExecute-GLGLD3JD.js +0 -379
- package/dist/chunk-4BIBE3J7.js +0 -48
- package/dist/chunk-5LZS5CVJ.js +0 -161
- package/dist/chunk-BDQBOLYG.js +0 -120
- package/dist/chunk-BPFEGDC7.js +0 -192
- package/dist/chunk-EPIYC3LA.js +0 -13770
- package/dist/chunk-G4ZGEQFT.js +0 -250
- package/dist/chunk-GQGOWACU.js +0 -770
- package/dist/chunk-J6ZBI6TI.js +0 -1079
- package/dist/chunk-JW3JRHH7.js +0 -12433
- package/dist/chunk-KQAMBXAW.js +0 -163
- package/dist/chunk-KUVV2NAB.js +0 -19125
- package/dist/chunk-LTLJRF6I.js +0 -44
- package/dist/chunk-PFBYGCOW.js +0 -449
- package/dist/chunk-QWB6ZYY4.js +0 -48
- package/dist/chunk-SGPRXN4C.js +0 -245
- package/dist/chunk-UZUHPHZC.js +0 -95
- package/dist/chunk-WBE7SQUB.js +0 -241
- package/dist/chunk-Y4WOJJM3.js +0 -147
- package/dist/commands/doctorCommand.js +0 -87
- package/dist/commands/headlessCommand.js +0 -380
- package/dist/commands/mcpCommand.js +0 -203
- package/dist/commands/updateCommand.js +0 -42
- package/dist/create-C4VEEEYR.js +0 -12
- package/dist/createFile-6PSPLW6R.js +0 -71
- package/dist/deleteFile-AUSRLWIK.js +0 -73
- package/dist/formatConverter-5QEJDW24.js +0 -7
- package/dist/globFiles-TSRN64N2.js +0 -120
- package/dist/grepSearch-634XWZOJ.js +0 -216
- package/dist/index.js +0 -6779
- package/dist/llmMarkdownGenerator-Z6NB26TT.js +0 -371
- package/dist/markdownGenerator-SK2ZQQL4.js +0 -269
- package/dist/mementoService-N4IM6QAC.js +0 -12
- package/dist/notificationDeduplicator-HUC53NEW.js +0 -9
- package/dist/src-F4KZCAA2.js +0 -319
- package/dist/src-ISX322I7.js +0 -1101
- package/dist/store-CAB6BV3P.js +0 -11
- package/dist/subtractCredits-D4KEM6VU.js +0 -12
- package/dist/terminalSetup-C5FHMLC3.js +0 -214
- package/dist/treeSitterEngine-4SGFQDY3.js +0 -330
- package/dist/types-KB5NP6T4.js +0 -7
- package/dist/utils-JCHWDM4Z.js +0 -31
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
//#region ../../b4m-core/packages/services/dist/pathValidation-Cgjh5WQO.mjs
|
|
5
|
+
/**
|
|
6
|
+
* Resolves a path to its canonical form, following symlinks.
|
|
7
|
+
* If the path doesn't exist (e.g., file being created), resolves the
|
|
8
|
+
* nearest existing ancestor and appends the remaining segments.
|
|
9
|
+
*/
|
|
10
|
+
function resolveRealPath(filePath) {
|
|
11
|
+
try {
|
|
12
|
+
return realpathSync(filePath);
|
|
13
|
+
} catch {
|
|
14
|
+
const parentDir = path.dirname(filePath);
|
|
15
|
+
const basename = path.basename(filePath);
|
|
16
|
+
if (parentDir === filePath) return filePath;
|
|
17
|
+
return path.join(resolveRealPath(parentDir), basename);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Validates if a path is within any of the allowed directories.
|
|
22
|
+
* Always includes process.cwd() as an allowed directory.
|
|
23
|
+
* Resolves symlinks to prevent symlink-based directory escapes.
|
|
24
|
+
*
|
|
25
|
+
* @param filePath - The path to validate (can be relative or absolute)
|
|
26
|
+
* @param allowedDirectories - Optional list of additional allowed directories
|
|
27
|
+
* @returns Object with validation result, resolved path, and matched directory if allowed
|
|
28
|
+
*/
|
|
29
|
+
function isPathAllowed(filePath, allowedDirectories) {
|
|
30
|
+
const cwd = resolveRealPath(process.cwd());
|
|
31
|
+
const allAllowed = [cwd, ...(allowedDirectories || []).map((d) => resolveRealPath(d))];
|
|
32
|
+
const normalizedPath = path.normalize(filePath);
|
|
33
|
+
const resolvedPath = resolveRealPath(path.isAbsolute(normalizedPath) ? normalizedPath : path.resolve(cwd, normalizedPath));
|
|
34
|
+
for (const dir of allAllowed) if (resolvedPath === dir || resolvedPath.startsWith(dir + path.sep)) return {
|
|
35
|
+
allowed: true,
|
|
36
|
+
resolvedPath,
|
|
37
|
+
matchedDirectory: dir
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
allowed: false,
|
|
41
|
+
resolvedPath
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Throws an error if the path is not within allowed directories.
|
|
46
|
+
*
|
|
47
|
+
* @param filePath - The path to validate
|
|
48
|
+
* @param allowedDirectories - Optional list of additional allowed directories
|
|
49
|
+
* @param operation - Description of the operation (for error message), e.g., 'read', 'write', 'delete'
|
|
50
|
+
* @returns The resolved absolute path if allowed
|
|
51
|
+
* @throws Error if path is not within allowed directories
|
|
52
|
+
*/
|
|
53
|
+
function assertPathAllowed(filePath, allowedDirectories, operation = "access") {
|
|
54
|
+
const result = isPathAllowed(filePath, allowedDirectories);
|
|
55
|
+
if (!result.allowed) {
|
|
56
|
+
const cwd = process.cwd();
|
|
57
|
+
const dirsMsg = allowedDirectories && allowedDirectories.length > 0 ? `Allowed directories: ${[cwd, ...allowedDirectories].join(", ")}` : `Working directory: ${cwd}`;
|
|
58
|
+
throw new Error(`Access denied: Cannot ${operation} files outside allowed directories. ${dirsMsg}`);
|
|
59
|
+
}
|
|
60
|
+
return result.resolvedPath;
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
export { isPathAllowed as n, assertPathAllowed as t };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { create } from "zustand";
|
|
3
|
+
//#region src/store/index.ts
|
|
4
|
+
/** Active job statuses (jobs still in progress) */
|
|
5
|
+
const ACTIVE_STATUSES = new Set(["running", "queued"]);
|
|
6
|
+
/** Check if a job status is active (running or queued) */
|
|
7
|
+
function isActiveStatus(status) {
|
|
8
|
+
return ACTIVE_STATUSES.has(status);
|
|
9
|
+
}
|
|
10
|
+
const useCliStore = create((set) => ({
|
|
11
|
+
session: null,
|
|
12
|
+
setSession: (session) => set({ session }),
|
|
13
|
+
addMessage: (message) => set((state) => {
|
|
14
|
+
if (!state.session) return state;
|
|
15
|
+
return { session: {
|
|
16
|
+
...state.session,
|
|
17
|
+
messages: [...state.session.messages, message],
|
|
18
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19
|
+
} };
|
|
20
|
+
}),
|
|
21
|
+
pendingMessages: [],
|
|
22
|
+
addPendingMessage: (message) => set((state) => ({ pendingMessages: [...state.pendingMessages, message] })),
|
|
23
|
+
updatePendingMessage: (index, message) => set((state) => {
|
|
24
|
+
const updated = [...state.pendingMessages];
|
|
25
|
+
updated[index] = message;
|
|
26
|
+
return { pendingMessages: updated };
|
|
27
|
+
}),
|
|
28
|
+
clearPendingMessages: () => set({ pendingMessages: [] }),
|
|
29
|
+
completePendingMessage: (index, finalMessage) => set((state) => {
|
|
30
|
+
const pending = [...state.pendingMessages];
|
|
31
|
+
pending.splice(index, 1);
|
|
32
|
+
const session = state.session;
|
|
33
|
+
if (!session) return { pendingMessages: pending };
|
|
34
|
+
return {
|
|
35
|
+
pendingMessages: pending,
|
|
36
|
+
session: {
|
|
37
|
+
...session,
|
|
38
|
+
messages: [...session.messages, finalMessage],
|
|
39
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}),
|
|
43
|
+
isThinking: false,
|
|
44
|
+
setIsThinking: (thinking) => set({ isThinking: thinking }),
|
|
45
|
+
inputValue: "",
|
|
46
|
+
setInputValue: (value) => set({ inputValue: value }),
|
|
47
|
+
clearInput: () => set({
|
|
48
|
+
inputValue: "",
|
|
49
|
+
pastedContent: null,
|
|
50
|
+
pastedLineCount: 0
|
|
51
|
+
}),
|
|
52
|
+
pastedContent: null,
|
|
53
|
+
pastedLineCount: 0,
|
|
54
|
+
setPastedContent: (content, lineCount) => set({
|
|
55
|
+
pastedContent: content,
|
|
56
|
+
pastedLineCount: lineCount,
|
|
57
|
+
inputValue: content
|
|
58
|
+
}),
|
|
59
|
+
clearPaste: () => set({
|
|
60
|
+
pastedContent: null,
|
|
61
|
+
pastedLineCount: 0,
|
|
62
|
+
inputValue: ""
|
|
63
|
+
}),
|
|
64
|
+
permissionPrompt: null,
|
|
65
|
+
permissionQueue: [],
|
|
66
|
+
enqueuePermissionPrompt: (prompt) => set((state) => {
|
|
67
|
+
if (!state.permissionPrompt) return { permissionPrompt: prompt };
|
|
68
|
+
return { permissionQueue: [...state.permissionQueue, prompt] };
|
|
69
|
+
}),
|
|
70
|
+
dequeuePermissionPrompt: () => set((state) => {
|
|
71
|
+
const [next, ...rest] = state.permissionQueue;
|
|
72
|
+
return {
|
|
73
|
+
permissionPrompt: next ?? null,
|
|
74
|
+
permissionQueue: rest
|
|
75
|
+
};
|
|
76
|
+
}),
|
|
77
|
+
userQuestionPrompt: null,
|
|
78
|
+
userQuestionQueue: [],
|
|
79
|
+
enqueueUserQuestionPrompt: (prompt) => set((state) => {
|
|
80
|
+
if (!state.userQuestionPrompt) return { userQuestionPrompt: prompt };
|
|
81
|
+
return { userQuestionQueue: [...state.userQuestionQueue, prompt] };
|
|
82
|
+
}),
|
|
83
|
+
dequeueUserQuestionPrompt: () => set((state) => {
|
|
84
|
+
const [next, ...rest] = state.userQuestionQueue;
|
|
85
|
+
return {
|
|
86
|
+
userQuestionPrompt: next ?? null,
|
|
87
|
+
userQuestionQueue: rest
|
|
88
|
+
};
|
|
89
|
+
}),
|
|
90
|
+
showConfigEditor: false,
|
|
91
|
+
setShowConfigEditor: (show) => set({ showConfigEditor: show }),
|
|
92
|
+
showMcpViewer: false,
|
|
93
|
+
setShowMcpViewer: (show) => set({ showMcpViewer: show }),
|
|
94
|
+
autoAcceptEdits: false,
|
|
95
|
+
toggleAutoAcceptEdits: () => set((state) => ({ autoAcceptEdits: !state.autoAcceptEdits })),
|
|
96
|
+
exitRequested: false,
|
|
97
|
+
setExitRequested: (requested) => set({ exitRequested: requested }),
|
|
98
|
+
backgroundAgents: [],
|
|
99
|
+
upsertBackgroundAgent: (job) => set((state) => {
|
|
100
|
+
const existing = state.backgroundAgents.findIndex((j) => j.id === job.id);
|
|
101
|
+
if (existing >= 0) {
|
|
102
|
+
const updated = [...state.backgroundAgents];
|
|
103
|
+
updated[existing] = job;
|
|
104
|
+
return { backgroundAgents: updated };
|
|
105
|
+
}
|
|
106
|
+
return { backgroundAgents: [...state.backgroundAgents, job] };
|
|
107
|
+
}),
|
|
108
|
+
cleanupCompletedBackgroundAgents: () => set((state) => ({ backgroundAgents: state.backgroundAgents.filter((j) => isActiveStatus(j.status)) })),
|
|
109
|
+
completedGroupNotifications: [],
|
|
110
|
+
addCompletedGroupNotification: (notification, groupDescription) => set((state) => ({ completedGroupNotifications: [...state.completedGroupNotifications, {
|
|
111
|
+
notification,
|
|
112
|
+
groupDescription,
|
|
113
|
+
timestamp: Date.now()
|
|
114
|
+
}] })),
|
|
115
|
+
clearCompletedGroupNotifications: () => set({ completedGroupNotifications: [] }),
|
|
116
|
+
pendingBackgroundTrigger: false,
|
|
117
|
+
setPendingBackgroundTrigger: (pending) => set({ pendingBackgroundTrigger: pending }),
|
|
118
|
+
tavernActivityLog: [],
|
|
119
|
+
addTavernLogEntry: (entry) => set((state) => {
|
|
120
|
+
const log = [...state.tavernActivityLog, entry];
|
|
121
|
+
return { tavernActivityLog: log.length > 200 ? log.slice(-200) : log };
|
|
122
|
+
}),
|
|
123
|
+
clearTavernActivityLog: () => set({ tavernActivityLog: [] })
|
|
124
|
+
}));
|
|
125
|
+
/** Select only active (running or queued) background agents */
|
|
126
|
+
const selectActiveBackgroundAgents = (state) => state.backgroundAgents.filter((j) => isActiveStatus(j.status));
|
|
127
|
+
//#endregion
|
|
128
|
+
export { useCliStore as n, selectActiveBackgroundAgents as t };
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, promises } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
//#region src/utils/terminalSetup.ts
|
|
6
|
+
/**
|
|
7
|
+
* Terminal setup utility for configuring Shift+Enter keybindings.
|
|
8
|
+
*
|
|
9
|
+
* Most terminal emulators send the same byte (\r) for both Enter and Shift+Enter,
|
|
10
|
+
* making them indistinguishable. This utility detects the user's terminal and provides
|
|
11
|
+
* instructions or automatic configuration to make Shift+Enter emit a distinct sequence.
|
|
12
|
+
*
|
|
13
|
+
* Terminals with native Shift+Enter support (no setup needed):
|
|
14
|
+
* - iTerm2, WezTerm, Ghostty, Kitty
|
|
15
|
+
*
|
|
16
|
+
* Terminals that need configuration:
|
|
17
|
+
* - VS Code integrated terminal
|
|
18
|
+
* - Alacritty
|
|
19
|
+
* - Zed
|
|
20
|
+
* - macOS Terminal.app
|
|
21
|
+
*/
|
|
22
|
+
const TERMINAL_INFO = {
|
|
23
|
+
iterm2: {
|
|
24
|
+
name: "iTerm2",
|
|
25
|
+
status: "native"
|
|
26
|
+
},
|
|
27
|
+
wezterm: {
|
|
28
|
+
name: "WezTerm",
|
|
29
|
+
status: "native"
|
|
30
|
+
},
|
|
31
|
+
ghostty: {
|
|
32
|
+
name: "Ghostty",
|
|
33
|
+
status: "native"
|
|
34
|
+
},
|
|
35
|
+
kitty: {
|
|
36
|
+
name: "Kitty",
|
|
37
|
+
status: "native"
|
|
38
|
+
},
|
|
39
|
+
vscode: {
|
|
40
|
+
name: "VS Code",
|
|
41
|
+
status: "configurable"
|
|
42
|
+
},
|
|
43
|
+
alacritty: {
|
|
44
|
+
name: "Alacritty",
|
|
45
|
+
status: "configurable"
|
|
46
|
+
},
|
|
47
|
+
zed: {
|
|
48
|
+
name: "Zed",
|
|
49
|
+
status: "manual"
|
|
50
|
+
},
|
|
51
|
+
terminal_app: {
|
|
52
|
+
name: "Terminal.app",
|
|
53
|
+
status: "manual"
|
|
54
|
+
},
|
|
55
|
+
warp: {
|
|
56
|
+
name: "Warp",
|
|
57
|
+
status: "manual"
|
|
58
|
+
},
|
|
59
|
+
unknown: {
|
|
60
|
+
name: "Unknown",
|
|
61
|
+
status: "unknown"
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Detect the current terminal emulator from environment variables.
|
|
66
|
+
*/
|
|
67
|
+
function detectTerminal() {
|
|
68
|
+
const termProgram = process.env["TERM_PROGRAM"] ?? "";
|
|
69
|
+
const term = process.env["TERM"] ?? "";
|
|
70
|
+
if (termProgram === "vscode" || process.env["VSCODE_PID"]) return {
|
|
71
|
+
id: "vscode",
|
|
72
|
+
...TERMINAL_INFO.vscode
|
|
73
|
+
};
|
|
74
|
+
if (termProgram === "iTerm.app" || process.env["ITERM_SESSION_ID"]) return {
|
|
75
|
+
id: "iterm2",
|
|
76
|
+
...TERMINAL_INFO.iterm2
|
|
77
|
+
};
|
|
78
|
+
if (termProgram === "WezTerm" || process.env["WEZTERM_PANE"]) return {
|
|
79
|
+
id: "wezterm",
|
|
80
|
+
...TERMINAL_INFO.wezterm
|
|
81
|
+
};
|
|
82
|
+
if (termProgram === "ghostty" || term === "xterm-ghostty") return {
|
|
83
|
+
id: "ghostty",
|
|
84
|
+
...TERMINAL_INFO.ghostty
|
|
85
|
+
};
|
|
86
|
+
if (term === "xterm-kitty" || process.env["KITTY_PID"]) return {
|
|
87
|
+
id: "kitty",
|
|
88
|
+
...TERMINAL_INFO.kitty
|
|
89
|
+
};
|
|
90
|
+
if (termProgram === "Alacritty" || term === "alacritty") return {
|
|
91
|
+
id: "alacritty",
|
|
92
|
+
...TERMINAL_INFO.alacritty
|
|
93
|
+
};
|
|
94
|
+
if (termProgram === "WarpTerminal" || process.env["WARP_IS_LOCAL_SHELL_SESSION"]) return {
|
|
95
|
+
id: "warp",
|
|
96
|
+
...TERMINAL_INFO.warp
|
|
97
|
+
};
|
|
98
|
+
if (termProgram === "zed") return {
|
|
99
|
+
id: "zed",
|
|
100
|
+
...TERMINAL_INFO.zed
|
|
101
|
+
};
|
|
102
|
+
if (termProgram === "Apple_Terminal") return {
|
|
103
|
+
id: "terminal_app",
|
|
104
|
+
...TERMINAL_INFO.terminal_app
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
id: "unknown",
|
|
108
|
+
...TERMINAL_INFO.unknown
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* VS Code keybindings.json entry for Shift+Enter → \x1b[13;2u (Kitty protocol)
|
|
113
|
+
*/
|
|
114
|
+
const VSCODE_KEYBINDING = {
|
|
115
|
+
key: "shift+enter",
|
|
116
|
+
command: "workbench.action.terminal.sendSequence",
|
|
117
|
+
args: { text: "\x1B[13;2u" },
|
|
118
|
+
when: "terminalFocus"
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Configure VS Code to send a Kitty-protocol escape sequence for Shift+Enter.
|
|
122
|
+
*/
|
|
123
|
+
async function setupVSCode() {
|
|
124
|
+
const vscodeDir = [
|
|
125
|
+
path.join(homedir(), "Library", "Application Support", "Code", "User"),
|
|
126
|
+
path.join(homedir(), ".config", "Code", "User"),
|
|
127
|
+
path.join(homedir(), "AppData", "Roaming", "Code", "User")
|
|
128
|
+
].find((dir) => existsSync(dir));
|
|
129
|
+
if (!vscodeDir) return {
|
|
130
|
+
success: false,
|
|
131
|
+
message: "Could not find VS Code settings directory.\nManually add this to your keybindings.json (Cmd+K Cmd+S → Open Keyboard Shortcuts JSON):\n\n" + JSON.stringify(VSCODE_KEYBINDING, null, 2)
|
|
132
|
+
};
|
|
133
|
+
const keybindingsPath = path.join(vscodeDir, "keybindings.json");
|
|
134
|
+
let keybindings = [];
|
|
135
|
+
if (existsSync(keybindingsPath)) {
|
|
136
|
+
const stripped = (await promises.readFile(keybindingsPath, "utf-8")).replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
137
|
+
try {
|
|
138
|
+
keybindings = JSON.parse(stripped);
|
|
139
|
+
} catch {
|
|
140
|
+
return {
|
|
141
|
+
success: false,
|
|
142
|
+
message: "Could not parse keybindings.json. Manually add this entry:\n\n" + JSON.stringify(VSCODE_KEYBINDING, null, 2)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (keybindings.some((binding) => binding["key"] === "shift+enter" && binding["command"] === "workbench.action.terminal.sendSequence" && binding["when"] === "terminalFocus")) return {
|
|
147
|
+
success: true,
|
|
148
|
+
message: "VS Code is already configured for Shift+Enter newlines."
|
|
149
|
+
};
|
|
150
|
+
keybindings.push(VSCODE_KEYBINDING);
|
|
151
|
+
await promises.writeFile(keybindingsPath, JSON.stringify(keybindings, null, 2) + "\n", "utf-8");
|
|
152
|
+
return {
|
|
153
|
+
success: true,
|
|
154
|
+
message: `Updated ${keybindingsPath}\nShift+Enter will now insert a newline in the B4M CLI.
|
|
155
|
+
Restart your VS Code terminal for the change to take effect.`
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Alacritty config entry for Shift+Enter → Kitty protocol sequence.
|
|
160
|
+
*/
|
|
161
|
+
const ALACRITTY_TOML_SNIPPET = `
|
|
162
|
+
# B4M CLI: Shift+Enter sends Kitty-protocol sequence for newline
|
|
163
|
+
[[keyboard.bindings]]
|
|
164
|
+
key = "Return"
|
|
165
|
+
mods = "Shift"
|
|
166
|
+
chars = "\\u001b[13;2u"
|
|
167
|
+
`.trim();
|
|
168
|
+
/**
|
|
169
|
+
* Configure Alacritty to send a distinct escape sequence for Shift+Enter.
|
|
170
|
+
*/
|
|
171
|
+
async function setupAlacritty() {
|
|
172
|
+
const configPaths = [path.join(homedir(), ".config", "alacritty", "alacritty.toml"), path.join(homedir(), ".alacritty.toml")];
|
|
173
|
+
const existingConfig = configPaths.find((p) => existsSync(p));
|
|
174
|
+
const configPath = existingConfig ?? configPaths[0];
|
|
175
|
+
let content = "";
|
|
176
|
+
if (existingConfig) {
|
|
177
|
+
content = await promises.readFile(configPath, "utf-8");
|
|
178
|
+
if (content.includes("[13;2u") || content.includes("\\u001b[13;2u")) return {
|
|
179
|
+
success: true,
|
|
180
|
+
message: "Alacritty is already configured for Shift+Enter newlines."
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const newContent = content ? content.trimEnd() + "\n\n" + ALACRITTY_TOML_SNIPPET + "\n" : ALACRITTY_TOML_SNIPPET + "\n";
|
|
184
|
+
const configDir = path.dirname(configPath);
|
|
185
|
+
if (!existsSync(configDir)) await promises.mkdir(configDir, { recursive: true });
|
|
186
|
+
await promises.writeFile(configPath, newContent, "utf-8");
|
|
187
|
+
return {
|
|
188
|
+
success: true,
|
|
189
|
+
message: `Updated ${configPath}\nShift+Enter will now insert a newline in the B4M CLI.
|
|
190
|
+
Restart Alacritty for the change to take effect.`
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get manual instructions for terminals that can't be auto-configured.
|
|
195
|
+
*/
|
|
196
|
+
function getManualInstructions(terminal) {
|
|
197
|
+
switch (terminal.id) {
|
|
198
|
+
case "zed": return "Add this to your Zed keymap.json (Zed → Settings → Open Key Bindings):\n\n" + JSON.stringify([{
|
|
199
|
+
context: "Terminal",
|
|
200
|
+
bindings: { "shift-enter": ["terminal::SendText", "\\u001b[13;2u"] }
|
|
201
|
+
}], null, 2);
|
|
202
|
+
case "terminal_app": return "macOS Terminal.app cannot send distinct Shift+Enter sequences.\n\nAlternatives:\n • Use Option+Enter (⌥+Enter) to insert newlines\n • Type \\ then Enter to insert newlines\n • Switch to iTerm2, WezTerm, or Ghostty for native Shift+Enter support";
|
|
203
|
+
case "warp": return "Warp terminal has limited keybinding customization.\n\nAlternatives:\n • Use Option+Enter (⌥+Enter) to insert newlines\n • Type \\ then Enter to insert newlines";
|
|
204
|
+
default: return "Your terminal needs to be configured to send a distinct escape sequence for Shift+Enter.\nConfigure Shift+Enter to send: \\x1b[13;2u (Kitty keyboard protocol)\n\nAlternatives that work in all terminals:\n • Option/Alt+Enter to insert newlines\n • Type \\ then Enter to insert newlines";
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Run the terminal setup flow.
|
|
209
|
+
*/
|
|
210
|
+
async function runTerminalSetup() {
|
|
211
|
+
const terminal = detectTerminal();
|
|
212
|
+
console.log(`\nDetected terminal: ${terminal.name}\n`);
|
|
213
|
+
switch (terminal.status) {
|
|
214
|
+
case "native":
|
|
215
|
+
console.log(`✅ ${terminal.name} natively supports Shift+Enter for newlines.\nNo configuration needed!
|
|
216
|
+
`);
|
|
217
|
+
break;
|
|
218
|
+
case "configurable": {
|
|
219
|
+
console.log(`Configuring ${terminal.name} for Shift+Enter support...\n`);
|
|
220
|
+
let result;
|
|
221
|
+
switch (terminal.id) {
|
|
222
|
+
case "vscode":
|
|
223
|
+
result = await setupVSCode();
|
|
224
|
+
break;
|
|
225
|
+
case "alacritty":
|
|
226
|
+
result = await setupAlacritty();
|
|
227
|
+
break;
|
|
228
|
+
default: result = {
|
|
229
|
+
success: false,
|
|
230
|
+
message: "No auto-configuration available."
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
console.log(result.success ? `✅ ${result.message}` : `⚠️ ${result.message}`);
|
|
234
|
+
console.log();
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
case "manual":
|
|
238
|
+
console.log(`⚠️ ${terminal.name} requires manual configuration.\n`);
|
|
239
|
+
console.log(getManualInstructions(terminal));
|
|
240
|
+
console.log();
|
|
241
|
+
break;
|
|
242
|
+
case "unknown":
|
|
243
|
+
console.log(getManualInstructions(terminal));
|
|
244
|
+
console.log();
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
console.log("Universal newline methods (work in all terminals):");
|
|
248
|
+
console.log(" • Option/Alt + Enter — insert newline");
|
|
249
|
+
console.log(" • \\ + Enter — insert newline (backslash-escape)");
|
|
250
|
+
console.log(" • Shift + Enter — insert newline (if terminal supports Kitty protocol)");
|
|
251
|
+
console.log();
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
export { runTerminalSetup };
|