pinokiod 3.85.0 → 3.87.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 (88) hide show
  1. package/Dockerfile +61 -0
  2. package/docker-entrypoint.sh +75 -0
  3. package/kernel/api/hf/index.js +1 -1
  4. package/kernel/api/index.js +8 -1
  5. package/kernel/api/shell/index.js +6 -0
  6. package/kernel/api/terminal/index.js +166 -0
  7. package/kernel/bin/caddy.js +10 -4
  8. package/kernel/bin/conda.js +3 -2
  9. package/kernel/bin/index.js +53 -2
  10. package/kernel/bin/setup.js +32 -0
  11. package/kernel/bin/vs.js +11 -2
  12. package/kernel/index.js +42 -2
  13. package/kernel/info.js +36 -0
  14. package/kernel/peer.js +42 -18
  15. package/kernel/prototype.js +1 -0
  16. package/kernel/router/index.js +23 -15
  17. package/kernel/router/localhost_static_router.js +0 -3
  18. package/kernel/router/pinokio_domain_router.js +333 -0
  19. package/kernel/shell.js +43 -2
  20. package/kernel/shells.js +21 -1
  21. package/kernel/util.js +4 -2
  22. package/package.json +2 -1
  23. package/pipe/views/login.ejs +1 -1
  24. package/script/install-mode.js +33 -0
  25. package/script/pinokio.json +7 -0
  26. package/server/index.js +636 -246
  27. package/server/public/Socket.js +48 -0
  28. package/server/public/common.js +1956 -257
  29. package/server/public/fseditor.js +71 -12
  30. package/server/public/install.js +1 -1
  31. package/server/public/layout.js +740 -0
  32. package/server/public/modalinput.js +0 -1
  33. package/server/public/opener.js +12 -11
  34. package/server/public/serve/style.css +1 -1
  35. package/server/public/style.css +122 -129
  36. package/server/public/tab-idle-notifier.js +629 -0
  37. package/server/public/terminal_input_tracker.js +63 -0
  38. package/server/public/urldropdown.css +780 -45
  39. package/server/public/urldropdown.js +806 -156
  40. package/server/public/window_storage.js +97 -28
  41. package/server/socket.js +40 -9
  42. package/server/views/404.ejs +1 -1
  43. package/server/views/500.ejs +3 -3
  44. package/server/views/app.ejs +3146 -1381
  45. package/server/views/bookmarklet.ejs +197 -0
  46. package/server/views/bootstrap.ejs +1 -1
  47. package/server/views/columns.ejs +2 -13
  48. package/server/views/connect/x.ejs +4 -4
  49. package/server/views/connect.ejs +13 -14
  50. package/server/views/container.ejs +3 -4
  51. package/server/views/d.ejs +225 -55
  52. package/server/views/download.ejs +1 -1
  53. package/server/views/editor.ejs +2 -2
  54. package/server/views/env_editor.ejs +3 -3
  55. package/server/views/explore.ejs +2 -2
  56. package/server/views/file_explorer.ejs +3 -3
  57. package/server/views/git.ejs +7 -7
  58. package/server/views/github.ejs +3 -3
  59. package/server/views/help.ejs +2 -2
  60. package/server/views/index.ejs +17 -16
  61. package/server/views/index2.ejs +7 -7
  62. package/server/views/init/index.ejs +15 -79
  63. package/server/views/install.ejs +4 -4
  64. package/server/views/keys.ejs +2 -2
  65. package/server/views/layout.ejs +105 -0
  66. package/server/views/mini.ejs +2 -2
  67. package/server/views/net.ejs +45 -13
  68. package/server/views/network.ejs +41 -27
  69. package/server/views/network2.ejs +11 -11
  70. package/server/views/old_network.ejs +10 -10
  71. package/server/views/partials/dynamic.ejs +3 -5
  72. package/server/views/partials/menu.ejs +3 -5
  73. package/server/views/partials/running.ejs +1 -1
  74. package/server/views/pro.ejs +369 -0
  75. package/server/views/prototype/index.ejs +3 -3
  76. package/server/views/required_env_editor.ejs +2 -2
  77. package/server/views/review.ejs +15 -27
  78. package/server/views/rows.ejs +2 -13
  79. package/server/views/screenshots.ejs +298 -142
  80. package/server/views/settings.ejs +6 -7
  81. package/server/views/setup.ejs +3 -4
  82. package/server/views/setup_home.ejs +2 -2
  83. package/server/views/share_editor.ejs +4 -4
  84. package/server/views/shell.ejs +280 -29
  85. package/server/views/start.ejs +2 -2
  86. package/server/views/task.ejs +2 -2
  87. package/server/views/terminal.ejs +326 -52
  88. package/server/views/tools.ejs +461 -17
package/Dockerfile ADDED
@@ -0,0 +1,61 @@
1
+ # Production image for pinokiod with required system tooling
2
+ FROM node:20 AS build
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update \
6
+ && apt-get install -y --no-install-recommends python3 make g++ \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ COPY package*.json ./
10
+ RUN npm ci --omit=dev && npm cache clean --force
11
+
12
+ FROM node:20 AS runtime
13
+ ENV NODE_ENV=production
14
+ WORKDIR /app
15
+
16
+ RUN apt-get update \
17
+ && apt-get install -y --no-install-recommends \
18
+ bluez \
19
+ curl \
20
+ git \
21
+ lsof \
22
+ net-tools \
23
+ openssh-client \
24
+ p7zip-full \
25
+ pv \
26
+ && rm -rf /var/lib/apt/lists/*
27
+
28
+ COPY --from=build /app/node_modules ./node_modules
29
+ COPY . .
30
+
31
+ # Pre-seed the Pinokio dev preset into the image as a compressed archive
32
+ RUN mkdir -p /app/.pinokio-seed \
33
+ && PINOKIO_HOME=/app/.pinokio-seed PINOKIO_SETUP_MODE=prod_dev node script/install-mode.js \
34
+ && rm -rf /app/.pinokio-seed/network \
35
+ && mkdir -p /app/.pinokio-seed/network \
36
+ && git clone --depth 1 https://github.com/pinokiocomputer/network /app/.pinokio-seed/network/system \
37
+ && rm -rf /app/.pinokio-seed/network/system/.git \
38
+ && rm -rf /app/.pinokio-seed/plugin \
39
+ && mkdir -p /app/.pinokio-seed/plugin \
40
+ && git clone --depth 1 https://github.com/pinokiocomputer/code /app/.pinokio-seed/plugin/code \
41
+ && rm -rf /app/.pinokio-seed/plugin/code/.git \
42
+ && rm -rf /app/.pinokio-seed/prototype/system \
43
+ && mkdir -p /app/.pinokio-seed/prototype \
44
+ && git clone --depth 1 https://github.com/pinokiocomputer/proto /app/.pinokio-seed/prototype/system \
45
+ && rm -rf /app/.pinokio-seed/prototype/system/.git \
46
+ && curl -fsSL https://raw.githubusercontent.com/pinokiocomputer/home/refs/heads/main/docs/README.md -o /app/.pinokio-seed/prototype/PINOKIO.md \
47
+ && curl -fsSL https://raw.githubusercontent.com/pinokiocomputer/pterm/refs/heads/main/README.md -o /app/.pinokio-seed/prototype/PTERM.md \
48
+ && tar -C /app/.pinokio-seed -czf /app/.pinokio-seed.tgz . \
49
+ && rm -rf /app/.pinokio-seed
50
+
51
+ COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
52
+ RUN chmod +x /usr/local/bin/docker-entrypoint.sh
53
+
54
+ ENV PINOKIO_HOME=/data/pinokio \
55
+ PINOKIO_HTTPS_ACTIVE=1 \
56
+ PINOKIO_NETWORK_ACTIVE=1
57
+ VOLUME ["/data/pinokio"]
58
+
59
+ EXPOSE 8080
60
+ ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
61
+ CMD ["npm", "start"]
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ -z "${PINOKIO_STDOUT_STREAMED:-}" ] && command -v stdbuf >/dev/null 2>&1; then
5
+ export PINOKIO_STDOUT_STREAMED=1
6
+ exec 1> >(stdbuf -oL cat)
7
+ exec 2> >(stdbuf -oL cat >&2)
8
+ fi
9
+
10
+ PINOKIO_HOME="${PINOKIO_HOME:-/data/pinokio}"
11
+ export PINOKIO_HOME
12
+
13
+ if [ ! -d "$PINOKIO_HOME/bin" ]; then
14
+ mkdir -p "$PINOKIO_HOME"
15
+ if [ -f "/app/.pinokio-seed.tgz" ]; then
16
+ echo "[entrypoint] Bootstrapping Pinokio home at $PINOKIO_HOME"
17
+ echo "[entrypoint] Extracting Pinokio seed archive (this may take a moment)"
18
+ if command -v pv >/dev/null 2>&1; then
19
+ step=${PINOKIO_PROGRESS_STEP:-5}
20
+ interval=${PINOKIO_PROGRESS_INTERVAL:-1}
21
+ progress_fifo_raw=$(mktemp -t pinokio-progress-raw.XXXXXX)
22
+ progress_fifo=$(mktemp -t pinokio-progress.XXXXXX)
23
+ rm -f "$progress_fifo_raw" "$progress_fifo"
24
+ mkfifo "$progress_fifo_raw" "$progress_fifo"
25
+ (
26
+ trap 'rm -f "$progress_fifo_raw" "$progress_fifo"; exit 0' EXIT INT TERM
27
+ tr '\r' '\n' <"$progress_fifo_raw" >"$progress_fifo"
28
+ ) &
29
+ progress_transform_pid=$!
30
+ (
31
+ last=-1
32
+ while IFS= read -r line; do
33
+ progress=$(printf '%s\n' "$line" | grep -oE '[0-9]+%' | tail -n 1 || true)
34
+ if [ -n "$progress" ]; then
35
+ pct=${progress%%%}
36
+ if [ "$pct" -gt "$last" ]; then
37
+ delta=$((pct - last))
38
+ if [ "$pct" -eq 100 ] || [ "$delta" -ge "$step" ] || [ "$last" -lt 0 ]; then
39
+ last=$pct
40
+ printf '[entrypoint] Extracting: %d%%\n' "$pct"
41
+ fi
42
+ fi
43
+ fi
44
+ done <"$progress_fifo"
45
+ if [ "$last" -lt 100 ]; then
46
+ printf '[entrypoint] Extracting: 100%%\n'
47
+ fi
48
+ ) &
49
+ progress_reader_pid=$!
50
+ pv -f -i "$interval" /app/.pinokio-seed.tgz 2>"$progress_fifo_raw" | tar -C "$PINOKIO_HOME" -xz -f -
51
+ status=$?
52
+ wait "$progress_reader_pid" || true
53
+ wait "$progress_transform_pid" || true
54
+ rm -f "$progress_fifo_raw" "$progress_fifo"
55
+ if [ $status -ne 0 ]; then
56
+ exit $status
57
+ fi
58
+ else
59
+ tar -C "$PINOKIO_HOME" -xzf /app/.pinokio-seed.tgz
60
+ fi
61
+ echo "[entrypoint] Seed archive extraction complete"
62
+ elif [ -d "/app/.pinokio-seed" ]; then
63
+ echo "[entrypoint] Bootstrapping Pinokio home at $PINOKIO_HOME"
64
+ echo "[entrypoint] Copying Pinokio seed directory"
65
+ cp -a /app/.pinokio-seed/. "$PINOKIO_HOME"/
66
+ echo "[entrypoint] Seed directory copy complete"
67
+ fi
68
+ fi
69
+
70
+ if [ -e "/app/.pinokio-seed" ] && [ ! -L "/app/.pinokio-seed" ]; then
71
+ rm -rf /app/.pinokio-seed
72
+ fi
73
+ ln -sfn "$PINOKIO_HOME" /app/.pinokio-seed
74
+
75
+ exec "$@"
@@ -26,7 +26,7 @@ class HF {
26
26
  delete params.path
27
27
  let chunks = unparse(params)
28
28
  let message = [
29
- `huggingface-cli download ${chunks.join(" ")}` + (kernel.platform === "win32" ? " && dir" : " ; ls")
29
+ `hf download ${chunks.join(" ")}` + (kernel.platform === "win32" ? " && dir" : " ; ls")
30
30
  ]
31
31
  console.log({ message, before: req.params.message })
32
32
  req.params.message = message
@@ -802,6 +802,13 @@ class Api {
802
802
  current: i,
803
803
  uri: request.uri,
804
804
  cwd,
805
+ exists: (...args) => {
806
+ return fs.existsSync(path.resolve(cwd, ...args))
807
+ },
808
+ running: (...args) => {
809
+ let fullpath = path.resolve(cwd, ...args)
810
+ return this.running[fullpath]
811
+ },
805
812
  name,
806
813
  self: script,
807
814
  port,
@@ -1324,7 +1331,7 @@ class Api {
1324
1331
  */
1325
1332
 
1326
1333
  // concurrency
1327
- let concurrency = (rawrpc.queue ? 1 : 10);
1334
+ let concurrency = (rawrpc.queue ? 1 : 1000);
1328
1335
 
1329
1336
  // queue_id
1330
1337
 
@@ -53,6 +53,9 @@ class Shell {
53
53
  } else if (req.parent && req.parent.path) {
54
54
  options.group = req.parent.path
55
55
  }
56
+ if (req.parent && req.parent.body && req.parent.body.title) {
57
+ options.title = req.parent.body.title
58
+ }
56
59
  let id = await kernel.shell.start(req.params, options, ondata)
57
60
  return id
58
61
  //let id = await kernel.shell.start(req.params, options)
@@ -130,6 +133,9 @@ class Shell {
130
133
  } else if (req.parent && req.parent.path) {
131
134
  options.group = req.parent.path
132
135
  }
136
+ if (req.parent && req.parent.body && req.parent.body.title) {
137
+ options.title = req.parent.body.title
138
+ }
133
139
  if (req.client) {
134
140
  req.params.rows = req.client.rows
135
141
  req.params.cols = req.client.cols
@@ -0,0 +1,166 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const sanitize = require('sanitize-filename')
4
+
5
+ class Terminal {
6
+ async upload(req, ondata, kernel) {
7
+ const params = req.params || {}
8
+ const files = Array.isArray(params.files) ? params.files : []
9
+ const buffers = params.buffers || {}
10
+
11
+ if (files.length === 0) {
12
+ if (ondata) {
13
+ ondata({ raw: "\r\nNo files provided for upload.\r\n" })
14
+ }
15
+ return { files: [] }
16
+ }
17
+
18
+ const shellInstance = this.resolveShellInstance(params, kernel)
19
+ const shellCwd = this.resolveShellCwd(params, kernel)
20
+ const baseCwd = shellInstance && typeof shellInstance.path === 'string' && shellInstance.path.trim().length > 0
21
+ ? shellInstance.path
22
+ : shellCwd
23
+
24
+ const uploadRoot = baseCwd
25
+ ? path.join(baseCwd, '.pinokio-temp')
26
+ : kernel.path('temp', 'web-terminal')
27
+
28
+ await fs.promises.mkdir(uploadRoot, { recursive: true })
29
+
30
+ const saved = []
31
+
32
+ for (const file of files) {
33
+ const key = file && file.key
34
+ if (!key || !buffers[key]) {
35
+ continue
36
+ }
37
+ const sourceBuffer = buffers[key]
38
+ const originalName = typeof file.name === 'string' && file.name.length > 0
39
+ ? file.name
40
+ : 'upload'
41
+ const sanitized = sanitize(originalName) || `upload-${Date.now()}`
42
+ const targetName = await this.uniqueFilename(uploadRoot, sanitized)
43
+ const targetPath = path.join(uploadRoot, targetName)
44
+
45
+ await fs.promises.writeFile(targetPath, sourceBuffer)
46
+ delete buffers[key]
47
+
48
+ const homeRelativePath = path.relative(kernel.homedir, targetPath)
49
+ const normalizedHomeRelativePath = homeRelativePath.split(path.sep).join('/')
50
+ const cliPath = targetPath
51
+ const cliBase = baseCwd || kernel.homedir
52
+ const cliRelative = cliBase ? path.relative(cliBase, targetPath) : null
53
+ const cliRelativePath = cliRelative ? cliRelative.split(path.sep).join('/') : null
54
+
55
+ saved.push({
56
+ originalName,
57
+ storedAs: targetName,
58
+ path: targetPath,
59
+ size: typeof file.size === 'number' ? file.size : sourceBuffer.length,
60
+ mimeType: typeof file.type === 'string' ? file.type : '',
61
+ homeRelativePath: normalizedHomeRelativePath,
62
+ displayPath: `~/${normalizedHomeRelativePath}`,
63
+ cliPath,
64
+ cliRelativePath
65
+ })
66
+
67
+ }
68
+
69
+ if (saved.length === 0) {
70
+ if (ondata) {
71
+ ondata({ raw: "\r\nNo files were saved.\r\n" })
72
+ }
73
+ return { files: saved }
74
+ }
75
+
76
+ const marker = '[attachment] '
77
+ if (params && params.id && kernel && kernel.shell && typeof kernel.shell.emit === 'function') {
78
+ try {
79
+ kernel.shell.emit({
80
+ id: params.id,
81
+ emit: marker,
82
+ paste: true
83
+ })
84
+ } catch (error) {
85
+ if (ondata) {
86
+ ondata({ raw: marker })
87
+ }
88
+ }
89
+ } else if (ondata) {
90
+ ondata({ raw: marker })
91
+ }
92
+
93
+ // Break references to potentially large buffers once handled.
94
+ if (req.params) {
95
+ req.params.buffers = {}
96
+ }
97
+
98
+ return { files: saved }
99
+ }
100
+
101
+ resolveShellInstance(params, kernel) {
102
+ if (!params || typeof params.id !== 'string') {
103
+ return null
104
+ }
105
+ if (!kernel || !kernel.shell || typeof kernel.shell.get !== 'function') {
106
+ return null
107
+ }
108
+ try {
109
+ return kernel.shell.get(params.id)
110
+ } catch (error) {
111
+ return null
112
+ }
113
+ }
114
+
115
+ resolveShellCwd(params, kernel) {
116
+ if (!params || typeof params.cwd !== 'string') {
117
+ return null
118
+ }
119
+ let cwd = params.cwd.trim()
120
+ if (!cwd) {
121
+ return null
122
+ }
123
+ const queryIndex = cwd.indexOf('?')
124
+ if (queryIndex !== -1) {
125
+ cwd = cwd.slice(0, queryIndex)
126
+ }
127
+ const hashIndex = cwd.indexOf('#')
128
+ if (hashIndex !== -1) {
129
+ cwd = cwd.slice(0, hashIndex)
130
+ }
131
+ if (!cwd) {
132
+ return null
133
+ }
134
+ if (cwd.startsWith('~/')) {
135
+ return path.resolve(kernel.homedir, cwd.slice(2))
136
+ }
137
+ if (path.isAbsolute(cwd)) {
138
+ return cwd
139
+ }
140
+ return path.resolve(kernel.homedir, cwd)
141
+ }
142
+
143
+ async uniqueFilename(dir, candidate) {
144
+ const parsed = path.parse(candidate)
145
+ const baseName = parsed.name && parsed.name.trim().length > 0 ? parsed.name : 'upload'
146
+ const extension = parsed.ext || ''
147
+ let attemptIndex = 0
148
+
149
+ // Ensure we do not end up in an infinite loop if fs.access throws non-ENOENT errors.
150
+ while (true) {
151
+ const attemptName = attemptIndex === 0 ? `${baseName}${extension}` : `${baseName}_${attemptIndex}${extension}`
152
+ const attemptPath = path.join(dir, attemptName)
153
+ try {
154
+ await fs.promises.access(attemptPath)
155
+ attemptIndex += 1
156
+ } catch (error) {
157
+ if (error && error.code === 'ENOENT') {
158
+ return attemptName
159
+ }
160
+ throw error
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ module.exports = Terminal
@@ -32,10 +32,16 @@ class Caddy {
32
32
  await new Promise((resolve, reject) => {
33
33
  let interval = setInterval(() => {
34
34
  if (this.kernel.processes.caddy_pid) {
35
- console.log("kill caddy", this.kernel.processes.caddy_pid)
36
- kill(this.kernel.processes.caddy_pid, "SIGKILL", true)
37
- console.log("killed existing caddy")
38
- resolve()
35
+ try {
36
+ console.log("kill caddy", this.kernel.processes.caddy_pid)
37
+ kill(this.kernel.processes.caddy_pid, "SIGKILL", true)
38
+ console.log("killed existing caddy")
39
+ clearInterval(interval)
40
+ resolve()
41
+ } catch (error) {
42
+ clearInterval(interval)
43
+ reject(error)
44
+ }
39
45
  } else {
40
46
  console.log("try killing existing caddy again in 1 sec")
41
47
  }
@@ -148,7 +148,8 @@ report_errors: false`)
148
148
  //if (String(version) === "24.7.0") {
149
149
  let channel = chunks[3]
150
150
  let coerced = semver.coerce(version)
151
- let mamba_requirement = ">=24.11.1"
151
+ //let mamba_requirement = ">=24.11.1"
152
+ let mamba_requirement = ">=25.4.0"
152
153
  //if (semver.satisfies(coerced, mamba_requirement) && channel === "conda-forge") {
153
154
  if (semver.satisfies(coerced, mamba_requirement)) {
154
155
  conda_check.mamba = true
@@ -266,7 +267,7 @@ report_errors: false`)
266
267
  let cmds = [
267
268
  //"conda clean -y --index-cache",
268
269
  "conda clean -y --all",
269
- `conda install -y -c conda-forge sqlite=3.47.2 ${mods}`,
270
+ `conda install -y -c conda-forge sqlite=3.47.2 conda-libmamba-solver>=25.4.0 ${mods}`,
270
271
 
271
272
  // `conda config --file ${this.kernel.path('condarc')} --set remote_connect_timeout_secs 20`,
272
273
  // `conda config --file ${this.kernel.path('condarc')} --set remote_read_timeout_secs 300`,
@@ -368,7 +368,7 @@ class Bin {
368
368
  //if (String(version) === "24.7.0") {
369
369
  let channel = chunks[3]
370
370
  let coerced = semver.coerce(version)
371
- let mamba_requirement = ">=24.11.1"
371
+ let mamba_requirement = ">=25.4.0"
372
372
  //if (semver.satisfies(coerced, mamba_requirement) && channel === "conda-forge") {
373
373
  if (semver.satisfies(coerced, mamba_requirement)) {
374
374
  conda_check.mamba = true
@@ -631,6 +631,57 @@ class Bin {
631
631
  return res
632
632
  }
633
633
  }
634
+ async resolveInstallRequirements(req, ondata) {
635
+ let params = req.params
636
+ let mode = req.mode
637
+ if (typeof params === 'string') {
638
+ let trimmed = params.trim()
639
+ if (trimmed.length === 0) {
640
+ params = null
641
+ } else if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
642
+ try {
643
+ params = JSON.parse(trimmed)
644
+ } catch (e) {
645
+ params = trimmed
646
+ }
647
+ } else {
648
+ params = trimmed
649
+ }
650
+ }
651
+ if (Array.isArray(params)) {
652
+ return params
653
+ }
654
+ if (params && typeof params === 'object') {
655
+ if (Array.isArray(params.requirements)) {
656
+ return params.requirements
657
+ }
658
+ if (typeof params.mode === 'string' && params.mode.trim().length > 0) {
659
+ mode = params.mode.trim()
660
+ }
661
+ }
662
+ if (!mode && typeof params === 'string' && params.length > 0) {
663
+ mode = params
664
+ }
665
+ if (!mode && req.params && typeof req.params.mode === 'string' && req.params.mode.trim().length > 0) {
666
+ mode = req.params.mode.trim()
667
+ }
668
+ if (!mode && typeof req.mode === 'string' && req.mode.trim().length > 0) {
669
+ mode = req.mode.trim()
670
+ }
671
+ if (!mode) {
672
+ throw new Error('kernel.bin.install requires `requirements` array or `mode` string in params')
673
+ }
674
+ const preset = this.preset(mode)
675
+ if (!preset) {
676
+ const available = Object.keys(Setup).sort().join(', ')
677
+ throw new Error(`Unknown setup mode "${mode}". Available modes: ${available}`)
678
+ }
679
+ if (ondata) {
680
+ ondata({ html: `<b>Resolving setup preset "${mode}"</b>` }, 'notify2')
681
+ }
682
+ const { requirements } = await this.check({ bin: preset })
683
+ return Array.isArray(requirements) ? requirements : []
684
+ }
634
685
  // async init_launcher(req, ondata) {
635
686
  // console.log("init_launcher", req)
636
687
  // try {
@@ -685,7 +736,7 @@ class Bin {
685
736
  } else {
686
737
  this.client = null
687
738
  }
688
- let requirements = JSON.parse(req.params)
739
+ let requirements = await this.resolveInstallRequirements(req, ondata)
689
740
  for(let x=0; x<10; x++) {
690
741
  console.log(`## Install Attempt ${x}`)
691
742
  let i = 0;
@@ -111,6 +111,38 @@ module.exports = {
111
111
  ]
112
112
  }
113
113
  },
114
+ prod_dev: (kernel) => {
115
+ let requirements = [
116
+ { name: "conda", },
117
+ { name: "zip", },
118
+ ]
119
+ if (platform === "darwin") {
120
+ requirements.push({ name: "brew" })
121
+ }
122
+ requirements = requirements.concat([
123
+ { name: "git", },
124
+ { name: "node", },
125
+ { name: "cli", },
126
+ { name: "uv", },
127
+ { name: "caddy", },
128
+ { name: "py", },
129
+ { name: "browserless" },
130
+ ])
131
+ let conda_requirements = [
132
+ zip_cmd,
133
+ "uv",
134
+ "node",
135
+ "git",
136
+ "caddy"
137
+ ]
138
+ return {
139
+ icon: "fa-solid fa-laptop-code",
140
+ title: "Coding (Essential)",
141
+ description: "Install common modules required for development (Node.js, python, Visual Studio Developer Tools (Windows), Xcode build tools (Mac)",
142
+ requirements,
143
+ conda_requirements,
144
+ }
145
+ },
114
146
  dev: (kernel) => {
115
147
  let requirements = [
116
148
  { name: "conda", },
package/kernel/bin/vs.js CHANGED
@@ -125,6 +125,15 @@ class VS {
125
125
 
126
126
  }
127
127
  async init() {
128
+ if (!this.kernel || this.kernel.platform !== "win32") {
129
+ this._env = {
130
+ MSVC_PATH: [],
131
+ BUILD_PATH: [],
132
+ CMAKE_PATH: [],
133
+ CL_PATH: [],
134
+ }
135
+ return
136
+ }
128
137
  if (this.kernel.platform === "win32") {
129
138
  /*
130
139
  {
@@ -151,12 +160,12 @@ class VS {
151
160
  }
152
161
  }
153
162
  env () {
154
- if (this.kernel.platform === "win32") {
163
+ if (this.kernel && this.kernel.platform === "win32") {
155
164
  return this._env
156
165
  }
157
166
  }
158
167
  async installed() {
159
- if (this.kernel.platform === "win32") {
168
+ if (this.kernel && this.kernel.platform === "win32") {
160
169
  await this.init()
161
170
  console.log("VS INSTALLED CHECK", this._env)
162
171
  return this._env.MSVC_PATH && this._env.MSVC_PATH.length > 0 && this._env.BUILD_PATH && this._env.BUILD_PATH.length > 0 && this._env.CMAKE_PATH && this._env.CMAKE_PATH.length > 0 && this._env.VCVARSALL_PATH && this._env.VCVARSALL_PATH.length > 0
package/kernel/index.js CHANGED
@@ -33,6 +33,7 @@ const Store = require('./store')
33
33
  const Proto = require('./prototype')
34
34
  const Plugin = require('./plugin')
35
35
  const Router = require("./router")
36
+ const PinokioDomainRouter = require("./router/pinokio_domain_router")
36
37
  const Procs = require('./procs')
37
38
  const Peer = require('./peer')
38
39
  const Git = require('./git')
@@ -388,11 +389,43 @@ class Kernel {
388
389
  return false
389
390
  }
390
391
  }
392
+ async resolvePinokioDomain() {
393
+ const envDomain = (process.env.PINOKIO_DOMAIN || '').trim()
394
+ if (envDomain.length > 0) {
395
+ return envDomain
396
+ }
397
+ if (!this.homedir) {
398
+ return ''
399
+ }
400
+ try {
401
+ const env = await Environment.get(this.homedir, this)
402
+ const value = (env.PINOKIO_DOMAIN || '').trim()
403
+ return value
404
+ } catch (e) {
405
+ return ''
406
+ }
407
+ }
408
+ async ensureRouterMode() {
409
+ const domain = await this.resolvePinokioDomain()
410
+ const shouldUseCustom = domain.length > 0
411
+ if (shouldUseCustom && this.router_kind !== 'custom-domain') {
412
+ console.log('[router] switching to custom-domain router mode')
413
+ this.router = new PinokioDomainRouter(this)
414
+ this.router_kind = 'custom-domain'
415
+ } else if (!shouldUseCustom && this.router_kind !== 'default') {
416
+ console.log('[router] switching to default router mode')
417
+ this.router = new Router(this)
418
+ this.router_kind = 'default'
419
+ }
420
+ this.pinokio_domain_value = domain
421
+ }
391
422
  async refresh(notify_peers) {
392
423
  const ts = Date.now()
393
424
 
394
425
  await this.peer.check(this)
395
426
 
427
+ await this.ensureRouterMode()
428
+
396
429
  if (this.peer.peer_active) {
397
430
  // 1. get the process list
398
431
  await this.processes.refresh()
@@ -769,7 +802,7 @@ class Kernel {
769
802
  /// }
770
803
  async init(options) {
771
804
 
772
- let home = this.store.get("home")
805
+ let home = this.store.get("home") || process.env.PINOKIO_HOME
773
806
 
774
807
  // reset shells if they exist
775
808
  if (this.shell) {
@@ -834,7 +867,14 @@ class Kernel {
834
867
  this.api = new Api(this)
835
868
  this.python = new Python(this)
836
869
  this.shell = new Shells(this)
837
- this.router = new Router(this)
870
+ this.pinokio_domain_value = (process.env.PINOKIO_DOMAIN || '').trim()
871
+ if (this.pinokio_domain_value) {
872
+ this.router = new PinokioDomainRouter(this)
873
+ this.router_kind = 'custom-domain'
874
+ } else {
875
+ this.router = new Router(this)
876
+ this.router_kind = 'default'
877
+ }
838
878
  this.connect = new Connect(this)
839
879
  this.system = system
840
880
  this.keys = {}
package/kernel/info.js CHANGED
@@ -68,6 +68,42 @@ class Info {
68
68
  let resolved_path = path.resolve(cwd, ...args)
69
69
  return this.kernel.memory.global[resolved_path] || {}
70
70
  }
71
+ scriptsByApi() {
72
+ if (!this.kernel || !this.kernel.memory || !this.kernel.memory.local) {
73
+ return {}
74
+ }
75
+
76
+ const apiRoot = this.kernel.path("api")
77
+ const scriptsByApi = {}
78
+
79
+ for (const [id, localVariables] of Object.entries(this.kernel.memory.local)) {
80
+ if (!id) continue
81
+
82
+ const scriptPath = id.split("?")[0]
83
+ if (!scriptPath || !this.isSubpath(apiRoot, scriptPath)) continue
84
+
85
+ const apiName = Util.api_name(scriptPath, this.kernel)
86
+ if (!apiName || apiName === '.' || apiName.startsWith('..')) continue
87
+
88
+ if (!scriptsByApi[apiName]) {
89
+ scriptsByApi[apiName] = []
90
+ }
91
+
92
+ scriptsByApi[apiName].push({
93
+ uri: scriptPath,
94
+ local: localVariables || {}
95
+ })
96
+ }
97
+
98
+ return scriptsByApi
99
+ }
100
+ isSubpath(parent, child) {
101
+ if (!parent || !child) {
102
+ return false
103
+ }
104
+ const relative = path.relative(parent, child)
105
+ return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative)
106
+ }
71
107
  }
72
108
 
73
109
  module.exports = Info