pinokiod 7.1.27 → 7.1.29
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/kernel/bin/bluefairy.js +4 -4
- package/package.json +1 -1
- package/server/index.js +112 -26
- package/server/lib/task_packages.js +8 -9
- package/server/public/common.js +10 -13
- package/server/public/run-task-save.js +109 -0
- package/server/public/task-launcher.js +54 -6
- package/server/public/terminal-settings.js +37 -14
- package/server/public/universal-launcher.css +4 -0
- package/server/views/shell.ejs +3 -1
- package/server/views/task_builder.ejs +18 -0
- package/server/views/terminal.ejs +5 -3
package/kernel/bin/bluefairy.js
CHANGED
|
@@ -4,7 +4,7 @@ const semver = require('semver')
|
|
|
4
4
|
|
|
5
5
|
class Bluefairy {
|
|
6
6
|
description = "Installs Bluefairy, a standalone package freshness guard."
|
|
7
|
-
version = ">=0.0.
|
|
7
|
+
version = ">=0.0.14"
|
|
8
8
|
|
|
9
9
|
packageName() {
|
|
10
10
|
return "bluefairy"
|
|
@@ -84,12 +84,12 @@ class Bluefairy {
|
|
|
84
84
|
return []
|
|
85
85
|
}
|
|
86
86
|
if (shell.isCmdShell()) {
|
|
87
|
-
return [`bluefairy-activate.cmd
|
|
87
|
+
return [`bluefairy-activate.cmd`]
|
|
88
88
|
}
|
|
89
89
|
if (shell.isPowerShell()) {
|
|
90
|
-
return [`& bluefairy-activate.ps1
|
|
90
|
+
return [`& bluefairy-activate.ps1`]
|
|
91
91
|
}
|
|
92
|
-
return [`. bluefairy-activate
|
|
92
|
+
return [`. bluefairy-activate`]
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -2782,6 +2782,7 @@ class Server {
|
|
|
2782
2782
|
execUrl: "~" + req.originalUrl.replace(/^\/_api/, "\/api"),
|
|
2783
2783
|
proxies: this.kernel.api.proxies[filepath],
|
|
2784
2784
|
cwd: req.query.cwd,
|
|
2785
|
+
taskSaveWorkspacesRoot: this.kernel.path("workspaces"),
|
|
2785
2786
|
script_id: (req.base ? `${full_filepath}?cwd=${req.query.cwd}` : null),
|
|
2786
2787
|
script_path: (req.base ? full_filepath : null),
|
|
2787
2788
|
}
|
|
@@ -6890,10 +6891,13 @@ class Server {
|
|
|
6890
6891
|
try {
|
|
6891
6892
|
if (!this.kernel.plugin.config) {
|
|
6892
6893
|
await this.kernel.plugin.init()
|
|
6894
|
+
} else {
|
|
6895
|
+
await this.kernel.plugin.setConfig()
|
|
6893
6896
|
}
|
|
6894
6897
|
const pluginMenu = this.kernel.plugin && this.kernel.plugin.config && Array.isArray(this.kernel.plugin.config.menu)
|
|
6895
6898
|
? this.kernel.plugin.config.menu
|
|
6896
6899
|
: []
|
|
6900
|
+
res.set("Cache-Control", "no-store")
|
|
6897
6901
|
res.json({ menu: pluginMenu })
|
|
6898
6902
|
} catch (error) {
|
|
6899
6903
|
console.warn('Failed to load plugin menu for create launcher modal', error)
|
|
@@ -7596,6 +7600,7 @@ class Server {
|
|
|
7596
7600
|
}
|
|
7597
7601
|
return copied
|
|
7598
7602
|
}
|
|
7603
|
+
const shellQuote = (value) => JSON.stringify(String(value))
|
|
7599
7604
|
const createLauncherTargetFolder = async (rootDir, folderName, options = {}) => {
|
|
7600
7605
|
if (!isValidTerminalWorkspaceName(folderName)) {
|
|
7601
7606
|
const error = new Error("Invalid folder name.")
|
|
@@ -7616,8 +7621,11 @@ class Server {
|
|
|
7616
7621
|
error.status = 409
|
|
7617
7622
|
throw error
|
|
7618
7623
|
}
|
|
7619
|
-
|
|
7620
|
-
if (
|
|
7624
|
+
const shouldCreateDirectory = options.createDirectory !== false
|
|
7625
|
+
if (shouldCreateDirectory) {
|
|
7626
|
+
await fs.promises.mkdir(targetPath, { recursive: false })
|
|
7627
|
+
}
|
|
7628
|
+
if (shouldCreateDirectory && options.initializeGit !== false) {
|
|
7621
7629
|
try {
|
|
7622
7630
|
await initializeLauncherTargetGitRepository(targetPath)
|
|
7623
7631
|
} catch (error) {
|
|
@@ -7655,9 +7663,6 @@ class Server {
|
|
|
7655
7663
|
if (!rawValue) {
|
|
7656
7664
|
return ""
|
|
7657
7665
|
}
|
|
7658
|
-
if (this.kernel && this.kernel.git && typeof this.kernel.git.canonicalRepoUrl === "function") {
|
|
7659
|
-
return this.kernel.git.canonicalRepoUrl(rawValue) || rawValue
|
|
7660
|
-
}
|
|
7661
7666
|
return rawValue
|
|
7662
7667
|
}
|
|
7663
7668
|
const cloneLauncherRemoteRepo = async ({ rootDir, folderName, ref }) => {
|
|
@@ -7668,17 +7673,14 @@ class Server {
|
|
|
7668
7673
|
throw error
|
|
7669
7674
|
}
|
|
7670
7675
|
const targetPath = await createLauncherTargetFolder(rootDir, folderName, {
|
|
7676
|
+
createDirectory: false,
|
|
7671
7677
|
initializeGit: false
|
|
7672
7678
|
})
|
|
7673
7679
|
try {
|
|
7674
|
-
await
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
url: normalizedRef,
|
|
7679
|
-
singleBranch: true,
|
|
7680
|
-
depth: 1
|
|
7681
|
-
})
|
|
7680
|
+
await this.kernel.exec({
|
|
7681
|
+
message: [`git clone --depth 1 --single-branch ${shellQuote(normalizedRef)} ${shellQuote(targetPath)}`],
|
|
7682
|
+
path: path.resolve(rootDir)
|
|
7683
|
+
}, () => {})
|
|
7682
7684
|
return targetPath
|
|
7683
7685
|
} catch (error) {
|
|
7684
7686
|
await fs.promises.rm(targetPath, { recursive: true, force: true }).catch(() => {})
|
|
@@ -7832,15 +7834,46 @@ class Server {
|
|
|
7832
7834
|
const nextInputs = JSON.parse(value)
|
|
7833
7835
|
return Array.isArray(nextInputs) ? nextInputs : []
|
|
7834
7836
|
}
|
|
7835
|
-
const
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7837
|
+
const normalizeTaskBuilderSourceWorkspace = (value) => {
|
|
7838
|
+
const raw = typeof value === "string" ? value.trim() : ""
|
|
7839
|
+
if (!raw) {
|
|
7840
|
+
return {
|
|
7841
|
+
cwd: "",
|
|
7842
|
+
label: ""
|
|
7843
|
+
}
|
|
7844
|
+
}
|
|
7845
|
+
const workspacesRoot = path.resolve(this.kernel.path("workspaces"))
|
|
7846
|
+
const normalizedPath = path.resolve(raw)
|
|
7847
|
+
const relative = path.relative(workspacesRoot, normalizedPath)
|
|
7848
|
+
if (!relative || relative === "." || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
7849
|
+
return {
|
|
7850
|
+
cwd: "",
|
|
7851
|
+
label: ""
|
|
7852
|
+
}
|
|
7853
|
+
}
|
|
7854
|
+
return {
|
|
7855
|
+
cwd: normalizedPath,
|
|
7856
|
+
label: path.basename(normalizedPath) || relative.split(path.sep).pop() || "workspace"
|
|
7857
|
+
}
|
|
7858
|
+
}
|
|
7859
|
+
const buildTaskBuilderDefaults = (body, inputs, options = {}) => {
|
|
7860
|
+
const sourceWorkspace = options.sourceWorkspace && typeof options.sourceWorkspace === "object"
|
|
7861
|
+
? options.sourceWorkspace
|
|
7862
|
+
: normalizeTaskBuilderSourceWorkspace(body?.sourceWorkspaceCwd)
|
|
7863
|
+
return {
|
|
7864
|
+
title: typeof body?.title === "string" ? body.title : "",
|
|
7865
|
+
description: typeof body?.description === "string" ? body.description : "",
|
|
7866
|
+
path: typeof body?.path === "string" && body.path.trim()
|
|
7867
|
+
? body.path.trim()
|
|
7868
|
+
: "workspaces",
|
|
7869
|
+
template: typeof body?.template === "string" ? body.template : "",
|
|
7870
|
+
inputs: Array.isArray(inputs) ? inputs : [],
|
|
7871
|
+
sourceWorkspaceCwd: sourceWorkspace.cwd || "",
|
|
7872
|
+
sourceWorkspaceLabel: sourceWorkspace.label || "",
|
|
7873
|
+
rememberCurrentWorkspace: body?.rememberCurrentWorkspace === "1" || body?.rememberCurrentWorkspace === "on" || body?.rememberCurrentWorkspace === true,
|
|
7874
|
+
lockPathSelection: body?.lockPathSelection === "1" || body?.lockPathSelection === "on" || body?.lockPathSelection === true
|
|
7875
|
+
}
|
|
7876
|
+
}
|
|
7844
7877
|
const summarizeTaskRemoteLabel = (value) => {
|
|
7845
7878
|
const raw = typeof value === "string" ? value.trim() : ""
|
|
7846
7879
|
if (!raw) {
|
|
@@ -8914,7 +8947,10 @@ class Server {
|
|
|
8914
8947
|
}))
|
|
8915
8948
|
|
|
8916
8949
|
this.app.get("/tasks/new", ex(async (req, res) => {
|
|
8950
|
+
const sourceWorkspace = normalizeTaskBuilderSourceWorkspace(req.query.sourceWorkspaceCwd)
|
|
8951
|
+
const lockPathSelection = req.query.lockPath === "1" || req.query.lockPath === "true"
|
|
8917
8952
|
await renderTaskBuilderPage(req, res, {
|
|
8953
|
+
allowPathSelection: !lockPathSelection,
|
|
8918
8954
|
defaults: {
|
|
8919
8955
|
path: typeof req.query.path === "string" && req.query.path.trim()
|
|
8920
8956
|
? req.query.path.trim()
|
|
@@ -8922,7 +8958,11 @@ class Server {
|
|
|
8922
8958
|
title: typeof req.query.title === "string" ? req.query.title : "",
|
|
8923
8959
|
description: typeof req.query.description === "string" ? req.query.description : "",
|
|
8924
8960
|
template: typeof req.query.template === "string" ? req.query.template : "",
|
|
8925
|
-
inputs: []
|
|
8961
|
+
inputs: [],
|
|
8962
|
+
sourceWorkspaceCwd: sourceWorkspace.cwd,
|
|
8963
|
+
sourceWorkspaceLabel: sourceWorkspace.label,
|
|
8964
|
+
rememberCurrentWorkspace: false,
|
|
8965
|
+
lockPathSelection
|
|
8926
8966
|
}
|
|
8927
8967
|
})
|
|
8928
8968
|
}))
|
|
@@ -8960,14 +9000,45 @@ class Server {
|
|
|
8960
9000
|
|
|
8961
9001
|
this.app.post("/tasks", ex(async (req, res) => {
|
|
8962
9002
|
const body = req.body && typeof req.body === "object" ? req.body : {}
|
|
9003
|
+
const sourceWorkspace = normalizeTaskBuilderSourceWorkspace(body.sourceWorkspaceCwd)
|
|
8963
9004
|
let parsedInputs = []
|
|
8964
9005
|
if (typeof body.inputsJson === "string" && body.inputsJson.trim()) {
|
|
8965
9006
|
try {
|
|
8966
9007
|
parsedInputs = parseTaskBuilderInputs(body.inputsJson)
|
|
8967
9008
|
} catch (error) {
|
|
9009
|
+
const defaults = buildTaskBuilderDefaults(body, [], { sourceWorkspace })
|
|
8968
9010
|
await renderTaskBuilderPage(req, res, {
|
|
8969
9011
|
error: "Inputs JSON is invalid.",
|
|
8970
|
-
|
|
9012
|
+
allowPathSelection: !defaults.lockPathSelection,
|
|
9013
|
+
defaults
|
|
9014
|
+
})
|
|
9015
|
+
return
|
|
9016
|
+
}
|
|
9017
|
+
}
|
|
9018
|
+
const defaults = buildTaskBuilderDefaults(body, parsedInputs, { sourceWorkspace })
|
|
9019
|
+
if (defaults.rememberCurrentWorkspace) {
|
|
9020
|
+
if (defaults.path !== "workspaces") {
|
|
9021
|
+
await renderTaskBuilderPage(req, res, {
|
|
9022
|
+
error: "Current workspace can only be remembered for workspace tasks.",
|
|
9023
|
+
allowPathSelection: !defaults.lockPathSelection,
|
|
9024
|
+
defaults
|
|
9025
|
+
})
|
|
9026
|
+
return
|
|
9027
|
+
}
|
|
9028
|
+
if (!sourceWorkspace.cwd) {
|
|
9029
|
+
await renderTaskBuilderPage(req, res, {
|
|
9030
|
+
error: "Current workspace is no longer available to remember.",
|
|
9031
|
+
allowPathSelection: !defaults.lockPathSelection,
|
|
9032
|
+
defaults
|
|
9033
|
+
})
|
|
9034
|
+
return
|
|
9035
|
+
}
|
|
9036
|
+
const sourceStats = await fs.promises.stat(sourceWorkspace.cwd).catch(() => null)
|
|
9037
|
+
if (!sourceStats || !sourceStats.isDirectory()) {
|
|
9038
|
+
await renderTaskBuilderPage(req, res, {
|
|
9039
|
+
error: "Current workspace could not be found.",
|
|
9040
|
+
allowPathSelection: !defaults.lockPathSelection,
|
|
9041
|
+
defaults
|
|
8971
9042
|
})
|
|
8972
9043
|
return
|
|
8973
9044
|
}
|
|
@@ -8983,11 +9054,25 @@ class Server {
|
|
|
8983
9054
|
},
|
|
8984
9055
|
template: typeof body.template === "string" ? body.template : ""
|
|
8985
9056
|
})
|
|
9057
|
+
if (defaults.rememberCurrentWorkspace) {
|
|
9058
|
+
const workspaceRef = taskWorkspaceLinks.createWorkspaceRef("workspaces", sourceWorkspace.cwd)
|
|
9059
|
+
if (!workspaceRef) {
|
|
9060
|
+
await taskPackages.deleteTaskPackage(task.id).catch(() => {})
|
|
9061
|
+
throw new Error("Failed to remember current workspace.")
|
|
9062
|
+
}
|
|
9063
|
+
try {
|
|
9064
|
+
await taskWorkspaceLinks.touchTaskWorkspace(task.id, workspaceRef)
|
|
9065
|
+
} catch (error) {
|
|
9066
|
+
await taskPackages.deleteTaskPackage(task.id).catch(() => {})
|
|
9067
|
+
throw error
|
|
9068
|
+
}
|
|
9069
|
+
}
|
|
8986
9070
|
res.redirect(buildTaskPath({ id: task.id }))
|
|
8987
9071
|
} catch (error) {
|
|
8988
9072
|
await renderTaskBuilderPage(req, res, {
|
|
8989
9073
|
error: error && error.message ? error.message : "Failed to create task.",
|
|
8990
|
-
|
|
9074
|
+
allowPathSelection: !defaults.lockPathSelection,
|
|
9075
|
+
defaults
|
|
8991
9076
|
})
|
|
8992
9077
|
}
|
|
8993
9078
|
}))
|
|
@@ -11683,7 +11768,8 @@ class Server {
|
|
|
11683
11768
|
done_message,
|
|
11684
11769
|
callback,
|
|
11685
11770
|
callback_target,
|
|
11686
|
-
running: (shell ? true : false)
|
|
11771
|
+
running: (shell ? true : false),
|
|
11772
|
+
taskSaveWorkspacesRoot: this.kernel.path("workspaces")
|
|
11687
11773
|
})
|
|
11688
11774
|
}))
|
|
11689
11775
|
this.app.get("/pro", ex(async (req, res) => {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const git = require("isomorphic-git");
|
|
4
|
-
const http = require("isomorphic-git/http/node");
|
|
5
4
|
|
|
6
5
|
const TASK_INDEX_VERSION = 1;
|
|
7
6
|
const TASK_INDEX_FILENAME = "index.json";
|
|
@@ -10,6 +9,10 @@ const TASK_TEMPLATE_FILENAME = "task.md";
|
|
|
10
9
|
const TASK_ID_PATTERN = /^t[1-9][0-9]*$/;
|
|
11
10
|
const TASK_INPUT_NAME_PATTERN = /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/;
|
|
12
11
|
|
|
12
|
+
function shellQuote(value) {
|
|
13
|
+
return JSON.stringify(String(value));
|
|
14
|
+
}
|
|
15
|
+
|
|
13
16
|
function slugify(value, fallback = "task") {
|
|
14
17
|
const normalized = typeof value === "string" ? value : "";
|
|
15
18
|
const slug = normalized
|
|
@@ -562,14 +565,10 @@ function createTaskPackageService({ kernel }) {
|
|
|
562
565
|
const taskDir = taskDirForId(allocation.id);
|
|
563
566
|
await fs.promises.mkdir(tasksRoot(), { recursive: true });
|
|
564
567
|
try {
|
|
565
|
-
await
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
url: rawRef,
|
|
570
|
-
singleBranch: true,
|
|
571
|
-
depth: 1
|
|
572
|
-
});
|
|
568
|
+
await kernel.exec({
|
|
569
|
+
message: [`git clone --depth 1 --single-branch ${shellQuote(rawRef)} ${shellQuote(taskDir)}`],
|
|
570
|
+
path: tasksRoot()
|
|
571
|
+
}, () => {});
|
|
573
572
|
await readTaskPackageById(allocation.id);
|
|
574
573
|
allocation.index.items.push({
|
|
575
574
|
id: allocation.id,
|
package/server/public/common.js
CHANGED
|
@@ -2688,7 +2688,16 @@ if (typeof hotkeys === 'function') {
|
|
|
2688
2688
|
try { overlay.remove(); } catch (_) {}
|
|
2689
2689
|
try { window.__pinokioConnectCurtainInstalled = true; window.__pinokioConnectCurtainInstalling = false; } catch (_) {}
|
|
2690
2690
|
};
|
|
2691
|
-
overlay.addEventListener('
|
|
2691
|
+
overlay.addEventListener('click', onTap, { once: true, capture: true });
|
|
2692
|
+
overlay.addEventListener('keydown', (e) => {
|
|
2693
|
+
if (!e) {
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
const key = e.key || '';
|
|
2697
|
+
if (key === 'Enter' || key === ' ' || key === 'Spacebar') {
|
|
2698
|
+
onTap(e);
|
|
2699
|
+
}
|
|
2700
|
+
}, { capture: true });
|
|
2692
2701
|
document.body.appendChild(overlay);
|
|
2693
2702
|
};
|
|
2694
2703
|
|
|
@@ -3701,18 +3710,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
3701
3710
|
} else {
|
|
3702
3711
|
api.showModal();
|
|
3703
3712
|
}
|
|
3704
|
-
wait_ready(null, { showLoader: false, requireStartup: true }).then(({ ready }) => {
|
|
3705
|
-
createLauncherDebugLog('guardUniversalLauncher wait_ready resolved', { ready });
|
|
3706
|
-
if (ready) {
|
|
3707
|
-
return;
|
|
3708
|
-
}
|
|
3709
|
-
if (api && typeof api.hideModal === 'function') {
|
|
3710
|
-
api.hideModal();
|
|
3711
|
-
}
|
|
3712
|
-
const callback = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash);
|
|
3713
|
-
createLauncherDebugLog('guardUniversalLauncher redirecting to /setup/dev', { callback });
|
|
3714
|
-
window.location.href = `/setup/dev?callback=${callback}`;
|
|
3715
|
-
});
|
|
3716
3713
|
}
|
|
3717
3714
|
|
|
3718
3715
|
function initializeCreateLauncherIntegration() {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
(function(window, document) {
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
function normalizePath(value) {
|
|
5
|
+
return typeof value === "string" && value.trim()
|
|
6
|
+
? value.trim().replace(/\\/g, "/").replace(/\/+$/, "")
|
|
7
|
+
: "";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isPathWithin(candidate, parent) {
|
|
11
|
+
const normalizedCandidate = normalizePath(candidate);
|
|
12
|
+
const normalizedParent = normalizePath(parent);
|
|
13
|
+
if (!normalizedCandidate || !normalizedParent) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
if (normalizedCandidate === normalizedParent) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return normalizedCandidate.startsWith(`${normalizedParent}/`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getFirstPromptLine(prompt) {
|
|
23
|
+
const lines = String(prompt || "").split(/\r?\n/);
|
|
24
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
25
|
+
const line = lines[index].trim();
|
|
26
|
+
if (line) {
|
|
27
|
+
return line;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getWorkspaceName(cwd) {
|
|
34
|
+
const normalized = normalizePath(cwd);
|
|
35
|
+
if (!normalized) {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
39
|
+
return segments.length > 0 ? segments[segments.length - 1] : "";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function suggestTitle(prompt, cwd) {
|
|
43
|
+
const firstLine = getFirstPromptLine(prompt);
|
|
44
|
+
if (firstLine) {
|
|
45
|
+
return firstLine.slice(0, 120);
|
|
46
|
+
}
|
|
47
|
+
const workspaceName = getWorkspaceName(cwd);
|
|
48
|
+
return workspaceName ? `Task for ${workspaceName}` : "New task";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getPrompt() {
|
|
52
|
+
try {
|
|
53
|
+
const params = new URLSearchParams(window.location.search || "");
|
|
54
|
+
return typeof params.get("prompt") === "string"
|
|
55
|
+
? params.get("prompt").trim()
|
|
56
|
+
: "";
|
|
57
|
+
} catch (_) {
|
|
58
|
+
return "";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function openTaskBuilder(url) {
|
|
63
|
+
const popup = window.open(url.toString(), "_blank", "noopener");
|
|
64
|
+
if (!popup) {
|
|
65
|
+
window.location.href = url.toString();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function initRunTaskSave() {
|
|
70
|
+
const button = document.querySelector("[data-save-task-button]");
|
|
71
|
+
const body = document.body;
|
|
72
|
+
if (!button || !body) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const cwd = body.dataset.taskSaveCwd || "";
|
|
77
|
+
const workspacesRoot = body.dataset.taskSaveWorkspacesRoot || "";
|
|
78
|
+
const prompt = getPrompt();
|
|
79
|
+
if (!cwd || !workspacesRoot || !prompt) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const normalizedCwd = normalizePath(cwd);
|
|
84
|
+
const normalizedRoot = normalizePath(workspacesRoot);
|
|
85
|
+
if (!isPathWithin(normalizedCwd, normalizedRoot) || normalizedCwd === normalizedRoot) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const url = new URL("/tasks/new", window.location.origin);
|
|
90
|
+
url.searchParams.set("path", "workspaces");
|
|
91
|
+
url.searchParams.set("lockPath", "1");
|
|
92
|
+
url.searchParams.set("title", suggestTitle(prompt, normalizedCwd));
|
|
93
|
+
url.searchParams.set("template", prompt);
|
|
94
|
+
url.searchParams.set("sourceWorkspaceCwd", cwd);
|
|
95
|
+
|
|
96
|
+
button.hidden = false;
|
|
97
|
+
button.classList.remove("hidden");
|
|
98
|
+
button.addEventListener("click", (event) => {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
openTaskBuilder(url);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (document.readyState === "loading") {
|
|
105
|
+
document.addEventListener("DOMContentLoaded", initRunTaskSave, { once: true });
|
|
106
|
+
} else {
|
|
107
|
+
initRunTaskSave();
|
|
108
|
+
}
|
|
109
|
+
})(window, document);
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
const CATEGORY_ORDER = ["CLI", "IDE"];
|
|
5
|
+
const TOOL_PREFERENCE_KEY = "pinokio.universalLauncher.tool";
|
|
6
|
+
const TOOL_VALUE_ALIASES = {
|
|
7
|
+
claude: "code/claude",
|
|
8
|
+
codex: "code/codex",
|
|
9
|
+
gemini: "code/gemini"
|
|
10
|
+
};
|
|
5
11
|
const FALLBACK_TOOLS = [
|
|
6
12
|
{
|
|
7
13
|
value: "code/claude",
|
|
@@ -71,6 +77,35 @@
|
|
|
71
77
|
return category === "IDE" ? "Desktop app" : category;
|
|
72
78
|
}
|
|
73
79
|
|
|
80
|
+
function normalizeToolValue(value) {
|
|
81
|
+
const trimmed = typeof value === "string"
|
|
82
|
+
? value.trim().replace(/^\/+|\/+$/g, "")
|
|
83
|
+
: "";
|
|
84
|
+
if (!trimmed) {
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
87
|
+
return TOOL_VALUE_ALIASES[trimmed] || trimmed;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getStoredToolPreference() {
|
|
91
|
+
try {
|
|
92
|
+
return normalizeToolValue(window.localStorage.getItem(TOOL_PREFERENCE_KEY));
|
|
93
|
+
} catch (_) {
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function setStoredToolPreference(value) {
|
|
99
|
+
try {
|
|
100
|
+
const normalizedValue = normalizeToolValue(value);
|
|
101
|
+
if (normalizedValue) {
|
|
102
|
+
window.localStorage.setItem(TOOL_PREFERENCE_KEY, normalizedValue);
|
|
103
|
+
} else {
|
|
104
|
+
window.localStorage.removeItem(TOOL_PREFERENCE_KEY);
|
|
105
|
+
}
|
|
106
|
+
} catch (_) {}
|
|
107
|
+
}
|
|
108
|
+
|
|
74
109
|
function buildTaskToolPicker(tools, host, hiddenInput) {
|
|
75
110
|
if (!host || !hiddenInput) {
|
|
76
111
|
return null;
|
|
@@ -291,11 +326,15 @@
|
|
|
291
326
|
}
|
|
292
327
|
|
|
293
328
|
const api = {
|
|
294
|
-
setValue(value) {
|
|
295
|
-
const
|
|
329
|
+
setValue(value, options) {
|
|
330
|
+
const opts = options && typeof options === "object" ? options : {};
|
|
331
|
+
const nextValue = normalizeToolValue(value);
|
|
296
332
|
const entry = nextValue ? getEntryByValue(nextValue) : null;
|
|
297
333
|
selectedValue = entry ? entry.meta.value : "";
|
|
298
334
|
hiddenInput.value = selectedValue;
|
|
335
|
+
if (opts.persist !== false) {
|
|
336
|
+
setStoredToolPreference(selectedValue);
|
|
337
|
+
}
|
|
299
338
|
syncTrigger();
|
|
300
339
|
},
|
|
301
340
|
openMenu() {
|
|
@@ -352,13 +391,20 @@
|
|
|
352
391
|
api.closeMenu();
|
|
353
392
|
}, true);
|
|
354
393
|
|
|
355
|
-
const initialValue = hiddenInput.value
|
|
356
|
-
const
|
|
394
|
+
const initialValue = normalizeToolValue(hiddenInput.value);
|
|
395
|
+
const storedValue = getStoredToolPreference();
|
|
396
|
+
const explicitEntry = initialValue ? getEntryByValue(initialValue) : null;
|
|
397
|
+
const storedEntry = explicitEntry ? null : (storedValue ? getEntryByValue(storedValue) : null);
|
|
398
|
+
if (!explicitEntry && storedValue && !storedEntry) {
|
|
399
|
+
setStoredToolPreference("");
|
|
400
|
+
}
|
|
401
|
+
const defaultTool = (explicitEntry && explicitEntry.meta)
|
|
402
|
+
|| (storedEntry && storedEntry.meta)
|
|
357
403
|
|| tools.find((tool) => tool.isDefault)
|
|
358
404
|
|| tools[0]
|
|
359
405
|
|| null;
|
|
360
406
|
if (defaultTool) {
|
|
361
|
-
api.setValue(defaultTool.value);
|
|
407
|
+
api.setValue(defaultTool.value, { persist: false });
|
|
362
408
|
} else {
|
|
363
409
|
syncTrigger();
|
|
364
410
|
}
|
|
@@ -368,7 +414,9 @@
|
|
|
368
414
|
|
|
369
415
|
async function getTools() {
|
|
370
416
|
try {
|
|
371
|
-
const response = await fetch("/api/plugin/menu"
|
|
417
|
+
const response = await fetch("/api/plugin/menu", {
|
|
418
|
+
cache: "no-store"
|
|
419
|
+
});
|
|
372
420
|
if (!response.ok) {
|
|
373
421
|
throw new Error(String(response.status));
|
|
374
422
|
}
|
|
@@ -120,33 +120,38 @@
|
|
|
120
120
|
event.preventDefault();
|
|
121
121
|
this.closeModal();
|
|
122
122
|
};
|
|
123
|
+
this.prefersModalInput = this.shouldPreferModalInput();
|
|
123
124
|
const stored = this.loadDirectTypingPreference();
|
|
124
|
-
if (
|
|
125
|
-
this.directTypingEnabled =
|
|
125
|
+
if (!this.prefersModalInput) {
|
|
126
|
+
this.directTypingEnabled = true;
|
|
127
|
+
} else if (stored === null) {
|
|
128
|
+
this.directTypingEnabled = false;
|
|
126
129
|
} else {
|
|
127
130
|
this.directTypingEnabled = stored;
|
|
128
131
|
}
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
shouldPreferModalInput() {
|
|
132
|
-
if (typeof
|
|
133
|
-
try {
|
|
134
|
-
if (window.matchMedia('(pointer: coarse)').matches) {
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
} catch (_) {}
|
|
135
|
+
if (typeof navigator !== 'undefined') {
|
|
138
136
|
try {
|
|
139
|
-
if (
|
|
140
|
-
|
|
137
|
+
if (navigator.userAgentData && typeof navigator.userAgentData.mobile === 'boolean') {
|
|
138
|
+
if (navigator.userAgentData.mobile) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
141
|
}
|
|
142
142
|
} catch (_) {}
|
|
143
|
-
}
|
|
144
|
-
if (typeof navigator !== 'undefined') {
|
|
145
143
|
const ua = navigator.userAgent || '';
|
|
146
144
|
if (/Mobi|Android|iPhone|iPad|Tablet/i.test(ua)) {
|
|
147
145
|
return true;
|
|
148
146
|
}
|
|
149
147
|
}
|
|
148
|
+
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
|
149
|
+
try {
|
|
150
|
+
if (window.matchMedia('(pointer: coarse)').matches) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
} catch (_) {}
|
|
154
|
+
}
|
|
150
155
|
return false;
|
|
151
156
|
}
|
|
152
157
|
|
|
@@ -294,7 +299,7 @@
|
|
|
294
299
|
}
|
|
295
300
|
|
|
296
301
|
setDirectTypingEnabled(enabled) {
|
|
297
|
-
const next = Boolean(enabled);
|
|
302
|
+
const next = this.prefersModalInput ? Boolean(enabled) : true;
|
|
298
303
|
if (next === this.directTypingEnabled) {
|
|
299
304
|
return;
|
|
300
305
|
}
|
|
@@ -1358,6 +1363,23 @@
|
|
|
1358
1363
|
});
|
|
1359
1364
|
}
|
|
1360
1365
|
|
|
1366
|
+
applyMobileRunnerLayout(runner) {
|
|
1367
|
+
if (!runner || !this.mobileInput || !this.mobileInput.prefersModalInput) {
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
const nodes = runner.querySelectorAll('#open-fs');
|
|
1371
|
+
if (!nodes || !nodes.length) {
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
nodes.forEach((node) => {
|
|
1375
|
+
if (!node) {
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
node.hidden = true;
|
|
1379
|
+
node.setAttribute('aria-hidden', 'true');
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1361
1383
|
initRunnerMenus() {
|
|
1362
1384
|
if (typeof document === 'undefined') {
|
|
1363
1385
|
return;
|
|
@@ -1383,12 +1405,13 @@
|
|
|
1383
1405
|
if (menu) {
|
|
1384
1406
|
this.menus.add(menu);
|
|
1385
1407
|
}
|
|
1386
|
-
if (this.mobileInput && !this.minimalRunnerMode) {
|
|
1408
|
+
if (this.mobileInput && this.mobileInput.prefersModalInput && !this.minimalRunnerMode) {
|
|
1387
1409
|
this.mobileInput.attachKeyboardButton(runner, utilities);
|
|
1388
1410
|
}
|
|
1389
1411
|
if (!this.minimalRunnerMode) {
|
|
1390
1412
|
this.attachForceResizeButton(runner, utilities);
|
|
1391
1413
|
}
|
|
1414
|
+
this.applyMobileRunnerLayout(runner);
|
|
1392
1415
|
this.applyMinimalRunnerLayout(runner);
|
|
1393
1416
|
});
|
|
1394
1417
|
}
|
|
@@ -250,6 +250,10 @@ body.universal-launcher-open {
|
|
|
250
250
|
display: none !important;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
.universal-launcher-overlay [hidden] {
|
|
254
|
+
display: none !important;
|
|
255
|
+
}
|
|
256
|
+
|
|
253
257
|
.universal-launcher-panel {
|
|
254
258
|
position: relative;
|
|
255
259
|
width: min(620px, calc(100vw - 28px));
|
package/server/views/shell.ejs
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
<script src="/terminal-settings.js"></script>
|
|
22
22
|
<script src="/pinokio-touch.js"></script>
|
|
23
23
|
<script src="/common.js"></script>
|
|
24
|
+
<script src="/run-task-save.js"></script>
|
|
24
25
|
<script src="/he.js"></script>
|
|
25
26
|
<script src="/opener.js"></script>
|
|
26
27
|
<!--
|
|
@@ -1848,7 +1849,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1848
1849
|
|
|
1849
1850
|
</script>
|
|
1850
1851
|
</head>
|
|
1851
|
-
<body class='<%=theme%>' data-agent="<%=agent%>">
|
|
1852
|
+
<body class='<%=theme%>' data-agent="<%=agent%>" data-task-save-cwd="<%= cwd || '' %>" data-task-save-workspaces-root="<%= taskSaveWorkspacesRoot || '' %>">
|
|
1852
1853
|
<% if (target === "_top") { %>
|
|
1853
1854
|
<header class='navheader grabbable'>
|
|
1854
1855
|
<% } else { %>
|
|
@@ -1867,6 +1868,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1867
1868
|
<div class='hidden btn run stop-btn'>
|
|
1868
1869
|
<span class='stop'><i class="fa-solid fa-stop"></i> Stop</span>
|
|
1869
1870
|
</div>
|
|
1871
|
+
<button class='hidden btn' type='button' data-save-task-button hidden><i class="fa-solid fa-bookmark"></i> Save task</button>
|
|
1870
1872
|
<div class='hidden btn stopped-btn'>
|
|
1871
1873
|
<span class='stopped'><i class="fa-solid fa-hand"></i> Stopped</span>
|
|
1872
1874
|
</div>
|
|
@@ -103,6 +103,24 @@
|
|
|
103
103
|
</section>
|
|
104
104
|
|
|
105
105
|
<input type="hidden" name="inputsJson" value="[]" data-task-inputs-json>
|
|
106
|
+
<input type="hidden" name="sourceWorkspaceCwd" value="<%= defaults.sourceWorkspaceCwd || '' %>">
|
|
107
|
+
<input type="hidden" name="lockPathSelection" value="<%= defaults.lockPathSelection ? '1' : '0' %>">
|
|
108
|
+
|
|
109
|
+
<% if (defaults.sourceWorkspaceCwd && (defaults.path || 'workspaces') === 'workspaces') { %>
|
|
110
|
+
<section class="task-section task-builder-workspace-section">
|
|
111
|
+
<div class="task-section-head">
|
|
112
|
+
<div>
|
|
113
|
+
<h2 class="task-section-title">Workspace</h2>
|
|
114
|
+
<p class="task-section-subcopy">Optional. Keep the task generic unless you want it to remember this workspace.</p>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
<label class="task-toggle">
|
|
118
|
+
<input type="checkbox" name="rememberCurrentWorkspace" value="1" <%= defaults.rememberCurrentWorkspace ? 'checked' : '' %>>
|
|
119
|
+
<span>Remember current workspace</span>
|
|
120
|
+
</label>
|
|
121
|
+
<p class="task-inline-help">This lets the task offer "Continue in <%= defaults.sourceWorkspaceLabel || 'this workspace' %>" later. No files are copied or saved.</p>
|
|
122
|
+
</section>
|
|
123
|
+
<% } %>
|
|
106
124
|
|
|
107
125
|
<div class="task-actions task-builder-actions">
|
|
108
126
|
<button class="task-button primary" type="submit">
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
<script src="/terminal-settings.js"></script>
|
|
22
22
|
<script src="/pinokio-touch.js"></script>
|
|
23
23
|
<script src="/common.js"></script>
|
|
24
|
+
<script src="/run-task-save.js"></script>
|
|
24
25
|
<script src="/he.js"></script>
|
|
25
26
|
<script src="/opener.js"></script>
|
|
26
27
|
<script src="/loading.js"></script>
|
|
@@ -2631,9 +2632,9 @@ const reloadMemory = async () => {
|
|
|
2631
2632
|
</script>
|
|
2632
2633
|
</head>
|
|
2633
2634
|
<% if (install_required) { %>
|
|
2634
|
-
<body class='terminal-page frozen <%=theme%> <%= locals.full_navbar ? "has-full-navbar" : "" %>'>
|
|
2635
|
+
<body class='terminal-page frozen <%=theme%> <%= locals.full_navbar ? "has-full-navbar" : "" %>' data-task-save-cwd="<%= cwd || '' %>" data-task-save-workspaces-root="<%= taskSaveWorkspacesRoot || '' %>">
|
|
2635
2636
|
<% } else { %>
|
|
2636
|
-
<body class='terminal-page <%=theme%> <%= locals.full_navbar ? "has-full-navbar" : "" %>'>
|
|
2637
|
+
<body class='terminal-page <%=theme%> <%= locals.full_navbar ? "has-full-navbar" : "" %>' data-task-save-cwd="<%= cwd || '' %>" data-task-save-workspaces-root="<%= taskSaveWorkspacesRoot || '' %>">
|
|
2637
2638
|
<% } %>
|
|
2638
2639
|
<% if (locals.full_navbar) { %>
|
|
2639
2640
|
<header class="navheader grabbable">
|
|
@@ -2675,8 +2676,9 @@ const reloadMemory = async () => {
|
|
|
2675
2676
|
<span class='stop hidden'><i class="fa-solid fa-stop"></i> Stop</span>
|
|
2676
2677
|
</div>
|
|
2677
2678
|
<% if (locals.filepath) { %>
|
|
2678
|
-
<button class='btn' id='open-fs' data-filepath="<%=filepath%>"><i class="fa-solid fa-
|
|
2679
|
+
<button class='btn' id='open-fs' data-filepath="<%=filepath%>" data-command="view"><i class="fa-solid fa-folder-open"></i> File Explorer</button>
|
|
2679
2680
|
<% } %>
|
|
2681
|
+
<button class='btn hidden' type='button' data-save-task-button hidden><i class="fa-solid fa-bookmark"></i> Save task</button>
|
|
2680
2682
|
<div id='status-window'></div>
|
|
2681
2683
|
<div id='progress-window' class='hidden'><div id='progress-bar'></div></div>
|
|
2682
2684
|
</div>
|