pinokiod 3.23.0 → 3.25.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.
@@ -0,0 +1,67 @@
1
+ const child_process = require('child_process')
2
+ module.exports = async (req, ondata, kernel) => {
3
+ /*
4
+ req := {
5
+ method: "exec",
6
+ params: {
7
+
8
+ // pinokio params
9
+ message: <message>,
10
+ path: <path>,
11
+
12
+ // node.js child_process.exec params
13
+
14
+ // Working directory
15
+ cwd: '/path/to/working/directory',
16
+
17
+ // Environment variables
18
+ env: { ...process.env, CUSTOM_VAR: 'value' },
19
+
20
+ // Encoding for stdout/stderr (default: 'utf8')
21
+ encoding: 'utf8', // or 'buffer', 'ascii', etc.
22
+
23
+ // Shell to execute the command
24
+ shell: '/bin/bash', // default varies by platform
25
+
26
+ // Timeout in milliseconds
27
+ timeout: 10000, // Kill process after 10 seconds
28
+
29
+ // Maximum buffer size for stdout/stderr
30
+ maxBuffer: 1024 * 1024, // 1MB (default: 1024 * 1024)
31
+
32
+ // Signal to send when killing due to timeout
33
+ killSignal: 'SIGTERM', // default: 'SIGTERM'
34
+
35
+ // User identity (Unix only)
36
+ uid: 1000,
37
+ gid: 1000,
38
+
39
+ // Windows-specific options
40
+ windowsHide: true, // Hide subprocess console window on Windows
41
+ windowsVerbatimArguments: false // Quote handling on Windows
42
+
43
+ }
44
+ }
45
+ */
46
+ if (req.params && req.params.message) {
47
+ // if cwd exists, use cwd
48
+ // if cwd doesn't exist, set pat
49
+ if (!req.params.cwd && req.params.path) {
50
+ req.params.cwd = req.params.path
51
+ }
52
+ req.params.env = Object.assign({}, kernel.envs, req.params.env)
53
+ console.log("env", JSON.stringify(req.params.env, null, 2))
54
+ ondata({ raw: `██ Exec: ${req.params.message}\r\n` })
55
+ let response = await new Promise((resolve, reject) => {
56
+ child_process.exec(req.params.message, req.params, (error, stdout, stderr) => {
57
+ resolve({
58
+ stdout,
59
+ error,
60
+ stderr
61
+ })
62
+ })
63
+ })
64
+ console.log({ response })
65
+ return response
66
+ }
67
+ }
@@ -1176,6 +1176,7 @@ class Api {
1176
1176
  // console.log("kernel.refresh after step")
1177
1177
  // this.kernel.refresh(true)
1178
1178
  } catch (e) {
1179
+ console.log("FUDKDFKSDFLJSDFHSDFLKHSDLFHSDFH", e)
1179
1180
  ondata({ raw: e.toString() })
1180
1181
  }
1181
1182
  }, concurrency)
package/kernel/plugin.js CHANGED
@@ -1,25 +1,55 @@
1
1
  const path = require('path')
2
+ const { glob } = require('glob')
3
+ const Info = require("./info")
2
4
  class Plugin {
3
5
  constructor(kernel) {
4
6
  this.kernel = kernel
5
7
  }
8
+ async setConfig() {
9
+ let plugin_dir = path.resolve(this.kernel.homedir, "plugin")
10
+ this.cache = {}
11
+
12
+ let plugin_paths = await glob('**/pinokio.js', { cwd: plugin_dir })
13
+
14
+ let plugins = []
15
+ for(let plugin_path of plugin_paths) {
16
+ let config = await this.kernel.require(path.resolve(plugin_dir, plugin_path))
17
+ let cwd = plugin_path.split("/").slice(0, -1).join("/")
18
+ config.image = "/asset/plugin/" + cwd + "/" + config.icon
19
+ plugins.push({
20
+ href: "/run/plugin/" + plugin_path,
21
+ ...config
22
+ })
23
+ }
24
+
25
+ this.config = {
26
+ menu: plugins.map((plugin) => {
27
+ plugin.text = plugin.title
28
+ return plugin
29
+ })
30
+ }
31
+ }
6
32
  async init() {
7
- if (this.kernel.bin.installed && this.kernel.bin.installed.conda && this.kernel.bin.installed.conda.has("git")) {
8
- // if ~/pinokio/prototype doesn't exist, clone
9
- let exists = await this.kernel.exists("plugin")
10
- if (!exists) {
33
+ let exists = await this.kernel.exists("plugin")
34
+ if (!exists) {
35
+ await fs.promises.mkdir(this.kernel.path("plugin"), { recursive: true }).catch((e) => {})
36
+ }
37
+ let code_exists = await this.kernel.exists("plugin/code")
38
+ if (!code_exists) {
39
+ if (this.kernel.bin.installed && this.kernel.bin.installed.conda && this.kernel.bin.installed.conda.has("git")) {
11
40
  await this.kernel.exec({
12
41
  //message: "git clone https://github.com/peanutcocktail/plugin",
13
- message: "git clone https://github.com/pinokiocomputer/plugin",
14
- path: this.kernel.homedir
42
+ //message: "git clone https://github.com/pinokiocomputer/plugin",
43
+ message: "git clone https://github.com/pinokiocomputer/code",
44
+ path: this.kernel.path("plugin")
15
45
  }, (e) => {
16
46
  process.stdout.write(e.raw)
17
47
  })
48
+ await this.setConfig()
49
+ return
18
50
  }
19
- let plugin_dir = path.resolve(this.kernel.homedir, "plugin")
20
- let pinokiojs = path.resolve(plugin_dir, "pinokio.js")
21
- this.config = await this.kernel.require(pinokiojs)
22
- this.cache = {}
51
+ } else {
52
+ await this.setConfig()
23
53
  }
24
54
  }
25
55
  async update() {
@@ -2,6 +2,7 @@ const fs = require('fs')
2
2
  const path = require('path')
3
3
  const { glob, sync, hasMagic } = require('glob-gitignore')
4
4
  const marked = require('marked')
5
+ const matter = require('gray-matter');
5
6
  class Proto {
6
7
  constructor(kernel) {
7
8
  this.kernel = kernel
@@ -49,6 +50,28 @@ class Proto {
49
50
  }
50
51
  console.log("Proto init done")
51
52
  }
53
+ async ai() {
54
+ let ai_path = this.kernel.path("prototype/system/ai/new/static")
55
+ let mds = await fs.promises.readdir(ai_path)
56
+ mds = mds.filter((md) => {
57
+ return md.endsWith(".md")
58
+ })
59
+ const results = []
60
+ for(let md of mds) {
61
+ let mdpath = path.resolve(ai_path, md)
62
+ let mdstr = await fs.promises.readFile(mdpath, "utf8")
63
+ const { data, content } = matter(mdstr)
64
+ // const html = marked.parse(content)
65
+ let { title, description, ...meta } = data
66
+ results.push({
67
+ title,
68
+ description,
69
+ meta,
70
+ content
71
+ })
72
+ }
73
+ return results
74
+ }
52
75
  async reset() {
53
76
  await fs.promises.rm(this.kernel.path("prototype"), { recursive: true })
54
77
  }
@@ -216,6 +216,19 @@ class Router {
216
216
  await this.fill()
217
217
  }
218
218
  }
219
+ this.config.apps.http.servers.main.routes.push({
220
+ "handle": [
221
+ {
222
+ "handler": "static_response",
223
+ "status_code": 302,
224
+ "headers": {
225
+ "Location": [
226
+ `https://${this.default_match}/launch?url={http.request.scheme}://{http.request.host}{http.request.uri}`
227
+ ]
228
+ }
229
+ }
230
+ ]
231
+ })
219
232
  this.mapping = this._mapping
220
233
  }
221
234
 
@@ -6,6 +6,17 @@ class LocalhostHomeRouter {
6
6
  this.router.add({ host: this.router.kernel.peer.host, dial: this.router.default_host + ":" + this.router.default_port, match: this.router.default_match })
7
7
  this.router.config = {
8
8
  "apps": {
9
+ "tls": {
10
+ "automation": {
11
+ "policies": [
12
+ {
13
+ "issuers": [{ "module": "internal" }],
14
+ "on_demand": true
15
+ }
16
+ ]
17
+ }
18
+ },
19
+
9
20
  "http": {
10
21
  "servers": {
11
22
  "main": {
package/kernel/shell.js CHANGED
@@ -16,6 +16,7 @@ const sudo = require("sudo-prompt-programfiles-x86");
16
16
  const unparse = require('yargs-unparser-custom-flag');
17
17
  const Util = require('./util')
18
18
  const Environment = require('./environment')
19
+ const ShellParser = require('./shell_parser')
19
20
  const home = os.homedir()
20
21
  class Shell {
21
22
  /*
@@ -1018,6 +1019,7 @@ class Shell {
1018
1019
  return params
1019
1020
  }
1020
1021
  async exec(params) {
1022
+ this.parser = new ShellParser()
1021
1023
  params = await this.activate(params)
1022
1024
  this.cmd = this.build(params)
1023
1025
  let res = await new Promise((resolve, reject) => {
@@ -1046,6 +1048,22 @@ class Shell {
1046
1048
  }
1047
1049
  this.monitor = this.monitor + data
1048
1050
  this.monitor = this.monitor.slice(-300) // last 300
1051
+
1052
+ let notifications = this.parser.processData(data)
1053
+ if (notifications.length > 0) {
1054
+ console.log({ notifications })
1055
+ for(let notif of notifications) {
1056
+ if (notif.type !== "bell") {
1057
+ Util.push({
1058
+ image: path.resolve(__dirname, "../server/public/pinokio-black.png"),
1059
+ message: notif.title,
1060
+ sound: true,
1061
+ timeout: 30,
1062
+ })
1063
+ }
1064
+ }
1065
+ }
1066
+
1049
1067
  if (!this.done) {
1050
1068
 
1051
1069
  // "request cursor position" handling: https://github.com/microsoft/node-pty/issues/535
@@ -1114,8 +1132,10 @@ class Shell {
1114
1132
  this.ptyProcess = undefined
1115
1133
  // automatically remove the shell from this.kernel.shells
1116
1134
  this.kernel.shell.rm(this.id)
1117
- this.ondata({ raw: `\r\n\r\n██ Terminated Shell ${this.id}\r\n████\r\n` })
1118
- this.ondata({ raw: "", type: "shell.kill" })
1135
+ if (!this.mute) {
1136
+ this.ondata({ raw: `\r\n\r\n██ Terminated Shell ${this.id}\r\n████\r\n` })
1137
+ this.ondata({ raw: "", type: "shell.kill" })
1138
+ }
1119
1139
  cb()
1120
1140
  } else {
1121
1141
  kill(this.ptyProcess.pid, "SIGKILL", true)
@@ -1123,13 +1143,17 @@ class Shell {
1123
1143
  this.ptyProcess = undefined
1124
1144
  // automatically remove the shell from this.kernel.shells
1125
1145
  this.kernel.shell.rm(this.id)
1126
- this.ondata({ raw: `\r\n\r\n██ Terminated Shell ${this.id}\r\n████\r\n` })
1127
- this.ondata({ raw: "", type: "shell.kill" })
1146
+ if (!this.mute) {
1147
+ this.ondata({ raw: `\r\n\r\n██ Terminated Shell ${this.id}\r\n████\r\n` })
1148
+ this.ondata({ raw: "", type: "shell.kill" })
1149
+ }
1128
1150
  }
1129
1151
  } else {
1130
1152
  this.kernel.shell.rm(this.id)
1131
- this.ondata({ raw: `\r\n\r\n██ Terminated Shell ${this.id}\r\n████\r\n` })
1132
- this.ondata({ raw: "", type: "shell.kill" })
1153
+ if (!this.mute) {
1154
+ this.ondata({ raw: `\r\n\r\n██ Terminated Shell ${this.id}\r\n████\r\n` })
1155
+ this.ondata({ raw: "", type: "shell.kill" })
1156
+ }
1133
1157
  }
1134
1158
 
1135
1159
 
@@ -0,0 +1,267 @@
1
+ class ShellParser {
2
+ constructor() {
3
+ this.buffer = '';
4
+ this.bufferMaxSize = 2000;
5
+ this.lastNotificationTime = 0;
6
+ this.debounceMs = 1000; // Prevent spam
7
+ }
8
+
9
+ /**
10
+ * Process terminal data and detect notification patterns
11
+ * @param {string} data - Raw terminal data chunk
12
+ * @returns {Array} Array of notification objects or empty array
13
+ */
14
+ processData(data) {
15
+ // Add to buffer
16
+ this.buffer += data;
17
+
18
+ // Keep buffer manageable
19
+ if (this.buffer.length > this.bufferMaxSize) {
20
+ this.buffer = this.buffer.slice(-this.bufferMaxSize);
21
+ }
22
+
23
+ const notifications = [];
24
+
25
+ // Check for each pattern type
26
+ // notifications.push(...this.detectBell(data));
27
+ notifications.push(...this.detectEscapeSequences(data));
28
+ // notifications.push(...this.detectTextPatterns(this.buffer));
29
+
30
+ // Apply debouncing and return
31
+ return this.debounceNotifications(notifications);
32
+ }
33
+
34
+ /**
35
+ * Detect bell character (ASCII 7)
36
+ */
37
+ detectBell(data) {
38
+ const notifications = [];
39
+ if (data.includes('\x07')) {
40
+ notifications.push({
41
+ type: 'bell',
42
+ title: 'Terminal Bell',
43
+ message: 'Bell character detected',
44
+ timestamp: Date.now()
45
+ });
46
+ }
47
+ return notifications;
48
+ }
49
+
50
+ /**
51
+ * Detect iTerm2-style escape sequences
52
+ * Format: \x1b]9;[title];[message]\x07
53
+ */
54
+ //detectEscapeSequences(data) {
55
+ // const notifications = [];
56
+ //
57
+ // // iTerm2 notification escape sequence
58
+ // const escapeRegex = /\x1b\]9;([^;]*);?([^\x07]*)\x07/g;
59
+ // let match;
60
+ //
61
+ // while ((match = escapeRegex.exec(data)) !== null) {
62
+ // notifications.push({
63
+ // type: 'escape_sequence',
64
+ // title: match[1] || 'Terminal Notification',
65
+ // message: match[2] || 'Notification from terminal',
66
+ // timestamp: Date.now()
67
+ // });
68
+ // }
69
+
70
+ // // Generic OSC (Operating System Command) sequences
71
+ // const oscRegex = /\x1b\]([0-9]+);([^\x07\x1b]*)\x07/g;
72
+ // while ((match = oscRegex.exec(data)) !== null) {
73
+ // if (match[1] === '9') { // Notification OSC
74
+ // notifications.push({
75
+ // type: 'osc_notification',
76
+ // title: 'System Notification',
77
+ // message: match[2] || 'OSC notification',
78
+ // timestamp: Date.now()
79
+ // });
80
+ // }
81
+ // }
82
+
83
+ // return notifications;
84
+ //}
85
+ detectEscapeSequences(data) {
86
+ const notifications = [];
87
+
88
+ // OSC 9 - Notification sequences (used by iTerm2 and others)
89
+ const notificationRegex = /\x1b\]9;([^;]*);?([^\x07]*)\x07/g;
90
+ let match;
91
+
92
+ while ((match = notificationRegex.exec(data)) !== null) {
93
+ notifications.push({
94
+ type: 'notification_escape',
95
+ title: match[1] || 'Terminal Notification',
96
+ message: match[2] || 'Notification from terminal',
97
+ timestamp: Date.now()
98
+ });
99
+ }
100
+
101
+ // Other OSC sequences (window title, etc.) - usually not notifications
102
+ const otherOscRegex = /\x1b\]([0-8]|[1-9][0-9]+);([^\x07\x1b]*)\x07/g;
103
+ while ((match = otherOscRegex.exec(data)) !== null) {
104
+ // Only log these for debugging, don't treat as notifications
105
+ console.debug(`OSC ${match[1]}: ${match[2]}`);
106
+ }
107
+
108
+ return notifications;
109
+ }
110
+
111
+ /**
112
+ * Detect text patterns that might indicate notifications
113
+ */
114
+ detectTextPatterns(buffer) {
115
+ const notifications = [];
116
+
117
+ const patterns = [
118
+ {
119
+ regex: /\b(?:alert|notification|notify|attention)\b[^\n\r]*$/im,
120
+ type: 'text_alert',
121
+ title: 'Alert Detected',
122
+ extractMessage: true
123
+ },
124
+ {
125
+ regex: /\berror\b[^\n\r]*$/im,
126
+ type: 'error',
127
+ title: 'Error Detected',
128
+ extractMessage: true
129
+ },
130
+ {
131
+ regex: /\b(?:completed|finished|done|success)\b[^\n\r]*$/im,
132
+ type: 'completion',
133
+ title: 'Task Complete',
134
+ extractMessage: true
135
+ },
136
+ {
137
+ regex: /claude\s+code.*?(?:completed|finished|ready|done)[^\n\r]*$/im,
138
+ type: 'claude_code',
139
+ title: 'Claude Code',
140
+ extractMessage: true
141
+ },
142
+ {
143
+ regex: /✅[^\n\r]*$/im,
144
+ type: 'success_emoji',
145
+ title: 'Success',
146
+ extractMessage: true
147
+ },
148
+ {
149
+ regex: /❌[^\n\r]*$/im,
150
+ type: 'error_emoji',
151
+ title: 'Error',
152
+ extractMessage: true
153
+ },
154
+ {
155
+ regex: /🔔[^\n\r]*$/im,
156
+ type: 'notification_emoji',
157
+ title: 'Notification',
158
+ extractMessage: true
159
+ }
160
+ ];
161
+
162
+ patterns.forEach(pattern => {
163
+ const match = buffer.match(pattern.regex);
164
+ if (match) {
165
+ let message = pattern.extractMessage ?
166
+ match[0].trim().slice(0, 100) :
167
+ `${pattern.title} detected`;
168
+
169
+ // Clean up ANSI escape codes from message
170
+ message = this.stripAnsiCodes(message);
171
+
172
+ notifications.push({
173
+ type: pattern.type,
174
+ title: pattern.title,
175
+ message: message,
176
+ timestamp: Date.now(),
177
+ rawMatch: match[0]
178
+ });
179
+ }
180
+ });
181
+
182
+ return notifications;
183
+ }
184
+
185
+ /**
186
+ * Remove ANSI escape codes from text
187
+ */
188
+ stripAnsiCodes(text) {
189
+ return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
190
+ }
191
+
192
+ /**
193
+ * Apply debouncing to prevent notification spam
194
+ */
195
+ debounceNotifications(notifications) {
196
+ const now = Date.now();
197
+ if (notifications.length > 0 && (now - this.lastNotificationTime) > this.debounceMs) {
198
+ this.lastNotificationTime = now;
199
+ return notifications;
200
+ }
201
+ return [];
202
+ }
203
+
204
+ /**
205
+ * Reset the internal state
206
+ */
207
+ reset() {
208
+ this.buffer = '';
209
+ this.lastNotificationTime = 0;
210
+ }
211
+
212
+ /**
213
+ * Add custom pattern
214
+ */
215
+ addCustomPattern(regex, type, title) {
216
+ // This would require refactoring detectTextPatterns to use a dynamic array
217
+ // For now, patterns are hardcoded above
218
+ }
219
+ }
220
+
221
+ // Usage example:
222
+ /*
223
+ const detector = new TerminalNotificationDetector();
224
+
225
+ // Simulate terminal data chunks
226
+ const testData = [
227
+ 'Running command...\n',
228
+ 'Alert: Process completed successfully\x07',
229
+ '\x1b]9;Claude Code;File created successfully\x07',
230
+ 'Error: File not found\n',
231
+ '✅ Build completed\n'
232
+ ];
233
+
234
+ testData.forEach(chunk => {
235
+ const notifications = detector.processData(chunk);
236
+ notifications.forEach(notif => {
237
+ console.log(`[${notif.type}] ${notif.title}: ${notif.message}`);
238
+ });
239
+ });
240
+ */
241
+ module.exports = ShellParser
242
+
243
+ // Example integration with node-pty:
244
+ /*
245
+ const pty = require('node-pty');
246
+ const detector = new TerminalNotificationDetector();
247
+
248
+ const ptyProcess = pty.spawn('bash', [], {
249
+ name: 'xterm-color',
250
+ cols: 80,
251
+ rows: 30,
252
+ cwd: process.cwd(),
253
+ env: process.env
254
+ });
255
+
256
+ ptyProcess.on('data', (data) => {
257
+ // Forward to stdout
258
+ process.stdout.write(data);
259
+
260
+ // Check for notifications
261
+ const notifications = detector.processData(data);
262
+ notifications.forEach(notif => {
263
+ console.log(`\n🔔 [${notif.type}] ${notif.title}: ${notif.message}\n`);
264
+ // Here you could send to notification system, websocket, etc.
265
+ });
266
+ });
267
+ */
package/kernel/shells.js CHANGED
@@ -142,59 +142,66 @@ class Shells {
142
142
  }
143
143
  }
144
144
  */
145
- if (params.on && Array.isArray(params.on)) {
146
- for(let i=0; i<params.on.length; i++) {
147
- let handler = params.on[i]
148
- // regexify
149
- //let matches = /^\/([^\/]+)\/([dgimsuy]*)$/.exec(handler.event)
150
- if (handler.event) {
151
- if (handler.notify) {
152
- // notify is a special case. check by line
153
- let matches = /^\/(.+)\/([dgimsuy]*)$/gs.exec(handler.event)
154
- if (!/g/.test(matches[2])) {
155
- matches[2] += "g" // if g option is not included, include it (need it for matchAll)
156
- }
157
- let re = new RegExp(matches[1], matches[2])
158
- let test = re.exec(sh.monitor)
159
- if (test && test.length > 0) {
160
- // reset monitor
161
- sh.monitor = ""
162
- let params = this.kernel.template.render(handler.notify, { event: test })
163
- if (params.image) {
164
- params.contentImage = path.resolve(req.cwd, params.image)
145
+ try {
146
+ if (params.on && Array.isArray(params.on)) {
147
+ for(let i=0; i<params.on.length; i++) {
148
+ let handler = params.on[i]
149
+ // regexify
150
+ //let matches = /^\/([^\/]+)\/([dgimsuy]*)$/.exec(handler.event)
151
+ if (handler.event) {
152
+ if (handler.notify) {
153
+ // notify is a special case. check by line
154
+ let matches = /^\/(.+)\/([dgimsuy]*)$/gs.exec(handler.event)
155
+ if (!/g/.test(matches[2])) {
156
+ matches[2] += "g" // if g option is not included, include it (need it for matchAll)
165
157
  }
166
- Util.push(params)
167
- }
168
- } else {
169
- let matches = /^\/(.+)\/([dgimsuy]*)$/gs.exec(handler.event)
170
- if (!/g/.test(matches[2])) {
171
- matches[2] += "g" // if g option is not included, include it (need it for matchAll)
172
- }
173
- let re = new RegExp(matches[1], matches[2])
174
- if (stream.cleaned) {
175
- let line = stream.cleaned.replaceAll(/[\r\n]/g, "")
176
- let rendered_event = [...line.matchAll(re)]
177
- // 3. if the rendered expression is truthy, run the "run" script
178
- if (rendered_event.length > 0) {
179
- stream.matches = rendered_event
180
- if (handler.kill) {
181
- m = rendered_event[0]
182
- matched_index = i
183
- sh.kill()
158
+ let re = new RegExp(matches[1], matches[2])
159
+ let test = re.exec(sh.monitor)
160
+ if (test && test.length > 0) {
161
+ // reset monitor
162
+ sh.monitor = ""
163
+ let params = this.kernel.template.render(handler.notify, { event: test })
164
+ if (params.image) {
165
+ params.contentImage = path.resolve(req.cwd, params.image)
184
166
  }
185
- if (handler.done) {
186
- m = rendered_event[0]
187
- matched_index = i
188
- sh.continue()
167
+ Util.push(params)
168
+ }
169
+ } else {
170
+ let matches = /^\/(.+)\/([dgimsuy]*)$/gs.exec(handler.event)
171
+ if (!/g/.test(matches[2])) {
172
+ matches[2] += "g" // if g option is not included, include it (need it for matchAll)
173
+ }
174
+ let re = new RegExp(matches[1], matches[2])
175
+ if (stream.cleaned) {
176
+ let line = stream.cleaned.replaceAll(/[\r\n]/g, "")
177
+ let rendered_event = [...line.matchAll(re)]
178
+ // 3. if the rendered expression is truthy, run the "run" script
179
+ if (rendered_event.length > 0) {
180
+ stream.matches = rendered_event
181
+ if (handler.kill) {
182
+ m = rendered_event[0]
183
+ matched_index = i
184
+ sh.kill()
185
+ }
186
+ if (handler.done) {
187
+ m = rendered_event[0]
188
+ matched_index = i
189
+ sh.continue()
190
+ }
189
191
  }
190
192
  }
191
193
  }
192
194
  }
193
195
  }
194
196
  }
195
- }
196
- if (ondata) {
197
- ondata(stream)
197
+ if (ondata) {
198
+ ondata(stream)
199
+ }
200
+ } catch (e) {
201
+ console.log("Capture error", e)
202
+ ondata({ raw: e.stack })
203
+ sh.mute = true
204
+ sh.kill()
198
205
  }
199
206
  })
200
207
  /*
package/kernel/util.js CHANGED
@@ -510,7 +510,11 @@ function p2u(localPath) {
510
510
  const path = match[2].replace(/\\/g, '/');
511
511
  return `/${drive}/${path}`;
512
512
  } else {
513
- return `${localPath}`;
513
+ if (localPath.startsWith("/")) {
514
+ return localPath.slice(1)
515
+ } else {
516
+ return localPath
517
+ }
514
518
  }
515
519
  }
516
520
  function u2p(urlPath) {