pinokiod 7.2.18 → 7.3.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 (89) hide show
  1. package/Dockerfile +2 -0
  2. package/kernel/api/index.js +13 -179
  3. package/kernel/api/process/index.js +44 -99
  4. package/kernel/bin/conda-python.js +30 -0
  5. package/kernel/bin/conda.js +22 -3
  6. package/kernel/bin/huggingface.js +1 -1
  7. package/kernel/bin/index.js +11 -1
  8. package/kernel/environment.js +11 -205
  9. package/kernel/git.js +13 -0
  10. package/kernel/index.js +1 -64
  11. package/kernel/plugin.js +58 -6
  12. package/kernel/prototype.js +0 -4
  13. package/kernel/shell.js +2 -23
  14. package/kernel/util.js +0 -60
  15. package/package.json +1 -1
  16. package/server/index.js +171 -229
  17. package/server/lib/content_validation.js +33 -47
  18. package/server/public/common.js +29 -103
  19. package/server/public/create-launcher.js +31 -4
  20. package/server/public/electron.css +6 -0
  21. package/server/public/style.css +0 -337
  22. package/server/public/task-launcher.css +3 -11
  23. package/server/public/task-launcher.js +32 -5
  24. package/server/public/universal-launcher.js +26 -3
  25. package/server/socket.js +11 -22
  26. package/server/views/app.ejs +30 -167
  27. package/server/views/d.ejs +35 -33
  28. package/server/views/editor.ejs +4 -25
  29. package/server/views/partials/main_sidebar.ejs +0 -1
  30. package/server/views/partials/menu.ejs +1 -1
  31. package/server/views/pre.ejs +1 -1
  32. package/server/views/shell.ejs +3 -11
  33. package/server/views/task_launch.ejs +10 -10
  34. package/server/views/terminal.ejs +5 -34
  35. package/spec/INSTRUCTION_SYNC.md +5 -5
  36. package/kernel/agent_instructions.js +0 -166
  37. package/kernel/api/shell_run_template.js +0 -273
  38. package/kernel/api/uri/index.js +0 -51
  39. package/kernel/plugin_sources.js +0 -289
  40. package/kernel/watch/context.js +0 -42
  41. package/kernel/watch/drivers/fs.js +0 -71
  42. package/kernel/watch/drivers/poll.js +0 -33
  43. package/kernel/watch/index.js +0 -185
  44. package/server/features/index.js +0 -13
  45. package/server/features/notes/index.js +0 -41
  46. package/server/features/notes/parser.js +0 -174
  47. package/server/features/notes/public/notes.css +0 -955
  48. package/server/features/notes/public/notes.js +0 -1149
  49. package/server/features/notes/registry_import.js +0 -412
  50. package/server/features/notes/routes.js +0 -156
  51. package/server/features/notes/service.js +0 -326
  52. package/server/features/notes/watcher.js +0 -74
  53. package/server/lib/workspace_catalog.js +0 -151
  54. package/server/lib/workspace_runtime.js +0 -390
  55. package/server/public/tasker.css +0 -336
  56. package/server/public/tasker.js +0 -407
  57. package/server/routes/workspaces.js +0 -44
  58. package/server/views/partials/workspace_row.ejs +0 -61
  59. package/server/views/tasker.ejs +0 -40
  60. package/server/views/workspaces.ejs +0 -813
  61. package/system/plugin/antigravity/antigravity.png +0 -0
  62. package/system/plugin/antigravity/pinokio.js +0 -35
  63. package/system/plugin/claude/claude.png +0 -0
  64. package/system/plugin/claude/pinokio.js +0 -61
  65. package/system/plugin/claude-auto/claude.png +0 -0
  66. package/system/plugin/claude-auto/pinokio.js +0 -72
  67. package/system/plugin/claude-desktop/icon.jpeg +0 -0
  68. package/system/plugin/claude-desktop/pinokio.js +0 -37
  69. package/system/plugin/codex/openai.webp +0 -0
  70. package/system/plugin/codex/pinokio.js +0 -56
  71. package/system/plugin/codex-auto/openai.webp +0 -0
  72. package/system/plugin/codex-auto/pinokio.js +0 -63
  73. package/system/plugin/codex-desktop/icon.png +0 -0
  74. package/system/plugin/codex-desktop/pinokio.js +0 -37
  75. package/system/plugin/crush/crush.png +0 -0
  76. package/system/plugin/crush/pinokio.js +0 -29
  77. package/system/plugin/cursor/cursor.jpeg +0 -0
  78. package/system/plugin/cursor/pinokio.js +0 -37
  79. package/system/plugin/gemini/gemini.jpeg +0 -0
  80. package/system/plugin/gemini/pinokio.js +0 -38
  81. package/system/plugin/gemini-auto/gemini.jpeg +0 -0
  82. package/system/plugin/gemini-auto/pinokio.js +0 -41
  83. package/system/plugin/qwen/pinokio.js +0 -48
  84. package/system/plugin/qwen/qwen.png +0 -0
  85. package/system/plugin/vscode/pinokio.js +0 -34
  86. package/system/plugin/vscode/vscode.png +0 -0
  87. package/system/plugin/windsurf/pinokio.js +0 -37
  88. package/system/plugin/windsurf/windsurf.png +0 -0
  89. package/test/plugin-sources.test.js +0 -45
@@ -895,7 +895,6 @@ const createPluginTerminalDiscoveryRefresher = (context = {}) => {
895
895
  const enabled = (() => {
896
896
  try {
897
897
  return window.location.pathname.startsWith("/run/plugin/")
898
- || window.location.pathname.startsWith("/pinokio/run/plugin/")
899
898
  || (window.location.pathname.startsWith("/run/api/") && /\/pinokio\.js$/i.test(window.location.pathname))
900
899
  } catch (_) {
901
900
  return false
@@ -1233,10 +1232,6 @@ document.addEventListener("DOMContentLoaded", async () => {
1233
1232
  const scriptAction = <% if (typeof action !== 'undefined') { %><%- JSON.stringify(action) %><% } else { %>null<% } %>
1234
1233
  const protectionAppId = <% if (typeof protection_app_id !== 'undefined' && protection_app_id) { %><%- JSON.stringify(protection_app_id) %><% } else { %>""<% } %>
1235
1234
  const initialProtectionEnabled = <% if (typeof protection_enabled !== 'undefined') { %><%- JSON.stringify(protection_enabled === true) %><% } else { %>false<% } %>
1236
- const activeProcessWait = <% if (typeof active_process_wait !== 'undefined' && active_process_wait) { %><%- JSON.stringify(active_process_wait) %><% } else { %>null<% } %>
1237
- if (activeProcessWait && (activeProcessWait.title || activeProcessWait.description) && window.PinokioWaitFooterStatus) {
1238
- window.PinokioWaitFooterStatus.show(activeProcessWait)
1239
- }
1240
1235
  const shouldBypassAiConsent = () => {
1241
1236
  const normalize = (value) => (typeof value === "string" ? value.replace(/\\/g, "/") : "")
1242
1237
  const uri = normalize(scriptUri || "")
@@ -1495,17 +1490,17 @@ document.addEventListener("DOMContentLoaded", async () => {
1495
1490
  refreshParent(packet)
1496
1491
  reloadMemory()
1497
1492
  runControls.set("running")
1498
- if (packet.data && (packet.data.title || packet.data.description)) {
1493
+ if (packet.data && packet.data.description) {
1499
1494
  if ('current' in packet.data) {
1500
1495
  document.querySelector("#status-window").innerHTML = `<b>
1501
1496
  <i class="fa-solid fa-circle-notch fa-spin"></i>(${packet.data.current+1}/${packet.data.total}) ${packet.data.title ? packet.data.title : ''}
1502
1497
  </b>
1503
- <div class='flexible content'>${packet.data.description ? packet.data.description : ''}</div>`
1498
+ <div class='flexible content'>${packet.data.description}</div>`
1504
1499
  } else {
1505
1500
  document.querySelector("#status-window").innerHTML = `<b>
1506
1501
  <i class="fa-solid fa-circle-notch fa-spin"></i> ${packet.data.title ? packet.data.title : ''}
1507
1502
  </b>
1508
- <div class='flexible content'>${packet.data.description ? packet.data.description : ''}</div>`
1503
+ <div class='flexible content'></div>`
1509
1504
  // <div class='toggle-expand'>
1510
1505
  // <i class="fa-solid fa-circle-chevron-up"></i>
1511
1506
  // </div>`
@@ -1683,14 +1678,6 @@ document.addEventListener("DOMContentLoaded", async () => {
1683
1678
  // uri: "~" + location.pathname,
1684
1679
  uri: packet.id
1685
1680
  })
1686
- } else if (packet.type === "process.wait.start") {
1687
- if (window.PinokioWaitFooterStatus) {
1688
- window.PinokioWaitFooterStatus.show(packet.data)
1689
- }
1690
- } else if (packet.type === "process.wait.end") {
1691
- if (window.PinokioWaitFooterStatus) {
1692
- window.PinokioWaitFooterStatus.hide()
1693
- }
1694
1681
  } else if (packet.type === 'wait') {
1695
1682
  await WaitModal(packet.data)
1696
1683
  } else if (packet.type === "htmlmodal") {
@@ -2784,10 +2771,6 @@ document.addEventListener("DOMContentLoaded", async () => {
2784
2771
  this.resizeSync.sendResize(this.term.cols, this.term.rows)
2785
2772
  });
2786
2773
  this.observer.observe(document.body)
2787
- const terminalContainer = document.querySelector(".terminal-container")
2788
- if (terminalContainer) {
2789
- this.observer.observe(terminalContainer)
2790
- }
2791
2774
  }
2792
2775
  }
2793
2776
  <% if (!install_required) { %>
@@ -2933,9 +2916,9 @@ const reloadMemory = async () => {
2933
2916
  </script>
2934
2917
  </head>
2935
2918
  <% if (install_required) { %>
2936
- <body class='terminal-page frozen <%=theme%> <%= locals.full_navbar ? "has-full-navbar" : "" %>' data-pinokio-notes-scope="true" data-task-save-cwd="<%= cwd || '' %>" data-task-save-workspaces-root="<%= taskSaveWorkspacesRoot || '' %>">
2919
+ <body class='terminal-page frozen <%=theme%> <%= locals.full_navbar ? "has-full-navbar" : "" %>' data-task-save-cwd="<%= cwd || '' %>" data-task-save-workspaces-root="<%= taskSaveWorkspacesRoot || '' %>">
2937
2920
  <% } else { %>
2938
- <body class='terminal-page <%=theme%> <%= locals.full_navbar ? "has-full-navbar" : "" %>' data-pinokio-notes-scope="true" data-task-save-cwd="<%= cwd || '' %>" data-task-save-workspaces-root="<%= taskSaveWorkspacesRoot || '' %>">
2921
+ <body class='terminal-page <%=theme%> <%= locals.full_navbar ? "has-full-navbar" : "" %>' data-task-save-cwd="<%= cwd || '' %>" data-task-save-workspaces-root="<%= taskSaveWorkspacesRoot || '' %>">
2939
2922
  <% } %>
2940
2923
  <% if (locals.full_navbar) { %>
2941
2924
  <header class="navheader grabbable">
@@ -2965,9 +2948,6 @@ const reloadMemory = async () => {
2965
2948
  </h1>
2966
2949
  </header>
2967
2950
  <% } %>
2968
- <% if (!install_required && typeof note_watch_enabled !== 'undefined' && note_watch_enabled) { %>
2969
- <div id="pinokio-notes-slot" class="pinokio-notes-slot" aria-live="polite"></div>
2970
- <% } %>
2971
2951
  <% if (!install_required) { %>
2972
2952
  <header class='navheader2'>
2973
2953
  <div class='runner'>
@@ -3106,14 +3086,5 @@ const reloadMemory = async () => {
3106
3086
  </a>
3107
3087
  </div>
3108
3088
  </div>
3109
- <% if (typeof note_watch_enabled !== 'undefined' && note_watch_enabled) { %>
3110
- <script>
3111
- window.PinokioNoteContext = {
3112
- cwd: <%- JSON.stringify((typeof note_watch_cwd !== 'undefined' && note_watch_cwd) ? note_watch_cwd : '').replace(/</g, '\\u003c') %>
3113
- };
3114
- </script>
3115
- <link rel="stylesheet" href="/notes.css?v=flow-footer-fullscreen">
3116
- <script src="/notes.js?v=flow-footer-fullscreen"></script>
3117
- <% } %>
3118
3089
  </body>
3119
3090
  </html>
@@ -18,6 +18,7 @@ Fix three related problems with minimal behavioral change:
18
18
 
19
19
  Paths:
20
20
 
21
+ - `PINOKIO_HOME/plugin/code`
21
22
  - `PINOKIO_HOME/prototype/system`
22
23
  - `PINOKIO_HOME/network/system`
23
24
 
@@ -31,8 +32,6 @@ Policy:
31
32
  - On normal startup, bootstrap only if missing.
32
33
  - At runtime, if a needed file inside one of these repos is missing, attempt a targeted Git restore for that path only.
33
34
 
34
- Built-in plugins are packaged under `pinokiod/system/plugin` and are not cloned, refreshed, or repaired inside `PINOKIO_HOME`.
35
-
36
35
  ### 2. Managed downloaded instruction sources
37
36
 
38
37
  Paths:
@@ -110,7 +109,7 @@ Policy:
110
109
 
111
110
  Paths:
112
111
 
113
- - everything under `PINOKIO_HOME/plugin/*`
112
+ - everything under `PINOKIO_HOME/plugin/*` except `plugin/code`
114
113
  - everything under `PINOKIO_HOME/prototype/*` except `prototype/system`, `prototype/PINOKIO.md`, and `prototype/PTERM.md`
115
114
  - everything under `PINOKIO_HOME/network/*` except `network/system`
116
115
 
@@ -130,6 +129,7 @@ Policy:
130
129
 
131
130
  Required behavior:
132
131
 
132
+ - refresh `plugin/code`
133
133
  - refresh `prototype/system`
134
134
  - refresh `network/system`
135
135
  - refresh `prototype/PINOKIO.md`
@@ -164,7 +164,7 @@ Ordering requirement:
164
164
 
165
165
  - After a version-change cleanup, Pinokio must not rely on the current startup order where `Environment.init({}, kernel)` can run before the async reclone/redownload of managed repos/docs completes.
166
166
  - Home output regeneration must either:
167
- - run after `prototype/system`, `network/system`, `PINOKIO.md`, and `PTERM.md` have been restored
167
+ - run after `plugin/code`, `prototype/system`, `network/system`, `PINOKIO.md`, and `PTERM.md` have been restored
168
168
  - or be rerun once those managed inputs are restored
169
169
 
170
170
  ### C. Runtime missing-file repair
@@ -298,7 +298,7 @@ File:
298
298
 
299
299
  Changes:
300
300
 
301
- - remove `PINOKIO_HOME/plugin` from managed refresh; built-in plugins are packaged under `pinokiod/system/plugin`
301
+ - replace whole-folder refresh of `PINOKIO_HOME/plugin` with refresh of `PINOKIO_HOME/plugin/code` only
302
302
  - replace whole-folder refresh of `PINOKIO_HOME/prototype` with refresh of:
303
303
  - `PINOKIO_HOME/prototype/system`
304
304
  - `PINOKIO_HOME/prototype/PINOKIO.md`
@@ -1,166 +0,0 @@
1
- const fs = require("fs")
2
- const path = require("path")
3
- const PluginSources = require("./plugin_sources")
4
-
5
- const NOTES_BEGIN = "<!-- PINOKIO:NOTES:BEGIN -->"
6
- const NOTES_END = "<!-- PINOKIO:NOTES:END -->"
7
- const LEGACY_DRAFTS_BEGIN = "<!-- PINOKIO:DRAFTS:BEGIN -->"
8
- const LEGACY_DRAFTS_END = "<!-- PINOKIO:DRAFTS:END -->"
9
-
10
- const AGENT_INSTRUCTION_FILES = [
11
- "AGENTS.md",
12
- "CLAUDE.md",
13
- "GEMINI.md",
14
- "QWEN.md",
15
- ".windsurfrules",
16
- ".cursorrules",
17
- ".clinerules",
18
- ]
19
-
20
- const escapeRegExp = (value) => String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
21
-
22
- const normalizeBlock = (block) => `${String(block || "").trim()}\n`
23
-
24
- const extractManagedBlock = (content, begin = NOTES_BEGIN, end = NOTES_END) => {
25
- const text = String(content || "")
26
- const start = text.indexOf(begin)
27
- const finish = text.indexOf(end, start + begin.length)
28
- if (start < 0 || finish < 0) {
29
- return ""
30
- }
31
- return text.slice(start, finish + end.length)
32
- }
33
-
34
- const insertionIndex = (content) => {
35
- const text = String(content || "")
36
- let offset = 0
37
-
38
- if (text.startsWith("---\n")) {
39
- const frontmatterEnd = text.indexOf("\n---\n", 4)
40
- if (frontmatterEnd >= 0) {
41
- offset = frontmatterEnd + "\n---\n".length
42
- while (text[offset] === "\n") {
43
- offset += 1
44
- }
45
- }
46
- }
47
-
48
- const rest = text.slice(offset)
49
- const h1 = rest.match(/^# .*(?:\n|$)/)
50
- if (h1) {
51
- return offset + h1[0].length
52
- }
53
-
54
- return offset
55
- }
56
-
57
- const insertManagedBlock = (content, block) => {
58
- const text = String(content || "").replace(/\r\n/g, "\n")
59
- const noteBlock = normalizeBlock(block)
60
- if (!text.trim()) {
61
- return noteBlock
62
- }
63
-
64
- const index = insertionIndex(text)
65
- const before = text.slice(0, index)
66
- const after = text.slice(index).replace(/^\n+/, "")
67
- const beforeGap = before.endsWith("\n\n") ? "" : before.endsWith("\n") ? "\n" : "\n\n"
68
- const afterGap = after ? "\n" : ""
69
-
70
- return `${before}${beforeGap}${noteBlock}${afterGap}${after}`
71
- }
72
-
73
- const upsertManagedBlock = (content, block, begin = NOTES_BEGIN, end = NOTES_END) => {
74
- let text = String(content || "").replace(/\r\n/g, "\n")
75
- const noteBlock = normalizeBlock(block)
76
- const pattern = new RegExp(`${escapeRegExp(begin)}[\\s\\S]*?${escapeRegExp(end)}\\n?`)
77
- const legacyPattern = new RegExp(`${escapeRegExp(LEGACY_DRAFTS_BEGIN)}[\\s\\S]*?${escapeRegExp(LEGACY_DRAFTS_END)}\\n?`)
78
-
79
- if (pattern.test(text)) {
80
- return text.replace(pattern, `${noteBlock}\n`)
81
- }
82
-
83
- text = text.replace(legacyPattern, "")
84
- return insertManagedBlock(text, noteBlock)
85
- }
86
-
87
- const containedBy = (child, parent) => {
88
- const relative = path.relative(path.resolve(parent), path.resolve(child))
89
- return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative))
90
- }
91
-
92
- const isPluginScriptPath = (kernel, scriptPath) => {
93
- if (!kernel || !scriptPath) {
94
- return false
95
- }
96
-
97
- const absolutePath = path.resolve(scriptPath)
98
- if (containedBy(absolutePath, kernel.path("plugin", "code"))) {
99
- return false
100
- }
101
-
102
- const roots = [
103
- PluginSources.systemPluginRoot(kernel),
104
- kernel.path("plugin"),
105
- ].filter(Boolean)
106
-
107
- return roots.some((root) => containedBy(absolutePath, root))
108
- }
109
-
110
- const ensureManagedBlockInFile = async (filePath, block) => {
111
- let content = ""
112
- try {
113
- content = await fs.promises.readFile(filePath, "utf8")
114
- } catch (e) {
115
- if (!e || e.code !== "ENOENT") {
116
- throw e
117
- }
118
- }
119
-
120
- const nextContent = upsertManagedBlock(content, block)
121
- if (nextContent !== content) {
122
- await fs.promises.writeFile(filePath, nextContent, "utf8")
123
- return true
124
- }
125
-
126
- return false
127
- }
128
-
129
- const ensureNoteInstructionsForCwd = async ({ kernel, cwd }) => {
130
- if (!kernel || !cwd) {
131
- return { updated: [], skipped: "missing-cwd" }
132
- }
133
-
134
- const targetDir = path.resolve(cwd)
135
- const stat = await fs.promises.stat(targetDir).catch(() => null)
136
- if (!stat || !stat.isDirectory()) {
137
- return { updated: [], skipped: "invalid-cwd" }
138
- }
139
-
140
- const sourcePath = kernel.path("prototype", "system", "AGENTS.md")
141
- const source = await fs.promises.readFile(sourcePath, "utf8").catch(() => "")
142
- const block = extractManagedBlock(source)
143
- if (!block) {
144
- return { updated: [], skipped: "missing-block" }
145
- }
146
-
147
- const updated = []
148
- for (const filename of AGENT_INSTRUCTION_FILES) {
149
- const filePath = path.join(targetDir, filename)
150
- if (await ensureManagedBlockInFile(filePath, block)) {
151
- updated.push(filePath)
152
- }
153
- }
154
-
155
- return { updated, skipped: null }
156
- }
157
-
158
- module.exports = {
159
- NOTES_BEGIN,
160
- NOTES_END,
161
- AGENT_INSTRUCTION_FILES,
162
- extractManagedBlock,
163
- upsertManagedBlock,
164
- isPluginScriptPath,
165
- ensureNoteInstructionsForCwd,
166
- }
@@ -1,273 +0,0 @@
1
- function isCmdShellName(shellName) {
2
- const name = (shellName || '').toLowerCase()
3
- return name.includes('cmd.exe') || name === 'cmd'
4
- }
5
-
6
- function isPowerShellName(shellName) {
7
- const name = (shellName || '').toLowerCase()
8
- return name.includes('powershell') || name.includes('pwsh')
9
- }
10
-
11
- const ENV_ARG_MARKER_RE = /__PINOKIO_ENVARG_(\d+)__/g
12
-
13
- function envArgMarker(index) {
14
- return `__PINOKIO_ENVARG_${index}__`
15
- }
16
-
17
- function isPinokioEnvArgKey(key) {
18
- return /^PINOKIO_ARG_\d+$/.test(key || "")
19
- }
20
-
21
- function hasEnvArgMarker(value) {
22
- ENV_ARG_MARKER_RE.lastIndex = 0
23
- return ENV_ARG_MARKER_RE.test(String(value))
24
- }
25
-
26
- function quotePosixLiteral(value) {
27
- const input = value == null ? "" : String(value)
28
- return `'${input.split("'").join("'\"'\"'")}'`
29
- }
30
-
31
- function quotePowerShellComposite(value) {
32
- const input = value == null ? "" : String(value)
33
- ENV_ARG_MARKER_RE.lastIndex = 0
34
- let output = '"'
35
- let lastIndex = 0
36
- for (const match of input.matchAll(ENV_ARG_MARKER_RE)) {
37
- const literal = input.slice(lastIndex, match.index)
38
- output += literal.replace(/[`"$]/g, (char) => "`" + char)
39
- output += "${env:PINOKIO_ARG_" + match[1] + "}"
40
- lastIndex = match.index + match[0].length
41
- }
42
- output += input.slice(lastIndex).replace(/[`"$]/g, (char) => "`" + char)
43
- output += '"'
44
- return output
45
- }
46
-
47
- function quoteCmdComposite(value) {
48
- const input = value == null ? "" : String(value)
49
- ENV_ARG_MARKER_RE.lastIndex = 0
50
- let output = '"'
51
- let lastIndex = 0
52
- for (const match of input.matchAll(ENV_ARG_MARKER_RE)) {
53
- const literal = input.slice(lastIndex, match.index)
54
- output += literal.replace(/([()%!^"<>&|])/g, '^$1')
55
- output += "!PINOKIO_ARG_" + match[1] + "!"
56
- lastIndex = match.index + match[0].length
57
- }
58
- output += input.slice(lastIndex).replace(/([()%!^"<>&|])/g, '^$1')
59
- output += '"'
60
- return output
61
- }
62
-
63
- function quoteEnvArgComposite(value, shellName) {
64
- const input = value == null ? "" : String(value)
65
- if (isCmdShellName(shellName)) {
66
- return quoteCmdComposite(input)
67
- }
68
- if (isPowerShellName(shellName)) {
69
- return quotePowerShellComposite(input)
70
- }
71
-
72
- ENV_ARG_MARKER_RE.lastIndex = 0
73
- const parts = []
74
- let lastIndex = 0
75
- for (const match of input.matchAll(ENV_ARG_MARKER_RE)) {
76
- const literal = input.slice(lastIndex, match.index)
77
- if (literal) {
78
- parts.push(quotePosixLiteral(literal))
79
- }
80
- parts.push('"$PINOKIO_ARG_' + match[1] + '"')
81
- lastIndex = match.index + match[0].length
82
- }
83
- const tail = input.slice(lastIndex)
84
- if (tail) {
85
- parts.push(quotePosixLiteral(tail))
86
- }
87
- return parts.length > 0 ? parts.join("") : "''"
88
- }
89
-
90
- function shellNameFor(kernel, params) {
91
- let shellName = kernel && kernel.platform === "win32" ? "cmd.exe" : "bash"
92
- if (params && typeof params.shell === "string" && params.shell.trim()) {
93
- shellName = params.shell
94
- }
95
- return shellName
96
- }
97
-
98
- function isPlainObject(value) {
99
- return value && value.constructor === Object
100
- }
101
-
102
- function hasMultiline(value) {
103
- return typeof value === "string" && /[\r\n]/.test(value)
104
- }
105
-
106
- function isStructuredArgvMessage(value) {
107
- return isPlainObject(value) && Array.isArray(value._)
108
- }
109
-
110
- function hasStructuredArgvMessage(value) {
111
- if (isStructuredArgvMessage(value)) {
112
- return true
113
- }
114
- if (Array.isArray(value)) {
115
- return value.some((item) => isStructuredArgvMessage(item))
116
- }
117
- return false
118
- }
119
-
120
- function protectStructuredString(value, state) {
121
- if (!hasMultiline(value)) {
122
- return value
123
- }
124
- const name = `PINOKIO_ARG_${state.args.length}`
125
- state.args.push({
126
- name,
127
- value: value == null ? "" : String(value)
128
- })
129
- return envArgMarker(state.args.length - 1)
130
- }
131
-
132
- function protectStructuredValue(value, state) {
133
- if (typeof value === "string") {
134
- return protectStructuredString(value, state)
135
- }
136
- if (Array.isArray(value)) {
137
- return value.map((item) => protectStructuredValue(item, state))
138
- }
139
- if (isPlainObject(value)) {
140
- const rendered = {}
141
- for (const [key, item] of Object.entries(value)) {
142
- rendered[key] = protectStructuredValue(item, state)
143
- }
144
- return rendered
145
- }
146
- return value
147
- }
148
-
149
- function protectStructuredMessage(value, state) {
150
- if (isStructuredArgvMessage(value)) {
151
- return protectStructuredValue(value, state)
152
- }
153
- if (Array.isArray(value)) {
154
- return value.map((item) => isStructuredArgvMessage(item) ? protectStructuredValue(item, state) : item)
155
- }
156
- return value
157
- }
158
-
159
- function renderEnvArgs(kernel, rpc, memory) {
160
- if (!rpc || rpc.method !== "shell.run" || !rpc.params || !hasStructuredArgvMessage(rpc.params.message)) {
161
- return rpc
162
- }
163
-
164
- const shellName = shellNameFor(kernel, rpc.params)
165
- const state = { args: [] }
166
- const message = protectStructuredMessage(rpc.params.message, state)
167
-
168
- if (state.args.length === 0) {
169
- return rpc
170
- }
171
-
172
- const env = Object.assign({}, rpc.params.env || {})
173
- for (const arg of state.args) {
174
- env[arg.name] = arg.value
175
- }
176
-
177
- return {
178
- ...rpc,
179
- params: {
180
- ...rpc.params,
181
- message,
182
- env,
183
- _pinokio_env_args: state.args,
184
- _pinokio_cmd_delayed_expansion: isCmdShellName(shellName)
185
- }
186
- }
187
- }
188
-
189
- function envArgDetails(value) {
190
- const normalized = String(value == null ? "" : value)
191
- .replace(/\r\n/g, "\n")
192
- .replace(/\r/g, "\n")
193
- .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "?")
194
- const lines = normalized.split("\n")
195
- const previewLines = []
196
- const maxLines = 8
197
- const maxChars = 800
198
- let used = 0
199
-
200
- for (const line of lines.slice(0, maxLines)) {
201
- const remaining = maxChars - used
202
- if (remaining <= 0) {
203
- break
204
- }
205
- if (line.length > remaining) {
206
- previewLines.push(line.slice(0, remaining) + "...")
207
- used = maxChars
208
- break
209
- }
210
- previewLines.push(line)
211
- used += line.length + 1
212
- }
213
-
214
- const preview = previewLines.join("\n")
215
- const truncated = lines.length > previewLines.length || normalized.length > preview.length
216
- if (truncated && previewLines[previewLines.length - 1] !== "...") {
217
- previewLines.push("...")
218
- }
219
-
220
- return {
221
- lineCount: normalized.length === 0 ? 0 : lines.length,
222
- previewLines,
223
- truncated
224
- }
225
- }
226
-
227
- function formatEnvArgsPreview(args) {
228
- if (!Array.isArray(args) || args.length === 0) {
229
- return ""
230
- }
231
- const lines = ["\r\nPinokio shell args", ""]
232
- for (const arg of args) {
233
- const details = envArgDetails(arg.value)
234
- lines.push(`${arg.name} ${details.lineCount} lines`)
235
- for (const previewLine of details.previewLines) {
236
- lines.push(` ${previewLine}`)
237
- }
238
- lines.push("")
239
- }
240
- return lines.join("\r\n") + "\r\n"
241
- }
242
-
243
- function envArgSummary(value) {
244
- const details = envArgDetails(value)
245
- return {
246
- type: "pinokio env arg",
247
- lines: details.lineCount,
248
- preview: details.previewLines.join("\n"),
249
- truncated: details.truncated
250
- }
251
- }
252
-
253
- function redactEnvArgs(env) {
254
- if (!env || typeof env !== "object") {
255
- return env
256
- }
257
- const redacted = { ...env }
258
- for (const key of Object.keys(redacted)) {
259
- if (isPinokioEnvArgKey(key)) {
260
- redacted[key] = envArgSummary(redacted[key])
261
- }
262
- }
263
- return redacted
264
- }
265
-
266
- module.exports = {
267
- renderEnvArgs,
268
- quoteEnvArgComposite,
269
- hasEnvArgMarker,
270
- isPinokioEnvArgKey,
271
- formatEnvArgsPreview,
272
- redactEnvArgs
273
- }
@@ -1,51 +0,0 @@
1
- const Util = require('../../util')
2
-
3
- const appendQueryParams = (uri, params) => {
4
- if (!params || typeof params !== 'object' || Array.isArray(params)) {
5
- return uri
6
- }
7
-
8
- const entries = []
9
- for (const [key, value] of Object.entries(params)) {
10
- const values = Array.isArray(value) ? value : [value]
11
- for (const item of values) {
12
- if (item === undefined || item === null) {
13
- continue
14
- }
15
- const serialized = typeof item === 'object' ? JSON.stringify(item) : String(item)
16
- entries.push(`${encodeURIComponent(key)}=${encodeURIComponent(serialized)}`)
17
- }
18
- }
19
-
20
- if (entries.length === 0) {
21
- return uri
22
- }
23
-
24
- const hashIndex = uri.indexOf('#')
25
- const base = hashIndex === -1 ? uri : uri.slice(0, hashIndex)
26
- const hash = hashIndex === -1 ? '' : uri.slice(hashIndex)
27
- const separator = base.includes('?')
28
- ? (base.endsWith('?') || base.endsWith('&') ? '' : '&')
29
- : '?'
30
-
31
- return `${base}${separator}${entries.join('&')}${hash}`
32
- }
33
-
34
- class URI {
35
- build(params = {}) {
36
- const uri = typeof params.uri === 'string' ? params.uri.trim() : ''
37
- if (!uri) {
38
- throw new Error('uri.open requires params.uri')
39
- }
40
- return appendQueryParams(uri, params.params)
41
- }
42
-
43
- async open(req, ondata, kernel) {
44
- const uri = this.build(req.params)
45
- ondata({ raw: `\r\nopening uri: ${uri}\r\n` })
46
- const result = await Util.openURI(uri)
47
- return { uri, result }
48
- }
49
- }
50
-
51
- module.exports = URI