replicate-predictions-downloader 2.0.2 ā 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -12
- package/example.js +5 -5
- package/index.js +102 -11
- package/package.json +16 -5
package/README.md
CHANGED
|
@@ -29,6 +29,10 @@ A Node.js script to download and organize all your Replicate predictions, includ
|
|
|
29
29
|
## Quick Start
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
+
# First time - download all predictions
|
|
33
|
+
npx replicate-predictions-downloader
|
|
34
|
+
|
|
35
|
+
# Subsequent runs - download only new predictions since last run (optional)
|
|
32
36
|
npx replicate-predictions-downloader --last-run
|
|
33
37
|
```
|
|
34
38
|
|
|
@@ -51,7 +55,31 @@ Replicate deletes predictions after 30 days. If you've been experimenting with m
|
|
|
51
55
|
|
|
52
56
|
### Installation
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
Choose the method that best fits your needs:
|
|
59
|
+
|
|
60
|
+
#### Option 1: Quick one-time use (no installation needed)
|
|
61
|
+
|
|
62
|
+
Perfect for trying out the tool or occasional use. Downloads and runs the latest version directly:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx replicate-predictions-downloader
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### Option 2: Install globally for repeated use
|
|
69
|
+
|
|
70
|
+
Best if you'll be using this tool regularly. Installs a permanent command on your system:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Install globally
|
|
74
|
+
npm install -g replicate-predictions-downloader
|
|
75
|
+
|
|
76
|
+
# Then run anytime with:
|
|
77
|
+
replicate-downloader
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### Option 3: Clone and customize
|
|
81
|
+
|
|
82
|
+
For developers who want to modify the code or contribute to the project:
|
|
55
83
|
|
|
56
84
|
```bash
|
|
57
85
|
# Clone the repository
|
|
@@ -60,12 +88,11 @@ cd replicate-predictions-downloader
|
|
|
60
88
|
|
|
61
89
|
# Install dependencies
|
|
62
90
|
npm install
|
|
63
|
-
```
|
|
64
91
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
92
|
+
# Run the tool
|
|
93
|
+
npm start
|
|
94
|
+
# or
|
|
95
|
+
node index.js
|
|
69
96
|
```
|
|
70
97
|
|
|
71
98
|
### API Token Setup
|
|
@@ -88,16 +115,19 @@ Set your API token using one of these methods:
|
|
|
88
115
|
|
|
89
116
|
### Basic Usage
|
|
90
117
|
|
|
91
|
-
Run the
|
|
92
|
-
```bash
|
|
93
|
-
# If installed locally
|
|
94
|
-
npm start
|
|
118
|
+
Run the tool based on how you installed it:
|
|
95
119
|
|
|
96
|
-
|
|
97
|
-
|
|
120
|
+
```bash
|
|
121
|
+
# If using npx (no installation)
|
|
122
|
+
npx replicate-predictions-downloader
|
|
98
123
|
|
|
99
124
|
# If installed globally
|
|
100
125
|
replicate-downloader
|
|
126
|
+
|
|
127
|
+
# If cloned locally
|
|
128
|
+
npm start
|
|
129
|
+
# or
|
|
130
|
+
node index.js
|
|
101
131
|
```
|
|
102
132
|
|
|
103
133
|
## What You'll See
|
package/example.js
CHANGED
|
@@ -17,19 +17,19 @@ async function example() {
|
|
|
17
17
|
|
|
18
18
|
// Example 1: Download all predictions (default behavior)
|
|
19
19
|
console.log('\nš„ Example 1: Download all predictions');
|
|
20
|
-
const
|
|
20
|
+
const _downloader1 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN);
|
|
21
21
|
// await downloader1.run(); // Uncomment to run
|
|
22
22
|
|
|
23
23
|
// Example 2: Download predictions since a specific date
|
|
24
24
|
console.log('\nš
Example 2: Download predictions since 2024-01-01');
|
|
25
|
-
const
|
|
25
|
+
const _downloader2 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
|
|
26
26
|
since: '2024-01-01'
|
|
27
27
|
});
|
|
28
28
|
// await downloader2.run(); // Uncomment to run
|
|
29
29
|
|
|
30
30
|
// Example 3: Download predictions in a date range
|
|
31
31
|
console.log('\nš
Example 3: Download predictions between 2024-01-01 and 2024-01-31');
|
|
32
|
-
const
|
|
32
|
+
const _downloader3 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
|
|
33
33
|
since: '2024-01-01',
|
|
34
34
|
until: '2024-01-31'
|
|
35
35
|
});
|
|
@@ -37,7 +37,7 @@ async function example() {
|
|
|
37
37
|
|
|
38
38
|
// Example 4: Incremental download (since last run)
|
|
39
39
|
console.log('\nš Example 4: Incremental download since last successful run');
|
|
40
|
-
const
|
|
40
|
+
const _downloader4 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
|
|
41
41
|
lastRun: true
|
|
42
42
|
});
|
|
43
43
|
// await downloader4.run(); // Uncomment to run
|
|
@@ -46,7 +46,7 @@ async function example() {
|
|
|
46
46
|
console.log('\nš
Example 5: Download predictions from the last 7 days');
|
|
47
47
|
const sevenDaysAgo = new Date();
|
|
48
48
|
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
49
|
-
const
|
|
49
|
+
const _downloader5 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
|
|
50
50
|
since: sevenDaysAgo.toISOString()
|
|
51
51
|
});
|
|
52
52
|
// await downloader5.run(); // Uncomment to run
|
package/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import fs from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import https from 'https';
|
|
@@ -66,17 +67,54 @@ class ReplicateDownloader {
|
|
|
66
67
|
// Parse date string to ISO format
|
|
67
68
|
parseDate(dateStr) {
|
|
68
69
|
if (!dateStr) return null;
|
|
69
|
-
|
|
70
|
-
//
|
|
70
|
+
|
|
71
|
+
// Support common relative phrases
|
|
72
|
+
const input = String(dateStr).trim().toLowerCase();
|
|
73
|
+
const nowMs = Date.now();
|
|
74
|
+
|
|
75
|
+
// Yesterday
|
|
76
|
+
if (input === 'yesterday') {
|
|
77
|
+
return new Date(nowMs - 24 * 60 * 60 * 1000).toISOString();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// "N <unit> ago" where unit ā minutes/hours/days/weeks/months/years
|
|
81
|
+
const relMatch = input.match(/^(\d+)\s*(minute|minutes|hour|hours|day|days|week|weeks|month|months|year|years)\s+ago$/i);
|
|
82
|
+
if (relMatch) {
|
|
83
|
+
const amount = parseInt(relMatch[1], 10);
|
|
84
|
+
const unit = relMatch[2];
|
|
85
|
+
|
|
86
|
+
const unitToMs = {
|
|
87
|
+
minute: 60 * 1000,
|
|
88
|
+
minutes: 60 * 1000,
|
|
89
|
+
hour: 60 * 60 * 1000,
|
|
90
|
+
hours: 60 * 60 * 1000,
|
|
91
|
+
day: 24 * 60 * 60 * 1000,
|
|
92
|
+
days: 24 * 60 * 60 * 1000,
|
|
93
|
+
week: 7 * 24 * 60 * 60 * 1000,
|
|
94
|
+
weeks: 7 * 24 * 60 * 60 * 1000,
|
|
95
|
+
// Approximations for broader ranges
|
|
96
|
+
month: 30 * 24 * 60 * 60 * 1000,
|
|
97
|
+
months: 30 * 24 * 60 * 60 * 1000,
|
|
98
|
+
year: 365 * 24 * 60 * 60 * 1000,
|
|
99
|
+
years: 365 * 24 * 60 * 60 * 1000
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const deltaMs = amount * (unitToMs[unit] || 0);
|
|
103
|
+
if (deltaMs > 0) {
|
|
104
|
+
return new Date(nowMs - deltaMs).toISOString();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Fallback to native parser (ISO, RFC, etc.)
|
|
71
109
|
const date = new Date(dateStr);
|
|
72
110
|
if (isNaN(date.getTime())) {
|
|
73
111
|
throw new Error(`Invalid date format: ${dateStr}. Use formats like "2024-01-15", "2024-01-15T10:30:00Z", or "2 days ago"`);
|
|
74
112
|
}
|
|
75
|
-
|
|
113
|
+
|
|
76
114
|
return date.toISOString();
|
|
77
115
|
}
|
|
78
116
|
|
|
79
|
-
// Get date filter parameters for API
|
|
117
|
+
// Get date filter parameters for API (server may ignore these)
|
|
80
118
|
getDateFilterParams() {
|
|
81
119
|
const params = new URLSearchParams();
|
|
82
120
|
|
|
@@ -108,6 +146,28 @@ class ReplicateDownloader {
|
|
|
108
146
|
return params.toString();
|
|
109
147
|
}
|
|
110
148
|
|
|
149
|
+
// Compute client-side date bounds for filtering
|
|
150
|
+
getDateFilterBounds() {
|
|
151
|
+
let sinceDate = this.dateFilters.since;
|
|
152
|
+
if (this.dateFilters.lastRun) {
|
|
153
|
+
const state = this.loadState();
|
|
154
|
+
if (state.lastSuccessfulRun) {
|
|
155
|
+
sinceDate = state.lastSuccessfulRun;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
let sinceIsoBuffer = null;
|
|
159
|
+
if (sinceDate) {
|
|
160
|
+
const parsedSince = this.parseDate(sinceDate);
|
|
161
|
+
const sinceBuffer = new Date(new Date(parsedSince).getTime() - 1000);
|
|
162
|
+
sinceIsoBuffer = sinceBuffer.toISOString();
|
|
163
|
+
}
|
|
164
|
+
let untilIso = null;
|
|
165
|
+
if (this.dateFilters.until) {
|
|
166
|
+
untilIso = this.parseDate(this.dateFilters.until);
|
|
167
|
+
}
|
|
168
|
+
return { sinceIsoBuffer, untilIso };
|
|
169
|
+
}
|
|
170
|
+
|
|
111
171
|
// Make authenticated request to Replicate API
|
|
112
172
|
async makeRequest(url) {
|
|
113
173
|
return new Promise((resolve, reject) => {
|
|
@@ -172,7 +232,7 @@ class ReplicateDownloader {
|
|
|
172
232
|
}
|
|
173
233
|
|
|
174
234
|
// Try to find any string input that looks like a prompt
|
|
175
|
-
for (const [
|
|
235
|
+
for (const [_key, value] of Object.entries(input)) {
|
|
176
236
|
if (typeof value === 'string' && value.length > 5 && value.length < 200) {
|
|
177
237
|
return this.sanitizeFilename(value);
|
|
178
238
|
}
|
|
@@ -219,11 +279,17 @@ class ReplicateDownloader {
|
|
|
219
279
|
let nextUrl = `${this.baseUrl}/predictions`;
|
|
220
280
|
let page = 1;
|
|
221
281
|
|
|
222
|
-
// Apply date filtering
|
|
282
|
+
// Apply date filtering (server may ignore these; we also filter client-side)
|
|
223
283
|
const dateParams = this.getDateFilterParams();
|
|
224
284
|
if (dateParams) {
|
|
225
285
|
nextUrl += `?${dateParams}`;
|
|
226
|
-
console.log('Applying date filters:', dateParams);
|
|
286
|
+
console.log('Applying date filters (server or client-side):', dateParams);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Client-side bounds
|
|
290
|
+
const { sinceIsoBuffer, untilIso } = this.getDateFilterBounds();
|
|
291
|
+
if (sinceIsoBuffer || untilIso) {
|
|
292
|
+
console.log('Client-side date bounds:', { sinceIsoBuffer, untilIso });
|
|
227
293
|
}
|
|
228
294
|
|
|
229
295
|
console.log('Starting to fetch predictions...');
|
|
@@ -235,9 +301,19 @@ class ReplicateDownloader {
|
|
|
235
301
|
const response = await this.makeRequest(nextUrl);
|
|
236
302
|
|
|
237
303
|
if (response.results) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
console.log(`
|
|
304
|
+
const pageResults = response.results;
|
|
305
|
+
this.allPredictions.push(...pageResults);
|
|
306
|
+
console.log(`Found ${pageResults.length} predictions on page ${page}`);
|
|
307
|
+
console.log(`Total so far (pre-filter): ${this.allPredictions.length}`);
|
|
308
|
+
|
|
309
|
+
// Heuristic early stop: if all items are older than since bound, stop paginating
|
|
310
|
+
if (sinceIsoBuffer && pageResults.length > 0) {
|
|
311
|
+
const allOlder = pageResults.every(p => p.created_at && p.created_at < sinceIsoBuffer);
|
|
312
|
+
if (allOlder) {
|
|
313
|
+
console.log('Reached items older than since bound; stopping pagination early');
|
|
314
|
+
nextUrl = null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
241
317
|
}
|
|
242
318
|
|
|
243
319
|
// Handle pagination with date filters
|
|
@@ -264,7 +340,22 @@ class ReplicateDownloader {
|
|
|
264
340
|
}
|
|
265
341
|
}
|
|
266
342
|
|
|
267
|
-
|
|
343
|
+
// Final client-side filter within bounds
|
|
344
|
+
if (this.allPredictions.length > 0) {
|
|
345
|
+
const before = this.allPredictions.length;
|
|
346
|
+
this.allPredictions = this.allPredictions.filter(p => {
|
|
347
|
+
const created = p.created_at;
|
|
348
|
+
if (!created) return true;
|
|
349
|
+
if (sinceIsoBuffer && created < sinceIsoBuffer) return false;
|
|
350
|
+
if (untilIso && created > untilIso) return false;
|
|
351
|
+
return true;
|
|
352
|
+
});
|
|
353
|
+
if (sinceIsoBuffer || untilIso) {
|
|
354
|
+
console.log(`Applied client-side date filter: ${before} -> ${this.allPredictions.length}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
console.log(`\nTotal predictions fetched (post-filter): ${this.allPredictions.length}`);
|
|
268
359
|
return this.allPredictions;
|
|
269
360
|
}
|
|
270
361
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "replicate-predictions-downloader",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "A Node.js script to download and organize all your Replicate predictions, including images, metadata, and other outputs",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"replicate-downloader": "./index.js",
|
|
9
|
+
"replicate-predictions-downloader": "./index.js"
|
|
10
|
+
},
|
|
7
11
|
"scripts": {
|
|
8
12
|
"start": "node index.js",
|
|
9
13
|
"download": "node index.js",
|
|
10
|
-
"example": "node example.js"
|
|
14
|
+
"example": "node example.js",
|
|
15
|
+
"lint": "eslint .",
|
|
16
|
+
"lint:fix": "eslint . --fix"
|
|
11
17
|
},
|
|
12
18
|
"keywords": [
|
|
13
19
|
"replicate",
|
|
@@ -41,13 +47,18 @@
|
|
|
41
47
|
},
|
|
42
48
|
"dependencies": {
|
|
43
49
|
"archiver": "^5.3.1",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
50
|
+
"commander": "^11.0.0",
|
|
51
|
+
"dotenv": "^16.3.1"
|
|
46
52
|
},
|
|
47
53
|
"files": [
|
|
48
54
|
"index.js",
|
|
49
55
|
"example.js",
|
|
50
56
|
"README.md",
|
|
51
57
|
"LICENSE.md"
|
|
52
|
-
]
|
|
58
|
+
],
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@eslint/js": "^9.35.0",
|
|
61
|
+
"eslint": "^9.13.0",
|
|
62
|
+
"globals": "^16.3.0"
|
|
63
|
+
}
|
|
53
64
|
}
|