@psychout98/tadaima 1.0.0

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 (57) hide show
  1. package/dist/config.d.ts +23 -0
  2. package/dist/config.d.ts.map +1 -0
  3. package/dist/config.js +25 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/daemon.d.ts +8 -0
  6. package/dist/daemon.d.ts.map +1 -0
  7. package/dist/daemon.js +91 -0
  8. package/dist/daemon.js.map +1 -0
  9. package/dist/download-handler.d.ts +15 -0
  10. package/dist/download-handler.d.ts.map +1 -0
  11. package/dist/download-handler.js +203 -0
  12. package/dist/download-handler.js.map +1 -0
  13. package/dist/downloader.d.ts +11 -0
  14. package/dist/downloader.d.ts.map +1 -0
  15. package/dist/downloader.js +65 -0
  16. package/dist/downloader.js.map +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +271 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/logger.d.ts +2 -0
  22. package/dist/logger.d.ts.map +1 -0
  23. package/dist/logger.js +74 -0
  24. package/dist/logger.js.map +1 -0
  25. package/dist/organizer.d.ts +12 -0
  26. package/dist/organizer.d.ts.map +1 -0
  27. package/dist/organizer.js +42 -0
  28. package/dist/organizer.js.map +1 -0
  29. package/dist/rd-client.d.ts +25 -0
  30. package/dist/rd-client.d.ts.map +1 -0
  31. package/dist/rd-client.js +129 -0
  32. package/dist/rd-client.js.map +1 -0
  33. package/dist/service.d.ts +3 -0
  34. package/dist/service.d.ts.map +1 -0
  35. package/dist/service.js +186 -0
  36. package/dist/service.js.map +1 -0
  37. package/dist/setup.d.ts +2 -0
  38. package/dist/setup.d.ts.map +1 -0
  39. package/dist/setup.js +92 -0
  40. package/dist/setup.js.map +1 -0
  41. package/dist/status-writer.d.ts +20 -0
  42. package/dist/status-writer.d.ts.map +1 -0
  43. package/dist/status-writer.js +34 -0
  44. package/dist/status-writer.js.map +1 -0
  45. package/dist/tui.d.ts +14 -0
  46. package/dist/tui.d.ts.map +1 -0
  47. package/dist/tui.js +73 -0
  48. package/dist/tui.js.map +1 -0
  49. package/dist/updater.d.ts +27 -0
  50. package/dist/updater.d.ts.map +1 -0
  51. package/dist/updater.js +191 -0
  52. package/dist/updater.js.map +1 -0
  53. package/dist/ws-client.d.ts +26 -0
  54. package/dist/ws-client.d.ts.map +1 -0
  55. package/dist/ws-client.js +155 -0
  56. package/dist/ws-client.js.map +1 -0
  57. package/package.json +62 -0
@@ -0,0 +1,23 @@
1
+ import Conf from "conf";
2
+ export interface AgentConfig {
3
+ relay: string;
4
+ deviceToken: string;
5
+ deviceId: string;
6
+ deviceName: string;
7
+ profileName: string;
8
+ directories: {
9
+ movies: string;
10
+ tv: string;
11
+ staging: string;
12
+ };
13
+ realDebrid: {
14
+ apiKey: string;
15
+ };
16
+ maxConcurrentDownloads: number;
17
+ rdPollInterval: number;
18
+ lastUpdateCheck: string;
19
+ updateChannel: "stable";
20
+ previousBinaryPath: string;
21
+ }
22
+ export declare const config: Conf<AgentConfig>;
23
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,MAAM,EAAE,MAAM,CAAC;QACf,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,UAAU,EAAE;QACV,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,sBAAsB,EAAE,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,QAAQ,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,MAAM,mBAsBjB,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,25 @@
1
+ import Conf from "conf";
2
+ export const config = new Conf({
3
+ projectName: "tadaima",
4
+ defaults: {
5
+ relay: "",
6
+ deviceToken: "",
7
+ deviceId: "",
8
+ deviceName: "",
9
+ profileName: "",
10
+ directories: {
11
+ movies: "",
12
+ tv: "",
13
+ staging: "",
14
+ },
15
+ realDebrid: {
16
+ apiKey: "",
17
+ },
18
+ maxConcurrentDownloads: 2,
19
+ rdPollInterval: 30,
20
+ lastUpdateCheck: "",
21
+ updateChannel: "stable",
22
+ previousBinaryPath: "",
23
+ },
24
+ });
25
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAuBxB,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,IAAI,CAAc;IAC1C,WAAW,EAAE,SAAS;IACtB,QAAQ,EAAE;QACR,KAAK,EAAE,EAAE;QACT,WAAW,EAAE,EAAE;QACf,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,EAAE;QACd,WAAW,EAAE,EAAE;QACf,WAAW,EAAE;YACX,MAAM,EAAE,EAAE;YACV,EAAE,EAAE,EAAE;YACN,OAAO,EAAE,EAAE;SACZ;QACD,UAAU,EAAE;YACV,MAAM,EAAE,EAAE;SACX;QACD,sBAAsB,EAAE,CAAC;QACzB,cAAc,EAAE,EAAE;QAClB,eAAe,EAAE,EAAE;QACnB,aAAa,EAAE,QAAQ;QACvB,kBAAkB,EAAE,EAAE;KACvB;CACF,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare function getLogPath(): string;
2
+ export declare function startDaemon(): void;
3
+ export declare function stopDaemon(): void;
4
+ export declare function getDaemonStatus(): {
5
+ running: boolean;
6
+ pid?: number;
7
+ };
8
+ //# sourceMappingURL=daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAoBA,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,WAAW,IAAI,IAAI,CAsClC;AAED,wBAAgB,UAAU,IAAI,IAAI,CAuBjC;AAED,wBAAgB,eAAe,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAqBpE"}
package/dist/daemon.js ADDED
@@ -0,0 +1,91 @@
1
+ import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync, createWriteStream, } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { spawn } from "node:child_process";
4
+ import { config } from "./config.js";
5
+ function getPidPath() {
6
+ return join(dirname(config.path), "tadaima.pid");
7
+ }
8
+ function getLogDir() {
9
+ return join(dirname(config.path), "logs");
10
+ }
11
+ export function getLogPath() {
12
+ return join(getLogDir(), "tadaima.log");
13
+ }
14
+ export function startDaemon() {
15
+ const pidPath = getPidPath();
16
+ if (existsSync(pidPath)) {
17
+ const oldPid = parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
18
+ if (Number.isNaN(oldPid)) {
19
+ console.log("Stale PID file (invalid contents). Removing.");
20
+ unlinkSync(pidPath);
21
+ }
22
+ else {
23
+ try {
24
+ process.kill(oldPid, 0);
25
+ console.log(`Agent already running (PID ${oldPid})`);
26
+ return;
27
+ }
28
+ catch {
29
+ unlinkSync(pidPath);
30
+ }
31
+ }
32
+ }
33
+ const logDir = getLogDir();
34
+ mkdirSync(logDir, { recursive: true });
35
+ const logPath = getLogPath();
36
+ const logStream = createWriteStream(logPath, { flags: "a" });
37
+ const child = spawn(process.execPath, [process.argv[1], "start"], {
38
+ detached: true,
39
+ stdio: ["ignore", logStream, logStream],
40
+ env: { ...process.env, TADAIMA_DAEMON: "1" },
41
+ });
42
+ child.unref();
43
+ if (child.pid) {
44
+ writeFileSync(pidPath, String(child.pid));
45
+ console.log(`Agent started in background (PID ${child.pid})`);
46
+ console.log(`Logs: ${logPath}`);
47
+ }
48
+ }
49
+ export function stopDaemon() {
50
+ const pidPath = getPidPath();
51
+ if (!existsSync(pidPath)) {
52
+ console.log("Agent is not running.");
53
+ return;
54
+ }
55
+ const pid = parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
56
+ if (Number.isNaN(pid)) {
57
+ console.log("Stale PID file (invalid contents). Removing.");
58
+ unlinkSync(pidPath);
59
+ return;
60
+ }
61
+ try {
62
+ process.kill(pid, "SIGTERM");
63
+ console.log(`Sent SIGTERM to PID ${pid}`);
64
+ unlinkSync(pidPath);
65
+ }
66
+ catch {
67
+ console.log(`Process ${pid} not found. Cleaning up PID file.`);
68
+ unlinkSync(pidPath);
69
+ }
70
+ }
71
+ export function getDaemonStatus() {
72
+ const pidPath = getPidPath();
73
+ if (!existsSync(pidPath)) {
74
+ return { running: false };
75
+ }
76
+ const pid = parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
77
+ if (Number.isNaN(pid)) {
78
+ console.log("Stale PID file (invalid contents). Removing.");
79
+ unlinkSync(pidPath);
80
+ return { running: false };
81
+ }
82
+ try {
83
+ process.kill(pid, 0);
84
+ return { running: true, pid };
85
+ }
86
+ catch {
87
+ unlinkSync(pidPath);
88
+ return { running: false };
89
+ }
90
+ }
91
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { AgentWebSocket } from "./ws-client.js";
2
+ export declare class DownloadHandler {
3
+ private ws;
4
+ private activeJobs;
5
+ private semaphore;
6
+ constructor(ws: AgentWebSocket);
7
+ get activeCount(): number;
8
+ handleRequest(msg: Record<string, unknown>): Promise<void>;
9
+ handleCancel(msg: Record<string, unknown>): void;
10
+ handleCacheCheck(msg: Record<string, unknown>): Promise<void>;
11
+ private executeDownload;
12
+ private sendProgress;
13
+ private sendMessage;
14
+ }
15
+ //# sourceMappingURL=download-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-handler.d.ts","sourceRoot":"","sources":["../src/download-handler.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAsBrD,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAAiB;IAC3B,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,SAAS,CAAS;gBAEd,EAAE,EAAE,cAAc;IAK9B,IAAI,WAAW,IAAI,MAAM,CAExB;IAEK,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA4EhE,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAS1C,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;YAcrD,eAAe;IA0G7B,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,WAAW;CAQpB"}
@@ -0,0 +1,203 @@
1
+ import { join } from "node:path";
2
+ import { rm } from "node:fs/promises";
3
+ import { createMessageId, createTimestamp } from "@tadaima/shared";
4
+ import { config } from "./config.js";
5
+ import { rdClient } from "./rd-client.js";
6
+ import { downloadFile } from "./downloader.js";
7
+ import { organizeFile } from "./organizer.js";
8
+ export class DownloadHandler {
9
+ ws;
10
+ activeJobs = new Map();
11
+ semaphore;
12
+ constructor(ws) {
13
+ this.ws = ws;
14
+ this.semaphore = config.get("maxConcurrentDownloads");
15
+ }
16
+ get activeCount() {
17
+ return this.activeJobs.size;
18
+ }
19
+ async handleRequest(msg) {
20
+ if (typeof msg.id !== "string" || !msg.id) {
21
+ console.error("download:request missing valid msg.id, ignoring:", msg);
22
+ return;
23
+ }
24
+ if (msg.payload == null || typeof msg.payload !== "object") {
25
+ console.error("download:request missing valid msg.payload, ignoring:", msg.id);
26
+ return;
27
+ }
28
+ const payload = msg.payload;
29
+ const requestId = msg.id;
30
+ if (typeof payload.magnet !== "string" || !payload.magnet) {
31
+ console.error("download:request missing required payload.magnet, ignoring:", requestId);
32
+ return;
33
+ }
34
+ if (typeof payload.title !== "string" || !payload.title) {
35
+ console.error("download:request missing required payload.title, ignoring:", requestId);
36
+ return;
37
+ }
38
+ if (this.activeJobs.size >= this.semaphore) {
39
+ this.sendMessage("download:rejected", {
40
+ requestId,
41
+ reason: "queue_full",
42
+ });
43
+ return;
44
+ }
45
+ const jobId = createMessageId();
46
+ const abortController = new AbortController();
47
+ const meta = {
48
+ tmdbId: payload.tmdbId,
49
+ imdbId: payload.imdbId,
50
+ title: payload.title,
51
+ year: payload.year,
52
+ mediaType: payload.mediaType,
53
+ season: payload.season,
54
+ episode: payload.episode,
55
+ episodeTitle: payload.episodeTitle,
56
+ magnet: payload.magnet,
57
+ torrentName: payload.torrentName,
58
+ expectedSize: payload.expectedSize,
59
+ };
60
+ const job = {
61
+ jobId,
62
+ requestId,
63
+ abortController,
64
+ phase: "adding",
65
+ meta,
66
+ };
67
+ this.activeJobs.set(jobId, job);
68
+ this.ws.setActiveJobs(this.activeJobs.size);
69
+ this.sendMessage("download:accepted", { jobId, requestId });
70
+ try {
71
+ await this.executeDownload(job);
72
+ }
73
+ catch (err) {
74
+ const errMsg = err instanceof Error ? err.message : String(err);
75
+ const retryable = !errMsg.includes("Cancelled");
76
+ this.sendMessage("download:failed", {
77
+ jobId,
78
+ error: errMsg,
79
+ phase: job.phase,
80
+ retryable,
81
+ _meta: meta,
82
+ });
83
+ }
84
+ finally {
85
+ this.activeJobs.delete(jobId);
86
+ this.ws.setActiveJobs(this.activeJobs.size);
87
+ }
88
+ }
89
+ handleCancel(msg) {
90
+ const payload = msg.payload;
91
+ const jobId = payload.jobId;
92
+ const job = this.activeJobs.get(jobId);
93
+ if (job) {
94
+ job.abortController.abort();
95
+ }
96
+ }
97
+ async handleCacheCheck(msg) {
98
+ const payload = msg.payload;
99
+ const requestId = payload.requestId;
100
+ const infoHashes = payload.infoHashes;
101
+ try {
102
+ const cached = await rdClient.checkCache(infoHashes);
103
+ this.sendMessage("cache:result", { requestId, cached });
104
+ }
105
+ catch (err) {
106
+ console.warn("Cache check failed", err);
107
+ this.sendMessage("cache:result", { requestId, cached: {} });
108
+ }
109
+ }
110
+ async executeDownload(job) {
111
+ const { meta, abortController } = job;
112
+ const signal = abortController.signal;
113
+ // Phase: adding
114
+ job.phase = "adding";
115
+ this.sendProgress(job.jobId, "adding", 0);
116
+ console.log(`[${job.jobId}] Adding magnet to RD...`);
117
+ const { id: torrentId } = await rdClient.addMagnet(meta.magnet);
118
+ if (signal.aborted)
119
+ throw new Error("Cancelled");
120
+ await rdClient.selectFiles(torrentId);
121
+ if (signal.aborted)
122
+ throw new Error("Cancelled");
123
+ // Phase: waiting
124
+ job.phase = "waiting";
125
+ this.sendProgress(job.jobId, "waiting", 0);
126
+ console.log(`[${job.jobId}] Waiting for RD to process...`);
127
+ const links = await rdClient.pollUntilReady(torrentId, undefined, undefined, (progress) => this.sendProgress(job.jobId, "waiting", progress), signal);
128
+ // Phase: unrestricting
129
+ job.phase = "unrestricting";
130
+ this.sendProgress(job.jobId, "unrestricting", 0);
131
+ console.log(`[${job.jobId}] Unrestricting ${links.length} links...`);
132
+ const unrestricted = await rdClient.unrestrictAll(links);
133
+ if (signal.aborted)
134
+ throw new Error("Cancelled");
135
+ // Phase: downloading
136
+ job.phase = "downloading";
137
+ console.log(`[${job.jobId}] Downloading files...`);
138
+ const stagingDir = config.get("directories.staging") || "/tmp/tadaima/staging";
139
+ const downloadedFiles = [];
140
+ let totalSize = 0;
141
+ for (const file of unrestricted) {
142
+ if (signal.aborted)
143
+ throw new Error("Cancelled");
144
+ const destPath = join(stagingDir, job.jobId, file.filename);
145
+ console.log(`[${job.jobId}] Downloading: ${file.filename}`);
146
+ const size = await downloadFile(file.url, destPath, (progress) => {
147
+ const pct = progress.totalBytes > 0
148
+ ? Math.round((progress.downloadedBytes / progress.totalBytes) * 100)
149
+ : 0;
150
+ this.sendMessage("download:progress", {
151
+ jobId: job.jobId,
152
+ phase: "downloading",
153
+ progress: pct,
154
+ downloadedBytes: progress.downloadedBytes,
155
+ totalBytes: progress.totalBytes,
156
+ speedBps: progress.speedBps,
157
+ eta: progress.eta,
158
+ });
159
+ }, signal);
160
+ downloadedFiles.push(destPath);
161
+ totalSize += size;
162
+ }
163
+ // Phase: organizing
164
+ job.phase = "organizing";
165
+ this.sendProgress(job.jobId, "organizing", 0);
166
+ console.log(`[${job.jobId}] Organizing files...`);
167
+ let finalPath = "";
168
+ for (const filePath of downloadedFiles) {
169
+ finalPath = await organizeFile({
170
+ title: meta.title,
171
+ year: meta.year,
172
+ tmdbId: meta.tmdbId,
173
+ mediaType: meta.mediaType,
174
+ season: meta.season,
175
+ episode: meta.episode,
176
+ episodeTitle: meta.episodeTitle,
177
+ sourcePath: filePath,
178
+ });
179
+ }
180
+ // Clean staging
181
+ await rm(join(stagingDir, job.jobId), { recursive: true, force: true }).catch(() => { });
182
+ // Done!
183
+ console.log(`[${job.jobId}] Complete: ${finalPath}`);
184
+ this.sendMessage("download:completed", {
185
+ jobId: job.jobId,
186
+ filePath: finalPath,
187
+ finalSize: totalSize,
188
+ _meta: meta,
189
+ });
190
+ }
191
+ sendProgress(jobId, phase, progress) {
192
+ this.sendMessage("download:progress", { jobId, phase, progress });
193
+ }
194
+ sendMessage(type, payload) {
195
+ this.ws.send({
196
+ id: createMessageId(),
197
+ type,
198
+ timestamp: createTimestamp(),
199
+ payload,
200
+ });
201
+ }
202
+ }
203
+ //# sourceMappingURL=download-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-handler.js","sourceRoot":"","sources":["../src/download-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface DownloadProgress {
2
+ downloadedBytes: number;
3
+ totalBytes: number;
4
+ speedBps: number;
5
+ eta: number;
6
+ }
7
+ /**
8
+ * Download a file via HTTP with progress reporting.
9
+ */
10
+ export declare function downloadFile(url: string, destPath: string, onProgress?: (progress: DownloadProgress) => void, signal?: AbortSignal, baseDir?: string): Promise<number>;
11
+ //# sourceMappingURL=downloader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"downloader.d.ts","sourceRoot":"","sources":["../src/downloader.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,EACjD,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAqEjB"}
@@ -0,0 +1,65 @@
1
+ import { createWriteStream } from "node:fs";
2
+ import { mkdir } from "node:fs/promises";
3
+ import { dirname, resolve } from "node:path";
4
+ import { pipeline } from "node:stream/promises";
5
+ import { Readable } from "node:stream";
6
+ /**
7
+ * Download a file via HTTP with progress reporting.
8
+ */
9
+ export async function downloadFile(url, destPath, onProgress, signal, baseDir) {
10
+ if (baseDir) {
11
+ const resolvedDest = resolve(destPath);
12
+ const resolvedBase = resolve(baseDir);
13
+ if (!resolvedDest.startsWith(resolvedBase + "/") && resolvedDest !== resolvedBase) {
14
+ throw new Error(`Path traversal detected: ${destPath} escapes base directory ${baseDir}`);
15
+ }
16
+ }
17
+ await mkdir(dirname(destPath), { recursive: true });
18
+ const res = await fetch(url, { signal });
19
+ if (!res.ok)
20
+ throw new Error(`Download failed: HTTP ${res.status}`);
21
+ if (!res.body)
22
+ throw new Error("No response body");
23
+ const totalBytes = parseInt(res.headers.get("content-length") ?? "0", 10);
24
+ let downloadedBytes = 0;
25
+ let lastReport = Date.now();
26
+ const startTime = Date.now();
27
+ const reader = res.body.getReader();
28
+ const fileStream = createWriteStream(destPath);
29
+ const readable = new Readable({
30
+ async read() {
31
+ try {
32
+ const { done, value } = await reader.read();
33
+ if (done) {
34
+ this.push(null);
35
+ return;
36
+ }
37
+ downloadedBytes += value.byteLength;
38
+ // Throttle progress to 1/sec
39
+ const now = Date.now();
40
+ if (now - lastReport >= 1000 && onProgress) {
41
+ const elapsed = (now - startTime) / 1000;
42
+ const speedBps = elapsed > 0 ? Math.round(downloadedBytes / elapsed) : 0;
43
+ const remaining = speedBps > 0 ? (totalBytes - downloadedBytes) / speedBps : 0;
44
+ onProgress({
45
+ downloadedBytes,
46
+ totalBytes,
47
+ speedBps,
48
+ eta: Math.round(remaining),
49
+ });
50
+ lastReport = now;
51
+ }
52
+ this.push(Buffer.from(value));
53
+ }
54
+ catch (err) {
55
+ this.destroy(err);
56
+ }
57
+ },
58
+ });
59
+ await pipeline(readable, fileStream);
60
+ if (totalBytes > 0 && downloadedBytes !== totalBytes) {
61
+ throw new Error(`Download incomplete: expected ${totalBytes} bytes but got ${downloadedBytes}`);
62
+ }
63
+ return downloadedBytes;
64
+ }
65
+ //# sourceMappingURL=downloader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"downloader.js","sourceRoot":"","sources":["../src/downloader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AASvC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAAW,EACX,QAAgB,EAChB,UAAiD,EACjD,MAAoB,EACpB,OAAgB;IAEhB,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,YAAY,GAAG,GAAG,CAAC,IAAI,YAAY,KAAK,YAAY,EAAE,CAAC;YAClF,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,2BAA2B,OAAO,EAAE,CAAC,CAAC;QAC5F,CAAC;IACH,CAAC;IAED,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEpD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACzC,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACpE,IAAI,CAAC,GAAG,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAEnD,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;IAC1E,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,IAAI,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;IACpC,MAAM,UAAU,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAE/C,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC;QAC5B,KAAK,CAAC,IAAI;YACR,IAAI,CAAC;gBACH,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC5C,IAAI,IAAI,EAAE,CAAC;oBACT,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBAChB,OAAO;gBACT,CAAC;gBAED,eAAe,IAAI,KAAK,CAAC,UAAU,CAAC;gBAEpC,6BAA6B;gBAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACvB,IAAI,GAAG,GAAG,UAAU,IAAI,IAAI,IAAI,UAAU,EAAE,CAAC;oBAC3C,MAAM,OAAO,GAAG,CAAC,GAAG,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;oBACzC,MAAM,QAAQ,GACZ,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC1D,MAAM,SAAS,GACb,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,eAAe,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;oBAE/D,UAAU,CAAC;wBACT,eAAe;wBACf,UAAU;wBACV,QAAQ;wBACR,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;qBAC3B,CAAC,CAAC;oBAEH,UAAU,GAAG,GAAG,CAAC;gBACnB,CAAC;gBAED,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YAChC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,OAAO,CAAC,GAAY,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;KACF,CAAC,CAAC;IAEH,MAAM,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAErC,IAAI,UAAU,GAAG,CAAC,IAAI,eAAe,KAAK,UAAU,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CACb,iCAAiC,UAAU,kBAAkB,eAAe,EAAE,CAC/E,CAAC;IACJ,CAAC;IAED,OAAO,eAAe,CAAC;AACzB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}