bytifi 0.2.0 → 0.2.2

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 CHANGED
@@ -55,11 +55,11 @@ Prefer the environment variable — keys on the command line can appear in shell
55
55
  bytifi upload ./photo.png
56
56
  bytifi upload "./my video (1).mp4"
57
57
  bytifi upload ./report.pdf --expires 60 --delete-on-download
58
- bytifi upload ./logs.txt --compress auto --concurrency 8 --json > upload.json
58
+ bytifi upload ./logs.txt --concurrency 8 --json > upload.json
59
59
  bytifi upload ./photo.png -q
60
60
  ```
61
61
 
62
- Files over **10 MB** use multipart upload automatically (lower memory use). Compression defaults to **`auto`**: gzip for text-like files, skipped for video/images/zip.
62
+ All uploads are gzip-compressed per chunk before encryption. Files over **10 MB** use multipart upload automatically.
63
63
 
64
64
  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
65
 
@@ -128,7 +128,6 @@ Note: with `npm exec`, put `--` before the file path so npm does not swallow `--
128
128
  | `-q, --quiet` | Print only the share URL |
129
129
  | `--verbose` | Print API error details to stderr |
130
130
  | `--mime-type` | Override detected MIME type |
131
- | `--compress` | Compression mode: `auto`, `gzip`, or `off` (default: `auto`) |
132
131
  | `--concurrency` | Parallel encrypt/upload workers, 1–16 (default: `4`) |
133
132
  | `--base-url` | API base URL (default: `https://bytifi.com`) |
134
133
 
@@ -156,34 +155,6 @@ JSON output for decrypt includes `outputPath`, `originalName`, `size`, `mimeType
156
155
 
157
156
  Files over ~100 MB encrypted use multipart upload automatically. Progress prints to stderr unless `--json` or `--quiet` is set.
158
157
 
159
- ## How it works
160
-
161
- **Upload**
162
-
163
- 1. Optionally gzip-compress each chunk (`--compress auto|gzip|off`)
164
- 2. Encrypts locally with AES-GCM (same format as the website, meta `version: 2` when compressed)
165
- 3. Uploads encrypted bytes via multipart pipeline for files >10 MB
166
- 4. Prints a share URL including `#token=...`
167
-
168
- ## Compatibility
169
-
170
- | Scenario | Works? |
171
- |----------|--------|
172
- | Old links (no compression) | Yes — everywhere |
173
- | New CLI upload + new CLI decrypt | Yes |
174
- | New CLI upload + **updated website** decrypt | Yes — **deploy Bytifi-Website** after upgrading CLI |
175
- | New CLI upload + old website (not deployed) | **Broken in browser** — garbled download |
176
- | New CLI upload + old CLI (≤0.1.5) decrypt | **Broken** — upgrade CLI |
177
- | Website upload (no compression) | Yes — unchanged |
178
-
179
- Compressed files larger than one chunk use **part-based storage**; decrypt via share link or CLI part download.
180
-
181
- **Decrypt**
182
-
183
- 1. Reads link metadata from `/api/link/:token`, from `--upload-json`, or from `--meta`
184
- 2. Downloads the encrypted file (unless you pass a local encrypted file)
185
- 3. Decrypts locally and writes the original file to disk
186
-
187
158
  ## Development
188
159
 
189
160
  ```bash
package/bin/bytifi.js CHANGED
@@ -8,7 +8,10 @@ import { decryptFile } from '../lib/decrypt.js'
8
8
  import { BytifiApiError, BytifiNetworkError, uploadFile } from '../lib/upload.js'
9
9
 
10
10
  const require = createRequire(import.meta.url)
11
- const { version } = require('../package.json')
11
+ const version =
12
+ typeof __BYTIFI_VERSION__ !== 'undefined'
13
+ ? __BYTIFI_VERSION__
14
+ : require('../package.json').version
12
15
 
13
16
  function printHelp() {
14
17
  process.stdout.write(`Bytifi CLI v${version} — encrypt, upload, and decrypt files
@@ -26,7 +29,6 @@ Upload options:
26
29
  -q, --quiet Print only the share URL
27
30
  --verbose Show API error details on stderr
28
31
  --mime-type <type> Override detected MIME type
29
- --compress <mode> Compression: auto|gzip|off (default: auto)
30
32
  --concurrency <n> Parallel part workers (default: 4)
31
33
  --base-url <url> API base URL (default: https://bytifi.com)
32
34
 
@@ -54,16 +56,16 @@ Exit codes:
54
56
  3 network error
55
57
 
56
58
  Examples:
59
+ bytifi --version
60
+ export BYTIFI_API_KEY=usk_your_key
61
+
57
62
  bytifi upload ./photo.png
58
- bytifi upload ./video.mp4 --expires 60 --json > upload.json
59
- bytifi upload ./logs.tar --compress gzip --concurrency 8
63
+ bytifi upload "./my report.pdf" --expires 60 --delete-on-download
64
+ bytifi upload ./logs.txt --concurrency 8 --json > upload.json
60
65
  bytifi upload ./large.iso -q
61
66
 
62
- bytifi decrypt 'https://bytifi.com/link?link=abc#token=...'
63
- bytifi decrypt abc --token 'ENCRYPTION_TOKEN' -o ./restored.mp4
64
-
65
- bytifi decrypt ./downloaded.encrypted --upload-json upload.json
66
- bytifi decrypt "./my file(1).mp4" --link abc --token 'ENCRYPTION_TOKEN'
67
+ bytifi decrypt 'https://bytifi.com/link?link=LINK#token=KEY'
68
+ bytifi decrypt ./downloaded.bin --upload-json upload.json -o ./restored.bin
67
69
  `)
68
70
  }
69
71
 
@@ -85,7 +87,6 @@ function parseUploadArgs(argv) {
85
87
  quiet: false,
86
88
  verbose: false,
87
89
  mimeType: '',
88
- compressionMode: 'auto',
89
90
  concurrency: 4,
90
91
  baseUrl: 'https://bytifi.com',
91
92
  help: false,
@@ -148,12 +149,6 @@ function parseUploadArgs(argv) {
148
149
  continue
149
150
  }
150
151
 
151
- if (arg === '--compress') {
152
- options.compressionMode = readFlagValue(argv, index, arg)
153
- index += 1
154
- continue
155
- }
156
-
157
152
  if (arg === '--concurrency') {
158
153
  const raw = readFlagValue(argv, index, arg)
159
154
  const concurrency = Number(raw)
@@ -334,7 +329,6 @@ async function runUpload(filePath, options) {
334
329
  expiresInMinutes: options.expiresInMinutes,
335
330
  deleteOnDownload: options.deleteOnDownload,
336
331
  mimeType: options.mimeType || undefined,
337
- compressionMode: options.compressionMode,
338
332
  concurrency: options.concurrency,
339
333
  signal: abortController.signal,
340
334
  onProgress: showProgress
package/lib/crypto.js CHANGED
@@ -13,19 +13,6 @@ export const PLAIN_CHUNK_SIZE = ENCRYPTED_PART_SIZE - 16
13
13
  export const DIRECT_UPLOAD_LIMIT_BYTES = 100 * 1024 * 1024
14
14
  export const MULTIPART_THRESHOLD_BYTES = 10 * 1024 * 1024
15
15
 
16
- const SKIP_COMPRESS_EXTENSIONS = new Set([
17
- '.7z', '.avi', '.br', '.bz2', '.gif', '.gz', '.jpeg', '.jpg', '.mkv', '.mov',
18
- '.mp3', '.mp4', '.png', '.rar', '.webp', '.woff', '.woff2', '.xz', '.zip', '.zst', '.iso',
19
- ])
20
-
21
- const SKIP_COMPRESS_MIME_EXACT = new Set([
22
- 'application/gzip',
23
- 'application/pdf',
24
- 'application/x-7z-compressed',
25
- 'application/x-rar-compressed',
26
- 'application/zip',
27
- ])
28
-
29
16
  export function buildChunkIv(noncePrefix, chunkIndex) {
30
17
  const iv = Buffer.alloc(12)
31
18
  noncePrefix.copy(iv, 0, 0, 8)
@@ -33,44 +20,8 @@ export function buildChunkIv(noncePrefix, chunkIndex) {
33
20
  return iv
34
21
  }
35
22
 
36
- export function resolveCompressionMode(requested, mimeType, originalName) {
37
- const mode = String(requested || 'auto').toLowerCase()
38
-
39
- if (mode === 'off' || mode === 'none') {
40
- return 'none'
41
- }
42
-
43
- if (mode === 'gzip') {
44
- return 'gzip'
45
- }
46
-
47
- if (mode === 'auto') {
48
- return shouldSkipAutoCompression(mimeType, originalName) ? 'none' : 'gzip'
49
- }
50
-
51
- throw new Error('compress must be one of: auto, gzip, off')
52
- }
53
-
54
- function shouldSkipAutoCompression(mimeType, originalName) {
55
- const extension = path.extname(String(originalName || '')).toLowerCase()
56
- if (SKIP_COMPRESS_EXTENSIONS.has(extension)) {
57
- return true
58
- }
59
-
60
- const normalizedMime = String(mimeType || '').toLowerCase()
61
- if (SKIP_COMPRESS_MIME_EXACT.has(normalizedMime)) {
62
- return true
63
- }
64
-
65
- if (normalizedMime.startsWith('video/') || normalizedMime.startsWith('audio/')) {
66
- return true
67
- }
68
-
69
- if (normalizedMime.startsWith('image/')) {
70
- return true
71
- }
72
-
73
- return false
23
+ export function resolveCompressionMode() {
24
+ return 'gzip'
74
25
  }
75
26
 
76
27
  export function usesChunkCompression(meta) {
@@ -112,17 +63,11 @@ export function buildClientEncryptionMeta({
112
63
  noncePrefix,
113
64
  originalSize,
114
65
  mimeType,
115
- compression = 'none',
116
66
  }) {
117
- const compressionMeta = normalizeCompressionMeta({
118
- algorithm: compression === 'gzip' ? 'gzip' : 'none',
119
- scope: 'chunk',
120
- })
121
-
122
67
  return {
123
- version: compressionMeta.algorithm === 'gzip' ? 2 : 1,
68
+ version: 2,
124
69
  algorithm: 'AES-GCM',
125
- compression: compressionMeta,
70
+ compression: { algorithm: 'gzip', scope: 'chunk' },
126
71
  chunkSize: plainChunkSize,
127
72
  chunkCount,
128
73
  noncePrefix: toBase64Url(noncePrefix),
@@ -151,12 +96,10 @@ export function createEncryptionContext({
151
96
  tokenBytes = crypto.randomBytes(32),
152
97
  noncePrefix = crypto.randomBytes(8),
153
98
  plainChunkSize = PLAIN_CHUNK_SIZE,
154
- compressionMode = 'auto',
155
99
  }) {
156
100
  if (tokenBytes.length !== 32) throw new Error('Encryption token must be 32 bytes.')
157
101
  if (noncePrefix.length !== 8) throw new Error('Nonce prefix must be 8 bytes.')
158
102
 
159
- const compression = resolveCompressionMode(compressionMode, mimeType, originalName)
160
103
  const { chunkCount, encryptedSize } = calculateEncryptedSize(originalSize, plainChunkSize)
161
104
  const meta = buildClientEncryptionMeta({
162
105
  plainChunkSize,
@@ -164,7 +107,6 @@ export function createEncryptionContext({
164
107
  noncePrefix,
165
108
  originalSize,
166
109
  mimeType,
167
- compression,
168
110
  })
169
111
 
170
112
  return {
@@ -178,7 +120,7 @@ export function createEncryptionContext({
178
120
  mimeType,
179
121
  originalSize,
180
122
  plainChunkSize,
181
- compression,
123
+ compression: 'gzip',
182
124
  }
183
125
  }
184
126
 
@@ -199,7 +141,6 @@ export async function resolveUploadFile(filePath, options = {}) {
199
141
  originalSize: stat.size,
200
142
  originalName,
201
143
  mimeType,
202
- compressionMode: options.compressionMode,
203
144
  }),
204
145
  }
205
146
  }
@@ -225,10 +166,7 @@ export async function encryptChunkFromFile(fileHandle, chunkIndex, context) {
225
166
  context.plainChunkSize,
226
167
  )
227
168
 
228
- let payload = plainChunk
229
- if (context.compression === 'gzip') {
230
- payload = await compressPlainChunk(plainChunk)
231
- }
169
+ let payload = await compressPlainChunk(plainChunk)
232
170
 
233
171
  return encryptChunk(payload, context.tokenBytes, context.noncePrefix, chunkIndex)
234
172
  }
package/lib/upload.js CHANGED
@@ -410,7 +410,6 @@ export async function uploadFile(filePath, options) {
410
410
 
411
411
  const { absolutePath, context } = await resolveUploadFile(filePath, {
412
412
  mimeType: options.mimeType,
413
- compressionMode: options.compressionMode,
414
413
  })
415
414
 
416
415
  if (context.originalSize <= MULTIPART_THRESHOLD_BYTES) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bytifi",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Official Bytifi CLI — encrypt, upload, and decrypt files from the terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,8 +16,8 @@
16
16
  "scripts": {
17
17
  "upload": "node bin/bytifi.js upload",
18
18
  "decrypt": "node bin/bytifi.js decrypt",
19
- "build:bundle": "esbuild bin/bytifi.js --bundle --platform=node --format=cjs --outfile=dist/bytifi-bundle.cjs",
20
- "build:win": "npm run build:bundle && pkg dist/bytifi-bundle.cjs --targets node22-win-x64 --output dist/bytifi.exe --compress GZip"
19
+ "build:bundle": "node scripts/build-bundle.mjs",
20
+ "build:win": "npm run build:bundle && pkg dist/bytifi-bundle.cjs --targets node22-win-x64 --output dist/bytifi.exe --compress GZip --public --public-packages \"*\""
21
21
  },
22
22
  "repository": {
23
23
  "type": "git",