pinokiod 3.270.0 → 3.272.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.
Files changed (56) hide show
  1. package/kernel/ansi_stream_tracker.js +115 -0
  2. package/kernel/api/app/index.js +422 -0
  3. package/kernel/api/htmlmodal/index.js +94 -0
  4. package/kernel/app_launcher/index.js +115 -0
  5. package/kernel/app_launcher/platform/base.js +276 -0
  6. package/kernel/app_launcher/platform/linux.js +229 -0
  7. package/kernel/app_launcher/platform/macos.js +163 -0
  8. package/kernel/app_launcher/platform/unsupported.js +34 -0
  9. package/kernel/app_launcher/platform/windows.js +247 -0
  10. package/kernel/bin/conda-meta.js +93 -0
  11. package/kernel/bin/conda.js +2 -4
  12. package/kernel/bin/index.js +2 -4
  13. package/kernel/index.js +9 -2
  14. package/kernel/peer.js +186 -19
  15. package/kernel/shell.js +212 -1
  16. package/package.json +1 -1
  17. package/server/index.js +491 -6
  18. package/server/public/common.js +224 -741
  19. package/server/public/create-launcher.js +754 -0
  20. package/server/public/htmlmodal.js +292 -0
  21. package/server/public/logs.js +715 -0
  22. package/server/public/resizeSync.js +117 -0
  23. package/server/public/style.css +651 -6
  24. package/server/public/tab-idle-notifier.js +34 -59
  25. package/server/public/tab-link-popover.js +7 -10
  26. package/server/public/terminal-settings.js +723 -9
  27. package/server/public/terminal_input_utils.js +72 -0
  28. package/server/public/terminal_key_caption.js +187 -0
  29. package/server/public/urldropdown.css +120 -3
  30. package/server/public/xterm-inline-bridge.js +116 -0
  31. package/server/socket.js +29 -0
  32. package/server/views/agents.ejs +1 -2
  33. package/server/views/app.ejs +55 -28
  34. package/server/views/bookmarklet.ejs +1 -1
  35. package/server/views/bootstrap.ejs +1 -0
  36. package/server/views/connect.ejs +1 -2
  37. package/server/views/create.ejs +63 -0
  38. package/server/views/editor.ejs +36 -4
  39. package/server/views/index.ejs +1 -2
  40. package/server/views/index2.ejs +1 -2
  41. package/server/views/init/index.ejs +36 -28
  42. package/server/views/install.ejs +20 -22
  43. package/server/views/layout.ejs +2 -8
  44. package/server/views/logs.ejs +155 -0
  45. package/server/views/mini.ejs +0 -18
  46. package/server/views/net.ejs +2 -2
  47. package/server/views/network.ejs +1 -2
  48. package/server/views/network2.ejs +1 -2
  49. package/server/views/old_network.ejs +1 -2
  50. package/server/views/pro.ejs +26 -23
  51. package/server/views/prototype/index.ejs +30 -34
  52. package/server/views/screenshots.ejs +1 -2
  53. package/server/views/settings.ejs +1 -20
  54. package/server/views/shell.ejs +59 -66
  55. package/server/views/terminal.ejs +118 -73
  56. package/server/views/tools.ejs +1 -2
@@ -0,0 +1,115 @@
1
+ const os = require('os')
2
+
3
+ const PLATFORM_MODULE = {
4
+ darwin: './platform/macos',
5
+ win32: './platform/windows',
6
+ linux: './platform/linux'
7
+ }
8
+
9
+ class AppLauncher {
10
+ constructor(kernel) {
11
+ this.kernel = kernel
12
+ this.platform = os.platform()
13
+ this.adapter = null
14
+ }
15
+
16
+ async init() {
17
+ try {
18
+ const adapter = await this.ensureAdapter()
19
+ if (adapter && adapter.ensureIndex) {
20
+ await adapter.ensureIndex({ force: false })
21
+ }
22
+ } catch (error) {
23
+ console.warn('[AppLauncher] init failed:', error.message)
24
+ }
25
+ }
26
+
27
+ resolveAdapterModule() {
28
+ return PLATFORM_MODULE[this.platform] || './platform/unsupported'
29
+ }
30
+
31
+ async ensureAdapter() {
32
+ if (this.adapter) {
33
+ return this.adapter
34
+ }
35
+ const modulePath = this.resolveAdapterModule()
36
+ const Adapter = require(modulePath)
37
+ this.adapter = new Adapter(this.kernel)
38
+ return this.adapter
39
+ }
40
+
41
+ async search(params = {}) {
42
+ const adapter = await this.ensureAdapter()
43
+ return adapter.search(params)
44
+ }
45
+
46
+ async info(params = {}) {
47
+ if (!params || !params.id) {
48
+ throw new Error('app.info requires params.id')
49
+ }
50
+ const adapter = await this.ensureAdapter()
51
+ return adapter.info(params.id, { force: params.refresh })
52
+ }
53
+
54
+ async findMatch(query, options = {}) {
55
+ if (!query) {
56
+ return null
57
+ }
58
+ const adapter = await this.ensureAdapter()
59
+ return adapter.findMatch(query, options)
60
+ }
61
+
62
+ async refresh(params = {}) {
63
+ const adapter = await this.ensureAdapter()
64
+ return adapter.refresh(params)
65
+ }
66
+
67
+ async launch(params = {}) {
68
+ if (!params || (!params.id && !params.app)) {
69
+ throw new Error('app.launch requires params.app or params.id')
70
+ }
71
+ const adapter = await this.ensureAdapter()
72
+ if (params.refresh) {
73
+ await adapter.ensureIndex({ force: true })
74
+ } else {
75
+ await adapter.ensureIndex({ force: false })
76
+ }
77
+ let entry = null
78
+ let confidence = null
79
+ if (params.id) {
80
+ entry = adapter.getEntry(params.id)
81
+ }
82
+ if (!entry && params.app) {
83
+ const match = await adapter.findMatch(params.app, { force: false })
84
+ if (match) {
85
+ entry = match.entry
86
+ confidence = match.score
87
+ }
88
+ }
89
+ if (!entry) {
90
+ if (!params.install && adapter && typeof adapter.launchUnknown === 'function') {
91
+ return adapter.launchUnknown(params)
92
+ }
93
+ const error = new Error(params.app ? `Application "${params.app}" was not found` : 'Application not found')
94
+ error.code = 'APP_NOT_FOUND'
95
+ error.app = params.app || params.id
96
+ throw error
97
+ }
98
+ const result = await adapter.launch(entry, params)
99
+ if (result && typeof result === 'object') {
100
+ const response = Object.assign({}, result)
101
+ response.id = response.id || entry.id
102
+ response.name = response.name || entry.name
103
+ response.platform = response.platform || entry.platform || this.platform
104
+ response.kind = response.kind || entry.kind
105
+ response.detail = response.detail || entry.detail
106
+ if (confidence !== null && typeof confidence !== 'undefined') {
107
+ response.confidence = confidence
108
+ }
109
+ return response
110
+ }
111
+ return result
112
+ }
113
+ }
114
+
115
+ module.exports = AppLauncher
@@ -0,0 +1,276 @@
1
+ const { spawn } = require('child_process')
2
+ const os = require('os')
3
+
4
+ class BasePlatformAdapter {
5
+ constructor(kernel) {
6
+ this.kernel = kernel
7
+ this.platform = os.platform()
8
+ this.entries = new Map()
9
+ this.indexTimestamp = 0
10
+ this.indexTTL = 1000 * 60 * 5
11
+ this.indexPromise = null
12
+ }
13
+
14
+ async ensureIndex(options = {}) {
15
+ const force = Boolean(options.force)
16
+ const now = Date.now()
17
+ const expired = !this.indexTimestamp || (now - this.indexTimestamp > this.indexTTL)
18
+ if (!force && this.entries.size > 0 && !expired) {
19
+ return
20
+ }
21
+ if (this.indexPromise) {
22
+ return this.indexPromise
23
+ }
24
+ this.indexPromise = (async () => {
25
+ try {
26
+ await this.buildIndex()
27
+ this.indexTimestamp = Date.now()
28
+ } finally {
29
+ this.indexPromise = null
30
+ }
31
+ })()
32
+ return this.indexPromise
33
+ }
34
+
35
+ // subclasses must override
36
+ async buildIndex() {
37
+ throw new Error('buildIndex() not implemented')
38
+ }
39
+
40
+ listEntries() {
41
+ return Array.from(this.entries.values())
42
+ }
43
+
44
+ getEntry(id) {
45
+ if (!id) {
46
+ return null
47
+ }
48
+ return this.entries.get(id)
49
+ }
50
+
51
+ addEntry(entry) {
52
+ if (!entry || !entry.id) {
53
+ return
54
+ }
55
+ const aliases = Array.isArray(entry.aliases) ? entry.aliases.filter(Boolean) : []
56
+ const uniqueAliases = Array.from(new Set([entry.name, ...aliases].filter(Boolean)))
57
+ entry.aliases = uniqueAliases
58
+ entry.platform = entry.platform || this.platform
59
+ this.entries.set(entry.id, entry)
60
+ }
61
+
62
+ normalize(text) {
63
+ return (text || '').trim().toLowerCase()
64
+ }
65
+
66
+ scoreEntry(query, entry) {
67
+ if (!query) {
68
+ return 0
69
+ }
70
+ const candidates = new Set()
71
+ if (entry.name) {
72
+ candidates.add(entry.name)
73
+ }
74
+ if (entry.aliases) {
75
+ for (const alias of entry.aliases) {
76
+ candidates.add(alias)
77
+ }
78
+ }
79
+ if (entry.id) {
80
+ candidates.add(entry.id)
81
+ }
82
+ if (entry.detail) {
83
+ candidates.add(entry.detail)
84
+ }
85
+ if (entry.bundleId) {
86
+ candidates.add(entry.bundleId)
87
+ }
88
+ if (entry.desktopId) {
89
+ candidates.add(entry.desktopId)
90
+ }
91
+ let best = Number.POSITIVE_INFINITY
92
+ for (const candidate of candidates) {
93
+ const normalized = this.normalize(candidate)
94
+ if (!normalized) {
95
+ continue
96
+ }
97
+ if (normalized === query) {
98
+ return 0
99
+ }
100
+ if (normalized.startsWith(query)) {
101
+ best = Math.min(best, 1)
102
+ } else if (normalized.includes(query)) {
103
+ best = Math.min(best, 2)
104
+ }
105
+ }
106
+ if (best < Number.POSITIVE_INFINITY) {
107
+ return best
108
+ }
109
+ return best
110
+ }
111
+
112
+ formatEntry(entry) {
113
+ if (!entry) {
114
+ return null
115
+ }
116
+ const payload = {
117
+ id: entry.id,
118
+ name: entry.name,
119
+ platform: entry.platform || this.platform,
120
+ kind: entry.kind || 'app'
121
+ }
122
+ if (entry.aliases && entry.aliases.length > 0) {
123
+ payload.aliases = entry.aliases
124
+ }
125
+ if (entry.detail) {
126
+ payload.detail = entry.detail
127
+ }
128
+ const meta = {}
129
+ if (entry.bundleId) {
130
+ meta.bundleId = entry.bundleId
131
+ }
132
+ if (entry.desktopId) {
133
+ meta.desktopId = entry.desktopId
134
+ }
135
+ if (entry.appId) {
136
+ meta.appId = entry.appId
137
+ }
138
+ if (entry.path) {
139
+ meta.path = entry.path
140
+ }
141
+ if (entry.execPath) {
142
+ meta.execPath = entry.execPath
143
+ }
144
+ if (Object.keys(meta).length > 0) {
145
+ payload.meta = meta
146
+ }
147
+ return payload
148
+ }
149
+
150
+ sanitizeArgs(args) {
151
+ if (!args) {
152
+ return []
153
+ }
154
+ if (!Array.isArray(args)) {
155
+ return [String(args)]
156
+ }
157
+ return args
158
+ .filter((arg) => typeof arg !== 'undefined' && arg !== null)
159
+ .map((arg) => String(arg))
160
+ }
161
+
162
+ async search(options = {}) {
163
+ const { query = '', limit = 20, refresh = false } = options
164
+ await this.ensureIndex({ force: refresh })
165
+ const normalized = this.normalize(query)
166
+ let entries = this.listEntries()
167
+ if (!normalized) {
168
+ entries = entries.sort((a, b) => a.name.localeCompare(b.name))
169
+ return entries.slice(0, limit).map((entry) => this.formatEntry(entry))
170
+ }
171
+ const scored = []
172
+ for (const entry of entries) {
173
+ const score = this.scoreEntry(normalized, entry)
174
+ if (!Number.isFinite(score)) {
175
+ continue
176
+ }
177
+ if (score === Number.POSITIVE_INFINITY) {
178
+ continue
179
+ }
180
+ scored.push({ entry, score })
181
+ }
182
+ scored.sort((a, b) => {
183
+ if (a.score !== b.score) {
184
+ return a.score - b.score
185
+ }
186
+ return a.entry.name.localeCompare(b.entry.name)
187
+ })
188
+ return scored.slice(0, limit).map((item) => this.formatEntry(item.entry))
189
+ }
190
+
191
+ async findMatch(name, options = {}) {
192
+ const normalized = this.normalize(name)
193
+ if (!normalized) {
194
+ return null
195
+ }
196
+ await this.ensureIndex({ force: options.force })
197
+ let best = null
198
+ let bestScore = Number.POSITIVE_INFINITY
199
+ for (const entry of this.entries.values()) {
200
+ const score = this.scoreEntry(normalized, entry)
201
+ if (score < bestScore) {
202
+ best = entry
203
+ bestScore = score
204
+ if (score === 0) {
205
+ break
206
+ }
207
+ }
208
+ }
209
+ if (!best) {
210
+ return null
211
+ }
212
+ return { entry: best, score: bestScore }
213
+ }
214
+
215
+ async info(id, options = {}) {
216
+ await this.ensureIndex({ force: options.force })
217
+ const entry = this.getEntry(id)
218
+ return this.formatEntry(entry)
219
+ }
220
+
221
+ async refresh() {
222
+ await this.ensureIndex({ force: true })
223
+ return {
224
+ entries: this.entries.size,
225
+ platform: this.platform,
226
+ updatedAt: this.indexTimestamp
227
+ }
228
+ }
229
+
230
+ async launch() {
231
+ throw new Error('launch() not implemented')
232
+ }
233
+
234
+ async launchUnknown(params = {}) {
235
+ if (!params.app) {
236
+ throw new Error('Application not found')
237
+ }
238
+ throw new Error(`Application "${params.app}" was not found in the app index`)
239
+ }
240
+
241
+ spawnDetached(command, args, options = {}) {
242
+ const spawnOptions = Object.assign({
243
+ detached: true,
244
+ stdio: 'ignore',
245
+ windowsHide: true
246
+ }, options || {})
247
+ return new Promise((resolve, reject) => {
248
+ let child
249
+ try {
250
+ child = spawn(command, args, spawnOptions)
251
+ } catch (error) {
252
+ error.command = command
253
+ error.args = args
254
+ return reject(error)
255
+ }
256
+ let resolved = false
257
+ child.once('error', (err) => {
258
+ if (!resolved) {
259
+ err.command = command
260
+ err.args = args
261
+ reject(err)
262
+ }
263
+ })
264
+ child.once('spawn', () => {
265
+ resolved = true
266
+ try {
267
+ child.unref()
268
+ } catch (_) {
269
+ }
270
+ resolve({ pid: child.pid, command, args })
271
+ })
272
+ })
273
+ }
274
+ }
275
+
276
+ module.exports = BasePlatformAdapter
@@ -0,0 +1,229 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const os = require('os')
4
+ const BaseAdapter = require('./base')
5
+
6
+ const DESKTOP_DIRS = [
7
+ '/usr/share/applications',
8
+ '/usr/local/share/applications',
9
+ '/var/lib/snapd/desktop/applications',
10
+ path.join(os.homedir(), '.local/share/applications')
11
+ ]
12
+
13
+ class LinuxAdapter extends BaseAdapter {
14
+ constructor(kernel) {
15
+ super(kernel)
16
+ this.desktopDirs = DESKTOP_DIRS
17
+ }
18
+
19
+ async buildIndex() {
20
+ this.entries.clear()
21
+ for (const dir of this.desktopDirs) {
22
+ await this.collectDesktopFiles(dir)
23
+ }
24
+ }
25
+
26
+ async collectDesktopFiles(root) {
27
+ if (!root) {
28
+ return
29
+ }
30
+ let entries
31
+ try {
32
+ entries = await fs.promises.readdir(root, { withFileTypes: true })
33
+ } catch (_) {
34
+ return
35
+ }
36
+ for (const entry of entries) {
37
+ const fullPath = path.join(root, entry.name)
38
+ if (entry.isDirectory()) {
39
+ await this.collectDesktopFiles(fullPath)
40
+ continue
41
+ }
42
+ if (!entry.isFile() || !entry.name.endsWith('.desktop')) {
43
+ continue
44
+ }
45
+ await this.addDesktopEntry(fullPath)
46
+ }
47
+ }
48
+
49
+ async addDesktopEntry(filePath) {
50
+ let content
51
+ try {
52
+ content = await fs.promises.readFile(filePath, 'utf8')
53
+ } catch (_) {
54
+ return
55
+ }
56
+ const data = this.parseDesktopFile(content)
57
+ const name = this.resolveLocalizedValue(data, 'Name')
58
+ const execLine = data.Exec
59
+ if (!name || !execLine) {
60
+ return
61
+ }
62
+ if (String(data.NoDisplay || '').toLowerCase() === 'true') {
63
+ return
64
+ }
65
+ const desktopId = path.basename(filePath, '.desktop')
66
+ let id = `desktop:${desktopId}`
67
+ if (this.entries.has(id)) {
68
+ let suffix = 1
69
+ while (this.entries.has(`${id}#${suffix}`)) {
70
+ suffix += 1
71
+ }
72
+ id = `${id}#${suffix}`
73
+ }
74
+ const aliases = [desktopId]
75
+ const generic = this.resolveLocalizedValue(data, 'GenericName')
76
+ if (generic) {
77
+ aliases.push(generic)
78
+ }
79
+ this.addEntry({
80
+ id,
81
+ name,
82
+ aliases,
83
+ kind: 'linux-desktop',
84
+ desktopId,
85
+ execLine,
86
+ path: filePath,
87
+ detail: filePath,
88
+ terminal: String(data.Terminal || '').toLowerCase() === 'true',
89
+ workingDirectory: data.Path || null
90
+ })
91
+ }
92
+
93
+ parseDesktopFile(content) {
94
+ const data = {}
95
+ const lines = content.split(/\r?\n/)
96
+ let inEntry = false
97
+ for (const rawLine of lines) {
98
+ const line = rawLine.trim()
99
+ if (!line || line.startsWith('#')) {
100
+ continue
101
+ }
102
+ if (line.startsWith('[')) {
103
+ inEntry = line === '[Desktop Entry]'
104
+ continue
105
+ }
106
+ if (!inEntry) {
107
+ continue
108
+ }
109
+ const idx = line.indexOf('=')
110
+ if (idx === -1) {
111
+ continue
112
+ }
113
+ const key = line.slice(0, idx).trim()
114
+ const value = line.slice(idx + 1).trim()
115
+ if (!(key in data)) {
116
+ data[key] = value
117
+ }
118
+ }
119
+ return data
120
+ }
121
+
122
+ resolveLocalizedValue(data, key) {
123
+ if (!data) {
124
+ return null
125
+ }
126
+ if (data[key]) {
127
+ return data[key]
128
+ }
129
+ const lang = process.env.LANG ? process.env.LANG.split('.')[0] : ''
130
+ if (lang) {
131
+ if (data[`${key}[${lang}]`]) {
132
+ return data[`${key}[${lang}]`]
133
+ }
134
+ const short = lang.split('_')[0]
135
+ if (short && data[`${key}[${short}]`]) {
136
+ return data[`${key}[${short}]`]
137
+ }
138
+ }
139
+ const localizedKey = Object.keys(data).find((k) => k.startsWith(`${key}[`))
140
+ if (localizedKey) {
141
+ return data[localizedKey]
142
+ }
143
+ return null
144
+ }
145
+
146
+ tokenizeExec(execLine) {
147
+ const tokens = []
148
+ let current = ''
149
+ let quote = null
150
+ let escape = false
151
+ for (const ch of execLine) {
152
+ if (escape) {
153
+ current += ch
154
+ escape = false
155
+ continue
156
+ }
157
+ if (ch === '\\') {
158
+ escape = true
159
+ continue
160
+ }
161
+ if (ch === '"' || ch === '\'') {
162
+ if (quote === ch) {
163
+ quote = null
164
+ } else if (!quote) {
165
+ quote = ch
166
+ } else {
167
+ current += ch
168
+ }
169
+ continue
170
+ }
171
+ if (!quote && /\s/.test(ch)) {
172
+ if (current) {
173
+ tokens.push(current)
174
+ current = ''
175
+ }
176
+ continue
177
+ }
178
+ current += ch
179
+ }
180
+ if (current) {
181
+ tokens.push(current)
182
+ }
183
+ return tokens
184
+ }
185
+
186
+ buildExecCommand(execLine, extraArgs) {
187
+ const tokens = this.tokenizeExec(execLine)
188
+ const cleaned = []
189
+ for (const token of tokens) {
190
+ if (!token) {
191
+ continue
192
+ }
193
+ let replaced = token.replace(/%%/g, '%')
194
+ replaced = replaced.replace(/%[fFuUdDnNickvm]/g, '')
195
+ if (!replaced) {
196
+ continue
197
+ }
198
+ cleaned.push(replaced)
199
+ }
200
+ const args = Array.isArray(extraArgs) ? extraArgs : []
201
+ return cleaned.concat(args)
202
+ }
203
+
204
+ async launch(entry, params = {}) {
205
+ const args = this.sanitizeArgs(params.args)
206
+ const commandParts = this.buildExecCommand(entry.execLine, args)
207
+ if (!commandParts.length) {
208
+ throw new Error(`Unable to determine launch command for ${entry.name}`)
209
+ }
210
+ let command = commandParts[0]
211
+ let finalArgs = commandParts.slice(1)
212
+ const options = {}
213
+ if (entry.workingDirectory) {
214
+ options.cwd = entry.workingDirectory
215
+ }
216
+ const result = await this.spawnDetached(command, finalArgs, options)
217
+ return {
218
+ success: true,
219
+ id: entry.id,
220
+ name: entry.name,
221
+ kind: entry.kind,
222
+ launcher: 'exec',
223
+ pid: result.pid || null,
224
+ detail: entry.execLine
225
+ }
226
+ }
227
+ }
228
+
229
+ module.exports = LinuxAdapter