aethel 0.4.0 → 1.2.0
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/CHANGELOG.md +23 -0
- package/README.md +95 -45
- package/docs/ARCHITECTURE.md +24 -0
- package/package.json +16 -3
- package/scripts/demo.js +416 -0
- package/scripts/render-demo-gif.py +90 -0
- package/scripts/render-demo-screenshot.js +65 -0
- package/src/cli.js +47 -13
- package/src/core/compress.js +285 -0
- package/src/core/config.js +119 -0
- package/src/core/diff.js +146 -7
- package/src/core/pack-manifest.js +163 -0
- package/src/core/pack.js +355 -0
- package/src/core/snapshot.js +55 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.0 (2026-04-15)
|
|
4
|
+
|
|
5
|
+
- Add Packing modules
|
|
6
|
+
|
|
7
|
+
## 1.1.0 (2026-04-15)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Directory Packing**: Pack large directories (e.g., `node_modules`) into compressed archives for faster sync
|
|
11
|
+
- Multi-algorithm compression support: gzip, brotli (built-in), zstd, xz (optional)
|
|
12
|
+
- Tree hash algorithm for fast directory fingerprinting (~30x faster than MD5)
|
|
13
|
+
- Pack-aware scanning that skips packed directories
|
|
14
|
+
- Pack change detection: PACK_NEW, PACK_LOCAL_MODIFIED, PACK_REMOTE_MODIFIED, PACK_SYNCED, PACK_CONFLICT
|
|
15
|
+
- `aethel status --verbose` shows synced packs
|
|
16
|
+
- `.aethelconfig` YAML file for packing configuration
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Upgraded ink from 6.8.0 to 7.0.0
|
|
20
|
+
- Upgraded react from 19.2.4 to 19.2.5
|
|
21
|
+
|
|
22
|
+
## 1.0.0 (2026-04-06)
|
|
23
|
+
|
|
24
|
+
- release: 1.0.0
|
|
25
|
+
|
|
3
26
|
## 0.4.0 (2026-04-06)
|
|
4
27
|
|
|
5
28
|
- Add pull --all for full remote download
|
package/README.md
CHANGED
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
Aethel brings a `snapshot → diff → stage → commit` workflow to Google Drive. Track changes on both sides, resolve conflicts explicitly, and keep a full sync history — all without leaving the command line. It also ships with a dual-pane TUI for hands-on file management.
|
|
11
11
|
|
|
12
|
-
---
|
|
13
|
-
|
|
14
12
|
## Install
|
|
15
13
|
|
|
16
14
|
```bash
|
|
@@ -33,6 +31,8 @@ npm run install:cli # symlinks `aethel` into ~/.local/bin
|
|
|
33
31
|
|
|
34
32
|
## Setup
|
|
35
33
|
|
|
34
|
+

|
|
35
|
+
|
|
36
36
|
### 1. Get Google OAuth Credentials
|
|
37
37
|
|
|
38
38
|
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
|
|
@@ -62,15 +62,20 @@ aethel auth # opens browser, saves token.json
|
|
|
62
62
|
|
|
63
63
|
### 4. Initialize a Workspace
|
|
64
64
|
|
|
65
|
+

|
|
66
|
+
|
|
65
67
|
```bash
|
|
66
68
|
aethel init --local-path ./my-drive # sync entire My Drive
|
|
67
69
|
aethel init --local-path ./workspace --drive-folder <folder-id> # sync specific folder
|
|
70
|
+
aethel pull --all -m "initial pull" # hydrate local files from the current remote tree
|
|
68
71
|
```
|
|
69
72
|
|
|
70
73
|
> `credentials.json` and `token.json` are local secrets — never commit them.
|
|
71
74
|
|
|
72
75
|
## Usage
|
|
73
76
|
|
|
77
|
+

|
|
78
|
+
|
|
74
79
|
```bash
|
|
75
80
|
aethel status # local vs remote changes at a glance
|
|
76
81
|
aethel diff --side all # detailed file-level diff
|
|
@@ -82,6 +87,8 @@ aethel pull --all # download the full remote tree to local
|
|
|
82
87
|
aethel push -m "push" # push local changes to Drive
|
|
83
88
|
```
|
|
84
89
|
|
|
90
|
+
`pull` applies remote changes relative to the latest snapshot. Use `pull --all` for the first full download or to rehydrate a local workspace from the current remote tree.
|
|
91
|
+
|
|
85
92
|
### Conflict Resolution
|
|
86
93
|
|
|
87
94
|
When both local and remote change the same path:
|
|
@@ -105,28 +112,28 @@ Processes deepest-first for single-pass convergence, caches child state to minim
|
|
|
105
112
|
|
|
106
113
|
## Commands
|
|
107
114
|
|
|
108
|
-
| Command
|
|
109
|
-
|
|
110
|
-
| `auth`
|
|
111
|
-
| `init`
|
|
112
|
-
| `status`
|
|
113
|
-
| `diff`
|
|
114
|
-
| `add`
|
|
115
|
-
| `reset`
|
|
116
|
-
| `commit`
|
|
117
|
-
| `pull`
|
|
118
|
-
| `push`
|
|
119
|
-
| `log`
|
|
120
|
-
| `fetch`
|
|
121
|
-
| `resolve`
|
|
122
|
-
| `ignore`
|
|
123
|
-
| `show`
|
|
124
|
-
| `restore`
|
|
125
|
-
| `rm`
|
|
126
|
-
| `mv`
|
|
127
|
-
| `clean`
|
|
128
|
-
| `dedupe-folders` | Detect and merge duplicate remote folders
|
|
129
|
-
| `tui`
|
|
115
|
+
| Command | Description |
|
|
116
|
+
| ------------------ | ------------------------------------------------------------------- |
|
|
117
|
+
| `auth` | OAuth flow — creates `token.json`, verifies Drive access |
|
|
118
|
+
| `init` | Initialize a local sync workspace |
|
|
119
|
+
| `status` | Show local vs remote changes |
|
|
120
|
+
| `diff` | Detailed file differences |
|
|
121
|
+
| `add` | Stage changes |
|
|
122
|
+
| `reset` | Unstage changes |
|
|
123
|
+
| `commit` | Execute staged sync operations |
|
|
124
|
+
| `pull` | Fetch and apply remote changes (`--all` for full remote download) |
|
|
125
|
+
| `push` | Push local changes to Drive |
|
|
126
|
+
| `log` | Sync history |
|
|
127
|
+
| `fetch` | Refresh remote state without applying |
|
|
128
|
+
| `resolve` | Resolve conflicts (local / remote / both) |
|
|
129
|
+
| `ignore` | Manage `.aethelignore` patterns |
|
|
130
|
+
| `show` | Inspect a saved snapshot |
|
|
131
|
+
| `restore` | Restore files from the last snapshot |
|
|
132
|
+
| `rm` | Remove local files and stage remote deletion |
|
|
133
|
+
| `mv` | Move or rename local files |
|
|
134
|
+
| `clean` | List and optionally trash/delete Drive files |
|
|
135
|
+
| `dedupe-folders` | Detect and merge duplicate remote folders |
|
|
136
|
+
| `tui` | Launch interactive terminal UI |
|
|
130
137
|
|
|
131
138
|
## TUI
|
|
132
139
|
|
|
@@ -136,20 +143,60 @@ aethel tui
|
|
|
136
143
|
|
|
137
144
|
Dual-pane file browser — local filesystem on the left, Google Drive on the right.
|
|
138
145
|
|
|
139
|
-
| Key
|
|
140
|
-
|
|
141
|
-
| `Tab`
|
|
142
|
-
| `Left` / `Right` | Navigate up / into directories
|
|
143
|
-
| `u`
|
|
144
|
-
| `s`
|
|
145
|
-
| `U`
|
|
146
|
-
| `n`
|
|
147
|
-
| `x`
|
|
148
|
-
| `Space`
|
|
149
|
-
| `t` / `d`
|
|
150
|
-
| `/`
|
|
151
|
-
| `f`
|
|
152
|
-
| `:`
|
|
146
|
+
| Key | Action |
|
|
147
|
+
| -------------------- | -------------------------------------------------- |
|
|
148
|
+
| `Tab` | Switch panes |
|
|
149
|
+
| `Left` / `Right` | Navigate up / into directories |
|
|
150
|
+
| `u` | Upload selected local file or folder to Drive |
|
|
151
|
+
| `s` | Batch sync local folder to current Drive directory |
|
|
152
|
+
| `U` | Upload from a manually entered path |
|
|
153
|
+
| `n` | Rename selected local item |
|
|
154
|
+
| `x` | Delete selected local item |
|
|
155
|
+
| `Space` | Toggle selection in Drive pane |
|
|
156
|
+
| `t` / `d` | Trash / permanently delete selected Drive items |
|
|
157
|
+
| `/` | Filter by name |
|
|
158
|
+
| `f` | Open the commands page and choose a TUI action |
|
|
159
|
+
| `:` | Run any Aethel CLI command inside the TUI |
|
|
160
|
+
|
|
161
|
+
## Directory Packing
|
|
162
|
+
|
|
163
|
+
Large directories with many small files (e.g., `node_modules`, `vendor`) can be slow to sync. Aethel can pack these into compressed archives for faster transfers.
|
|
164
|
+
|
|
165
|
+
### Enable Packing
|
|
166
|
+
|
|
167
|
+
Create `.aethelconfig` in your workspace root:
|
|
168
|
+
|
|
169
|
+
```yaml
|
|
170
|
+
packing:
|
|
171
|
+
enabled: true
|
|
172
|
+
compression:
|
|
173
|
+
default:
|
|
174
|
+
algorithm: gzip # gzip, brotli, zstd, xz, or none
|
|
175
|
+
level: 6
|
|
176
|
+
rules:
|
|
177
|
+
- path: node_modules
|
|
178
|
+
strategy: full
|
|
179
|
+
- path: vendor
|
|
180
|
+
strategy: full
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### How It Works
|
|
184
|
+
|
|
185
|
+
1. **Tree Hash**: Directories are fingerprinted using mtime+size (30x faster than MD5)
|
|
186
|
+
2. **Pack Detection**: `aethel status` shows pack states (P+, PL, PR, P=, P!)
|
|
187
|
+
3. **Compression**: Archives use gzip/brotli (built-in) or zstd/xz (if installed)
|
|
188
|
+
|
|
189
|
+
### Pack Status Codes
|
|
190
|
+
|
|
191
|
+
| Code | Meaning |
|
|
192
|
+
|------|---------|
|
|
193
|
+
| `P+` | New pack (not yet synced) |
|
|
194
|
+
| `PL` | Pack changed locally |
|
|
195
|
+
| `PR` | Pack changed on Drive |
|
|
196
|
+
| `P=` | Pack up to date |
|
|
197
|
+
| `P!` | Pack conflict |
|
|
198
|
+
|
|
199
|
+
Use `aethel status --verbose` to show synced packs.
|
|
153
200
|
|
|
154
201
|
## Ignore Patterns
|
|
155
202
|
|
|
@@ -166,11 +213,11 @@ build/
|
|
|
166
213
|
|
|
167
214
|
## Environment Variables
|
|
168
215
|
|
|
169
|
-
| Variable
|
|
170
|
-
|
|
171
|
-
| `GOOGLE_DRIVE_CREDENTIALS_PATH` | `~/.config/aethel/credentials.json` | Path to OAuth credentials
|
|
172
|
-
| `GOOGLE_DRIVE_TOKEN_PATH`
|
|
173
|
-
| `AETHEL_DRIVE_CONCURRENCY`
|
|
216
|
+
| Variable | Default | Description |
|
|
217
|
+
| --------------------------------- | ------------------------------------- | --------------------------------- |
|
|
218
|
+
| `GOOGLE_DRIVE_CREDENTIALS_PATH` | `~/.config/aethel/credentials.json` | Path to OAuth credentials |
|
|
219
|
+
| `GOOGLE_DRIVE_TOKEN_PATH` | `~/.config/aethel/token.json` | Path to cached OAuth token |
|
|
220
|
+
| `AETHEL_DRIVE_CONCURRENCY` | `40` | Max concurrent Drive API requests |
|
|
174
221
|
|
|
175
222
|
## Architecture
|
|
176
223
|
|
|
@@ -187,10 +234,13 @@ src/
|
|
|
187
234
|
│ ├── drive-api.js Google Drive API wrapper
|
|
188
235
|
│ ├── local-fs.js Local filesystem operations
|
|
189
236
|
│ ├── remote-cache.js Short-lived remote file cache
|
|
190
|
-
│ ├── snapshot.js
|
|
237
|
+
│ ├── snapshot.js Local scanning & snapshot creation
|
|
191
238
|
│ ├── staging.js Stage/unstage operations
|
|
192
239
|
│ ├── sync.js Execute staged changes
|
|
193
|
-
│
|
|
240
|
+
│ ├── ignore.js .aethelignore pattern matching
|
|
241
|
+
│ ├── compress.js Multi-algorithm compression (gzip, brotli, zstd, xz)
|
|
242
|
+
│ ├── pack.js Tar archive operations & tree hash
|
|
243
|
+
│ └── pack-manifest.js Pack manifest CRUD operations
|
|
194
244
|
└── tui/
|
|
195
245
|
├── app.js React (Ink) dual-pane component
|
|
196
246
|
├── index.js TUI entry
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -45,6 +45,15 @@ The core design is not a live mirror between local storage and Drive. Instead, s
|
|
|
45
45
|
- Manages `.aethelignore`
|
|
46
46
|
- `src/core/remote-cache.js`
|
|
47
47
|
- Short-lived cache for remote listings
|
|
48
|
+
- `src/core/compress.js`
|
|
49
|
+
- Multi-algorithm compression (gzip, brotli, zstd, xz)
|
|
50
|
+
- Compression profiles and algorithm detection
|
|
51
|
+
- `src/core/pack.js`
|
|
52
|
+
- Tar archive creation and extraction
|
|
53
|
+
- Tree hash algorithm for fast directory fingerprinting
|
|
54
|
+
- `src/core/pack-manifest.js`
|
|
55
|
+
- CRUD operations for pack manifest
|
|
56
|
+
- Tracks packed directories and their sync state
|
|
48
57
|
|
|
49
58
|
### 2.3 State Storage Layer
|
|
50
59
|
|
|
@@ -55,16 +64,20 @@ After workspace initialization, the project root contains:
|
|
|
55
64
|
config.json
|
|
56
65
|
index.json
|
|
57
66
|
.hash-cache.json
|
|
67
|
+
pack-manifest.json
|
|
58
68
|
snapshots/
|
|
59
69
|
latest.json
|
|
60
70
|
history/
|
|
71
|
+
.aethelconfig
|
|
61
72
|
```
|
|
62
73
|
|
|
63
74
|
- `config.json`: sync root configuration
|
|
64
75
|
- `index.json`: currently staged operations
|
|
65
76
|
- `.hash-cache.json`: local file hash cache
|
|
77
|
+
- `pack-manifest.json`: tracks packed directories and their sync state
|
|
66
78
|
- `snapshots/latest.json`: baseline state after the most recent successful sync
|
|
67
79
|
- `snapshots/history/`: archived older snapshots
|
|
80
|
+
- `.aethelconfig` (workspace root): YAML configuration for directory packing
|
|
68
81
|
|
|
69
82
|
## 3. Core Data Flow
|
|
70
83
|
|
|
@@ -124,6 +137,7 @@ That means the system does not compare only "local vs remote". It also asks:
|
|
|
124
137
|
|
|
125
138
|
`src/core/diff.js` classifies changes as:
|
|
126
139
|
|
|
140
|
+
**File changes:**
|
|
127
141
|
- `remote_added`
|
|
128
142
|
- `remote_modified`
|
|
129
143
|
- `remote_deleted`
|
|
@@ -132,6 +146,13 @@ That means the system does not compare only "local vs remote". It also asks:
|
|
|
132
146
|
- `local_deleted`
|
|
133
147
|
- `conflict`
|
|
134
148
|
|
|
149
|
+
**Pack changes (for packed directories):**
|
|
150
|
+
- `pack_new` - directory newly configured for packing
|
|
151
|
+
- `pack_local_modified` - packed directory changed locally
|
|
152
|
+
- `pack_remote_modified` - packed directory changed on Drive
|
|
153
|
+
- `pack_synced` - packed directory up to date
|
|
154
|
+
- `pack_conflict` - both sides changed the packed directory
|
|
155
|
+
|
|
135
156
|
It also provides default suggested actions for each category:
|
|
136
157
|
|
|
137
158
|
- Drive added/modified -> `download`
|
|
@@ -139,6 +160,9 @@ It also provides default suggested actions for each category:
|
|
|
139
160
|
- Local added/modified -> `upload`
|
|
140
161
|
- Local deleted -> `delete_remote`
|
|
141
162
|
- Both sides changed the same path -> `conflict`
|
|
163
|
+
- Pack new/local modified -> `push_pack`
|
|
164
|
+
- Pack remote modified -> `pull_pack`
|
|
165
|
+
- Pack conflict -> `resolve_pack`
|
|
142
166
|
|
|
143
167
|
### 4.3 Execution Model
|
|
144
168
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aethel",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Git-style Google Drive sync CLI with interactive TUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"src/",
|
|
29
|
+
"scripts/demo.js",
|
|
30
|
+
"scripts/render-demo-gif.py",
|
|
31
|
+
"scripts/render-demo-screenshot.js",
|
|
32
|
+
"docs/demo.gif",
|
|
33
|
+
"docs/demo-screenshot.svg",
|
|
29
34
|
"LICENSE",
|
|
30
35
|
"README.md",
|
|
31
36
|
"CHANGELOG.md",
|
|
@@ -33,6 +38,12 @@
|
|
|
33
38
|
".env.example"
|
|
34
39
|
],
|
|
35
40
|
"scripts": {
|
|
41
|
+
"demo": "node scripts/demo.js",
|
|
42
|
+
"demo:gif": "python3 scripts/render-demo-gif.py",
|
|
43
|
+
"demo:screenshot": "node scripts/render-demo-screenshot.js",
|
|
44
|
+
"demo:setup": "python3 scripts/generate-cast.py && agg docs/setup.cast docs/setup.gif --font-size 32 --theme dracula",
|
|
45
|
+
"demo:init": "python3 scripts/generate-init-cast.py && agg docs/init.cast docs/init.gif --font-size 32 --theme dracula",
|
|
46
|
+
"demo:usage": "python3 scripts/generate-usage-cast.py && agg docs/usage.cast docs/usage.gif --font-size 32 --theme dracula",
|
|
36
47
|
"start": "node src/cli.js",
|
|
37
48
|
"auth": "node src/cli.js auth",
|
|
38
49
|
"clean": "node src/cli.js clean",
|
|
@@ -48,12 +59,14 @@
|
|
|
48
59
|
"commander": "^14.0.3",
|
|
49
60
|
"googleapis": "^171.4.0",
|
|
50
61
|
"ignore": "^7.0.5",
|
|
51
|
-
"ink": "^
|
|
62
|
+
"ink": "^7.0.0",
|
|
52
63
|
"ink-select-input": "^6.0.0",
|
|
53
64
|
"ink-spinner": "^5.0.0",
|
|
54
65
|
"ink-text-input": "^6.0.0",
|
|
55
66
|
"open": "^11.0.0",
|
|
56
|
-
"react": "^19.2.4"
|
|
67
|
+
"react": "^19.2.4",
|
|
68
|
+
"tar": "^7.5.13",
|
|
69
|
+
"yaml": "^2.8.3"
|
|
57
70
|
},
|
|
58
71
|
"engines": {
|
|
59
72
|
"node": ">=18"
|