pinokiod 7.5.19 → 7.5.21

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 (34) hide show
  1. package/kernel/bin/index.js +0 -5
  2. package/kernel/bin/setup.js +0 -1
  3. package/kernel/index.js +0 -3
  4. package/package.json +1 -1
  5. package/server/public/common.js +1 -1
  6. package/server/public/style.css +34 -0
  7. package/server/public/urldropdown.css +272 -0
  8. package/server/public/urldropdown.js +44 -12
  9. package/server/views/app.ejs +190 -146
  10. package/server/views/autolaunch.ejs +10 -1
  11. package/server/views/columns.ejs +13 -1
  12. package/server/views/d.ejs +54 -137
  13. package/server/views/index.ejs +14 -6
  14. package/server/views/partials/app_autolaunch_modal_helpers.ejs +4 -0
  15. package/server/views/partials/d_column_subtitle.ejs +10 -0
  16. package/server/views/partials/d_menu_column.ejs +18 -0
  17. package/server/views/partials/d_terminal_column.ejs +2 -11
  18. package/server/views/partials/d_terminal_options.ejs +1 -18
  19. package/server/views/partials/d_tool_tab.ejs +32 -0
  20. package/server/views/partials/main_sidebar.ejs +3 -0
  21. package/server/views/rows.ejs +13 -1
  22. package/system/plugin/antigravity-cli/common.js +10 -16
  23. package/test/antigravity-cli-plugin.test.js +26 -19
  24. package/test/home-autolaunch-live-ui.test.js +70 -0
  25. package/test/launch-requirements-browser.test.js +259 -3
  26. package/test/plugin-dev-iframe.test.js +67 -1
  27. package/test/plugin-sources.test.js +1 -1
  28. package/test/url-split-modal.test.js +107 -0
  29. package/system/plugin/crush/crush.png +0 -0
  30. package/system/plugin/crush/pinokio.js +0 -15
  31. package/system/plugin/qwen/pinokio.js +0 -34
  32. package/system/plugin/qwen/qwen.png +0 -0
  33. package/system/plugin/windsurf/pinokio.js +0 -23
  34. package/system/plugin/windsurf/windsurf.png +0 -0
@@ -27,6 +27,7 @@ const playwright = loadPlaywright()
27
27
 
28
28
  const root = path.resolve(__dirname, "..")
29
29
  const appViewPath = path.resolve(root, "server/views/app.ejs")
30
+ const autolaunchViewPath = path.resolve(root, "server/views/autolaunch.ejs")
30
31
  const statusClientPath = path.resolve(root, "server/views/partials/launch_requirements_status_client.ejs")
31
32
  const homeViewPath = path.resolve(root, "server/views/index.ejs")
32
33
 
@@ -55,6 +56,19 @@ async function renderAppAutolaunchScript(initialState) {
55
56
  })
56
57
  }
57
58
 
59
+ async function renderGlobalAutolaunchScript(state) {
60
+ const view = await fs.readFile(autolaunchViewPath, "utf8")
61
+ const start = view.indexOf("<script>\nlet autolaunchApps =")
62
+ assert.notEqual(start, -1)
63
+ const end = view.indexOf("\n</script>", start)
64
+ assert.notEqual(end, -1)
65
+ return ejs.render(view.slice(start + "<script>".length, end), {
66
+ appsJson: JSON.stringify([state.targetApp])
67
+ }, {
68
+ filename: autolaunchViewPath
69
+ })
70
+ }
71
+
58
72
  async function renderLaunchRequirementsStatusClient(enabled) {
59
73
  const template = await fs.readFile(statusClientPath, "utf8")
60
74
  return ejs.render(template, {
@@ -89,6 +103,9 @@ function html(body, script = "") {
89
103
  body { font-family: sans-serif; }
90
104
  .app-autolaunch-modal { position: fixed; inset: 20px; background: white; border: 1px solid #ccc; padding: 16px; }
91
105
  .app-autolaunch-script-option, .app-autolaunch-dependency-option, .app-autolaunch-dependency-row { display: block; margin: 6px 0; }
106
+ .transformed-sidebar-fixture { display: flex; width: 100%; min-height: 520px; }
107
+ .transformed-sidebar-fixture > aside { width: 280px; flex: 0 0 280px; transform: translateX(0); }
108
+ .transformed-sidebar-fixture > main { flex: 1 1 auto; }
92
109
  .launch-requirements-card { width: 560px; margin: 40px auto; }
93
110
  .launch-requirements-row { display: grid; grid-template-columns: 32px 1fr auto; gap: 8px; align-items: center; }
94
111
  </style>
@@ -100,9 +117,8 @@ function html(body, script = "") {
100
117
  </html>`
101
118
  }
102
119
 
103
- async function appFixtureHtml(initialApp) {
104
- const script = await renderAppAutolaunchScript(initialApp)
105
- return html(`
120
+ function appAutolaunchMarkup(initialApp) {
121
+ return `
106
122
  <div class="app-autolaunch" data-app-autolaunch data-app-id="target">
107
123
  <button type="button" class="app-autolaunch-row" data-app-autolaunch-button data-enabled="${initialApp.autolaunch_enabled ? "true" : "false"}" aria-haspopup="dialog" aria-expanded="false">
108
124
  <span class="app-autolaunch-label">Autolaunch</span>
@@ -122,6 +138,37 @@ async function appFixtureHtml(initialApp) {
122
138
  <div data-app-autolaunch-feedback></div>
123
139
  </div>
124
140
  </div>
141
+ `
142
+ }
143
+
144
+ async function appFixtureHtml(initialApp) {
145
+ const script = await renderAppAutolaunchScript(initialApp)
146
+ return html(appAutolaunchMarkup(initialApp), script)
147
+ }
148
+
149
+ async function transformedSidebarAppFixtureHtml(initialApp) {
150
+ const script = await renderAppAutolaunchScript(initialApp)
151
+ return html(`
152
+ <div class="appcanvas transformed-sidebar-fixture">
153
+ <aside>
154
+ ${appAutolaunchMarkup(initialApp)}
155
+ </aside>
156
+ <main></main>
157
+ </div>
158
+ `, script)
159
+ }
160
+
161
+ async function globalAutolaunchFixtureHtml(state) {
162
+ const script = await renderGlobalAutolaunchScript(state)
163
+ return html(`
164
+ <main class="autolaunch-page">
165
+ <strong data-enabled-count>0</strong>
166
+ <strong data-app-count>1</strong>
167
+ <button type="button" data-disable-all disabled>Disable startup</button>
168
+ <input type="search" data-app-search>
169
+ <div data-app-list></div>
170
+ <section data-detail></section>
171
+ </main>
125
172
  `, script)
126
173
  }
127
174
 
@@ -201,6 +248,8 @@ function readBody(req) {
201
248
  async function startFixtureServer(state) {
202
249
  const pages = {
203
250
  app: await appFixtureHtml(state.targetApp),
251
+ transformedApp: await transformedSidebarAppFixtureHtml(state.targetApp),
252
+ autolaunch: await globalAutolaunchFixtureHtml(state),
204
253
  openWithoutLaunching: await openWithoutLaunchingHtml(),
205
254
  status: await statusFixtureHtml(),
206
255
  home: await homeFixtureHtml()
@@ -213,6 +262,16 @@ async function startFixtureServer(state) {
213
262
  res.end(pages.app)
214
263
  return
215
264
  }
265
+ if (req.method === "GET" && url.pathname === "/app-transformed-sidebar-fixture") {
266
+ res.writeHead(200, { "Content-Type": "text/html" })
267
+ res.end(pages.transformedApp)
268
+ return
269
+ }
270
+ if (req.method === "GET" && url.pathname === "/autolaunch-fixture") {
271
+ res.writeHead(200, { "Content-Type": "text/html" })
272
+ res.end(pages.autolaunch)
273
+ return
274
+ }
216
275
  if (req.method === "GET" && url.pathname === "/open-without-launching") {
217
276
  res.writeHead(200, { "Content-Type": "text/html" })
218
277
  res.end(pages.openWithoutLaunching)
@@ -392,6 +451,15 @@ async function textContent(page, selector) {
392
451
  return page.locator(selector).first().textContent()
393
452
  }
394
453
 
454
+ async function waitForState(predicate, message, timeoutMs = 2000) {
455
+ const started = Date.now()
456
+ while (Date.now() - started < timeoutMs) {
457
+ if (predicate()) return
458
+ await new Promise((resolve) => setTimeout(resolve, 25))
459
+ }
460
+ assert.ok(predicate(), message)
461
+ }
462
+
395
463
  browserTest("browser: selecting a launch script from empty state persists and stays off", async () => {
396
464
  const state = baseState({ targetScript: "" })
397
465
  await withBrowser(state, async ({ page, baseUrl }) => {
@@ -414,6 +482,194 @@ browserTest("browser: selecting a launch script from empty state persists and st
414
482
  })
415
483
  })
416
484
 
485
+ browserTest("browser: app autolaunch modal is viewport sized from transformed sidebar", async () => {
486
+ const state = baseState({ targetScript: "" })
487
+ await withBrowser(state, async ({ page, baseUrl }) => {
488
+ await page.setViewportSize({ width: 1000, height: 700 })
489
+ await page.goto(`${baseUrl}/app-transformed-sidebar-fixture`)
490
+ await page.click("[data-app-autolaunch-button]")
491
+ await page.waitForSelector("[data-app-autolaunch-modal]:not(.hidden)")
492
+
493
+ const rect = await page.locator("[data-app-autolaunch-modal]").evaluate((modal) => {
494
+ const box = modal.getBoundingClientRect()
495
+ return {
496
+ left: box.left,
497
+ top: box.top,
498
+ right: box.right,
499
+ bottom: box.bottom,
500
+ width: box.width,
501
+ height: box.height,
502
+ parentTag: modal.parentElement ? modal.parentElement.tagName : ""
503
+ }
504
+ })
505
+
506
+ assert.equal(rect.parentTag, "BODY")
507
+ assert.ok(rect.width > 900, `modal width should span the viewport, got ${rect.width}`)
508
+ assert.ok(Math.abs(rect.left - 20) <= 1, `modal should use viewport left inset, got ${rect.left}`)
509
+ assert.ok(Math.abs(rect.right - 980) <= 1, `modal should use viewport right inset, got ${rect.right}`)
510
+ })
511
+ })
512
+
513
+ browserTest("browser: startup toggle from empty selection saves the single eligible default", async () => {
514
+ const state = baseState({ targetScript: "" })
515
+ await withBrowser(state, async ({ page, baseUrl }) => {
516
+ await page.goto(`${baseUrl}/app-fixture`)
517
+ await page.click("[data-app-autolaunch-button]")
518
+ await page.waitForSelector('input[name="app-autolaunch-script"][value="start.js"]')
519
+
520
+ assert.equal(await page.isChecked('input[name="app-autolaunch-script"][value="start.js"]'), false)
521
+
522
+ await page.click("[data-app-autolaunch-switch]")
523
+ await waitForState(
524
+ () => state.autolaunchPosts.length === 1,
525
+ "Start with Pinokio should save the single eligible default"
526
+ )
527
+
528
+ assert.deepEqual(state.autolaunchPosts[0], {
529
+ app: "target",
530
+ script: "start.js",
531
+ enabled: true
532
+ })
533
+ assert.equal(state.launchRequirementsGets, 0)
534
+ assert.equal(await page.isChecked('input[name="app-autolaunch-script"][value="start.js"]'), true)
535
+ assert.equal((await textContent(page, "[data-app-autolaunch-status]")).trim(), "ON")
536
+ })
537
+ })
538
+
539
+ browserTest("browser: startup toggle from empty selection warns when no eligible default exists", async () => {
540
+ const cases = [{
541
+ name: "no default",
542
+ menu: [{ script: "start.js", label: "Start" }],
543
+ other: [{ script: "target.custom.js", label: "Custom" }]
544
+ }, {
545
+ name: "multiple defaults",
546
+ menu: [
547
+ { script: "start.js", label: "Start", menu_default: true },
548
+ { script: "target.custom.js", label: "Custom", menu_default: true }
549
+ ],
550
+ other: []
551
+ }, {
552
+ name: "params default",
553
+ menu: [{ script: "start.js", label: "Start", menu_default: true, has_params: true }],
554
+ other: [{ script: "target.custom.js", label: "Custom" }]
555
+ }]
556
+
557
+ for (const scenario of cases) {
558
+ const state = baseState({ targetScript: "" })
559
+ state.candidates.target.menu = scenario.menu
560
+ state.candidates.target.other = scenario.other
561
+ await withBrowser(state, async ({ page, baseUrl }) => {
562
+ await page.goto(`${baseUrl}/app-fixture`)
563
+ await page.click("[data-app-autolaunch-button]")
564
+ await page.waitForSelector("[data-app-autolaunch-switch]")
565
+
566
+ await page.click("[data-app-autolaunch-switch]")
567
+ await page.waitForFunction(() => {
568
+ const feedback = document.querySelector("[data-app-autolaunch-feedback]")
569
+ return feedback && /Choose .*launch script/i.test(feedback.textContent || "")
570
+ })
571
+
572
+ assert.equal(state.autolaunchPosts.length, 0, scenario.name)
573
+ assert.equal((await textContent(page, "[data-app-autolaunch-status]")).trim(), "OFF")
574
+ })
575
+ }
576
+ })
577
+
578
+ browserTest("browser: global startup toggle from empty selection saves the single eligible default", async () => {
579
+ const state = baseState({ targetScript: "" })
580
+ await withBrowser(state, async ({ page, baseUrl }) => {
581
+ await page.goto(`${baseUrl}/autolaunch-fixture?app=target`)
582
+ await page.waitForSelector('input[name="autolaunch-script"][value="start.js"]')
583
+
584
+ assert.equal(await page.isChecked('input[name="autolaunch-script"][value="start.js"]'), false)
585
+
586
+ await page.click("[data-autolaunch-switch]")
587
+ await waitForState(
588
+ () => state.autolaunchPosts.length === 1,
589
+ "Global Start with Pinokio should save the single eligible default"
590
+ )
591
+
592
+ assert.deepEqual(state.autolaunchPosts[0], {
593
+ app: "target",
594
+ script: "start.js",
595
+ enabled: true
596
+ })
597
+ assert.equal(state.launchRequirementsGets, 0)
598
+ await page.waitForFunction(() => {
599
+ const input = document.querySelector('input[name="autolaunch-script"][value="start.js"]')
600
+ const enabledCount = document.querySelector("[data-enabled-count]")
601
+ return input && input.checked && enabledCount && enabledCount.textContent.trim() === "1"
602
+ })
603
+ assert.equal(await page.isChecked('input[name="autolaunch-script"][value="start.js"]'), true)
604
+ assert.equal((await textContent(page, "[data-enabled-count]")).trim(), "1")
605
+ })
606
+ })
607
+
608
+ browserTest("browser: global startup toggle from empty selection warns when no eligible default exists", async () => {
609
+ const cases = [{
610
+ name: "no default",
611
+ menu: [{ script: "start.js", label: "Start" }],
612
+ other: [{ script: "target.custom.js", label: "Custom" }]
613
+ }, {
614
+ name: "multiple defaults",
615
+ menu: [
616
+ { script: "start.js", label: "Start", menu_default: true },
617
+ { script: "target.custom.js", label: "Custom", menu_default: true }
618
+ ],
619
+ other: []
620
+ }, {
621
+ name: "params default",
622
+ menu: [{ script: "start.js", label: "Start", menu_default: true, has_params: true }],
623
+ other: [{ script: "target.custom.js", label: "Custom" }]
624
+ }]
625
+
626
+ for (const scenario of cases) {
627
+ const state = baseState({ targetScript: "" })
628
+ state.candidates.target.menu = scenario.menu
629
+ state.candidates.target.other = scenario.other
630
+ await withBrowser(state, async ({ page, baseUrl }) => {
631
+ await page.goto(`${baseUrl}/autolaunch-fixture?app=target`)
632
+ await page.waitForSelector("[data-manual-script]")
633
+
634
+ await page.click("[data-autolaunch-switch]")
635
+ await page.waitForFunction(() => {
636
+ const feedback = document.querySelector("[data-feedback]")
637
+ return feedback && /Choose .*launch script/i.test(feedback.textContent || "")
638
+ })
639
+
640
+ assert.equal(state.autolaunchPosts.length, 0, scenario.name)
641
+ assert.equal((await textContent(page, "[data-enabled-count]")).trim(), "0")
642
+ })
643
+ }
644
+ })
645
+
646
+ browserTest("browser: global startup toggle uses pending manual script before eligible default", async () => {
647
+ const state = baseState({ targetScript: "" })
648
+ await withBrowser(state, async ({ page, baseUrl }) => {
649
+ await page.goto(`${baseUrl}/autolaunch-fixture?app=target`)
650
+ await page.waitForSelector("[data-manual-script]")
651
+
652
+ await page.fill("[data-manual-script]", "target.custom.js")
653
+ await page.click("[data-autolaunch-switch]")
654
+ await waitForState(
655
+ () => state.autolaunchPosts.length === 1,
656
+ "Global Start with Pinokio should save the pending manual script"
657
+ )
658
+
659
+ assert.deepEqual(state.autolaunchPosts[0], {
660
+ app: "target",
661
+ script: "target.custom.js",
662
+ enabled: true
663
+ })
664
+ assert.equal(await page.isChecked('input[name="autolaunch-script"][value="start.js"]'), false)
665
+ await page.waitForFunction(() => {
666
+ const enabledCount = document.querySelector("[data-enabled-count]")
667
+ return enabledCount && enabledCount.textContent.trim() === "1"
668
+ })
669
+ assert.equal((await textContent(page, "[data-enabled-count]")).trim(), "1")
670
+ })
671
+ })
672
+
417
673
  browserTest("browser: adding a requirement with an existing script still requires script confirmation", async () => {
418
674
  const state = baseState({ targetScript: "target.custom.js", helperScript: "start.js" })
419
675
  await withBrowser(state, async ({ page, baseUrl }) => {
@@ -1,10 +1,13 @@
1
1
  const assert = require("assert")
2
+ const ejs = require("ejs")
2
3
  const fs = require("node:fs")
3
4
  const path = require("node:path")
4
5
  const test = require("node:test")
5
6
 
7
+ const viewPath = path.join(__dirname, "..", "server", "views", "d.ejs")
8
+
6
9
  test("dev iframe checks plugin install state before posting a launch request", () => {
7
- const view = fs.readFileSync(path.join(__dirname, "..", "server", "views", "d.ejs"), "utf8")
10
+ const view = fs.readFileSync(viewPath, "utf8")
8
11
  const launchStart = view.indexOf("const launchTab = async (tab) =>")
9
12
  const redirectIndex = view.indexOf("if (await redirectToPluginInstallIfNeeded(href))", launchStart)
10
13
  const postMessageIndex = view.indexOf("window.parent.postMessage", launchStart)
@@ -15,3 +18,66 @@ test("dev iframe checks plugin install state before posting a launch request", (
15
18
  assert.ok(view.includes("window.top.location.href = redirectHref"))
16
19
  assert.ok(view.includes("/api/plugin/install-state?path="))
17
20
  })
21
+
22
+ test("dev iframe renders plugin apps before project shell and shows description metadata", async () => {
23
+ const html = await ejs.renderFile(viewPath, {
24
+ filepath: "/tmp/pinokio-dev-view",
25
+ retry: false,
26
+ theme: "light",
27
+ agent: "browser",
28
+ dynamic: [{
29
+ icon: "fa-solid fa-terminal",
30
+ title: "Terminals",
31
+ subtitle: "Open a project shell, with or without Python activated.",
32
+ menu: [{
33
+ icon: "fa-solid fa-terminal",
34
+ title: "Bash",
35
+ subtitle: "Plain shell plus detected Python environments",
36
+ menu: [{
37
+ icon: "fa-solid fa-terminal",
38
+ title: "Shell",
39
+ subtitle: "Open a plain Bash shell",
40
+ href: "/shell/dev.bash.plain?path=%2Ftmp%2Fpinokio-dev-view"
41
+ }]
42
+ }]
43
+ }, {
44
+ icon: "fa-solid fa-plug-circle-bolt",
45
+ title: "Terminal Apps",
46
+ subtitle: "Terminal apps provided by your installed",
47
+ subtitle_link_href: "/plugins",
48
+ subtitle_link_label: "plugins",
49
+ menu: [{
50
+ icon: "fa-solid fa-terminal",
51
+ title: "Agent CLI",
52
+ description: "Runs the agent inside this workspace.",
53
+ href: "/run/plugin/agent"
54
+ }]
55
+ }, {
56
+ icon: "fa-solid fa-arrow-up-right-from-square",
57
+ title: "Desktop Apps",
58
+ subtitle: "Desktop apps provided by your installed",
59
+ subtitle_link_href: "/plugins",
60
+ subtitle_link_label: "plugins",
61
+ menu: [{
62
+ icon: "fa-solid fa-code",
63
+ title: "Editor",
64
+ metadata: {
65
+ description: "Opens the workspace in a desktop editor."
66
+ },
67
+ href: "/run/plugin/editor"
68
+ }]
69
+ }],
70
+ })
71
+
72
+ const terminalAppsIndex = html.indexOf("Terminal Apps")
73
+ const desktopAppsIndex = html.indexOf("Desktop Apps")
74
+ const projectShellIndex = html.indexOf("Project Shell")
75
+
76
+ assert.ok(terminalAppsIndex !== -1)
77
+ assert.ok(desktopAppsIndex !== -1)
78
+ assert.ok(projectShellIndex !== -1)
79
+ assert.ok(terminalAppsIndex < projectShellIndex)
80
+ assert.ok(desktopAppsIndex < projectShellIndex)
81
+ assert.ok(html.includes("Runs the agent inside this workspace."))
82
+ assert.ok(html.includes("Opens the workspace in a desktop editor."))
83
+ })
@@ -189,7 +189,7 @@ module.exports = {
189
189
  const systemPlugins = menu.filter((item) => item.source === "system")
190
190
  const localPlugins = menu.filter((item) => item.source === "local")
191
191
 
192
- assert.strictEqual(systemPlugins.length, 13)
192
+ assert.strictEqual(systemPlugins.length, 10)
193
193
  assert.ok(systemPlugins.every((item) => item.system === true))
194
194
  assert.ok(systemPlugins.every((item) => item.href.startsWith("/pinokio/run/plugin/")))
195
195
  assert.ok(systemPlugins.every((item) => item.image.startsWith("/pinokio/asset/plugin/")))
@@ -0,0 +1,107 @@
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 dropdownScriptPath = path.resolve(root, "server/public/urldropdown.js")
9
+
10
+ function jsonResponse(payload) {
11
+ return {
12
+ ok: true,
13
+ status: 200,
14
+ statusText: "OK",
15
+ json: async () => payload
16
+ }
17
+ }
18
+
19
+ async function waitFor(predicate, message = "condition") {
20
+ const deadline = Date.now() + 2000
21
+ while (Date.now() < deadline) {
22
+ if (predicate()) return
23
+ await new Promise((resolve) => setTimeout(resolve, 20))
24
+ }
25
+ assert.fail(`Timed out waiting for ${message}`)
26
+ }
27
+
28
+ async function createDropdownDom() {
29
+ const script = await fs.readFile(dropdownScriptPath, "utf8")
30
+ const virtualConsole = new VirtualConsole()
31
+ virtualConsole.on("jsdomError", (error) => {
32
+ throw error
33
+ })
34
+
35
+ const dom = new JSDOM(`<!doctype html>
36
+ <html>
37
+ <head><title>Current app</title></head>
38
+ <body>
39
+ <form class="urlbar"><input type="url"></form>
40
+ <div id="url-dropdown"></div>
41
+ <script>${script}</script>
42
+ </body>
43
+ </html>`, {
44
+ url: "http://127.0.0.1:42000/p/current",
45
+ runScripts: "dangerously",
46
+ resources: "usable",
47
+ pretendToBeVisual: true,
48
+ virtualConsole,
49
+ beforeParse(window) {
50
+ window.fetch = async (url) => {
51
+ if (String(url).includes("/info/apps")) {
52
+ return jsonResponse({
53
+ apps: [
54
+ { name: "MMAudio", title: "MMAudio" },
55
+ { name: "AllTalk-TTS", title: "AllTalk-TTS v2" }
56
+ ]
57
+ })
58
+ }
59
+ if (String(url).includes("/info/procs")) {
60
+ return jsonResponse({ info: [] })
61
+ }
62
+ return jsonResponse({})
63
+ }
64
+ }
65
+ })
66
+
67
+ const { window } = dom
68
+ await new Promise((resolve) => window.addEventListener("DOMContentLoaded", resolve, { once: true }))
69
+ window.initUrlDropdown()
70
+ return dom
71
+ }
72
+
73
+ test("split URL modal uses inline submit and omits Files actions", async () => {
74
+ const dom = await createDropdownDom()
75
+ const { window } = dom
76
+ const { document } = window
77
+
78
+ const selection = window.PinokioUrlDropdown.openSplitModal({
79
+ title: "Split Into Columns",
80
+ description: "Choose a running process or use the current tab URL for the new pane.",
81
+ confirmLabel: "Open in pane"
82
+ })
83
+
84
+ await waitFor(() => document.querySelector(".url-modal-content.split-mode .url-mode-button"), "split modal app rows")
85
+
86
+ const overlay = document.getElementById("url-modal-overlay")
87
+ const content = document.querySelector(".url-modal-content")
88
+ const inlineSubmit = document.querySelector(".url-modal-inline-submit")
89
+ const actions = document.querySelector(".url-modal-actions")
90
+ const modeLabels = Array.from(document.querySelectorAll(".url-modal-content.split-mode .url-mode-button span"))
91
+ .map((node) => node.textContent)
92
+
93
+ assert.equal(overlay.classList.contains("split-mode"), true)
94
+ assert.equal(content.classList.contains("split-mode"), true)
95
+ assert.equal(inlineSubmit.textContent, "Open in pane")
96
+ assert.ok(actions)
97
+ assert.deepEqual([...new Set(modeLabels)].sort(), ["Dev", "Run"])
98
+ assert.doesNotMatch(content.textContent, /\bFiles\b/)
99
+
100
+ const input = document.querySelector(".url-modal-input")
101
+ input.value = "http://localhost:7860"
102
+ input.dispatchEvent(new window.Event("input", { bubbles: true }))
103
+ assert.equal(inlineSubmit.disabled, false)
104
+ inlineSubmit.click()
105
+
106
+ assert.equal(await selection, "/container?url=http%3A%2F%2Flocalhost%3A7860")
107
+ })
Binary file
@@ -1,15 +0,0 @@
1
- module.exports = {
2
- title: "Crush",
3
- icon: "crush.png",
4
- link: "https://github.com/charmbracelet/crush",
5
- run: [{
6
- id: "run",
7
- method: "shell.run",
8
- params: {
9
- message: "npx -y @charmland/crush@latest",
10
- path: "{{args.cwd}}",
11
- buffer: 1024,
12
- input: true
13
- }
14
- }]
15
- }
@@ -1,34 +0,0 @@
1
- module.exports = {
2
- title: "Qwen Code",
3
- link: "https://github.com/QwenLM/qwen-code",
4
- icon: "qwen.png",
5
- env: [{
6
- key: "OPENAI_API_KEY",
7
- default: "OPENAI_API_KEY"
8
- }, {
9
- key: "OPENAI_BASE_URL",
10
- description: "use the OpenAI API compatible api endpoint",
11
- default: "http://localhost:1234/v1"
12
- }, {
13
- key: "OPENAI_MODEL",
14
- description: "the openai compatible model",
15
- default: "mradermacher/Bootes-Qwen3_Coder-Reasoning-i1-GGUF"
16
- }],
17
- run: [{
18
- id: "run",
19
- method: "shell.run",
20
- params: {
21
- message: {
22
- _: [
23
- "npx",
24
- "-y",
25
- "@qwen-code/qwen-code@latest"
26
- ],
27
- "prompt-interactive": "{{args.prompt || undefined}}"
28
- },
29
- path: "{{args.cwd}}",
30
- buffer: 1024,
31
- input: true
32
- }
33
- }]
34
- }
Binary file
@@ -1,23 +0,0 @@
1
- module.exports = {
2
- title: "Windsurf",
3
- link: "https://windsurf.com/",
4
- icon: "windsurf.png",
5
- description: "The AI Code Editor",
6
- launch_type: "desktop",
7
- run: [{
8
- method: "uri.open",
9
- params: {
10
- uri: "windsurf://cascade/newChat",
11
- params: {
12
- prompt: "{{args.prompt || ''}}",
13
- folder: "{{args.cwd || ''}}"
14
- }
15
- }
16
- }, {
17
- method: "process.wait",
18
- params: {
19
- title: "Launched",
20
- description: "Click the stop button to stop watching file changes"
21
- }
22
- }]
23
- }
Binary file