pinokiod 3.22.0 → 3.24.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/plugin.js CHANGED
@@ -1,26 +1,73 @@
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
  }
6
8
  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) {
9
+ let exists = await this.kernel.exists("plugin")
10
+ if (!exists) {
11
+ await fs.promises.mkdir(this.kernel.path("plugin"), { recursive: true }).catch((e) => {})
12
+ if (this.kernel.bin.installed && this.kernel.bin.installed.conda && this.kernel.bin.installed.conda.has("git")) {
11
13
  await this.kernel.exec({
12
14
  //message: "git clone https://github.com/peanutcocktail/plugin",
13
- message: "git clone https://github.com/pinokiocomputer/plugin",
14
- path: this.kernel.homedir
15
+ //message: "git clone https://github.com/pinokiocomputer/plugin",
16
+ message: "git clone https://github.com/pinokiocomputer/code",
17
+ path: this.kernel.path("plugin")
15
18
  }, (e) => {
16
19
  process.stdout.write(e.raw)
17
20
  })
18
21
  }
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 = {}
23
22
  }
23
+ let plugin_dir = path.resolve(this.kernel.homedir, "plugin")
24
+ // this.config = await this.kernel.require(pinokiojs)
25
+ this.cache = {}
26
+
27
+ let plugin_paths = await glob('**/pinokio.js', { cwd: plugin_dir })
28
+ console.log({ plugin_paths })
29
+
30
+ let plugins = []
31
+ // let info = new Info(this.kernel)
32
+ // info.cwd = () => {
33
+ // return plugin_dir
34
+ // }
35
+ for(let plugin_path of plugin_paths) {
36
+ let config = await this.kernel.require(path.resolve(plugin_dir, plugin_path))
37
+ let cwd = plugin_path.split("/").slice(0, -1).join("/")
38
+ config.image = "/asset/plugin/" + cwd + "/" + config.icon
39
+ plugins.push({
40
+ href: "/run/plugin/" + plugin_path,
41
+ ...config
42
+ })
43
+ }
44
+
45
+ this.config = {
46
+ menu: plugins.map((plugin) => {
47
+ plugin.text = plugin.title
48
+ return plugin
49
+ })
50
+ }
51
+ console.log("THIS.config", JSON.stringify(this.config, null, 2))
52
+
53
+ // if (this.kernel.bin.installed && this.kernel.bin.installed.conda && this.kernel.bin.installed.conda.has("git")) {
54
+ // // if ~/pinokio/prototype doesn't exist, clone
55
+ // let exists = await this.kernel.exists("plugin")
56
+ // if (!exists) {
57
+ // await this.kernel.exec({
58
+ // //message: "git clone https://github.com/peanutcocktail/plugin",
59
+ // //message: "git clone https://github.com/pinokiocomputer/plugin",
60
+ // message: "git clone https://github.com/pinokiocomputer/code",
61
+ // path: this.kernel.homedir
62
+ // }, (e) => {
63
+ // process.stdout.write(e.raw)
64
+ // })
65
+ // }
66
+ // let plugin_dir = path.resolve(this.kernel.homedir, "plugin")
67
+ // let pinokiojs = path.resolve(plugin_dir, "pinokio.js")
68
+ // this.config = await this.kernel.require(pinokiojs)
69
+ // this.cache = {}
70
+ // }
24
71
  }
25
72
  async update() {
26
73
  if (this.kernel.bin.installed && this.kernel.bin.installed.conda && this.kernel.bin.installed.conda.has("git")) {
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
  /*
@@ -208,6 +209,7 @@ class Shell {
208
209
  }
209
210
  }
210
211
  async start(params, ondata) {
212
+ console.log("SHELL START", params)
211
213
  this.ondata = ondata
212
214
 
213
215
  /*
@@ -1018,6 +1020,7 @@ class Shell {
1018
1020
  return params
1019
1021
  }
1020
1022
  async exec(params) {
1023
+ this.parser = new ShellParser()
1021
1024
  params = await this.activate(params)
1022
1025
  this.cmd = this.build(params)
1023
1026
  let res = await new Promise((resolve, reject) => {
@@ -1046,6 +1049,24 @@ class Shell {
1046
1049
  }
1047
1050
  this.monitor = this.monitor + data
1048
1051
  this.monitor = this.monitor.slice(-300) // last 300
1052
+
1053
+ let notifications = this.parser.processData(data)
1054
+ if (notifications.length > 0) {
1055
+ console.log({ notifications })
1056
+ for(let notif of notifications) {
1057
+ if (notif.type !== "bell") {
1058
+ Util.push({
1059
+ // title: notif.type,
1060
+ // subtitle: notif.title,
1061
+ image: path.resolve(__dirname, "../server/public/pinokio-black.png"),
1062
+ message: notif.title,//`šŸ”” [${notif.type}] ${notif.title}: ${notif.message}\n`,
1063
+ sound: true,
1064
+ timeout: 30,
1065
+ })
1066
+ }
1067
+ }
1068
+ }
1069
+
1049
1070
  if (!this.done) {
1050
1071
 
1051
1072
  // "request cursor position" handling: https://github.com/microsoft/node-pty/issues/535
@@ -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
@@ -490,6 +490,8 @@ class Shells {
490
490
 
491
491
  if (request.id) {
492
492
 
493
+ console.log("this.shells", this.shells.map((x) => { return x.id }))
494
+
493
495
  let shells = []
494
496
  for(let i=0; i<this.shells.length; i++) {
495
497
  shells.push(this.shells[i])
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "3.22.0",
3
+ "version": "3.24.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {