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.
Files changed (4) hide show
  1. package/README.md +24 -76
  2. package/example.js +5 -5
  3. package/index.js +101 -11
  4. 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
- - šŸ“„ Downloads all predictions from your Replicate account
21
- - šŸ—‚ļø Organizes files by model and date
22
- - šŸ“… **NEW: Date filtering** to avoid duplicate downloads
23
- - šŸ”„ **NEW: 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
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 downloader1 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN);
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 downloader2 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
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 downloader3 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
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 downloader4 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
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 downloader5 = new ReplicateDownloader(process.env.REPLICATE_API_TOKEN, {
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
- // Handle various date formats
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 [key, value] of Object.entries(input)) {
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
- this.allPredictions.push(...response.results);
239
- console.log(`Found ${response.results.length} predictions on page ${page}`);
240
- console.log(`Total so far: ${this.allPredictions.length}`);
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
- console.log(`\nTotal predictions fetched: ${this.allPredictions.length}`);
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.1",
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
- "dotenv": "^16.3.1",
45
- "commander": "^11.0.0"
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
  }