replicate-predictions-downloader 2.0.1 ā 2.0.3
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 +24 -76
- package/example.js +5 -5
- package/index.js +101 -11
- package/package.json +12 -5
package/README.md
CHANGED
|
@@ -17,19 +17,31 @@ A Node.js script to download and organize all your Replicate predictions, includ
|
|
|
17
17
|
|
|
18
18
|
## Features
|
|
19
19
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
20
|
+
- Downloads all predictions from your Replicate account
|
|
21
|
+
- Organizes files by model and date
|
|
22
|
+
- Date filtering to avoid duplicate downloads
|
|
23
|
+
- Incremental downloads since last successful run
|
|
24
|
+
- Creates ZIP archives for each model (optional)
|
|
25
|
+
- Saves enhanced metadata for all predictions
|
|
26
|
+
- Shows detailed download statistics
|
|
27
|
+
- Preserves predictions before they expire
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx replicate-predictions-downloader --last-run
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
You'll need your API token from [replicate.com/account/api-tokens](https://replicate.com/account/api-tokens) in the environment as `REPLICATE_API_TOKEN`.
|
|
28
36
|
|
|
29
37
|
## Why Use This Tool?
|
|
30
38
|
|
|
31
39
|
Replicate only stores your predictions temporarily. This tool helps you preserve your valuable work before it disappears, with intelligent organization to make your outputs easy to find later.
|
|
32
40
|
|
|
41
|
+
## Background
|
|
42
|
+
|
|
43
|
+
Replicate deletes predictions after 30 days. If you've been experimenting with models and want to keep your outputs - for portfolios, research, or just because you like them - you need to download them yourself. This tool does that, with some organization thrown in so you can actually find things later.
|
|
44
|
+
|
|
33
45
|
## Setup
|
|
34
46
|
|
|
35
47
|
### Prerequisites
|
|
@@ -88,6 +100,10 @@ node index.js
|
|
|
88
100
|
replicate-downloader
|
|
89
101
|
```
|
|
90
102
|
|
|
103
|
+
## What You'll See
|
|
104
|
+
|
|
105
|
+
When you run the tool, you'll see real-time progress as it fetches and downloads your predictions. The output includes details about what's being processed, download progress, and final statistics. Your files end up organized by model and date. There's also a metadata JSON if you're into that sort of thing.
|
|
106
|
+
|
|
91
107
|
### Date Filtering Options
|
|
92
108
|
|
|
93
109
|
Avoid duplicate downloads with intelligent date filtering:
|
|
@@ -202,74 +218,6 @@ The included .gitignore will help prevent this.
|
|
|
202
218
|
|
|
203
219
|
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.
|
|
204
220
|
|
|
205
|
-
## Background
|
|
206
|
-
|
|
207
|
-
Replicate is a powerful platform for running AI models, but predictions don't last forever - they expire after a period of time. This tool was originally created while working on LLM behavior/personality research when I realized I was losing valuable generated content.
|
|
208
|
-
|
|
209
|
-
After generating hundreds of images with Stable Diffusion, video clips, and other AI outputs, I needed a way to systematically download and organize everything before it disappeared. The tool has since evolved into a comprehensive solution for anyone who wants to preserve their Replicate predictions with intelligent organization and duplicate prevention.
|
|
210
|
-
|
|
211
|
-
## Quick Start
|
|
212
|
-
|
|
213
|
-
Get up and running in 2 minutes:
|
|
214
|
-
|
|
215
|
-
1. **Get your API token**: Visit [replicate.com/account/api-tokens](https://replicate.com/account/api-tokens)
|
|
216
|
-
|
|
217
|
-
2. **Set your token**:
|
|
218
|
-
```bash
|
|
219
|
-
export REPLICATE_API_TOKEN=your_token_here
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
3. **Install and run**:
|
|
223
|
-
```bash
|
|
224
|
-
# Clone and install
|
|
225
|
-
git clone https://github.com/closestfriend/replicate-predictions-downloader.git
|
|
226
|
-
cd replicate-predictions-downloader
|
|
227
|
-
npm install
|
|
228
|
-
|
|
229
|
-
# Download all your predictions
|
|
230
|
-
npm start
|
|
231
|
-
|
|
232
|
-
# Or just new ones since last run (recommended for regular use)
|
|
233
|
-
node index.js --last-run
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
That's it! Your predictions will be downloaded and organized by model and date.
|
|
237
|
-
|
|
238
|
-
## What You'll See
|
|
239
|
-
|
|
240
|
-
When you run the downloader, here's what happens:
|
|
241
|
-
|
|
242
|
-
```
|
|
243
|
-
Starting Replicate Predictions Downloader v2.0.0
|
|
244
|
-
Using date filter: since 2024-01-01
|
|
245
|
-
Fetching predictions from Replicate API...
|
|
246
|
-
Found 47 predictions to process
|
|
247
|
-
|
|
248
|
-
PROCESSING STATISTICS:
|
|
249
|
-
āāā Total predictions: 47
|
|
250
|
-
āāā Successful: 43 (91.5%)
|
|
251
|
-
āāā Failed: 4 (8.5%)
|
|
252
|
-
āāā Models found: 8
|
|
253
|
-
|
|
254
|
-
DOWNLOADING OUTPUTS:
|
|
255
|
-
āāā stable-diffusion-xl: 25 images
|
|
256
|
-
āāā midjourney-v6: 12 images
|
|
257
|
-
āāā whisper-large-v3: 8 audio files
|
|
258
|
-
āāā llama-2-70b-chat: 6 text files
|
|
259
|
-
|
|
260
|
-
SAVING TO: replicate_outputs_2024-01-15/
|
|
261
|
-
āāā by-model/
|
|
262
|
-
ā āāā stable-diffusion-xl/
|
|
263
|
-
ā āāā midjourney-v6/
|
|
264
|
-
ā āāā ...
|
|
265
|
-
āāā stable-diffusion-xl.zip
|
|
266
|
-
āāā midjourney-v6.zip
|
|
267
|
-
āāā replicate_metadata_2024-01-15.json
|
|
268
|
-
|
|
269
|
-
Complete! Downloaded 51 files (127.3 MB)
|
|
270
|
-
Elapsed time: 2m 34s
|
|
271
|
-
Next time, use --last-run to download only new predictions
|
|
272
|
-
```
|
|
273
221
|
|
|
274
222
|
## Common Issues
|
|
275
223
|
|
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
|
@@ -66,17 +66,54 @@ class ReplicateDownloader {
|
|
|
66
66
|
// Parse date string to ISO format
|
|
67
67
|
parseDate(dateStr) {
|
|
68
68
|
if (!dateStr) return null;
|
|
69
|
-
|
|
70
|
-
//
|
|
69
|
+
|
|
70
|
+
// Support common relative phrases
|
|
71
|
+
const input = String(dateStr).trim().toLowerCase();
|
|
72
|
+
const nowMs = Date.now();
|
|
73
|
+
|
|
74
|
+
// Yesterday
|
|
75
|
+
if (input === 'yesterday') {
|
|
76
|
+
return new Date(nowMs - 24 * 60 * 60 * 1000).toISOString();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// "N <unit> ago" where unit ā minutes/hours/days/weeks/months/years
|
|
80
|
+
const relMatch = input.match(/^(\d+)\s*(minute|minutes|hour|hours|day|days|week|weeks|month|months|year|years)\s+ago$/i);
|
|
81
|
+
if (relMatch) {
|
|
82
|
+
const amount = parseInt(relMatch[1], 10);
|
|
83
|
+
const unit = relMatch[2];
|
|
84
|
+
|
|
85
|
+
const unitToMs = {
|
|
86
|
+
minute: 60 * 1000,
|
|
87
|
+
minutes: 60 * 1000,
|
|
88
|
+
hour: 60 * 60 * 1000,
|
|
89
|
+
hours: 60 * 60 * 1000,
|
|
90
|
+
day: 24 * 60 * 60 * 1000,
|
|
91
|
+
days: 24 * 60 * 60 * 1000,
|
|
92
|
+
week: 7 * 24 * 60 * 60 * 1000,
|
|
93
|
+
weeks: 7 * 24 * 60 * 60 * 1000,
|
|
94
|
+
// Approximations for broader ranges
|
|
95
|
+
month: 30 * 24 * 60 * 60 * 1000,
|
|
96
|
+
months: 30 * 24 * 60 * 60 * 1000,
|
|
97
|
+
year: 365 * 24 * 60 * 60 * 1000,
|
|
98
|
+
years: 365 * 24 * 60 * 60 * 1000
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const deltaMs = amount * (unitToMs[unit] || 0);
|
|
102
|
+
if (deltaMs > 0) {
|
|
103
|
+
return new Date(nowMs - deltaMs).toISOString();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Fallback to native parser (ISO, RFC, etc.)
|
|
71
108
|
const date = new Date(dateStr);
|
|
72
109
|
if (isNaN(date.getTime())) {
|
|
73
110
|
throw new Error(`Invalid date format: ${dateStr}. Use formats like "2024-01-15", "2024-01-15T10:30:00Z", or "2 days ago"`);
|
|
74
111
|
}
|
|
75
|
-
|
|
112
|
+
|
|
76
113
|
return date.toISOString();
|
|
77
114
|
}
|
|
78
115
|
|
|
79
|
-
// Get date filter parameters for API
|
|
116
|
+
// Get date filter parameters for API (server may ignore these)
|
|
80
117
|
getDateFilterParams() {
|
|
81
118
|
const params = new URLSearchParams();
|
|
82
119
|
|
|
@@ -108,6 +145,28 @@ class ReplicateDownloader {
|
|
|
108
145
|
return params.toString();
|
|
109
146
|
}
|
|
110
147
|
|
|
148
|
+
// Compute client-side date bounds for filtering
|
|
149
|
+
getDateFilterBounds() {
|
|
150
|
+
let sinceDate = this.dateFilters.since;
|
|
151
|
+
if (this.dateFilters.lastRun) {
|
|
152
|
+
const state = this.loadState();
|
|
153
|
+
if (state.lastSuccessfulRun) {
|
|
154
|
+
sinceDate = state.lastSuccessfulRun;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
let sinceIsoBuffer = null;
|
|
158
|
+
if (sinceDate) {
|
|
159
|
+
const parsedSince = this.parseDate(sinceDate);
|
|
160
|
+
const sinceBuffer = new Date(new Date(parsedSince).getTime() - 1000);
|
|
161
|
+
sinceIsoBuffer = sinceBuffer.toISOString();
|
|
162
|
+
}
|
|
163
|
+
let untilIso = null;
|
|
164
|
+
if (this.dateFilters.until) {
|
|
165
|
+
untilIso = this.parseDate(this.dateFilters.until);
|
|
166
|
+
}
|
|
167
|
+
return { sinceIsoBuffer, untilIso };
|
|
168
|
+
}
|
|
169
|
+
|
|
111
170
|
// Make authenticated request to Replicate API
|
|
112
171
|
async makeRequest(url) {
|
|
113
172
|
return new Promise((resolve, reject) => {
|
|
@@ -172,7 +231,7 @@ class ReplicateDownloader {
|
|
|
172
231
|
}
|
|
173
232
|
|
|
174
233
|
// Try to find any string input that looks like a prompt
|
|
175
|
-
for (const [
|
|
234
|
+
for (const [_key, value] of Object.entries(input)) {
|
|
176
235
|
if (typeof value === 'string' && value.length > 5 && value.length < 200) {
|
|
177
236
|
return this.sanitizeFilename(value);
|
|
178
237
|
}
|
|
@@ -219,11 +278,17 @@ class ReplicateDownloader {
|
|
|
219
278
|
let nextUrl = `${this.baseUrl}/predictions`;
|
|
220
279
|
let page = 1;
|
|
221
280
|
|
|
222
|
-
// Apply date filtering
|
|
281
|
+
// Apply date filtering (server may ignore these; we also filter client-side)
|
|
223
282
|
const dateParams = this.getDateFilterParams();
|
|
224
283
|
if (dateParams) {
|
|
225
284
|
nextUrl += `?${dateParams}`;
|
|
226
|
-
console.log('Applying date filters:', dateParams);
|
|
285
|
+
console.log('Applying date filters (server or client-side):', dateParams);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Client-side bounds
|
|
289
|
+
const { sinceIsoBuffer, untilIso } = this.getDateFilterBounds();
|
|
290
|
+
if (sinceIsoBuffer || untilIso) {
|
|
291
|
+
console.log('Client-side date bounds:', { sinceIsoBuffer, untilIso });
|
|
227
292
|
}
|
|
228
293
|
|
|
229
294
|
console.log('Starting to fetch predictions...');
|
|
@@ -235,9 +300,19 @@ class ReplicateDownloader {
|
|
|
235
300
|
const response = await this.makeRequest(nextUrl);
|
|
236
301
|
|
|
237
302
|
if (response.results) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
console.log(`
|
|
303
|
+
const pageResults = response.results;
|
|
304
|
+
this.allPredictions.push(...pageResults);
|
|
305
|
+
console.log(`Found ${pageResults.length} predictions on page ${page}`);
|
|
306
|
+
console.log(`Total so far (pre-filter): ${this.allPredictions.length}`);
|
|
307
|
+
|
|
308
|
+
// Heuristic early stop: if all items are older than since bound, stop paginating
|
|
309
|
+
if (sinceIsoBuffer && pageResults.length > 0) {
|
|
310
|
+
const allOlder = pageResults.every(p => p.created_at && p.created_at < sinceIsoBuffer);
|
|
311
|
+
if (allOlder) {
|
|
312
|
+
console.log('Reached items older than since bound; stopping pagination early');
|
|
313
|
+
nextUrl = null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
241
316
|
}
|
|
242
317
|
|
|
243
318
|
// Handle pagination with date filters
|
|
@@ -264,7 +339,22 @@ class ReplicateDownloader {
|
|
|
264
339
|
}
|
|
265
340
|
}
|
|
266
341
|
|
|
267
|
-
|
|
342
|
+
// Final client-side filter within bounds
|
|
343
|
+
if (this.allPredictions.length > 0) {
|
|
344
|
+
const before = this.allPredictions.length;
|
|
345
|
+
this.allPredictions = this.allPredictions.filter(p => {
|
|
346
|
+
const created = p.created_at;
|
|
347
|
+
if (!created) return true;
|
|
348
|
+
if (sinceIsoBuffer && created < sinceIsoBuffer) return false;
|
|
349
|
+
if (untilIso && created > untilIso) return false;
|
|
350
|
+
return true;
|
|
351
|
+
});
|
|
352
|
+
if (sinceIsoBuffer || untilIso) {
|
|
353
|
+
console.log(`Applied client-side date filter: ${before} -> ${this.allPredictions.length}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
console.log(`\nTotal predictions fetched (post-filter): ${this.allPredictions.length}`);
|
|
268
358
|
return this.allPredictions;
|
|
269
359
|
}
|
|
270
360
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "replicate-predictions-downloader",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
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
7
|
"scripts": {
|
|
8
8
|
"start": "node index.js",
|
|
9
9
|
"download": "node index.js",
|
|
10
|
-
"example": "node example.js"
|
|
10
|
+
"example": "node example.js",
|
|
11
|
+
"lint": "eslint .",
|
|
12
|
+
"lint:fix": "eslint . --fix"
|
|
11
13
|
},
|
|
12
14
|
"keywords": [
|
|
13
15
|
"replicate",
|
|
@@ -41,13 +43,18 @@
|
|
|
41
43
|
},
|
|
42
44
|
"dependencies": {
|
|
43
45
|
"archiver": "^5.3.1",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
+
"commander": "^11.0.0",
|
|
47
|
+
"dotenv": "^16.3.1"
|
|
46
48
|
},
|
|
47
49
|
"files": [
|
|
48
50
|
"index.js",
|
|
49
51
|
"example.js",
|
|
50
52
|
"README.md",
|
|
51
53
|
"LICENSE.md"
|
|
52
|
-
]
|
|
54
|
+
],
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@eslint/js": "^9.35.0",
|
|
57
|
+
"eslint": "^9.13.0",
|
|
58
|
+
"globals": "^16.3.0"
|
|
59
|
+
}
|
|
53
60
|
}
|