pinokiod 6.0.86 → 6.0.89
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/index.js +29 -261
- package/kernel/api/launcher_target.js +150 -0
- package/kernel/bin/cli.js +3 -2
- package/package.json +1 -1
- package/server/index.js +36 -0
- package/server/socket.js +4 -2
- package/server/views/shell.ejs +3 -0
- package/server/views/terminals.ejs +8 -8
package/kernel/api/index.js
CHANGED
|
@@ -28,221 +28,6 @@ class Api {
|
|
|
28
28
|
this.child_procs = {}
|
|
29
29
|
this.lproxy = new Lproxy()
|
|
30
30
|
}
|
|
31
|
-
parseQueryString(query) {
|
|
32
|
-
const result = {}
|
|
33
|
-
if (typeof query !== "string" || query.length === 0) {
|
|
34
|
-
return result
|
|
35
|
-
}
|
|
36
|
-
const segments = query.split("&")
|
|
37
|
-
for (const segment of segments) {
|
|
38
|
-
if (!segment) {
|
|
39
|
-
continue
|
|
40
|
-
}
|
|
41
|
-
const equalsIndex = segment.indexOf("=")
|
|
42
|
-
const rawKey = equalsIndex >= 0 ? segment.slice(0, equalsIndex) : segment
|
|
43
|
-
if (!rawKey) {
|
|
44
|
-
continue
|
|
45
|
-
}
|
|
46
|
-
const rawValue = equalsIndex >= 0 ? segment.slice(equalsIndex + 1) : ""
|
|
47
|
-
const plusDecodedKey = rawKey.replace(/\+/g, " ")
|
|
48
|
-
const plusDecodedValue = rawValue.replace(/\+/g, " ")
|
|
49
|
-
let key
|
|
50
|
-
let value
|
|
51
|
-
try {
|
|
52
|
-
key = decodeURIComponent(plusDecodedKey)
|
|
53
|
-
} catch (_) {
|
|
54
|
-
key = plusDecodedKey
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
value = decodeURIComponent(plusDecodedValue)
|
|
58
|
-
} catch (_) {
|
|
59
|
-
value = plusDecodedValue
|
|
60
|
-
}
|
|
61
|
-
if (Object.prototype.hasOwnProperty.call(result, key)) {
|
|
62
|
-
if (Array.isArray(result[key])) {
|
|
63
|
-
result[key].push(value)
|
|
64
|
-
} else {
|
|
65
|
-
result[key] = [result[key], value]
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
result[key] = value
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return result
|
|
72
|
-
}
|
|
73
|
-
stringifyQueryParams(params) {
|
|
74
|
-
const searchParams = new URLSearchParams()
|
|
75
|
-
if (!params || typeof params !== "object") {
|
|
76
|
-
return searchParams.toString()
|
|
77
|
-
}
|
|
78
|
-
for (const [key, rawValue] of Object.entries(params)) {
|
|
79
|
-
if (typeof key !== "string" || key.length === 0) {
|
|
80
|
-
continue
|
|
81
|
-
}
|
|
82
|
-
if (Array.isArray(rawValue)) {
|
|
83
|
-
for (const value of rawValue) {
|
|
84
|
-
searchParams.append(key, String(value))
|
|
85
|
-
}
|
|
86
|
-
} else if (typeof rawValue !== "undefined") {
|
|
87
|
-
searchParams.append(key, String(rawValue))
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return searchParams.toString()
|
|
91
|
-
}
|
|
92
|
-
splitUriQuery(uri) {
|
|
93
|
-
if (typeof uri !== "string" || uri.length === 0) {
|
|
94
|
-
return {
|
|
95
|
-
uri,
|
|
96
|
-
query: "",
|
|
97
|
-
input: {}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
const queryIndex = uri.indexOf("?")
|
|
101
|
-
if (queryIndex === -1) {
|
|
102
|
-
return {
|
|
103
|
-
uri,
|
|
104
|
-
query: "",
|
|
105
|
-
input: {}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
const query = uri.slice(queryIndex + 1)
|
|
109
|
-
return {
|
|
110
|
-
uri: uri.slice(0, queryIndex),
|
|
111
|
-
query,
|
|
112
|
-
input: this.parseQueryString(query)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
mergeRequestInput(uriInput, requestInput) {
|
|
116
|
-
const merged = {}
|
|
117
|
-
const sources = [uriInput, requestInput]
|
|
118
|
-
for (const source of sources) {
|
|
119
|
-
if (!source || typeof source !== "object") {
|
|
120
|
-
continue
|
|
121
|
-
}
|
|
122
|
-
for (const [key, value] of Object.entries(source)) {
|
|
123
|
-
merged[key] = value
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return merged
|
|
127
|
-
}
|
|
128
|
-
normalizeMenuSelector(selector) {
|
|
129
|
-
if (typeof selector !== "string") {
|
|
130
|
-
return null
|
|
131
|
-
}
|
|
132
|
-
const trimmed = selector.trim()
|
|
133
|
-
if (!trimmed) {
|
|
134
|
-
return null
|
|
135
|
-
}
|
|
136
|
-
const { uri, input } = this.splitUriQuery(trimmed)
|
|
137
|
-
return {
|
|
138
|
-
href: uri,
|
|
139
|
-
params: input
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
normalizeMenuItem(item) {
|
|
143
|
-
if (!item || typeof item.href !== "string") {
|
|
144
|
-
return null
|
|
145
|
-
}
|
|
146
|
-
const href = item.href.trim()
|
|
147
|
-
if (!href) {
|
|
148
|
-
return null
|
|
149
|
-
}
|
|
150
|
-
const { uri, input } = this.splitUriQuery(href)
|
|
151
|
-
const params = Object.assign({}, input)
|
|
152
|
-
if (item.params && typeof item.params === "object") {
|
|
153
|
-
for (const [key, value] of Object.entries(item.params)) {
|
|
154
|
-
params[key] = value
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return {
|
|
158
|
-
item,
|
|
159
|
-
href: uri,
|
|
160
|
-
params
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
menuSelectorMatches(selector, normalizedItem) {
|
|
164
|
-
const normalizedSelector = this.normalizeMenuSelector(selector)
|
|
165
|
-
if (!normalizedSelector || !normalizedItem) {
|
|
166
|
-
return false
|
|
167
|
-
}
|
|
168
|
-
if (normalizedSelector.href !== normalizedItem.href) {
|
|
169
|
-
return false
|
|
170
|
-
}
|
|
171
|
-
for (const [key, expectedRaw] of Object.entries(normalizedSelector.params || {})) {
|
|
172
|
-
if (!Object.prototype.hasOwnProperty.call(normalizedItem.params || {}, key)) {
|
|
173
|
-
return false
|
|
174
|
-
}
|
|
175
|
-
const actualRaw = normalizedItem.params[key]
|
|
176
|
-
const expectedValues = Array.isArray(expectedRaw) ? expectedRaw.map((value) => String(value)) : [String(expectedRaw)]
|
|
177
|
-
const actualValues = Array.isArray(actualRaw) ? actualRaw.map((value) => String(value)) : [String(actualRaw)]
|
|
178
|
-
if (expectedValues.length !== actualValues.length) {
|
|
179
|
-
return false
|
|
180
|
-
}
|
|
181
|
-
for (let i = 0; i < expectedValues.length; i++) {
|
|
182
|
-
if (expectedValues[i] !== actualValues[i]) {
|
|
183
|
-
return false
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return true
|
|
188
|
-
}
|
|
189
|
-
flattenMenuItems(menu, flattened = []) {
|
|
190
|
-
if (!Array.isArray(menu)) {
|
|
191
|
-
return flattened
|
|
192
|
-
}
|
|
193
|
-
for (const item of menu) {
|
|
194
|
-
if (!item || typeof item !== "object") {
|
|
195
|
-
continue
|
|
196
|
-
}
|
|
197
|
-
const normalized = this.normalizeMenuItem(item)
|
|
198
|
-
if (normalized) {
|
|
199
|
-
flattened.push(normalized)
|
|
200
|
-
}
|
|
201
|
-
if (Array.isArray(item.menu)) {
|
|
202
|
-
this.flattenMenuItems(item.menu, flattened)
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return flattened
|
|
206
|
-
}
|
|
207
|
-
async launcherMenuItems(repo_path) {
|
|
208
|
-
let launcher = await this.launcher({
|
|
209
|
-
path: repo_path
|
|
210
|
-
})
|
|
211
|
-
let config = launcher.script
|
|
212
|
-
if (!config || !config.menu) {
|
|
213
|
-
return []
|
|
214
|
-
}
|
|
215
|
-
if (typeof config.menu === "function") {
|
|
216
|
-
if (config.menu.constructor.name === "AsyncFunction") {
|
|
217
|
-
config.menu = await config.menu(this.kernel, this.kernel.info)
|
|
218
|
-
} else {
|
|
219
|
-
config.menu = config.menu(this.kernel, this.kernel.info)
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return this.flattenMenuItems(config.menu)
|
|
223
|
-
}
|
|
224
|
-
resolveMenuItemHref(repo_path, normalizedItem) {
|
|
225
|
-
if (!normalizedItem || !normalizedItem.href) {
|
|
226
|
-
return undefined
|
|
227
|
-
}
|
|
228
|
-
const query = this.stringifyQueryParams(normalizedItem.params)
|
|
229
|
-
const resolvedHref = normalizedItem.href.startsWith("http")
|
|
230
|
-
? normalizedItem.href
|
|
231
|
-
: path.resolve(repo_path, normalizedItem.href)
|
|
232
|
-
if (query && query.length > 0) {
|
|
233
|
-
return `${resolvedHref}?${query}`
|
|
234
|
-
}
|
|
235
|
-
return resolvedHref
|
|
236
|
-
}
|
|
237
|
-
isRunningMenuScript(repo_path, normalizedItem) {
|
|
238
|
-
if (!normalizedItem || !normalizedItem.href || normalizedItem.href.startsWith("http")) {
|
|
239
|
-
return false
|
|
240
|
-
}
|
|
241
|
-
const scriptPath = path.isAbsolute(normalizedItem.href)
|
|
242
|
-
? normalizedItem.href
|
|
243
|
-
: path.resolve(repo_path, normalizedItem.href)
|
|
244
|
-
return Boolean(this.kernel.status(scriptPath))
|
|
245
|
-
}
|
|
246
31
|
async launcher_path(name) {
|
|
247
32
|
let root_path = this.kernel.path("api", name)
|
|
248
33
|
let primary_path = path.resolve(root_path, "pinokio.js")
|
|
@@ -810,14 +595,11 @@ class Api {
|
|
|
810
595
|
modpath = this.resolveGitURI(uri)
|
|
811
596
|
} else if (uri.startsWith("~/")) {
|
|
812
597
|
// absolute path
|
|
813
|
-
|
|
814
|
-
modpath = path.resolve(this.kernel.homedir, strippedUri.slice(2))
|
|
598
|
+
modpath = path.resolve(this.kernel.homedir, uri.slice(2))
|
|
815
599
|
} else if (path.isAbsolute(uri)) {
|
|
816
|
-
|
|
817
|
-
modpath = strippedUri
|
|
600
|
+
modpath = uri
|
|
818
601
|
} else if (cwd) {
|
|
819
|
-
|
|
820
|
-
modpath = path.resolve(cwd, strippedUri)
|
|
602
|
+
modpath = path.resolve(cwd, uri)
|
|
821
603
|
} else {
|
|
822
604
|
throw new Error("uri must be either an http uri or start with ~/")
|
|
823
605
|
}
|
|
@@ -834,16 +616,13 @@ class Api {
|
|
|
834
616
|
modpath = this.resolveGitURI(uri)
|
|
835
617
|
} else if (uri.startsWith("~/")) {
|
|
836
618
|
// absolute path
|
|
837
|
-
|
|
838
|
-
modpath = path.resolve(this.kernel.homedir, strippedUri.slice(2))
|
|
619
|
+
modpath = path.resolve(this.kernel.homedir, uri.slice(2))
|
|
839
620
|
} else if (path.isAbsolute(uri)) {
|
|
840
|
-
|
|
841
|
-
modpath = strippedUri
|
|
621
|
+
modpath = uri
|
|
842
622
|
} else {
|
|
843
623
|
if (cwd) {
|
|
844
624
|
// relative path against the cwd (current execution path)
|
|
845
|
-
|
|
846
|
-
modpath = path.resolve(cwd, strippedUri)
|
|
625
|
+
modpath = path.resolve(cwd, uri)
|
|
847
626
|
} else {
|
|
848
627
|
throw new Error("resolving relative paths require an additional cwd argument")
|
|
849
628
|
}
|
|
@@ -1653,38 +1432,34 @@ class Api {
|
|
|
1653
1432
|
this.process(request, resolve)
|
|
1654
1433
|
})
|
|
1655
1434
|
}
|
|
1656
|
-
async get_default(repo_path
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
return undefined
|
|
1660
|
-
}
|
|
1661
|
-
const defaultItem = menuItems.find((item) => {
|
|
1662
|
-
return item.item && item.item.default
|
|
1663
|
-
})
|
|
1664
|
-
if (defaultItem) {
|
|
1665
|
-
return this.resolveMenuItemHref(repo_path, defaultItem)
|
|
1666
|
-
}
|
|
1667
|
-
const preferredSelectors = Array.isArray(preferred)
|
|
1668
|
-
? preferred.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
1669
|
-
: (typeof preferred === "string" && preferred.trim().length > 0 ? [preferred] : [])
|
|
1670
|
-
if (preferredSelectors.length === 0) {
|
|
1671
|
-
return undefined
|
|
1672
|
-
}
|
|
1673
|
-
const readyUrl = menuItems.find((item) => {
|
|
1674
|
-
return item.href && item.href.startsWith("http")
|
|
1435
|
+
async get_default(repo_path) {
|
|
1436
|
+
let launcher = await this.launcher({
|
|
1437
|
+
path: repo_path
|
|
1675
1438
|
})
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1439
|
+
let config = launcher.script
|
|
1440
|
+
if (config && config.menu) {
|
|
1441
|
+
if (typeof config.menu === "function") {
|
|
1442
|
+
if (config.menu.constructor.name === "AsyncFunction") {
|
|
1443
|
+
config.menu = await config.menu(this.kernel, this.kernel.info)
|
|
1444
|
+
} else {
|
|
1445
|
+
config.menu = config.menu(this.kernel, this.kernel.info)
|
|
1680
1446
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
1447
|
+
}
|
|
1448
|
+
let running = config.menu.filter((item) => {
|
|
1449
|
+
return item.default
|
|
1450
|
+
})
|
|
1451
|
+
if (running.length > 0) {
|
|
1452
|
+
let r = running[0]
|
|
1453
|
+
if (r.href) {
|
|
1454
|
+
if (r.href.startsWith("http")) {
|
|
1455
|
+
return r.href
|
|
1456
|
+
} else {
|
|
1457
|
+
let href = path.resolve(repo_path, r.href)
|
|
1458
|
+
return href
|
|
1459
|
+
}
|
|
1683
1460
|
}
|
|
1684
|
-
return this.resolveMenuItemHref(repo_path, item)
|
|
1685
1461
|
}
|
|
1686
1462
|
}
|
|
1687
|
-
return undefined
|
|
1688
1463
|
}
|
|
1689
1464
|
async process(request, done) {
|
|
1690
1465
|
/**************************************************************
|
|
@@ -1740,13 +1515,6 @@ class Api {
|
|
|
1740
1515
|
this.counter++;
|
|
1741
1516
|
// API Call
|
|
1742
1517
|
|
|
1743
|
-
const uriInput = request.uri.startsWith("http")
|
|
1744
|
-
? {}
|
|
1745
|
-
: this.splitUriQuery(request.uri).input
|
|
1746
|
-
if (Object.keys(uriInput).length > 0) {
|
|
1747
|
-
request.input = this.mergeRequestInput(uriInput, request.input)
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
1518
|
request.path = this.resolvePath(this.userdir, request.uri)
|
|
1751
1519
|
|
|
1752
1520
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
const path = require("path")
|
|
2
|
+
|
|
3
|
+
const filterSelectors = (preferred) => {
|
|
4
|
+
if (Array.isArray(preferred)) {
|
|
5
|
+
return preferred.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
6
|
+
}
|
|
7
|
+
if (typeof preferred === "string" && preferred.trim().length > 0) {
|
|
8
|
+
return [preferred]
|
|
9
|
+
}
|
|
10
|
+
return []
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const appendInputValue = (input, key, value) => {
|
|
14
|
+
if (Object.prototype.hasOwnProperty.call(input, key)) {
|
|
15
|
+
if (Array.isArray(input[key])) {
|
|
16
|
+
input[key].push(value)
|
|
17
|
+
} else {
|
|
18
|
+
input[key] = [input[key], value]
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
input[key] = value
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const menuTarget = (item) => {
|
|
26
|
+
if (!item || typeof item.href !== "string") {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
let href = item.href.trim()
|
|
30
|
+
if (!href) {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
let queryIndex = href.indexOf("?")
|
|
34
|
+
let uri = queryIndex >= 0 ? href.slice(0, queryIndex) : href
|
|
35
|
+
let searchParams = new URLSearchParams(queryIndex >= 0 ? href.slice(queryIndex + 1) : "")
|
|
36
|
+
if (item.params && typeof item.params === "object") {
|
|
37
|
+
for (let [key, rawValue] of Object.entries(item.params)) {
|
|
38
|
+
if (Array.isArray(rawValue)) {
|
|
39
|
+
for (let value of rawValue) {
|
|
40
|
+
searchParams.append(key, String(value))
|
|
41
|
+
}
|
|
42
|
+
} else if (typeof rawValue !== "undefined") {
|
|
43
|
+
searchParams.append(key, String(rawValue))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
let input = {}
|
|
48
|
+
for (let [key, value] of searchParams.entries()) {
|
|
49
|
+
appendInputValue(input, key, value)
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
uri,
|
|
53
|
+
href: searchParams.toString() ? `${uri}?${searchParams.toString()}` : uri,
|
|
54
|
+
input,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const selectorMatches = (selector, target) => {
|
|
59
|
+
if (typeof selector !== "string" || !target) {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
let candidate = selector.trim()
|
|
63
|
+
if (!candidate) {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
let queryIndex = candidate.indexOf("?")
|
|
67
|
+
let candidateUri = queryIndex >= 0 ? candidate.slice(0, queryIndex) : candidate
|
|
68
|
+
if (candidateUri !== target.uri) {
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
if (queryIndex === -1) {
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
let selectorParams = new URLSearchParams(candidate.slice(queryIndex + 1))
|
|
75
|
+
for (let key of new Set(selectorParams.keys())) {
|
|
76
|
+
if (!Object.prototype.hasOwnProperty.call(target.input, key)) {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
let expected = selectorParams.getAll(key)
|
|
80
|
+
let actual = Array.isArray(target.input[key]) ? target.input[key] : [target.input[key]]
|
|
81
|
+
if (expected.length !== actual.length) {
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
for (let i = 0; i < expected.length; i++) {
|
|
85
|
+
if (expected[i] !== actual[i]) {
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const loadMenu = async (api, repoPath) => {
|
|
94
|
+
let launcher = await api.launcher({
|
|
95
|
+
path: repoPath
|
|
96
|
+
})
|
|
97
|
+
let config = launcher.script
|
|
98
|
+
if (!config || !config.menu) {
|
|
99
|
+
return []
|
|
100
|
+
}
|
|
101
|
+
if (typeof config.menu === "function") {
|
|
102
|
+
if (config.menu.constructor.name === "AsyncFunction") {
|
|
103
|
+
config.menu = await config.menu(api.kernel, api.kernel.info)
|
|
104
|
+
} else {
|
|
105
|
+
config.menu = config.menu(api.kernel, api.kernel.info)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return Array.isArray(config.menu) ? config.menu : []
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = async (api, repoPath, preferred = []) => {
|
|
112
|
+
let defaultTarget = await api.get_default(repoPath)
|
|
113
|
+
if (defaultTarget) {
|
|
114
|
+
return {
|
|
115
|
+
uri: defaultTarget
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
let selectors = filterSelectors(preferred)
|
|
119
|
+
if (selectors.length === 0) {
|
|
120
|
+
return undefined
|
|
121
|
+
}
|
|
122
|
+
let stack = [...(await loadMenu(api, repoPath))]
|
|
123
|
+
while (stack.length > 0) {
|
|
124
|
+
let item = stack.shift()
|
|
125
|
+
if (!item || typeof item !== "object") {
|
|
126
|
+
continue
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(item.menu)) {
|
|
129
|
+
stack.unshift(...item.menu)
|
|
130
|
+
}
|
|
131
|
+
let target = menuTarget(item)
|
|
132
|
+
if (!target) {
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
for (let selector of selectors) {
|
|
136
|
+
if (!selectorMatches(selector, target)) {
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
if (target.uri.startsWith("http")) {
|
|
140
|
+
return {
|
|
141
|
+
uri: target.href
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
uri: path.resolve(repoPath, target.uri),
|
|
146
|
+
input: Object.keys(target.input).length > 0 ? target.input : undefined
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
package/kernel/bin/cli.js
CHANGED
|
@@ -2,7 +2,7 @@ const path = require('path')
|
|
|
2
2
|
const semver = require('semver')
|
|
3
3
|
const Util = require('../util')
|
|
4
4
|
class CLI {
|
|
5
|
-
version = ">=0.0.
|
|
5
|
+
version = ">=0.0.21"
|
|
6
6
|
async install(req, ondata) {
|
|
7
7
|
await this.kernel.exec({
|
|
8
8
|
message: "npm install -g pterm@latest --force",
|
|
@@ -21,7 +21,8 @@ class CLI {
|
|
|
21
21
|
? this.kernel.path("bin/npm/node_modules")
|
|
22
22
|
: this.kernel.path("bin/npm/lib/node_modules")
|
|
23
23
|
const pkgPath = require.resolve("pterm/package.json", { paths: [moduleRoot] })
|
|
24
|
-
const {
|
|
24
|
+
const { resolved } = await this.kernel.loader.load(pkgPath)
|
|
25
|
+
const { version } = resolved || {}
|
|
25
26
|
const coerced = semver.coerce(version)
|
|
26
27
|
if (coerced && semver.satisfies(coerced, this.version)) {
|
|
27
28
|
return true
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -7734,6 +7734,22 @@ class Server {
|
|
|
7734
7734
|
if (typeof startCommand !== "string" || startCommand.trim().length === 0) {
|
|
7735
7735
|
failStart(500, `No start command configured for ${provider.label || provider.key || "provider"}.`)
|
|
7736
7736
|
}
|
|
7737
|
+
const managedLaunchOverrides = {
|
|
7738
|
+
shell: null,
|
|
7739
|
+
env: {}
|
|
7740
|
+
}
|
|
7741
|
+
const isWindowsPlatform = (this.kernel && typeof this.kernel.platform === "string"
|
|
7742
|
+
? this.kernel.platform
|
|
7743
|
+
: process.platform) === "win32"
|
|
7744
|
+
if (isWindowsPlatform && (provider.key === "codex" || provider.key === "claude")) {
|
|
7745
|
+
const gitBashPath = this.kernel.path("bin/miniconda/Library/bin/bash.exe")
|
|
7746
|
+
const bashExists = await fs.promises.access(gitBashPath, fs.constants.F_OK).then(() => true).catch(() => false)
|
|
7747
|
+
if (bashExists) {
|
|
7748
|
+
if (provider.key === "claude") {
|
|
7749
|
+
managedLaunchOverrides.env.CLAUDE_CODE_GIT_BASH_PATH = gitBashPath
|
|
7750
|
+
}
|
|
7751
|
+
}
|
|
7752
|
+
}
|
|
7737
7753
|
const now = new Date()
|
|
7738
7754
|
const pad = (value) => String(value).padStart(2, "0")
|
|
7739
7755
|
const timestamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`
|
|
@@ -7920,6 +7936,17 @@ class Server {
|
|
|
7920
7936
|
params.set("terminal_id", terminalId)
|
|
7921
7937
|
params.set("message", startCommand)
|
|
7922
7938
|
params.set("input", "1")
|
|
7939
|
+
if (managedLaunchOverrides.shell) {
|
|
7940
|
+
params.set("shell", managedLaunchOverrides.shell)
|
|
7941
|
+
}
|
|
7942
|
+
const managedLaunchEnvEntries = Object.entries(managedLaunchOverrides.env)
|
|
7943
|
+
for (let i = 0; i < managedLaunchEnvEntries.length; i++) {
|
|
7944
|
+
const [key, value] = managedLaunchEnvEntries[i]
|
|
7945
|
+
if (typeof key !== "string" || typeof value !== "string" || !key || !value) {
|
|
7946
|
+
continue
|
|
7947
|
+
}
|
|
7948
|
+
params.set(`env.${key}`, value)
|
|
7949
|
+
}
|
|
7923
7950
|
const safeWorkspaceName = workspaceFolderName && workspaceFolderName.trim().length > 0
|
|
7924
7951
|
? workspaceFolderName.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
7925
7952
|
: "workspace"
|
|
@@ -8993,6 +9020,14 @@ class Server {
|
|
|
8993
9020
|
}
|
|
8994
9021
|
//let message = req.query.message ? req.query.message : null
|
|
8995
9022
|
let venv = req.query.venv ? decodeURIComponent(req.query.venv) : null
|
|
9023
|
+
let launchShell = null
|
|
9024
|
+
if (typeof req.query.shell === "string" && req.query.shell.length > 0) {
|
|
9025
|
+
try {
|
|
9026
|
+
launchShell = decodeURIComponent(req.query.shell)
|
|
9027
|
+
} catch (error) {
|
|
9028
|
+
launchShell = req.query.shell
|
|
9029
|
+
}
|
|
9030
|
+
}
|
|
8996
9031
|
let input = req.query.input ? true : false
|
|
8997
9032
|
let callback = req.query.callback ? decodeURIComponent(req.query.callback) : null
|
|
8998
9033
|
let callback_target = req.query.callback_target ? decodeURIComponent(req.query.callback_target) : null
|
|
@@ -9037,6 +9072,7 @@ class Server {
|
|
|
9037
9072
|
message,
|
|
9038
9073
|
restart_message: restartMessage,
|
|
9039
9074
|
venv,
|
|
9075
|
+
launch_shell: launchShell,
|
|
9040
9076
|
conda: (conda_exists ? conda: null),
|
|
9041
9077
|
env,
|
|
9042
9078
|
// pattern,
|
package/server/socket.js
CHANGED
|
@@ -5,6 +5,7 @@ const os = require('os')
|
|
|
5
5
|
const fs = require('fs')
|
|
6
6
|
const Util = require("../kernel/util")
|
|
7
7
|
const Environment = require("../kernel/environment")
|
|
8
|
+
const getLaunchTarget = require("../kernel/api/launcher_target")
|
|
8
9
|
const NOTIFICATION_CHANNEL = 'kernel.notifications'
|
|
9
10
|
class Socket {
|
|
10
11
|
normalizeLaunchSource(source = "") {
|
|
@@ -196,10 +197,11 @@ class Socket {
|
|
|
196
197
|
// get the default script and respond
|
|
197
198
|
let id = this.parent.kernel.api.filePath(req.uri)
|
|
198
199
|
try {
|
|
199
|
-
let
|
|
200
|
+
let defaultTarget = await getLaunchTarget(this.parent.kernel.api, id, req.default)
|
|
200
201
|
ws.send(JSON.stringify({
|
|
201
202
|
data: {
|
|
202
|
-
uri:
|
|
203
|
+
uri: defaultTarget ? defaultTarget.uri : undefined,
|
|
204
|
+
input: defaultTarget ? defaultTarget.input : undefined
|
|
203
205
|
}
|
|
204
206
|
}))
|
|
205
207
|
} catch (e) {
|
package/server/views/shell.ejs
CHANGED
|
@@ -568,6 +568,9 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
568
568
|
<% if (venv) { %>
|
|
569
569
|
venv: "<%-JSON.stringify(venv).slice(1, -1)%>",
|
|
570
570
|
<% } %>
|
|
571
|
+
<% if (launch_shell) { %>
|
|
572
|
+
shell: "<%-JSON.stringify(launch_shell).slice(1, -1)%>",
|
|
573
|
+
<% } %>
|
|
571
574
|
<% if (kill) { %>
|
|
572
575
|
on: [{
|
|
573
576
|
event: "<%=kill%>",
|
|
@@ -9415,16 +9415,16 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
|
|
|
9415
9415
|
</div>
|
|
9416
9416
|
</header>` : ""}
|
|
9417
9417
|
<div class="terminals-chooser-content">
|
|
9418
|
+
<section class="terminals-workspaces-header" aria-label="Agent workspaces intro">
|
|
9419
|
+
<div class="terminals-workspace-header-actions terminals-workspaces-header-actions" data-workspaces-header-actions></div>
|
|
9420
|
+
</section>
|
|
9421
|
+
<section class="terminals-workspace-sessions-header hidden" aria-label="Workspace sessions intro">
|
|
9422
|
+
<div class="terminals-list-intro-breadcrumb terminals-workspace-header-breadcrumb" data-workspace-header-breadcrumb aria-label="Workspace breadcrumb"></div>
|
|
9423
|
+
<p data-workspace-header-path></p>
|
|
9424
|
+
<div class="terminals-workspace-header-actions" data-workspace-header-actions></div>
|
|
9425
|
+
</section>
|
|
9418
9426
|
<div class="terminals-empty" data-state="loading" aria-live="polite"></div>
|
|
9419
9427
|
<div class="terminals-list-wrap">
|
|
9420
|
-
<section class="terminals-workspaces-header" aria-label="Agent workspaces intro">
|
|
9421
|
-
<div class="terminals-workspace-header-actions terminals-workspaces-header-actions" data-workspaces-header-actions></div>
|
|
9422
|
-
</section>
|
|
9423
|
-
<section class="terminals-workspace-sessions-header hidden" aria-label="Workspace sessions intro">
|
|
9424
|
-
<div class="terminals-list-intro-breadcrumb terminals-workspace-header-breadcrumb" data-workspace-header-breadcrumb aria-label="Workspace breadcrumb"></div>
|
|
9425
|
-
<p data-workspace-header-path></p>
|
|
9426
|
-
<div class="terminals-workspace-header-actions" data-workspace-header-actions></div>
|
|
9427
|
-
</section>
|
|
9428
9428
|
<div class="terminals-list"></div>
|
|
9429
9429
|
</div>
|
|
9430
9430
|
</div>
|