bytifi 0.2.6 → 0.2.7
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/LICENSE +21 -0
- package/README.md +19 -5
- package/bin/bytifi.js +168 -66
- package/lib/api.js +203 -0
- package/lib/base64url.js +10 -2
- package/lib/crypto.js +0 -1
- package/lib/decrypt.js +184 -136
- package/lib/progress.js +50 -0
- package/lib/upload.js +94 -261
- package/lib/url.js +26 -0
- package/package.json +5 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bytifi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -45,7 +45,8 @@ Create an API key in **Account → API** on bytifi.com.
|
|
|
45
45
|
## Setup
|
|
46
46
|
|
|
47
47
|
Set your API key via environment variable (see Install above) or pass `--api-key` per command.
|
|
48
|
-
|
|
48
|
+
|
|
49
|
+
**Security:** Prefer `BYTIFI_API_KEY` over `--api-key` on the command line. API keys and encryption tokens passed as CLI flags may appear in shell history and process lists (`ps`). The CLI warns when secrets are passed on argv.
|
|
49
50
|
|
|
50
51
|
## Usage
|
|
51
52
|
|
|
@@ -59,7 +60,9 @@ bytifi upload ./logs.txt --concurrency 8 --json > upload.json
|
|
|
59
60
|
bytifi upload ./photo.png -q
|
|
60
61
|
```
|
|
61
62
|
|
|
62
|
-
Files up to **10 MB**
|
|
63
|
+
Files up to **10 MB** use a single direct upload with gzip-compressed chunks. Files **over 10 MB** use multipart upload with fixed-size encrypted parts (no compression — gzip can expand ISO/zip data past the server part limit).
|
|
64
|
+
|
|
65
|
+
Upload concurrency auto-scales when `--concurrency` is omitted: ≤1 GB → 4 workers, 1–3 GB → 3 workers, ≥3 GB → 2 workers.
|
|
63
66
|
|
|
64
67
|
Upload accepts **one file at a time**. Quote paths that contain spaces. Avoid shell globs like `**` — your shell may expand them into dozens of paths.
|
|
65
68
|
|
|
@@ -70,6 +73,7 @@ Download and decrypt directly from a share URL or link token. No API key require
|
|
|
70
73
|
```bash
|
|
71
74
|
bytifi decrypt 'https://bytifi.com/link?link=LINK_ID#token=ENCRYPTION_TOKEN'
|
|
72
75
|
bytifi decrypt LINK_ID --token ENCRYPTION_TOKEN -o ./restored.mp4
|
|
76
|
+
bytifi decrypt LINK_ID --token ENCRYPTION_TOKEN --concurrency 4
|
|
73
77
|
```
|
|
74
78
|
|
|
75
79
|
### Decrypt a downloaded encrypted file
|
|
@@ -84,6 +88,12 @@ bytifi decrypt ./downloaded-file --upload-json upload.json
|
|
|
84
88
|
bytifi decrypt "./my video (1).mp4" \
|
|
85
89
|
--link LINK_ID \
|
|
86
90
|
--token ENCRYPTION_TOKEN
|
|
91
|
+
|
|
92
|
+
# Force local-path mode when the filename looks like a URL
|
|
93
|
+
bytifi decrypt ./https-example.bin --local-file --upload-json upload.json
|
|
94
|
+
|
|
95
|
+
# Overwrite an existing output file
|
|
96
|
+
bytifi decrypt ./downloaded-file --upload-json upload.json --force
|
|
87
97
|
```
|
|
88
98
|
|
|
89
99
|
#### Link ID vs encryption token
|
|
@@ -128,7 +138,7 @@ Note: with `npm exec`, put `--` before the file path so npm does not swallow `--
|
|
|
128
138
|
| `-q, --quiet` | Print only the share URL |
|
|
129
139
|
| `--verbose` | Print API error details to stderr |
|
|
130
140
|
| `--mime-type` | Override detected MIME type |
|
|
131
|
-
| `--concurrency` | Parallel encrypt/upload workers, 1–16 (default:
|
|
141
|
+
| `--concurrency` | Parallel encrypt/upload workers, 1–16 (default: auto-scaled by file size) |
|
|
132
142
|
| `--base-url` | API base URL (default: `https://bytifi.com`) |
|
|
133
143
|
|
|
134
144
|
### Decrypt options
|
|
@@ -140,24 +150,28 @@ Note: with `npm exec`, put `--` before the file path so npm does not swallow `--
|
|
|
140
150
|
| `--upload-json` | Upload `--json` output file (recommended for downloaded files) |
|
|
141
151
|
| `--meta` | Saved `clientEncryptionMeta` JSON for offline decrypt |
|
|
142
152
|
| `--share-url` | Share URL to read token/metadata while decrypting a local file |
|
|
153
|
+
| `--local-file` | Treat input as a local path even if it looks like a URL |
|
|
143
154
|
| `-o, --output` | Output file path |
|
|
144
155
|
| `--output-dir` | Output directory when saving under the original filename |
|
|
156
|
+
| `--force` | Overwrite existing output file |
|
|
157
|
+
| `--concurrency` | Parallel part download workers, 1–8 (default: `2`) |
|
|
145
158
|
| `--json` | Machine-readable JSON output |
|
|
146
159
|
| `-q, --quiet` | Print only the output file path |
|
|
147
160
|
| `--verbose` | Print error details to stderr |
|
|
148
161
|
| `--base-url` | API base URL (default: `https://bytifi.com`) |
|
|
149
162
|
|
|
150
|
-
Exit codes: `0` success, `1` usage error, `2` API error, `3` network error.
|
|
163
|
+
Exit codes: `0` success, `1` usage error, `2` API error, `3` network error, `130` interrupted (Ctrl+C).
|
|
151
164
|
|
|
152
165
|
JSON output (`--json`) for upload includes `shareUrl`, `encryptedFile`, `link`, `encryptionToken`, `clientEncryptionMeta`, `compression`, and `expiresAt`.
|
|
153
166
|
|
|
154
167
|
JSON output for decrypt includes `outputPath`, `originalName`, `size`, `mimeType`, `expiresAt`, `link`, `storageMode`, and `sourcePath` (for local decrypt).
|
|
155
168
|
|
|
156
|
-
|
|
169
|
+
Progress prints to stderr with throughput and ETA unless `--json` or `--quiet` is set.
|
|
157
170
|
|
|
158
171
|
## Development
|
|
159
172
|
|
|
160
173
|
```bash
|
|
174
|
+
npm test
|
|
161
175
|
node bin/bytifi.js upload ./file.png --json > upload.json
|
|
162
176
|
node bin/bytifi.js decrypt ./file.encrypted --upload-json upload.json
|
|
163
177
|
```
|
package/bin/bytifi.js
CHANGED
|
@@ -5,7 +5,8 @@ import path from 'node:path'
|
|
|
5
5
|
import process from 'node:process'
|
|
6
6
|
import { createRequire } from 'node:module'
|
|
7
7
|
import { decryptFile } from '../lib/decrypt.js'
|
|
8
|
-
import { formatProgressLine } from '../lib/progress.js'
|
|
8
|
+
import { createProgressTracker, formatProgressLine } from '../lib/progress.js'
|
|
9
|
+
import { validateBaseUrl } from '../lib/url.js'
|
|
9
10
|
import { BytifiApiError, BytifiNetworkError, uploadFile } from '../lib/upload.js'
|
|
10
11
|
|
|
11
12
|
const require = createRequire(import.meta.url)
|
|
@@ -14,15 +15,41 @@ const version =
|
|
|
14
15
|
? __BYTIFI_VERSION__
|
|
15
16
|
: require('../package.json').version
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
const SECRET_FLAGS = new Set(['--api-key', '-k', '--token'])
|
|
19
|
+
|
|
20
|
+
function printGlobalHelp() {
|
|
18
21
|
process.stdout.write(`Bytifi CLI v${version} — encrypt, upload, and decrypt files
|
|
19
22
|
|
|
20
23
|
Usage:
|
|
21
24
|
bytifi upload <file> [options]
|
|
22
|
-
bytifi decrypt <
|
|
23
|
-
|
|
25
|
+
bytifi decrypt <input> [options]
|
|
26
|
+
|
|
27
|
+
Commands:
|
|
28
|
+
upload Encrypt and upload a file
|
|
29
|
+
decrypt Decrypt from a share link or local encrypted file
|
|
30
|
+
|
|
31
|
+
Global:
|
|
32
|
+
-V, --version Show version
|
|
33
|
+
-h, --help Show help
|
|
34
|
+
|
|
35
|
+
Run \`bytifi upload --help\` or \`bytifi decrypt --help\` for command options.
|
|
36
|
+
|
|
37
|
+
Exit codes:
|
|
38
|
+
0 success
|
|
39
|
+
1 usage or validation error
|
|
40
|
+
2 API error (4xx/5xx response)
|
|
41
|
+
3 network error
|
|
42
|
+
130 interrupted (Ctrl+C)
|
|
43
|
+
`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function printUploadHelp() {
|
|
47
|
+
process.stdout.write(`Bytifi upload — encrypt and upload a file
|
|
48
|
+
|
|
49
|
+
Usage:
|
|
50
|
+
bytifi upload <file> [options]
|
|
24
51
|
|
|
25
|
-
|
|
52
|
+
Options:
|
|
26
53
|
-k, --api-key <key> API key (default: BYTIFI_API_KEY env var)
|
|
27
54
|
-e, --expires <minutes> Link lifetime: 5|15|30|60|120 (default: 30)
|
|
28
55
|
--delete-on-download Remove file after first download
|
|
@@ -30,43 +57,49 @@ Upload options:
|
|
|
30
57
|
-q, --quiet Print only the share URL
|
|
31
58
|
--verbose Show API error details on stderr
|
|
32
59
|
--mime-type <type> Override detected MIME type
|
|
33
|
-
--concurrency <n> Parallel part workers (default:
|
|
60
|
+
--concurrency <n> Parallel part workers, 1–16 (default: auto)
|
|
34
61
|
--base-url <url> API base URL (default: https://bytifi.com)
|
|
62
|
+
-h, --help Show this help
|
|
63
|
+
|
|
64
|
+
Concurrency auto-scales by file size when --concurrency is omitted:
|
|
65
|
+
≤1 GB → 4 workers, 1–3 GB → 3 workers, ≥3 GB → 2 workers.
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
export BYTIFI_API_KEY=usk_your_key
|
|
69
|
+
bytifi upload ./photo.png
|
|
70
|
+
bytifi upload "./my report.pdf" --expires 60 --delete-on-download
|
|
71
|
+
bytifi upload ./logs.txt --concurrency 8 --json > upload.json
|
|
72
|
+
bytifi upload ./large.iso -q
|
|
73
|
+
`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function printDecryptHelp() {
|
|
77
|
+
process.stdout.write(`Bytifi decrypt — decrypt from a link or local encrypted file
|
|
35
78
|
|
|
36
|
-
|
|
79
|
+
Usage:
|
|
80
|
+
bytifi decrypt <url-or-token|encrypted-file> [options]
|
|
81
|
+
|
|
82
|
+
Options:
|
|
37
83
|
--token <token> Encryption key from #token=... (not the link ID)
|
|
38
84
|
--link <id> Link ID from upload JSON "link" field (/f/LINK_ID)
|
|
39
85
|
--upload-json <path> Upload --json output (easiest for downloaded files)
|
|
40
86
|
--meta <path> Saved clientEncryptionMeta JSON (offline decrypt)
|
|
41
|
-
--share-url <url> Share URL for token/metadata when decrypting
|
|
87
|
+
--share-url <url> Share URL for token/metadata when decrypting locally
|
|
88
|
+
--local-file Treat input as a local path even if it looks like a URL
|
|
42
89
|
-o, --output <path> Output file path (default: original filename)
|
|
43
90
|
--output-dir <dir> Directory for decrypted file (default: cwd)
|
|
91
|
+
--force Overwrite existing output file
|
|
92
|
+
--concurrency <n> Parallel download workers, 1–8 (default: 2)
|
|
44
93
|
--json Print machine-readable JSON to stdout
|
|
45
94
|
-q, --quiet Print only the output file path
|
|
46
95
|
--verbose Show error details on stderr
|
|
47
96
|
--base-url <url> API base URL (default: https://bytifi.com)
|
|
48
|
-
|
|
49
|
-
Global:
|
|
50
|
-
-V, --version Show version
|
|
51
97
|
-h, --help Show this help
|
|
52
98
|
|
|
53
|
-
Exit codes:
|
|
54
|
-
0 success
|
|
55
|
-
1 usage or validation error
|
|
56
|
-
2 API error (4xx/5xx response)
|
|
57
|
-
3 network error
|
|
58
|
-
|
|
59
99
|
Examples:
|
|
60
|
-
bytifi --version
|
|
61
|
-
export BYTIFI_API_KEY=usk_your_key
|
|
62
|
-
|
|
63
|
-
bytifi upload ./photo.png
|
|
64
|
-
bytifi upload "./my report.pdf" --expires 60 --delete-on-download
|
|
65
|
-
bytifi upload ./logs.txt --concurrency 8 --json > upload.json
|
|
66
|
-
bytifi upload ./large.iso -q
|
|
67
|
-
|
|
68
100
|
bytifi decrypt 'https://bytifi.com/link?link=LINK#token=KEY'
|
|
69
101
|
bytifi decrypt ./downloaded.bin --upload-json upload.json -o ./restored.bin
|
|
102
|
+
bytifi decrypt ./parts-dir/ --upload-json upload.json --force
|
|
70
103
|
`)
|
|
71
104
|
}
|
|
72
105
|
|
|
@@ -88,7 +121,7 @@ function parseUploadArgs(argv) {
|
|
|
88
121
|
quiet: false,
|
|
89
122
|
verbose: false,
|
|
90
123
|
mimeType: '',
|
|
91
|
-
concurrency:
|
|
124
|
+
concurrency: null,
|
|
92
125
|
baseUrl: 'https://bytifi.com',
|
|
93
126
|
help: false,
|
|
94
127
|
version: false,
|
|
@@ -187,6 +220,9 @@ function parseDecryptArgs(argv) {
|
|
|
187
220
|
shareUrl: '',
|
|
188
221
|
output: '',
|
|
189
222
|
outputDirectory: '',
|
|
223
|
+
localFile: false,
|
|
224
|
+
force: false,
|
|
225
|
+
concurrency: 2,
|
|
190
226
|
json: false,
|
|
191
227
|
quiet: false,
|
|
192
228
|
verbose: false,
|
|
@@ -223,6 +259,16 @@ function parseDecryptArgs(argv) {
|
|
|
223
259
|
continue
|
|
224
260
|
}
|
|
225
261
|
|
|
262
|
+
if (arg === '--force') {
|
|
263
|
+
options.force = true
|
|
264
|
+
continue
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (arg === '--local-file') {
|
|
268
|
+
options.localFile = true
|
|
269
|
+
continue
|
|
270
|
+
}
|
|
271
|
+
|
|
226
272
|
if (arg === '--token') {
|
|
227
273
|
options.encryptionToken = readFlagValue(argv, index, arg)
|
|
228
274
|
index += 1
|
|
@@ -265,6 +311,17 @@ function parseDecryptArgs(argv) {
|
|
|
265
311
|
continue
|
|
266
312
|
}
|
|
267
313
|
|
|
314
|
+
if (arg === '--concurrency') {
|
|
315
|
+
const raw = readFlagValue(argv, index, arg)
|
|
316
|
+
const concurrency = Number(raw)
|
|
317
|
+
if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 8) {
|
|
318
|
+
throw new Error('concurrency must be an integer between 1 and 8.')
|
|
319
|
+
}
|
|
320
|
+
options.concurrency = concurrency
|
|
321
|
+
index += 1
|
|
322
|
+
continue
|
|
323
|
+
}
|
|
324
|
+
|
|
268
325
|
if (arg === '--base-url') {
|
|
269
326
|
options.baseUrl = readFlagValue(argv, index, arg)
|
|
270
327
|
index += 1
|
|
@@ -281,6 +338,18 @@ function parseDecryptArgs(argv) {
|
|
|
281
338
|
return { positional, options }
|
|
282
339
|
}
|
|
283
340
|
|
|
341
|
+
function warnSecretFlags(argv) {
|
|
342
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
343
|
+
const arg = argv[index]
|
|
344
|
+
if (SECRET_FLAGS.has(arg)) {
|
|
345
|
+
process.stderr.write(
|
|
346
|
+
`Warning: ${arg} on the command line may appear in shell history and process lists. Prefer BYTIFI_API_KEY for uploads.\n`,
|
|
347
|
+
)
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
284
353
|
function validateExpires(minutes) {
|
|
285
354
|
const allowed = new Set([5, 15, 30, 60, 120])
|
|
286
355
|
if (!allowed.has(minutes)) {
|
|
@@ -288,8 +357,7 @@ function validateExpires(minutes) {
|
|
|
288
357
|
}
|
|
289
358
|
}
|
|
290
359
|
|
|
291
|
-
function writeProgress(
|
|
292
|
-
const line = formatProgressLine(info)
|
|
360
|
+
function writeProgress(line) {
|
|
293
361
|
if (line) {
|
|
294
362
|
process.stderr.write(`\r${line}`)
|
|
295
363
|
}
|
|
@@ -303,31 +371,52 @@ function clearProgressLine() {
|
|
|
303
371
|
process.stderr.write('\r\x1b[K')
|
|
304
372
|
}
|
|
305
373
|
|
|
374
|
+
function createAbortContext(label) {
|
|
375
|
+
const abortController = new AbortController()
|
|
376
|
+
let abortedByUser = false
|
|
377
|
+
|
|
378
|
+
const handleSignal = () => {
|
|
379
|
+
abortedByUser = true
|
|
380
|
+
abortController.abort()
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
process.on('SIGINT', handleSignal)
|
|
384
|
+
process.on('SIGTERM', handleSignal)
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
signal: abortController.signal,
|
|
388
|
+
cleanup() {
|
|
389
|
+
process.off('SIGINT', handleSignal)
|
|
390
|
+
process.off('SIGTERM', handleSignal)
|
|
391
|
+
},
|
|
392
|
+
checkAborted() {
|
|
393
|
+
if (abortedByUser || abortController.signal.aborted) {
|
|
394
|
+
clearProgressLine()
|
|
395
|
+
process.stderr.write(`${label} aborted.\n`)
|
|
396
|
+
process.exit(130)
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
306
402
|
async function runUpload(filePath, options) {
|
|
307
403
|
validateExpires(options.expiresInMinutes)
|
|
404
|
+
options.baseUrl = validateBaseUrl(options.baseUrl)
|
|
308
405
|
|
|
309
406
|
if (!options.apiKey) {
|
|
310
407
|
throw new Error('Missing API key. Pass --api-key or set BYTIFI_API_KEY.')
|
|
311
408
|
}
|
|
312
409
|
|
|
313
410
|
const resolvedPath = path.resolve(filePath)
|
|
411
|
+
const stat = await fs.stat(resolvedPath)
|
|
314
412
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
} catch {
|
|
318
|
-
throw new Error(`File not found or not readable: ${resolvedPath}`)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const abortController = new AbortController()
|
|
322
|
-
|
|
323
|
-
const handleSignal = () => {
|
|
324
|
-
abortController.abort()
|
|
413
|
+
if (!stat.isFile()) {
|
|
414
|
+
throw new Error(`Upload path must be a file: ${resolvedPath}`)
|
|
325
415
|
}
|
|
326
416
|
|
|
327
|
-
|
|
328
|
-
process.on('SIGTERM', handleSignal)
|
|
329
|
-
|
|
417
|
+
const abort = createAbortContext('Upload')
|
|
330
418
|
const showProgress = !options.quiet && !options.json
|
|
419
|
+
const tracker = createProgressTracker()
|
|
331
420
|
let lastLine = ''
|
|
332
421
|
|
|
333
422
|
try {
|
|
@@ -337,19 +426,22 @@ async function runUpload(filePath, options) {
|
|
|
337
426
|
expiresInMinutes: options.expiresInMinutes,
|
|
338
427
|
deleteOnDownload: options.deleteOnDownload,
|
|
339
428
|
mimeType: options.mimeType || undefined,
|
|
340
|
-
concurrency: options.concurrency,
|
|
341
|
-
signal:
|
|
429
|
+
concurrency: options.concurrency ?? undefined,
|
|
430
|
+
signal: abort.signal,
|
|
342
431
|
onProgress: showProgress
|
|
343
432
|
? (info) => {
|
|
344
|
-
const
|
|
433
|
+
const enriched = tracker.update(info)
|
|
434
|
+
const line = formatProgressLine(enriched)
|
|
345
435
|
if (line && line !== lastLine) {
|
|
346
436
|
lastLine = line
|
|
347
|
-
writeProgress(
|
|
437
|
+
writeProgress(line)
|
|
348
438
|
}
|
|
349
439
|
}
|
|
350
440
|
: undefined,
|
|
351
441
|
})
|
|
352
442
|
|
|
443
|
+
abort.checkAborted()
|
|
444
|
+
|
|
353
445
|
if (showProgress) {
|
|
354
446
|
finishProgressLine()
|
|
355
447
|
}
|
|
@@ -367,23 +459,20 @@ async function runUpload(filePath, options) {
|
|
|
367
459
|
process.stdout.write(`Share URL:\n${result.shareUrl}\n`)
|
|
368
460
|
process.stdout.write(`Encrypted file:\n${result.encryptedFile}\n`)
|
|
369
461
|
process.stdout.write(`Expires: ${result.expiresAt}\n`)
|
|
462
|
+
} catch (error) {
|
|
463
|
+
abort.checkAborted()
|
|
464
|
+
throw error
|
|
370
465
|
} finally {
|
|
371
|
-
|
|
372
|
-
process.off('SIGTERM', handleSignal)
|
|
466
|
+
abort.cleanup()
|
|
373
467
|
}
|
|
374
468
|
}
|
|
375
469
|
|
|
376
470
|
async function runDecrypt(input, options) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const handleSignal = () => {
|
|
380
|
-
abortController.abort()
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
process.on('SIGINT', handleSignal)
|
|
384
|
-
process.on('SIGTERM', handleSignal)
|
|
471
|
+
options.baseUrl = validateBaseUrl(options.baseUrl)
|
|
385
472
|
|
|
473
|
+
const abort = createAbortContext('Decrypt')
|
|
386
474
|
const showProgress = !options.quiet && !options.json
|
|
475
|
+
const tracker = createProgressTracker()
|
|
387
476
|
let lastLine = ''
|
|
388
477
|
|
|
389
478
|
try {
|
|
@@ -395,19 +484,25 @@ async function runDecrypt(input, options) {
|
|
|
395
484
|
shareUrl: options.shareUrl,
|
|
396
485
|
output: options.output || undefined,
|
|
397
486
|
outputDirectory: options.outputDirectory || undefined,
|
|
487
|
+
localFile: options.localFile,
|
|
488
|
+
force: options.force,
|
|
489
|
+
concurrency: options.concurrency,
|
|
398
490
|
baseUrl: options.baseUrl,
|
|
399
|
-
signal:
|
|
491
|
+
signal: abort.signal,
|
|
400
492
|
onProgress: showProgress
|
|
401
493
|
? (info) => {
|
|
402
|
-
const
|
|
494
|
+
const enriched = tracker.update(info)
|
|
495
|
+
const line = formatProgressLine(enriched)
|
|
403
496
|
if (line && line !== lastLine) {
|
|
404
497
|
lastLine = line
|
|
405
|
-
writeProgress(
|
|
498
|
+
writeProgress(line)
|
|
406
499
|
}
|
|
407
500
|
}
|
|
408
501
|
: undefined,
|
|
409
502
|
})
|
|
410
503
|
|
|
504
|
+
abort.checkAborted()
|
|
505
|
+
|
|
411
506
|
if (showProgress) {
|
|
412
507
|
finishProgressLine()
|
|
413
508
|
}
|
|
@@ -425,9 +520,11 @@ async function runDecrypt(input, options) {
|
|
|
425
520
|
process.stdout.write(`Saved: ${result.outputPath}\n`)
|
|
426
521
|
process.stdout.write(`Original name: ${result.originalName}\n`)
|
|
427
522
|
process.stdout.write(`Expires: ${result.expiresAt}\n`)
|
|
523
|
+
} catch (error) {
|
|
524
|
+
abort.checkAborted()
|
|
525
|
+
throw error
|
|
428
526
|
} finally {
|
|
429
|
-
|
|
430
|
-
process.off('SIGTERM', handleSignal)
|
|
527
|
+
abort.cleanup()
|
|
431
528
|
}
|
|
432
529
|
}
|
|
433
530
|
|
|
@@ -457,10 +554,13 @@ function printError(error, verbose) {
|
|
|
457
554
|
}
|
|
458
555
|
|
|
459
556
|
async function main() {
|
|
460
|
-
const
|
|
557
|
+
const argv = process.argv.slice(2)
|
|
558
|
+
warnSecretFlags(argv)
|
|
559
|
+
|
|
560
|
+
const [command, ...rest] = argv
|
|
461
561
|
|
|
462
562
|
if (!command || command === '--help' || command === '-h') {
|
|
463
|
-
|
|
563
|
+
printGlobalHelp()
|
|
464
564
|
process.exit(0)
|
|
465
565
|
}
|
|
466
566
|
|
|
@@ -470,7 +570,7 @@ async function main() {
|
|
|
470
570
|
}
|
|
471
571
|
|
|
472
572
|
if (command === 'help') {
|
|
473
|
-
|
|
573
|
+
printGlobalHelp()
|
|
474
574
|
process.exit(0)
|
|
475
575
|
}
|
|
476
576
|
|
|
@@ -478,7 +578,7 @@ async function main() {
|
|
|
478
578
|
const { positional, options } = parseUploadArgs(rest)
|
|
479
579
|
|
|
480
580
|
if (options.help) {
|
|
481
|
-
|
|
581
|
+
printUploadHelp()
|
|
482
582
|
process.exit(0)
|
|
483
583
|
}
|
|
484
584
|
|
|
@@ -510,7 +610,7 @@ async function main() {
|
|
|
510
610
|
const { positional, options } = parseDecryptArgs(rest)
|
|
511
611
|
|
|
512
612
|
if (options.help) {
|
|
513
|
-
|
|
613
|
+
printDecryptHelp()
|
|
514
614
|
process.exit(0)
|
|
515
615
|
}
|
|
516
616
|
|
|
@@ -529,7 +629,9 @@ async function main() {
|
|
|
529
629
|
}
|
|
530
630
|
|
|
531
631
|
if (positional.length > 1) {
|
|
532
|
-
|
|
632
|
+
throw new Error(
|
|
633
|
+
`Decrypt accepts one input at a time (got ${positional.length}). Quote paths with spaces.`,
|
|
634
|
+
)
|
|
533
635
|
}
|
|
534
636
|
|
|
535
637
|
await runDecrypt(input, options)
|