pinokiod 7.2.17 → 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 (78) hide show
  1. package/Dockerfile +2 -0
  2. package/kernel/api/index.js +1 -42
  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/index.js +11 -1
  7. package/kernel/environment.js +2 -182
  8. package/kernel/git.js +13 -0
  9. package/kernel/index.js +1 -64
  10. package/kernel/plugin.js +58 -6
  11. package/kernel/shell.js +2 -21
  12. package/kernel/util.js +0 -60
  13. package/package.json +1 -1
  14. package/server/index.js +149 -176
  15. package/server/lib/content_validation.js +25 -28
  16. package/server/public/common.js +29 -103
  17. package/server/public/create-launcher.js +31 -4
  18. package/server/public/electron.css +6 -0
  19. package/server/public/style.css +0 -337
  20. package/server/public/task-launcher.js +32 -5
  21. package/server/public/universal-launcher.js +26 -3
  22. package/server/socket.js +11 -22
  23. package/server/views/app.ejs +30 -167
  24. package/server/views/d.ejs +33 -0
  25. package/server/views/editor.ejs +4 -25
  26. package/server/views/partials/main_sidebar.ejs +0 -1
  27. package/server/views/shell.ejs +3 -11
  28. package/server/views/terminal.ejs +3 -23
  29. package/server/views/terminals.ejs +0 -1
  30. package/spec/INSTRUCTION_SYNC.md +5 -5
  31. package/kernel/api/shell_run_template.js +0 -273
  32. package/kernel/api/uri/index.js +0 -51
  33. package/kernel/plugin_sources.js +0 -236
  34. package/kernel/watch/context.js +0 -42
  35. package/kernel/watch/drivers/fs.js +0 -71
  36. package/kernel/watch/drivers/poll.js +0 -33
  37. package/kernel/watch/index.js +0 -158
  38. package/server/features/drafts/index.js +0 -41
  39. package/server/features/drafts/parser.js +0 -169
  40. package/server/features/drafts/public/drafts.js +0 -1504
  41. package/server/features/drafts/registry_import.js +0 -412
  42. package/server/features/drafts/routes.js +0 -68
  43. package/server/features/drafts/service.js +0 -261
  44. package/server/features/drafts/watcher.js +0 -76
  45. package/server/features/index.js +0 -13
  46. package/server/lib/workspace_catalog.js +0 -151
  47. package/server/lib/workspace_runtime.js +0 -390
  48. package/server/routes/workspaces.js +0 -44
  49. package/server/views/partials/workspace_row.ejs +0 -61
  50. package/server/views/workspaces.ejs +0 -812
  51. package/system/plugin/antigravity/antigravity.png +0 -0
  52. package/system/plugin/antigravity/pinokio.js +0 -37
  53. package/system/plugin/claude/claude.png +0 -0
  54. package/system/plugin/claude/pinokio.js +0 -63
  55. package/system/plugin/claude-auto/claude.png +0 -0
  56. package/system/plugin/claude-auto/pinokio.js +0 -74
  57. package/system/plugin/claude-desktop/icon.jpeg +0 -0
  58. package/system/plugin/claude-desktop/pinokio.js +0 -39
  59. package/system/plugin/codex/openai.webp +0 -0
  60. package/system/plugin/codex/pinokio.js +0 -58
  61. package/system/plugin/codex-auto/openai.webp +0 -0
  62. package/system/plugin/codex-auto/pinokio.js +0 -65
  63. package/system/plugin/codex-desktop/icon.png +0 -0
  64. package/system/plugin/codex-desktop/pinokio.js +0 -39
  65. package/system/plugin/crush/crush.png +0 -0
  66. package/system/plugin/crush/pinokio.js +0 -31
  67. package/system/plugin/cursor/cursor.jpeg +0 -0
  68. package/system/plugin/cursor/pinokio.js +0 -39
  69. package/system/plugin/gemini/gemini.jpeg +0 -0
  70. package/system/plugin/gemini/pinokio.js +0 -40
  71. package/system/plugin/gemini-auto/gemini.jpeg +0 -0
  72. package/system/plugin/gemini-auto/pinokio.js +0 -43
  73. package/system/plugin/qwen/pinokio.js +0 -50
  74. package/system/plugin/qwen/qwen.png +0 -0
  75. package/system/plugin/vscode/pinokio.js +0 -36
  76. package/system/plugin/vscode/vscode.png +0 -0
  77. package/system/plugin/windsurf/pinokio.js +0 -39
  78. package/system/plugin/windsurf/windsurf.png +0 -0
@@ -1,412 +0,0 @@
1
- const express = require("express")
2
- const fs = require("fs")
3
- const path = require("path")
4
- const axios = require("axios")
5
- const FormData = require("form-data")
6
- const mime = require("mime-types")
7
- const {
8
- describeMediaRefs,
9
- extractTitleAndBody,
10
- normalizeTitle
11
- } = require("./parser")
12
-
13
- const DEFAULT_MAX_FILES = 10
14
- const DEFAULT_MAX_FILE_BYTES = 25 * 1024 * 1024
15
-
16
- const asyncHandler = (fn) => (req, res, next) => {
17
- Promise.resolve(fn(req, res, next)).catch(next)
18
- }
19
-
20
- const escapeHtml = (value) => String(value || "")
21
- .replace(/&/g, "&")
22
- .replace(/</g, "&lt;")
23
- .replace(/>/g, "&gt;")
24
- .replace(/"/g, "&quot;")
25
-
26
- function renderMessage(res, status, title, message) {
27
- res.status(status).send(`<!doctype html>
28
- <html>
29
- <head>
30
- <meta charset="utf-8">
31
- <title>${escapeHtml(title)}</title>
32
- <style>
33
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 40px; color: #111827; }
34
- .box { max-width: 680px; border: 1px solid #d1d5db; border-radius: 8px; padding: 18px; }
35
- h1 { margin: 0 0 8px; font-size: 22px; }
36
- p { margin: 0; color: #4b5563; line-height: 1.5; }
37
- </style>
38
- </head>
39
- <body>
40
- <div class="box">
41
- <h1>${escapeHtml(title)}</h1>
42
- <p>${escapeHtml(message)}</p>
43
- </div>
44
- </body>
45
- </html>`)
46
- }
47
-
48
- function renderImportLauncher(res, { authorizeUrl, autoOpen }) {
49
- res.status(200).send(`<!doctype html>
50
- <html>
51
- <head>
52
- <meta charset="utf-8">
53
- <title>Import draft</title>
54
- <style>
55
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 40px; color: #111827; background: #f8fafc; }
56
- .box { max-width: 680px; border: 1px solid #d1d5db; border-radius: 8px; padding: 18px; background: white; box-shadow: 0 16px 42px rgba(15, 23, 42, 0.08); }
57
- h1 { margin: 0 0 8px; font-size: 22px; }
58
- p { margin: 0 0 14px; color: #4b5563; line-height: 1.5; }
59
- button, a.button { display: inline-flex; align-items: center; justify-content: center; min-height: 34px; border: 1px solid #111827; border-radius: 6px; background: #111827; color: white; padding: 7px 12px; font-weight: 700; text-decoration: none; cursor: pointer; }
60
- .muted { color: #6b7280; font-size: 13px; }
61
- </style>
62
- </head>
63
- <body>
64
- <div class="box">
65
- <h1>Import draft</h1>
66
- <p id="status">${autoOpen ? "Opening the registry authorization page in your browser..." : "Click Open registry to authorize the import."}</p>
67
- <button id="open" type="button">Open registry</button>
68
- <div class="muted" style="margin-top:12px;">The registry will return to Pinokio after authorization.</div>
69
- </div>
70
- <script>
71
- window.__PINOKIO_DRAFT_IMPORT_VERSION = "metadata-b64";
72
- const authorizeUrl = ${JSON.stringify(authorizeUrl)};
73
- const autoOpen = ${JSON.stringify(Boolean(autoOpen))};
74
- const statusEl = document.getElementById("status");
75
- const openButton = document.getElementById("open");
76
-
77
- function setStatus(message) {
78
- statusEl.textContent = message;
79
- }
80
-
81
- async function openRegistry() {
82
- setStatus("Opening registry in your browser...");
83
- const response = await fetch("/pinokio/open", {
84
- method: "POST",
85
- headers: { "Content-Type": "application/json" },
86
- body: JSON.stringify({
87
- url: authorizeUrl,
88
- surface: "browser"
89
- })
90
- });
91
- if (!response.ok) {
92
- throw new Error("Unable to open registry.");
93
- }
94
- setStatus("Authorize the import in your browser.");
95
- }
96
-
97
- openButton.addEventListener("click", () => {
98
- openRegistry().catch((error) => {
99
- setStatus(error && error.message ? error.message : "Unable to open registry.");
100
- });
101
- });
102
- if (autoOpen) {
103
- window.setTimeout(() => {
104
- openRegistry().catch((error) => {
105
- setStatus(error && error.message ? error.message : "Unable to open registry.");
106
- });
107
- }, 100);
108
- }
109
- </script>
110
- </body>
111
- </html>`)
112
- }
113
-
114
- function normalizeRegistryBase(raw, fallback) {
115
- const value = String(raw || fallback || "").trim()
116
- if (!value) return ""
117
- try {
118
- const url = new URL(value)
119
- if (url.protocol !== "https:" && url.protocol !== "http:") return ""
120
- url.hash = ""
121
- url.search = ""
122
- return url.toString().replace(/\/$/, "")
123
- } catch (_) {
124
- return ""
125
- }
126
- }
127
-
128
- function requestOrigin(req) {
129
- const host = req.get("host") || "localhost:42000"
130
- return `${req.protocol || "http"}://${host}`
131
- }
132
-
133
- function buildDraftImportReturnUrl(req, item, bundle) {
134
- const returnUrl = new URL("/registry/draft-import/callback", requestOrigin(req))
135
- returnUrl.searchParams.set("draft", item.id)
136
- if (bundle.appSlug) returnUrl.searchParams.set("app", bundle.appSlug)
137
- return returnUrl.toString()
138
- }
139
-
140
- function buildAuthorizeUrl(req, registryBase, item, bundle) {
141
- const authorizeUrl = new URL("/draft-import/authorize", registryBase)
142
- authorizeUrl.searchParams.set("handoff", "callback")
143
- authorizeUrl.searchParams.set("return", buildDraftImportReturnUrl(req, item, bundle))
144
- if (bundle.appSlug) authorizeUrl.searchParams.set("app", bundle.appSlug)
145
- return authorizeUrl
146
- }
147
-
148
- async function findDraftById(drafts, id) {
149
- const normalized = String(id || "").trim()
150
- if (!normalized) return null
151
- const items = await drafts.listPending({})
152
- return (items || []).find((item) => item && item.id === normalized) || null
153
- }
154
-
155
- function isRegistryPostPublish(publish) {
156
- if (!publish || typeof publish !== "object") return false
157
- const target = String(publish.target || "").trim().toLowerCase()
158
- const type = String(publish.type || "post").trim().toLowerCase()
159
- return target === "registry" && type === "post"
160
- }
161
-
162
- function normalizeParent(parent) {
163
- if (!parent || typeof parent !== "object" || Array.isArray(parent)) return null
164
- const type = String(parent.type || "").trim().toLowerCase()
165
- const url = String(parent.url || parent.repoUrl || "").trim()
166
- if (type !== "app" || !url) return null
167
- return { type: "app", url }
168
- }
169
-
170
- async function buildDraftBundle(item, query = {}) {
171
- const markdown = await fs.promises.readFile(item.postPath, "utf8")
172
- const resultDir = path.dirname(item.postPath)
173
- const titleFallback = item.title || (item.workspaceName ? `Draft for ${item.workspaceName}` : "Draft")
174
- const extracted = extractTitleAndBody(markdown, titleFallback)
175
- const metadataTitle = item.metadata && typeof item.metadata.title === "string"
176
- ? normalizeTitle(item.metadata.title)
177
- : ""
178
- const title = metadataTitle || extracted.title
179
- const body = metadataTitle && extracted.title && normalizeTitle(extracted.title) === metadataTitle
180
- ? extracted.body
181
- : (!metadataTitle ? extracted.body : String(markdown || "").trim())
182
- const publish = item.publish && typeof item.publish === "object" ? item.publish : null
183
- const media = await describeMediaRefs(markdown, resultDir, { mediaOnly: false })
184
- return {
185
- title,
186
- body,
187
- publish,
188
- parent: normalizeParent(publish && publish.parent),
189
- appSlug: String(query.app || "").trim(),
190
- media
191
- }
192
- }
193
-
194
- function preflightBundle(bundle, options = {}) {
195
- const maxFiles = Number(options.maxFiles || DEFAULT_MAX_FILES)
196
- const maxFileBytes = Number(options.maxFileBytes || DEFAULT_MAX_FILE_BYTES)
197
- if (!bundle.title) {
198
- return "Draft title is missing."
199
- }
200
- if (!isRegistryPostPublish(bundle.publish)) {
201
- return "This draft is not configured for registry publishing."
202
- }
203
- if (bundle.media.length > maxFiles) {
204
- return `Draft has ${bundle.media.length} media files. The registry limit is ${maxFiles}.`
205
- }
206
- const missing = bundle.media.filter((item) => !item.exists)
207
- if (missing.length > 0) {
208
- return `Draft references missing media: ${missing.map((item) => item.ref).join(", ")}`
209
- }
210
- const oversized = bundle.media.find((item) => item.bytes > maxFileBytes)
211
- if (oversized) {
212
- return `Media file is too large: ${oversized.ref}. The per-file limit is ${Math.round(maxFileBytes / 1024 / 1024)} MB.`
213
- }
214
- return ""
215
- }
216
-
217
- async function uploadBundle(registryBase, token, bundle) {
218
- const form = new FormData()
219
- const metadata = JSON.stringify({
220
- title: bundle.title,
221
- body: bundle.body,
222
- app: bundle.appSlug || "",
223
- parent: bundle.parent || null,
224
- media: bundle.media.map((item) => ({ path: item.ref }))
225
- })
226
- form.append("metadata_b64", Buffer.from(metadata, "utf8").toString("base64"))
227
- for (const item of bundle.media) {
228
- form.append("files", fs.createReadStream(item.path), {
229
- filename: path.basename(item.ref),
230
- contentType: mime.lookup(item.path) || "application/octet-stream",
231
- knownLength: item.bytes
232
- })
233
- }
234
- const endpoint = `${registryBase}/registry-bridge/draft-imports`
235
- const headers = {
236
- Authorization: `Bearer ${token}`,
237
- ...form.getHeaders()
238
- }
239
- const contentLength = await new Promise((resolve) => {
240
- form.getLength((error, length) => resolve(error ? null : length))
241
- })
242
- if (Number.isFinite(contentLength)) {
243
- headers["Content-Length"] = contentLength
244
- }
245
- console.log("[draft-import] request", {
246
- endpoint,
247
- media: bundle.media.length,
248
- contentLength: Number.isFinite(contentLength) ? contentLength : null
249
- })
250
- const response = await axios.post(endpoint, form, {
251
- timeout: 180000,
252
- maxContentLength: Infinity,
253
- maxBodyLength: Infinity,
254
- validateStatus: () => true,
255
- headers
256
- })
257
- if (response.status < 200 || response.status >= 300) {
258
- const error = new Error(
259
- response.data && response.data.error
260
- ? String(response.data.error)
261
- : `Registry upload failed with status ${response.status}.`
262
- )
263
- error.status = response.status
264
- error.registryEndpoint = endpoint
265
- error.responseData = response.data
266
- throw error
267
- }
268
- return response.data || {}
269
- }
270
-
271
- async function uploadDraftFromRequest(drafts, query, token, registryBase, options = {}) {
272
- const item = await findDraftById(drafts, query.draft)
273
- if (!item) {
274
- const error = new Error("The local draft is no longer available.")
275
- error.status = 404
276
- throw error
277
- }
278
- const bundle = await buildDraftBundle(item, query)
279
- const problem = preflightBundle(bundle, options)
280
- if (problem) {
281
- const error = new Error(problem)
282
- error.status = 400
283
- throw error
284
- }
285
- return uploadBundle(registryBase, token, bundle)
286
- }
287
-
288
- function registerDraftImportRoutes(app, options = {}) {
289
- const drafts = options.drafts
290
- if (!drafts) {
291
- throw new Error("drafts is required")
292
- }
293
- const defaultRegistryUrl = options.defaultRegistryUrl || "https://beta.pinokio.co"
294
- const router = express.Router()
295
-
296
- router.get("/registry/draft-import/authorize-url", asyncHandler(async (req, res) => {
297
- const item = await findDraftById(drafts, req.query.draft)
298
- if (!item) {
299
- return res.status(404).json({ error: "The local draft is no longer available." })
300
- }
301
- const bundle = await buildDraftBundle(item, req.query)
302
- const problem = preflightBundle(bundle, options)
303
- if (problem) {
304
- return res.status(400).json({ error: problem })
305
- }
306
- const registryBase = normalizeRegistryBase(req.query.registry, defaultRegistryUrl)
307
- if (!registryBase) {
308
- return res.status(400).json({ error: "The registry URL is invalid." })
309
- }
310
- const authorizeUrl = buildAuthorizeUrl(req, registryBase, item, bundle)
311
- res.setHeader("Cache-Control", "no-store")
312
- return res.json({
313
- draftId: item.id,
314
- authorizeUrl: authorizeUrl.toString()
315
- })
316
- }))
317
-
318
- router.get("/registry/draft-import/start", asyncHandler(async (req, res) => {
319
- const item = await findDraftById(drafts, req.query.draft)
320
- if (!item) {
321
- return renderMessage(res, 404, "Draft not found", "The local draft is no longer available.")
322
- }
323
- const bundle = await buildDraftBundle(item, req.query)
324
- const problem = preflightBundle(bundle, options)
325
- if (problem) {
326
- return renderMessage(res, 400, "Draft is not ready", problem)
327
- }
328
- const registryBase = normalizeRegistryBase(req.query.registry, defaultRegistryUrl)
329
- if (!registryBase) {
330
- return renderMessage(res, 400, "Registry unavailable", "The registry URL is invalid.")
331
- }
332
- const authorizeUrl = buildAuthorizeUrl(req, registryBase, item, bundle)
333
- res.setHeader("Cache-Control", "no-store")
334
- res.setHeader("Cross-Origin-Opener-Policy", "unsafe-none")
335
- return renderImportLauncher(res, {
336
- authorizeUrl: authorizeUrl.toString(),
337
- autoOpen: req.query.auto === "1"
338
- })
339
- }))
340
-
341
- router.post("/registry/draft-import/complete", asyncHandler(async (req, res) => {
342
- const token = String(req.body && req.body.token || "").trim()
343
- if (!token) {
344
- return res.status(400).json({ error: "Missing registry token." })
345
- }
346
- const registryBase = normalizeRegistryBase(req.body && req.body.registry, defaultRegistryUrl)
347
- if (!registryBase) {
348
- return res.status(400).json({ error: "The registry URL is invalid." })
349
- }
350
- try {
351
- console.log("[draft-import] uploading", {
352
- draft: req.body && req.body.draft,
353
- registry: registryBase,
354
- app: req.body && req.body.app ? String(req.body.app) : ""
355
- })
356
- const result = await uploadDraftFromRequest(
357
- drafts,
358
- { draft: req.body && req.body.draft, app: req.body && req.body.app },
359
- token,
360
- registryBase,
361
- options
362
- )
363
- if (result && result.editUrl) {
364
- return res.json({ ok: true, editUrl: String(result.editUrl) })
365
- }
366
- return res.json({ ok: true, editUrl: registryBase })
367
- } catch (error) {
368
- const response = error && error.response
369
- const status = response && response.status ? response.status : (error && error.status ? error.status : 500)
370
- const endpoint = error && error.registryEndpoint ? error.registryEndpoint : `${registryBase}/registry-bridge/draft-imports`
371
- const responseData = response && response.data ? response.data : (error && error.responseData ? error.responseData : null)
372
- console.warn("[draft-import] upload failed", {
373
- status,
374
- endpoint,
375
- error: error && error.message ? error.message : "Upload failed.",
376
- response: typeof responseData === "string" ? responseData.slice(0, 500) : responseData
377
- })
378
- const message = response && response.data && response.data.error
379
- ? response.data.error
380
- : (error && error.message ? error.message : "Upload failed.")
381
- return res.status(status).json({ error: message, status, endpoint })
382
- }
383
- }))
384
-
385
- router.get("/registry/draft-import/callback", asyncHandler(async (req, res) => {
386
- const token = String(req.query.token || "").trim()
387
- if (!token) {
388
- return renderMessage(res, 400, "Missing token", "The registry did not return an import token.")
389
- }
390
- const registryBase = normalizeRegistryBase(req.query.registry, defaultRegistryUrl)
391
- if (!registryBase) {
392
- return renderMessage(res, 400, "Registry unavailable", "The registry URL is invalid.")
393
- }
394
- try {
395
- const result = await uploadDraftFromRequest(drafts, req.query, token, registryBase, options)
396
- if (result && result.editUrl) {
397
- return res.redirect(String(result.editUrl))
398
- }
399
- return renderMessage(res, 200, "Draft imported", "The registry accepted the draft.")
400
- } catch (error) {
401
- const response = error && error.response
402
- const message = response && response.data && response.data.error
403
- ? response.data.error
404
- : (error && error.message ? error.message : "Upload failed.")
405
- return renderMessage(res, response && response.status ? response.status : 500, "Import failed", message)
406
- }
407
- }))
408
-
409
- app.use(router)
410
- }
411
-
412
- module.exports = registerDraftImportRoutes
@@ -1,68 +0,0 @@
1
- const express = require("express")
2
- const path = require("path")
3
- const registerDraftImportRoutes = require("./registry_import")
4
-
5
- function asyncHandler(fn) {
6
- return (req, res, next) => {
7
- Promise.resolve(fn(req, res, next)).catch(next)
8
- }
9
- }
10
-
11
- function registerDraftRoutes(app, options = {}) {
12
- const drafts = options.drafts
13
- if (!drafts) {
14
- throw new Error("drafts is required")
15
- }
16
-
17
- const router = express.Router()
18
- router.get("/drafts", asyncHandler(async (req, res) => {
19
- const cwd = typeof req.query.cwd === "string" ? req.query.cwd : ""
20
- const items = await drafts.listPending({ cwd })
21
- res.json({
22
- ok: true,
23
- items
24
- })
25
- }))
26
-
27
- router.post("/drafts/:id/dismiss", asyncHandler(async (req, res) => {
28
- const ok = await drafts.dismiss(req.params.id, req.body && req.body.revision)
29
- res.json({ ok })
30
- }))
31
-
32
- router.get("/drafts/:id/media/:index", asyncHandler(async (req, res) => {
33
- const item = typeof drafts.getPendingById === "function"
34
- ? await drafts.getPendingById(req.params.id)
35
- : null
36
- if (!item) {
37
- res.status(404).send("Draft not found")
38
- return
39
- }
40
- const index = Number(req.params.index)
41
- const media = Array.isArray(item.media) && Number.isInteger(index)
42
- ? item.media[index]
43
- : null
44
- if (!media || !media.exists || !media.path) {
45
- res.status(404).send("Media not found")
46
- return
47
- }
48
- const filePath = path.resolve(media.path)
49
- const basePath = path.resolve(item.resultDir)
50
- const relative = path.relative(basePath, filePath)
51
- if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
52
- res.status(403).send("Media path is outside the draft")
53
- return
54
- }
55
- res.setHeader("Cache-Control", "no-store")
56
- res.sendFile(filePath)
57
- }))
58
-
59
- router.get("/drafts.js", (req, res) => {
60
- res.setHeader("Cache-Control", "no-store")
61
- res.sendFile(path.resolve(__dirname, "public", "drafts.js"))
62
- })
63
-
64
- app.use(router)
65
- registerDraftImportRoutes(app, options)
66
- }
67
-
68
- module.exports = registerDraftRoutes