@melaya/runner 1.1.61 → 1.1.63
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/dist/assistantFiles.d.ts +29 -0
- package/dist/assistantFiles.js +113 -0
- package/dist/assistantHost.py +76 -1
- package/dist/cli.js +7 -0
- package/dist/connection.js +146 -55
- package/dist/folderPicker.d.ts +24 -0
- package/dist/folderPicker.js +139 -0
- package/dist/localRelay.d.ts +8 -1
- package/dist/localRelay.js +21 -1
- package/dist/localVault.py +745 -0
- package/dist/localVaultMirror.py +507 -0
- package/dist/obsidianApp.d.ts +324 -0
- package/dist/obsidianApp.js +1143 -0
- package/dist/obsidianAppCli.d.ts +15 -0
- package/dist/obsidianAppCli.js +123 -0
- package/dist/vaultMirror.d.ts +104 -0
- package/dist/vaultMirror.js +305 -0
- package/dist/vaults.d.ts +350 -0
- package/dist/vaults.js +1130 -0
- package/localVault.py +745 -0
- package/localVaultMirror.py +507 -0
- package/package.json +4 -2
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native OS folder dialog, shared by runner:pick-folder (static-context / RAG
|
|
3
|
+
* folders) and runner:vault-pick-folder (Obsidian vaults).
|
|
4
|
+
*
|
|
5
|
+
* Browsers can't reveal absolute paths for security, so the runner — which
|
|
6
|
+
* lives on the user's box — pops the dialog. Fixed argv; the prompt is a
|
|
7
|
+
* constant chosen by the caller, never user or model input.
|
|
8
|
+
*
|
|
9
|
+
* - Windows: the Explorer folder dialog (IFileOpenDialog + FOS_PICKFOLDERS)
|
|
10
|
+
* through PowerShell. WinForms' FolderBrowserDialog is the old tree view
|
|
11
|
+
* on Windows PowerShell (.NET Framework), so it is only the fallback. The
|
|
12
|
+
* dialog is owned by a small topmost window so it opens in front of the
|
|
13
|
+
* browser instead of behind it.
|
|
14
|
+
* - macOS: AppleScript `choose folder` run by osascript itself. Wrapping it
|
|
15
|
+
* in `tell application "System Events"` needs the Automation permission
|
|
16
|
+
* and fails (-1743) or hides the dialog when it is not granted.
|
|
17
|
+
* - Linux: zenity, then kdialog.
|
|
18
|
+
*/
|
|
19
|
+
import { execFile } from "child_process";
|
|
20
|
+
// Selected path on stdout, nothing on cancel. Paths are written as UTF-8 so
|
|
21
|
+
// non-ASCII folder names survive the console code page.
|
|
22
|
+
const WINDOWS_PICKER = String.raw `
|
|
23
|
+
$ErrorActionPreference = 'Stop'
|
|
24
|
+
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
25
|
+
$src = @"
|
|
26
|
+
using System;
|
|
27
|
+
using System.Drawing;
|
|
28
|
+
using System.Runtime.InteropServices;
|
|
29
|
+
using System.Windows.Forms;
|
|
30
|
+
public static class MelFolderPicker {
|
|
31
|
+
[ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")] class FileOpenDialogCoClass {}
|
|
32
|
+
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
33
|
+
interface IShellItem {
|
|
34
|
+
void BindToHandler(); void GetParent();
|
|
35
|
+
void GetDisplayName(uint sigdn, [MarshalAs(UnmanagedType.LPWStr)] out string name);
|
|
36
|
+
void GetAttributes(); void Compare();
|
|
37
|
+
}
|
|
38
|
+
[ComImport, Guid("D57C7288-D4AD-4768-BE02-9D969532D960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
39
|
+
interface IFileOpenDialog {
|
|
40
|
+
[PreserveSig] int Show(IntPtr owner);
|
|
41
|
+
void SetFileTypes(); void SetFileTypeIndex(); void GetFileTypeIndex(); void Advise(); void Unadvise();
|
|
42
|
+
void SetOptions(uint fos); void GetOptions(out uint fos);
|
|
43
|
+
void SetDefaultFolder(); void SetFolder(); void GetFolder(); void GetCurrentSelection();
|
|
44
|
+
void SetFileName(); void GetFileName();
|
|
45
|
+
void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string title);
|
|
46
|
+
void SetOkButtonLabel(); void SetFileNameLabel();
|
|
47
|
+
void GetResult(out IShellItem item);
|
|
48
|
+
}
|
|
49
|
+
public static string Pick(string title) {
|
|
50
|
+
var owner = new Form {
|
|
51
|
+
TopMost = true, ShowInTaskbar = false, FormBorderStyle = FormBorderStyle.None,
|
|
52
|
+
StartPosition = FormStartPosition.CenterScreen, Size = new Size(1, 1), Opacity = 0,
|
|
53
|
+
};
|
|
54
|
+
owner.Show(); owner.Activate();
|
|
55
|
+
try {
|
|
56
|
+
var dlg = (IFileOpenDialog)new FileOpenDialogCoClass();
|
|
57
|
+
uint fos; dlg.GetOptions(out fos);
|
|
58
|
+
dlg.SetOptions(fos | 0x20 | 0x40 | 0x800); // PICKFOLDERS | FORCEFILESYSTEM | PATHMUSTEXIST
|
|
59
|
+
dlg.SetTitle(title);
|
|
60
|
+
if (dlg.Show(owner.Handle) != 0) return ""; // cancelled
|
|
61
|
+
IShellItem item; dlg.GetResult(out item);
|
|
62
|
+
string path; item.GetDisplayName(0x80058000, out path); // SIGDN_FILESYSPATH
|
|
63
|
+
return path ?? "";
|
|
64
|
+
} finally { owner.Close(); }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
"@
|
|
68
|
+
Add-Type -TypeDefinition $src -ReferencedAssemblies System.Windows.Forms, System.Drawing
|
|
69
|
+
try {
|
|
70
|
+
[Console]::Out.Write([MelFolderPicker]::Pick($env:MEL_PICK_TITLE))
|
|
71
|
+
} catch {
|
|
72
|
+
# Very old shells without the COM dialog: the WinForms tree dialog.
|
|
73
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
74
|
+
$f = New-Object System.Windows.Forms.FolderBrowserDialog
|
|
75
|
+
$f.Description = $env:MEL_PICK_TITLE
|
|
76
|
+
$f.ShowNewFolderButton = $true
|
|
77
|
+
if ($f.ShowDialog() -eq 'OK') { [Console]::Out.Write($f.SelectedPath) }
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
function run(cmd, args, env, exit1IsCancel = false) {
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
execFile(cmd, args, { maxBuffer: 1024 * 64, encoding: "utf8", windowsHide: false, env: env ?? process.env }, (err, stdout, stderr) => {
|
|
83
|
+
const out = (stdout || "").trim();
|
|
84
|
+
if (err) {
|
|
85
|
+
const errMsg = (stderr || err.message || "").trim();
|
|
86
|
+
// osascript: "User canceled. (-128)"; zenity / kdialog: exit 1, no output.
|
|
87
|
+
if (!out && (/User cancel+ed|\(-128\)/i.test(errMsg) || (exit1IsCancel && err.code === 1)))
|
|
88
|
+
return resolve("");
|
|
89
|
+
return reject(new Error(errMsg || "pick failed"));
|
|
90
|
+
}
|
|
91
|
+
resolve(out);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
export async function pickNativeFolder(prompt) {
|
|
96
|
+
// Quotes and backslashes would break out of the AppleScript string literal.
|
|
97
|
+
const safePrompt = prompt.replace(/["'`$\\]/g, "");
|
|
98
|
+
let picked = "";
|
|
99
|
+
try {
|
|
100
|
+
if (process.platform === "darwin") {
|
|
101
|
+
// `activate` brings osascript's own dialog to the front; the result is a
|
|
102
|
+
// POSIX path ("/Users/me/Vault/").
|
|
103
|
+
picked = await run("osascript", [
|
|
104
|
+
"-e", "activate",
|
|
105
|
+
"-e", `POSIX path of (choose folder with prompt "${safePrompt}")`,
|
|
106
|
+
]);
|
|
107
|
+
picked = picked.replace(/\/$/, "");
|
|
108
|
+
}
|
|
109
|
+
else if (process.platform === "win32") {
|
|
110
|
+
// The title goes through the environment, never into the script text.
|
|
111
|
+
const encoded = Buffer.from(WINDOWS_PICKER, "utf16le").toString("base64");
|
|
112
|
+
picked = await run("powershell.exe", ["-NoProfile", "-NonInteractive", "-STA", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encoded], { ...process.env, MEL_PICK_TITLE: safePrompt });
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
try {
|
|
116
|
+
picked = await run("zenity", ["--file-selection", "--directory", `--title=${safePrompt}`], undefined, true);
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
if (!/ENOENT/i.test(String(e?.message)))
|
|
120
|
+
throw e;
|
|
121
|
+
picked = await run("kdialog", ["--getexistingdirectory", process.env.HOME || "/", "--title", safePrompt], undefined, true);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
const msg = String(e?.message || "pick failed");
|
|
127
|
+
let hint = "";
|
|
128
|
+
if (process.platform === "linux" && /ENOENT|not found|spawn/i.test(msg)) {
|
|
129
|
+
hint = "Install 'zenity' (apt install zenity) or type the path manually.";
|
|
130
|
+
}
|
|
131
|
+
else if (process.platform === "darwin" && /-1743|not allowed|No user interaction/i.test(msg)) {
|
|
132
|
+
hint = "macOS blocked the dialog: run the runner from a logged-in session (Terminal), or type the path manually.";
|
|
133
|
+
}
|
|
134
|
+
throw new Error(hint ? `${msg} — ${hint}` : msg);
|
|
135
|
+
}
|
|
136
|
+
if (!picked)
|
|
137
|
+
return { cancelled: true };
|
|
138
|
+
return { path: picked };
|
|
139
|
+
}
|
package/dist/localRelay.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - Per-run nonce required in the URL path to prevent injection from
|
|
10
10
|
* other processes on the same machine
|
|
11
11
|
*/
|
|
12
|
+
import http from "http";
|
|
12
13
|
import type { Socket } from "socket.io-client";
|
|
13
14
|
export interface LocalRelay {
|
|
14
15
|
port: number;
|
|
@@ -16,4 +17,10 @@ export interface LocalRelay {
|
|
|
16
17
|
close: () => void;
|
|
17
18
|
}
|
|
18
19
|
export declare function setActiveProject(project: string): void;
|
|
19
|
-
export
|
|
20
|
+
export interface LocalRelayRoutes {
|
|
21
|
+
/** POST /vaults/op (Obsidian vault tools, protocol §2). Authenticated by the
|
|
22
|
+
* vault capability in `Authorization: Vault <cap>`, not by the nonce: the
|
|
23
|
+
* handler verifies it (local device vaults) or forwards it to the server. */
|
|
24
|
+
vaultOp?: (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void> | void;
|
|
25
|
+
}
|
|
26
|
+
export declare function startLocalRelay(socket: Socket, verbose: boolean, serverUrl?: string, routes?: LocalRelayRoutes): Promise<LocalRelay>;
|
package/dist/localRelay.js
CHANGED
|
@@ -55,7 +55,7 @@ function _flushThrottle(socket, verbose) {
|
|
|
55
55
|
_throttleQueue = [];
|
|
56
56
|
_throttleTimer = null;
|
|
57
57
|
}
|
|
58
|
-
export function startLocalRelay(socket, verbose, serverUrl) {
|
|
58
|
+
export function startLocalRelay(socket, verbose, serverUrl, routes = {}) {
|
|
59
59
|
const nonce = crypto.randomUUID().replace(/-/g, "");
|
|
60
60
|
return new Promise((resolve, reject) => {
|
|
61
61
|
const expectedPath = `/events/relay/${nonce}`;
|
|
@@ -147,6 +147,26 @@ export function startLocalRelay(socket, verbose, serverUrl) {
|
|
|
147
147
|
});
|
|
148
148
|
return;
|
|
149
149
|
}
|
|
150
|
+
// ── Obsidian vault ops (docs/plans/obsidian-vault-protocol.md §2) ──
|
|
151
|
+
if (req.method === "POST" && req.url && /^\/vaults\/op\/?(\?.*)?$/.test(req.url)) {
|
|
152
|
+
if (!routes.vaultOp) {
|
|
153
|
+
res.writeHead(404);
|
|
154
|
+
res.end("Not found");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
Promise.resolve(routes.vaultOp(req, res)).catch(() => {
|
|
158
|
+
if (res.headersSent) {
|
|
159
|
+
try {
|
|
160
|
+
res.end();
|
|
161
|
+
}
|
|
162
|
+
catch { /* closed */ }
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
166
|
+
res.end('{"ok":false,"error":"relay_failed"}');
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
150
170
|
if (req.method !== "POST" || req.url !== expectedPath) {
|
|
151
171
|
res.writeHead(404);
|
|
152
172
|
res.end("Not found");
|