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.
Files changed (4) hide show
  1. package/README.md +42 -12
  2. package/example.js +5 -5
  3. package/index.js +102 -11
  4. 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
- #### Option 1: Clone and install locally
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
- #### Option 2: Install via npm
66
-
67
- ```bash
68
- npm install -g replicate-predictions-downloader
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 script:
92
- ```bash
93
- # If installed locally
94
- npm start
118
+ Run the tool based on how you installed it:
95
119
 
96
- # Or directly
97
- node index.js
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 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
@@ -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
- // Handle various date formats
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 [key, value] of Object.entries(input)) {
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
- 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}`);
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
- console.log(`\nTotal predictions fetched: ${this.allPredictions.length}`);
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.2",
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
- "dotenv": "^16.3.1",
45
- "commander": "^11.0.0"
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
  }