pinokiod 7.5.10 → 7.5.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.5.10",
3
+ "version": "7.5.11",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,91 @@
1
+ # Pinokio 8.0.0 UI Updates
2
+
3
+ This release brings Pinokio's main surfaces into a quieter, more consistent workspace: app discovery, app launch controls, logs, tools, plugins, skills, autolaunch, setup, and mobile navigation now share the same visual language and action patterns.
4
+
5
+ The notes below are grouped by what users will notice, not by commit history. Screenshots were captured from the live local UI after comparing `de83785` to the current `HEAD`.
6
+
7
+ ## Cleaner App Discovery
8
+
9
+ Pinokio's home screen now behaves more like a compact app manager. The sidebar is unified with the rest of the product, the app list is easier to scan, search and sort stay prominent, and each app row exposes quick actions without forcing users into the full app page first.
10
+
11
+ ![Home app list with unified sidebar](assets/home-sidebar-apps.png)
12
+
13
+ The new action panel gives installed apps a focused command surface for common launch, install, update, reset, file, and dev actions. Related commands are grouped in one drawer, which reduces row clutter while keeping the next action close to the app.
14
+
15
+ ![Home app action panel](assets/home-app-actions.png)
16
+
17
+ ## App Workspace And Assistance
18
+
19
+ App pages have been rebuilt around a more durable workspace shell. The page chrome, left command rail, right utility panels, app title area, status indicators, resource labels, and launch actions have been tightened so app-specific work feels connected to the rest of Pinokio.
20
+
21
+ ![App workspace chrome](assets/app-detail-header-actions.png)
22
+
23
+ Ask AI now opens as an in-app assistance drawer with searchable terminal and desktop agents. Instead of a generic help entry point, users can choose from bundled agents such as Codex, Claude Code, Qwen Code, Antigravity, Cursor, VS Code, and desktop integrations directly inside the app context.
24
+
25
+ ![Ask AI agent drawer](assets/app-ask-ai-drawer.png)
26
+
27
+ ## Plugins, Agents, And Skills
28
+
29
+ The Plugins page now separates terminal plugins from desktop plugins and shows install/manage affordances in a single list. This makes the new bundled native tools easier to understand: terminal agents launch inside Pinokio, while desktop tools launch externally.
30
+
31
+ ![Plugins and bundled agents](assets/plugins-agent-tools.png)
32
+
33
+ Pinokio also has a new Skills management surface. Users can see where managed skills are stored, where they sync, whether each skill is valid, and whether it is currently enabled. The page also supports downloading skills from a Git URL.
34
+
35
+ ![Skills management](assets/skills-management.png)
36
+
37
+ ## Startup And Launch Requirements
38
+
39
+ Autolaunch is now a first-class configuration page. Users can search installed apps, choose which launch script should run at startup, see how many apps are enabled, and toggle startup behavior without digging through app internals.
40
+
41
+ ![Autolaunch and launch script selection](assets/autolaunch-dependencies.png)
42
+
43
+ Setup and requirements screens now present blocked routes as a clear checklist. When a destination such as Local network needs a package update, Pinokio shows what is installed, what needs attention, and the action required before continuing.
44
+
45
+ ![Network setup requirements](assets/network-phone-access.png)
46
+
47
+ ## Logs, Diagnostics, And Issue Reporting
48
+
49
+ Logs have been redesigned around a file tree and live reader. Users can inspect logs from the machine, stream a selected file, clear the view, keep auto-scroll on, and generate a diagnostic zip from the same screen.
50
+
51
+ ![Logs file tree and live reader](assets/logs-terminal-reporting.png)
52
+
53
+ Behind that surface, the issue-reporting flow is more capable: recent logs can be summarized, redacted, copied, handed to Ask AI, or prepared as a registry draft. Those states require an app failure or report context, so they are not shown in this static screenshot set.
54
+
55
+ ## Tools And Runtime Readiness
56
+
57
+ The Tools page now groups package bundles with readiness counts, per-bundle install state, and package-level status. Users can see which runtime bundles are ready, which need installation, and which package manager modules are present without reading terminal output first.
58
+
59
+ ![Tools and package bundle readiness](assets/tools-runtime-management.png)
60
+
61
+ ## Login And Connected Services
62
+
63
+ The Hugging Face login flow has been redesigned as a clear device-authorization page. It states the connection state, the credential provider, the reason to log in, and the next action without mixing auth details into unrelated setup UI.
64
+
65
+ ![Hugging Face login](assets/connect-huggingface-login.png)
66
+
67
+ GitHub and git-related modals also received layout cleanup: modal tabs, footers, repository status controls, checkpoint rows, and history/diff areas were tightened so longer repository workflows fit better in the available space.
68
+
69
+ ## Mobile And Remote Access
70
+
71
+ Mobile layouts now prioritize the app list and move primary navigation to a bottom bar. Search, sort, app cards, favorites, and action buttons remain reachable on a phone-width viewport without the desktop sidebar competing for space.
72
+
73
+ ![Mobile home and footer navigation](assets/mobile-home-footer-nav.png)
74
+
75
+ Phone and LAN access also received targeted polish. QR and network access entry points are gated behind setup readiness, install terminals attach correctly over LAN, and the old mobile "Tap to connect" curtain has been replaced with a quieter first-interaction audio primer.
76
+
77
+ ## Smaller Visual Polish
78
+
79
+ Several changes are intentionally broad but subtle:
80
+
81
+ - Shared sidebar styling now covers home, app, tools, plugins, skills, logs, settings, setup, network, and auth pages.
82
+ - App navigation is more responsive, especially where action labels previously wrapped or crowded the header.
83
+ - Screenshot/capture modals, footer status indicators, popovers, tabs, and terminal controls use flatter, tighter styling.
84
+ - Checkpoints rows, git history dialogs, network troubleshoot panels, and auth pages were normalized to match the newer page shell.
85
+ - Hidden or unused toolbar buttons were removed from the visible chrome so users see fewer inactive controls.
86
+
87
+ ## Screenshot Coverage Notes
88
+
89
+ Captured screenshots cover the major visible themes: home discovery, app actions, app workspace chrome, Ask AI, plugins, skills, autolaunch, setup requirements, logs, tools, connected-service login, and mobile navigation.
90
+
91
+ Some UI states were not practical to screenshot without changing runtime state or forcing failures: failed-terminal fallback, report-draft creation, live dependency orchestration while an app is launching, completed peer-network routing, and git/checkpoint modal edge cases. Those changes are still included above because they are user-visible when the relevant workflow is active.
@@ -505,7 +505,39 @@
505
505
  window.location.href = href
506
506
  return true
507
507
  }
508
- dispatchAskAiLaunch(tool, prompt) {
508
+ createAskAiLaunchId() {
509
+ try {
510
+ if (window.crypto && typeof window.crypto.randomUUID === 'function') {
511
+ return window.crypto.randomUUID()
512
+ }
513
+ } catch (_) {}
514
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`
515
+ }
516
+ waitForAskAiLaunchAck(launchId, timeoutMs = 1500) {
517
+ return new Promise((resolve) => {
518
+ let settled = false
519
+ let timer = null
520
+ const finish = (value) => {
521
+ if (settled) return
522
+ settled = true
523
+ if (timer) {
524
+ window.clearTimeout(timer)
525
+ }
526
+ window.removeEventListener('message', onMessage)
527
+ resolve(value)
528
+ }
529
+ const onMessage = (event) => {
530
+ const data = event && event.data && typeof event.data === 'object' ? event.data : null
531
+ if (!data || data.e !== 'pinokio:ask-ai-launch-result' || data.launchId !== launchId) {
532
+ return
533
+ }
534
+ finish(data.opened !== false)
535
+ }
536
+ window.addEventListener('message', onMessage)
537
+ timer = window.setTimeout(() => finish(false), timeoutMs)
538
+ })
539
+ }
540
+ async dispatchAskAiLaunch(tool, prompt) {
509
541
  const launchHref = this.buildAskAiLaunchHref(tool, prompt)
510
542
  if (!launchHref) {
511
543
  this.setStatus('Could not launch this plugin.', true)
@@ -531,11 +563,38 @@
531
563
  }
532
564
  } catch (_) {}
533
565
  }
566
+ try {
567
+ const parentDrawer = window.parent && window.parent !== window ? window.parent.PinokioAskAiDrawer : null
568
+ if (parentDrawer && typeof parentDrawer.openWithAgent === 'function') {
569
+ const opened = parentDrawer.openWithAgent(payload)
570
+ if (opened !== false) {
571
+ this.setStatus('Launching Ask AI…')
572
+ return true
573
+ }
574
+ } else if (parentDrawer && typeof parentDrawer.openWithUrl === 'function') {
575
+ const opened = parentDrawer.openWithUrl(launchHref, {
576
+ workspaceCwd: this.workspaceCwd,
577
+ prompt: payload.prompt
578
+ })
579
+ if (opened !== false) {
580
+ this.setStatus('Launching Ask AI…')
581
+ return true
582
+ }
583
+ }
584
+ } catch (_) {}
534
585
  try {
535
586
  if (window.parent && window.parent !== window && typeof window.parent.postMessage === 'function') {
587
+ const launchId = this.createAskAiLaunchId()
588
+ payload.launchId = launchId
589
+ const ack = this.waitForAskAiLaunchAck(launchId)
536
590
  window.parent.postMessage(payload, '*')
537
- this.setStatus('Launching Ask AI…')
538
- return true
591
+ const opened = await ack
592
+ if (opened) {
593
+ this.setStatus('Launching Ask AI…')
594
+ return true
595
+ }
596
+ this.setStatus('Could not confirm Ask AI launch.', true)
597
+ return false
539
598
  }
540
599
  } catch (_) {}
541
600
  window.location.href = launchHref
@@ -730,7 +789,7 @@
730
789
  }
731
790
  syncRunState()
732
791
  }
733
- const run = () => {
792
+ const run = async () => {
734
793
  const prompt = String(promptTextarea.value || '').trim()
735
794
  const tool = this.selectedAskAiTool()
736
795
  if (!prompt) {
@@ -748,7 +807,7 @@
748
807
  close()
749
808
  return
750
809
  }
751
- if (this.dispatchAskAiLaunch(tool, prompt)) {
810
+ if (await this.dispatchAskAiLaunch(tool, prompt)) {
752
811
  close()
753
812
  }
754
813
  }
@@ -867,6 +926,7 @@
867
926
  this.populateAskAiLauncherTools(tools)
868
927
  launcher.error.textContent = ''
869
928
  launcher.promptTextarea.value = ASK_AI_DEFAULT_PROMPT
929
+ launcher.syncRunState()
870
930
  launcher.setOpen(true)
871
931
  }
872
932
  formatGenerated(value) {
@@ -17093,7 +17093,16 @@ document.addEventListener("DOMContentLoaded", () => {
17093
17093
  return
17094
17094
  }
17095
17095
  if (messageType === "pinokio:ask-ai-launch") {
17096
- openWithAgent(event.data)
17096
+ const opened = openWithAgent(event.data)
17097
+ if (event.source && event.data.launchId) {
17098
+ try {
17099
+ event.source.postMessage({
17100
+ e: "pinokio:ask-ai-launch-result",
17101
+ launchId: event.data.launchId,
17102
+ opened: opened !== false
17103
+ }, event.origin && event.origin !== "null" ? event.origin : "*")
17104
+ } catch (_) {}
17105
+ }
17097
17106
  } else if (messageType === "pinokio:ask-ai-open-url") {
17098
17107
  openWithUrl(event.data.url, {
17099
17108
  workspaceCwd: typeof event.data.workspaceCwd === "string" ? event.data.workspaceCwd : ""
@@ -0,0 +1,245 @@
1
+ const assert = require("node:assert/strict")
2
+ const fs = require("node:fs/promises")
3
+ const path = require("node:path")
4
+ const test = require("node:test")
5
+ const { JSDOM, VirtualConsole } = require("jsdom")
6
+
7
+ const root = path.resolve(__dirname, "..")
8
+ const logsScriptPath = path.resolve(root, "server/public/logs.js")
9
+
10
+ function jsonResponse(payload, status = 200) {
11
+ return {
12
+ ok: status >= 200 && status < 300,
13
+ status,
14
+ json: async () => payload,
15
+ text: async () => JSON.stringify(payload)
16
+ }
17
+ }
18
+
19
+ function createReportFixture() {
20
+ return {
21
+ app_id: "target",
22
+ title: "Target",
23
+ repo_url: "https://example.test/target.git",
24
+ generated_at: "2026-06-28T22:00:00.000Z",
25
+ markdown: "# Target report\n\nDetails",
26
+ sections: [{
27
+ file: "logs/api/run.js/latest",
28
+ source: "api",
29
+ script: "run.js",
30
+ lines: 1,
31
+ included_lines: 1,
32
+ bytes: 7,
33
+ content: "Details"
34
+ }]
35
+ }
36
+ }
37
+
38
+ function createPluginMenuFixture() {
39
+ return {
40
+ menu: [{
41
+ title: "OpenAI Codex Auto",
42
+ href: "/pinokio/run/plugin/codex-auto/pinokio.js",
43
+ image: "/pinokio/asset/plugin/codex-auto/openai.webp",
44
+ category: "cli",
45
+ categoryTitle: "Terminal Plugin",
46
+ pluginPath: "/pinokio/run/plugin/codex-auto/pinokio.js"
47
+ }]
48
+ }
49
+ }
50
+
51
+ function createLogsHtml(script) {
52
+ return `<!doctype html>
53
+ <html>
54
+ <body>
55
+ <section id="logs-root">
56
+ <button id="logs-copy-report" type="button">Copy</button>
57
+ <button id="logs-refresh-report" type="button">Refresh</button>
58
+ <button id="logs-ask-ai" type="button" disabled>Ask AI</button>
59
+ <button id="logs-create-draft" type="button" disabled>Ask Community</button>
60
+ <input id="logs-draft-title">
61
+ <span id="logs-draft-title-note"></span>
62
+ <button id="logs-run-filter" type="button">Run privacy filter</button>
63
+ <pre id="logs-report-output"></pre>
64
+ <div id="logs-report-status"></div>
65
+ <div id="logs-report-files"></div>
66
+ <div id="logs-report-generated"></div>
67
+ <div id="logs-report-sections"></div>
68
+ <div id="logs-draft-size-badge"></div>
69
+ <div id="logs-draft-meter-fill"></div>
70
+ <div id="logs-draft-status"></div>
71
+ <div id="logs-redaction-list"></div>
72
+ <div id="logs-redaction-filters"></div>
73
+ <div id="logs-redaction-count"></div>
74
+ <button id="logs-generate-archive" type="button">Generate zip</button>
75
+ <a id="logs-download-archive"></a>
76
+ <div id="logs-zip-status"></div>
77
+ <button id="logs-refresh-tree" type="button">Refresh tree</button>
78
+ <div id="logs-tree"></div>
79
+ <div id="logs-viewer-output"></div>
80
+ <div id="logs-viewer-status"></div>
81
+ <div id="logs-viewer-path"></div>
82
+ <button id="logs-clear-viewer" type="button">Clear</button>
83
+ <input id="logs-autoscroll" type="checkbox">
84
+ <button data-logs-view="latest" type="button">Latest</button>
85
+ <button data-logs-view="raw" type="button">Raw</button>
86
+ <section id="logs-latest-panel"></section>
87
+ <section id="logs-raw-panel"></section>
88
+ </section>
89
+ <script>
90
+ window.LOGS_PAGE_DATA = {
91
+ workspace: "target",
92
+ workspaceCwd: "/Users/x/pinokio/api/target",
93
+ reportUrl: "/apps/logs/target/report",
94
+ initialView: "latest"
95
+ };
96
+ </script>
97
+ <script>${script}</script>
98
+ </body>
99
+ </html>`
100
+ }
101
+
102
+ async function createLogsDom(options = {}) {
103
+ const script = await fs.readFile(logsScriptPath, "utf8")
104
+ const parentWindow = options.parentWindow || null
105
+ const virtualConsole = new VirtualConsole()
106
+ virtualConsole.on("jsdomError", (error) => {
107
+ throw error
108
+ })
109
+
110
+ const dom = new JSDOM(createLogsHtml(script), {
111
+ url: "http://127.0.0.1:42000/logs?workspace=target&embed=1&view=latest",
112
+ runScripts: "dangerously",
113
+ resources: "usable",
114
+ pretendToBeVisual: true,
115
+ virtualConsole,
116
+ beforeParse(window) {
117
+ window.matchMedia = () => ({ matches: false })
118
+ window.ResizeObserver = class {
119
+ observe() {}
120
+ disconnect() {}
121
+ }
122
+ window.fetch = async (url) => {
123
+ if (String(url).startsWith("/apps/logs/target/report")) {
124
+ return jsonResponse(createReportFixture())
125
+ }
126
+ if (String(url).startsWith("/api/plugin/menu")) {
127
+ return jsonResponse(createPluginMenuFixture())
128
+ }
129
+ if (String(url).startsWith("/api/logs/tree")) {
130
+ return jsonResponse({ entries: [] })
131
+ }
132
+ return jsonResponse({})
133
+ }
134
+ if (parentWindow) {
135
+ Object.defineProperty(window, "parent", {
136
+ configurable: true,
137
+ value: parentWindow
138
+ })
139
+ }
140
+ }
141
+ })
142
+
143
+ const { window } = dom
144
+ await new Promise((resolve) => window.addEventListener("DOMContentLoaded", resolve, { once: true }))
145
+ await waitFor(() => !window.document.getElementById("logs-ask-ai").disabled, "Ask AI button enabled")
146
+ return { dom }
147
+ }
148
+
149
+ async function waitFor(predicate, message = "condition") {
150
+ const deadline = Date.now() + 2000
151
+ while (Date.now() < deadline) {
152
+ if (predicate()) return
153
+ await new Promise((resolve) => setTimeout(resolve, 20))
154
+ }
155
+ assert.fail(`Timed out waiting for ${message}`)
156
+ }
157
+
158
+ test("log Ask AI modal enables Run after assigning the default prompt", async () => {
159
+ const { dom } = await createLogsDom()
160
+ const { window } = dom
161
+ const { document } = window
162
+
163
+ window.localStorage.setItem("pinokio.universalLauncher.tool", "pinokio/run/plugin/codex-auto")
164
+ document.getElementById("logs-ask-ai").click()
165
+
166
+ await waitFor(() => document.querySelector(".logs-ask-ai-launcher:not([hidden])"), "Ask AI modal")
167
+ const textarea = document.querySelector(".logs-ask-ai-launcher-textarea")
168
+ const select = document.querySelector(".logs-ask-ai-tool-select")
169
+ const runButton = document.querySelector(".logs-ask-ai-launcher .universal-launcher-button-primary")
170
+
171
+ assert.match(textarea.value, /Investigate what went wrong/)
172
+ assert.equal(select.selectedOptions[0].textContent, "OpenAI Codex Auto (Terminal)")
173
+ assert.equal(runButton.disabled, false)
174
+ })
175
+
176
+ test("log Ask AI launch calls a same-origin parent drawer before postMessage fallback", async () => {
177
+ const opened = []
178
+ const posted = []
179
+ const parentWindow = {
180
+ PinokioAskAiDrawer: {
181
+ openWithAgent(payload) {
182
+ opened.push(payload)
183
+ return true
184
+ }
185
+ },
186
+ postMessage(payload) {
187
+ posted.push(payload)
188
+ }
189
+ }
190
+ const { dom } = await createLogsDom({ parentWindow })
191
+ const { window } = dom
192
+ const { document } = window
193
+
194
+ window.localStorage.setItem("pinokio.universalLauncher.tool", "pinokio/run/plugin/codex-auto")
195
+ document.getElementById("logs-ask-ai").click()
196
+ await waitFor(() => {
197
+ const runButton = document.querySelector(".logs-ask-ai-launcher .universal-launcher-button-primary")
198
+ return runButton && runButton.disabled === false
199
+ }, "enabled Run button")
200
+
201
+ document.querySelector(".logs-ask-ai-launcher .universal-launcher-button-primary").click()
202
+
203
+ await waitFor(() => document.querySelector(".logs-ask-ai-launcher").hidden, "modal close after direct parent launch")
204
+ assert.equal(opened.length, 1)
205
+ assert.equal(posted.length, 0)
206
+ assert.equal(opened[0].agentHref.includes("/pinokio/run/plugin/codex-auto/pinokio.js"), true)
207
+ assert.equal(opened[0].agentHref.includes("prompt="), true)
208
+ assert.equal(document.querySelector(".logs-ask-ai-launcher").hidden, true)
209
+ })
210
+
211
+ test("log Ask AI launch waits for parent postMessage acknowledgement before closing", async () => {
212
+ let childWindow = null
213
+ const posted = []
214
+ const parentWindow = {
215
+ postMessage(payload) {
216
+ posted.push(payload)
217
+ childWindow.dispatchEvent(new childWindow.MessageEvent("message", {
218
+ data: {
219
+ e: "pinokio:ask-ai-launch-result",
220
+ launchId: payload.launchId,
221
+ opened: true
222
+ },
223
+ origin: "http://127.0.0.1:42000"
224
+ }))
225
+ }
226
+ }
227
+ const { dom } = await createLogsDom({ parentWindow })
228
+ childWindow = dom.window
229
+ const { window } = dom
230
+ const { document } = window
231
+
232
+ window.localStorage.setItem("pinokio.universalLauncher.tool", "pinokio/run/plugin/codex-auto")
233
+ document.getElementById("logs-ask-ai").click()
234
+ await waitFor(() => {
235
+ const runButton = document.querySelector(".logs-ask-ai-launcher .universal-launcher-button-primary")
236
+ return runButton && runButton.disabled === false
237
+ }, "enabled Run button")
238
+
239
+ document.querySelector(".logs-ask-ai-launcher .universal-launcher-button-primary").click()
240
+
241
+ await waitFor(() => document.querySelector(".logs-ask-ai-launcher").hidden, "modal close after acknowledged launch")
242
+ assert.equal(posted.length, 1)
243
+ assert.equal(Boolean(posted[0].launchId), true)
244
+ assert.equal(posted[0].agentHref.includes("/pinokio/run/plugin/codex-auto/pinokio.js"), true)
245
+ })