pinokiod 3.209.0 → 3.210.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/kernel/api/terminal/index.js +97 -1
- package/kernel/bracketed_paste_detector.js +99 -0
- package/kernel/environment.js +2 -2
- package/kernel/shell.js +26 -0
- package/kernel/shells.js +58 -3
- package/package.json +1 -1
|
@@ -136,7 +136,10 @@ class Terminal {
|
|
|
136
136
|
return { files: saved, errors: failures }
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
const marker =
|
|
139
|
+
const marker = this.buildAttachmentPayload(saved, {
|
|
140
|
+
shellInstance,
|
|
141
|
+
params
|
|
142
|
+
})
|
|
140
143
|
let shellEmitAttempted = false
|
|
141
144
|
let shellEmitSucceeded = false
|
|
142
145
|
if (params && params.id && kernel && kernel.shell && typeof kernel.shell.emit === 'function') {
|
|
@@ -173,6 +176,99 @@ class Terminal {
|
|
|
173
176
|
return { files: saved, errors: failures, shellEmit: shellEmitSucceeded, shellEmitAttempted }
|
|
174
177
|
}
|
|
175
178
|
|
|
179
|
+
buildAttachmentPayload(files, context = {}) {
|
|
180
|
+
const markerPrefix = ''
|
|
181
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
182
|
+
return markerPrefix
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const shellName = this.resolveShellName(context)
|
|
186
|
+
const escapedPaths = files
|
|
187
|
+
.map((file) => {
|
|
188
|
+
if (!file || typeof file !== 'object') {
|
|
189
|
+
return null
|
|
190
|
+
}
|
|
191
|
+
const rawPath = this.pickBestPath(file)
|
|
192
|
+
if (!rawPath) {
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
195
|
+
return this.escapePathForShell(shellName, rawPath)
|
|
196
|
+
})
|
|
197
|
+
.filter(Boolean)
|
|
198
|
+
|
|
199
|
+
if (escapedPaths.length === 0) {
|
|
200
|
+
return markerPrefix
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return escapedPaths.join(' ')
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
resolveShellName(context) {
|
|
207
|
+
if (!context) {
|
|
208
|
+
return null
|
|
209
|
+
}
|
|
210
|
+
const { shellInstance, params } = context
|
|
211
|
+
if (shellInstance && typeof shellInstance.shell === 'string' && shellInstance.shell.trim()) {
|
|
212
|
+
return shellInstance.shell.trim()
|
|
213
|
+
}
|
|
214
|
+
if (params && typeof params.shell === 'string' && params.shell.trim()) {
|
|
215
|
+
return params.shell.trim()
|
|
216
|
+
}
|
|
217
|
+
return null
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
escapePathForShell(shellName, rawPath) {
|
|
221
|
+
if (!rawPath || typeof rawPath !== 'string') {
|
|
222
|
+
return ''
|
|
223
|
+
}
|
|
224
|
+
const normalizedPath = rawPath
|
|
225
|
+
if (!shellName) {
|
|
226
|
+
return this.escapePosixPath(normalizedPath)
|
|
227
|
+
}
|
|
228
|
+
const lowerShell = shellName.toLowerCase()
|
|
229
|
+
if (lowerShell.includes('powershell') || lowerShell.includes('pwsh')) {
|
|
230
|
+
return this.escapePowershellPath(normalizedPath)
|
|
231
|
+
}
|
|
232
|
+
if (lowerShell.includes('cmd.exe') || lowerShell === 'cmd') {
|
|
233
|
+
return this.escapeCmdPath(normalizedPath)
|
|
234
|
+
}
|
|
235
|
+
return this.escapePosixPath(normalizedPath)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
pickBestPath(file) {
|
|
239
|
+
const candidates = [
|
|
240
|
+
file.cliRelativePath,
|
|
241
|
+
file.cliPath,
|
|
242
|
+
file.path,
|
|
243
|
+
file.displayPath,
|
|
244
|
+
file.homeRelativePath,
|
|
245
|
+
file.storedAs,
|
|
246
|
+
file.originalName,
|
|
247
|
+
typeof file.name === 'string' ? file.name : null
|
|
248
|
+
]
|
|
249
|
+
for (const candidate of candidates) {
|
|
250
|
+
if (typeof candidate === 'string') {
|
|
251
|
+
const trimmed = candidate.trim()
|
|
252
|
+
if (trimmed.length > 0) {
|
|
253
|
+
return trimmed
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return null
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
escapePosixPath(input) {
|
|
261
|
+
return `'${input.split("'").join("'\\''")}'`
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
escapePowershellPath(input) {
|
|
265
|
+
return `'${input.split("'").join("''")}'`
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
escapeCmdPath(input) {
|
|
269
|
+
return `"${input.replace(/(["^%])/g, '^$1')}"`
|
|
270
|
+
}
|
|
271
|
+
|
|
176
272
|
resolveShellInstance(params, kernel) {
|
|
177
273
|
if (!params || typeof params.id !== 'string') {
|
|
178
274
|
return null
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const { spawn } = require('child_process')
|
|
2
|
+
const os = require('os')
|
|
3
|
+
|
|
4
|
+
const cache = new Map()
|
|
5
|
+
|
|
6
|
+
const OK_MARKER = '__PINOKIO_BP_OK__'
|
|
7
|
+
const NO_MARKER = '__PINOKIO_BP_NO__'
|
|
8
|
+
|
|
9
|
+
function normalizeKey(shell, platform) {
|
|
10
|
+
return `${platform || os.platform()}::${(shell || '').toLowerCase()}`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isWindowsShell(shell) {
|
|
14
|
+
const name = (shell || '').toLowerCase()
|
|
15
|
+
return name.includes('cmd.exe') || name === 'cmd' || name.includes('powershell') || name.includes('pwsh')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isBash(shell) {
|
|
19
|
+
return (shell || '').toLowerCase().includes('bash')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isZsh(shell) {
|
|
23
|
+
return (shell || '').toLowerCase().includes('zsh')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function runProbe(shell, args, script) {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
let stdout = ''
|
|
29
|
+
const child = spawn(shell, [...args, script], {
|
|
30
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
31
|
+
})
|
|
32
|
+
child.stdout.setEncoding('utf8')
|
|
33
|
+
child.stdout.on('data', (chunk) => {
|
|
34
|
+
stdout += chunk
|
|
35
|
+
})
|
|
36
|
+
child.on('error', () => resolve(null))
|
|
37
|
+
child.on('close', () => resolve(stdout))
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function detect(shell, platform = os.platform()) {
|
|
42
|
+
const key = normalizeKey(shell, platform)
|
|
43
|
+
if (cache.has(key)) {
|
|
44
|
+
return cache.get(key)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let result
|
|
48
|
+
try {
|
|
49
|
+
result = await runDetection(shell, platform)
|
|
50
|
+
} catch (_) {
|
|
51
|
+
result = null
|
|
52
|
+
}
|
|
53
|
+
if (result === null) {
|
|
54
|
+
result = true
|
|
55
|
+
}
|
|
56
|
+
cache.set(key, result)
|
|
57
|
+
return result
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function runDetection(shell, platform) {
|
|
61
|
+
if (!shell) {
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
if (isWindowsShell(shell)) {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (isBash(shell)) {
|
|
69
|
+
const script = `if bind -v 2>/dev/null | grep -q 'enable-bracketed-paste'; then bind 'set enable-bracketed-paste on' >/dev/null 2>&1; printf '${OK_MARKER}\n'; else printf '${NO_MARKER}\n'; fi`
|
|
70
|
+
const output = await runProbe(shell, ['--noprofile', '--norc', '-ic'], script)
|
|
71
|
+
return parseProbeOutput(output)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isZsh(shell)) {
|
|
75
|
+
const script = `if setopt -q bracketed-paste 2>/dev/null; then printf '${OK_MARKER}\n'; exit 0; fi; if setopt bracketed-paste 2>/dev/null; then printf '${OK_MARKER}\n'; exit 0; fi; printf '${NO_MARKER}\n'`
|
|
76
|
+
const output = await runProbe(shell, ['-f', '-c'], script)
|
|
77
|
+
return parseProbeOutput(output)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// For other shells assume supported unless clearly Windows-only.
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseProbeOutput(output) {
|
|
85
|
+
if (typeof output !== 'string') {
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
if (output.includes(OK_MARKER)) {
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
if (output.includes(NO_MARKER)) {
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
detect
|
|
99
|
+
}
|
package/kernel/environment.js
CHANGED
package/kernel/shell.js
CHANGED
|
@@ -40,6 +40,13 @@ class Shell {
|
|
|
40
40
|
this.platform = os.platform()
|
|
41
41
|
this.logs = {}
|
|
42
42
|
this.shell = this.platform === 'win32' ? 'cmd.exe' : 'bash';
|
|
43
|
+
this.supportsBracketedPaste = this.computeBracketedPasteSupport(this.shell)
|
|
44
|
+
if (this.kernel && this.kernel.bracketedPasteSupport) {
|
|
45
|
+
const cached = this.kernel.bracketedPasteSupport[(this.shell || '').toLowerCase()]
|
|
46
|
+
if (typeof cached === 'boolean') {
|
|
47
|
+
this.supportsBracketedPaste = cached
|
|
48
|
+
}
|
|
49
|
+
}
|
|
43
50
|
this.decsyncBuffer = ''
|
|
44
51
|
|
|
45
52
|
// Windows: /D => ignore AutoRun Registry Key
|
|
@@ -276,6 +283,12 @@ class Shell {
|
|
|
276
283
|
this.EOL = os.EOL
|
|
277
284
|
if (this.params.shell) {
|
|
278
285
|
this.shell = this.params.shell
|
|
286
|
+
const normalizedShell = (this.shell || '').toLowerCase()
|
|
287
|
+
if (this.kernel && this.kernel.bracketedPasteSupport && typeof this.kernel.bracketedPasteSupport[normalizedShell] === 'boolean') {
|
|
288
|
+
this.supportsBracketedPaste = this.kernel.bracketedPasteSupport[normalizedShell]
|
|
289
|
+
} else {
|
|
290
|
+
this.supportsBracketedPaste = this.computeBracketedPasteSupport(this.shell)
|
|
291
|
+
}
|
|
279
292
|
if (/bash/i.test(this.shell)) {
|
|
280
293
|
this.args = ["--noprofile", "--norc"]
|
|
281
294
|
//this.args = [ "--login", "-i"]
|
|
@@ -579,6 +592,19 @@ class Shell {
|
|
|
579
592
|
const regex = new RegExp(pattern, 'gi')
|
|
580
593
|
return str.replaceAll(regex, '');
|
|
581
594
|
}
|
|
595
|
+
computeBracketedPasteSupport(shellName) {
|
|
596
|
+
const name = (shellName || '').toLowerCase()
|
|
597
|
+
if (!name) {
|
|
598
|
+
return true
|
|
599
|
+
}
|
|
600
|
+
if (name.includes('cmd.exe') || name === 'cmd') {
|
|
601
|
+
return false
|
|
602
|
+
}
|
|
603
|
+
if (name.includes('powershell') || name.includes('pwsh')) {
|
|
604
|
+
return false
|
|
605
|
+
}
|
|
606
|
+
return true
|
|
607
|
+
}
|
|
582
608
|
exists(abspath) {
|
|
583
609
|
return new Promise(r=>fs.access(abspath, fs.constants.F_OK, e => r(!e)))
|
|
584
610
|
}
|
package/kernel/shells.js
CHANGED
|
@@ -9,10 +9,12 @@ const {
|
|
|
9
9
|
|
|
10
10
|
const path = require('path')
|
|
11
11
|
const Shell = require("./shell")
|
|
12
|
+
const { detect: detectBracketedPasteSupport } = require('./bracketed_paste_detector')
|
|
12
13
|
class Shells {
|
|
13
14
|
constructor(kernel) {
|
|
14
15
|
this.kernel = kernel
|
|
15
16
|
this.shells = []
|
|
17
|
+
this.bracketedPasteDetections = new Map()
|
|
16
18
|
|
|
17
19
|
}
|
|
18
20
|
/*
|
|
@@ -40,6 +42,14 @@ class Shells {
|
|
|
40
42
|
env: this.kernel.bin.envs({}),
|
|
41
43
|
})
|
|
42
44
|
|
|
45
|
+
await this.ensureBracketedPasteSupport(sh.shell)
|
|
46
|
+
if (this.kernel.bracketedPasteSupport) {
|
|
47
|
+
const cached = this.kernel.bracketedPasteSupport[(sh.shell || '').toLowerCase()]
|
|
48
|
+
if (typeof cached === 'boolean') {
|
|
49
|
+
sh.supportsBracketedPaste = cached
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
this.kernel.envs = sh.env
|
|
44
54
|
// also set the uppercase variables if they're not set
|
|
45
55
|
for(let key in sh.env) {
|
|
@@ -67,6 +77,44 @@ class Shells {
|
|
|
67
77
|
}
|
|
68
78
|
})
|
|
69
79
|
}
|
|
80
|
+
async ensureBracketedPasteSupport(shellName) {
|
|
81
|
+
if (!shellName) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
const lower = (shellName || '').toLowerCase()
|
|
85
|
+
if (!lower) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
if (!this.kernel.bracketedPasteSupport) {
|
|
89
|
+
this.kernel.bracketedPasteSupport = {}
|
|
90
|
+
}
|
|
91
|
+
if (Object.prototype.hasOwnProperty.call(this.kernel.bracketedPasteSupport, lower)) {
|
|
92
|
+
return this.kernel.bracketedPasteSupport[lower]
|
|
93
|
+
}
|
|
94
|
+
if (this.bracketedPasteDetections.has(lower)) {
|
|
95
|
+
return this.bracketedPasteDetections.get(lower)
|
|
96
|
+
}
|
|
97
|
+
const fallback = !(lower.includes('cmd.exe') || lower === 'cmd' || lower.includes('powershell') || lower.includes('pwsh'))
|
|
98
|
+
const detectionPromise = detectBracketedPasteSupport(shellName, this.kernel.platform || os.platform())
|
|
99
|
+
.then((support) => {
|
|
100
|
+
const value = typeof support === 'boolean' ? support : fallback
|
|
101
|
+
this.kernel.bracketedPasteSupport[lower] = value
|
|
102
|
+
return value
|
|
103
|
+
})
|
|
104
|
+
.catch((error) => {
|
|
105
|
+
console.warn('[shells.ensureBracketedPasteSupport] detection failed', {
|
|
106
|
+
shell: shellName,
|
|
107
|
+
error: error && error.message ? error.message : error
|
|
108
|
+
})
|
|
109
|
+
this.kernel.bracketedPasteSupport[lower] = fallback
|
|
110
|
+
return fallback
|
|
111
|
+
})
|
|
112
|
+
.finally(() => {
|
|
113
|
+
this.bracketedPasteDetections.delete(lower)
|
|
114
|
+
})
|
|
115
|
+
this.bracketedPasteDetections.set(lower, detectionPromise)
|
|
116
|
+
return detectionPromise
|
|
117
|
+
}
|
|
70
118
|
async launch(params, options, ondata) {
|
|
71
119
|
// if array, duplicate the action
|
|
72
120
|
if (Array.isArray(params.message)) {
|
|
@@ -112,6 +160,9 @@ class Shells {
|
|
|
112
160
|
let exec_path = (params.path ? params.path : ".") // use the current path if not specified
|
|
113
161
|
let cwd = (options && options.cwd ? options.cwd : this.kernel.homedir) // if cwd exists, use it. Otherwise the cwd is pinokio home folder (~/pinokio)
|
|
114
162
|
params.path = this.kernel.api.resolvePath(cwd, exec_path)
|
|
163
|
+
|
|
164
|
+
const plannedShell = params.shell || (this.kernel.platform === 'win32' ? 'cmd.exe' : 'bash')
|
|
165
|
+
await this.ensureBracketedPasteSupport(plannedShell)
|
|
115
166
|
let sh = new Shell(this.kernel)
|
|
116
167
|
if (options) {
|
|
117
168
|
params.group = options.group // set group
|
|
@@ -387,9 +438,13 @@ class Shells {
|
|
|
387
438
|
let session = this.get(params.id)
|
|
388
439
|
if (session) {
|
|
389
440
|
if (params.paste) {
|
|
390
|
-
|
|
391
|
-
session.
|
|
392
|
-
|
|
441
|
+
const payload = params.emit != null ? String(params.emit) : ''
|
|
442
|
+
if (session.supportsBracketedPaste !== false) {
|
|
443
|
+
//session.emit("\x1b[?2004h\x1b[200~" + params.emit+ "\x1b[201~")
|
|
444
|
+
session.emit("\x1b[200~" + payload + "\x1b[201~")
|
|
445
|
+
} else {
|
|
446
|
+
session.emit(payload)
|
|
447
|
+
}
|
|
393
448
|
} else {
|
|
394
449
|
session.emit(params.emit)
|
|
395
450
|
}
|