aethel 0.1.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/.env.example ADDED
@@ -0,0 +1,2 @@
1
+ GOOGLE_DRIVE_CREDENTIALS_PATH=credentials.json
2
+ GOOGLE_DRIVE_TOKEN_PATH=token.json
package/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial npm release of the Aethel CLI and Ink TUI
6
+ - Google Drive OAuth authentication and workspace initialization
7
+ - Git-style sync workflow with snapshot, diff, staging, pull, push, and commit
8
+ - Conflict resolution, restore, ignore management, and history inspection
9
+ - Duplicate-folder detection and reconciliation for Google Drive
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Aethel Contributors
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 ADDED
@@ -0,0 +1,190 @@
1
+ # Aethel
2
+
3
+ Git-style Google Drive sync CLI with an interactive terminal UI.
4
+
5
+ Aethel lets you manage a local workspace mirrored to Google Drive using
6
+ familiar commands (`init`, `status`, `diff`, `add`, `commit`, `pull`, `push`)
7
+ and ships with a dual-pane TUI for browsing, uploading, and deleting files
8
+ across local and remote directories.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install -g aethel
14
+ ```
15
+
16
+ Or install from source:
17
+
18
+ ```bash
19
+ git clone https://github.com/aethel/aethel.git
20
+ cd aethel
21
+ npm install
22
+ npm run install:cli # symlinks `aethel` into ~/.local/bin
23
+ ```
24
+
25
+ **Requirements:** Node.js >= 18
26
+
27
+ ## Release Readiness
28
+
29
+ - npm package metadata is defined in `package.json`
30
+ - publish contents are restricted through the `files` allowlist
31
+ - local secrets such as `credentials.json` and `token.json` are excluded from Git and npm publish
32
+ - release validation runs with `npm test` and `npm run pack:check`
33
+
34
+ ## GitHub Automation
35
+
36
+ - `CI`: runs `npm ci`, `npm test`, and `npm run pack:check` on every push and pull request across Node.js 18, 20, and 22
37
+ - `Release`: publishes to npm with trusted publishing when a GitHub Release is published or when the workflow is triggered manually
38
+ - `Dependabot`: opens weekly dependency update PRs for npm packages and GitHub Actions
39
+
40
+ To enable npm trusted publishing, configure a trusted publisher for package `aethel` on npm with:
41
+
42
+ - provider: GitHub Actions
43
+ - owner: `CCJ-0617`
44
+ - repository: `Aethel`
45
+ - workflow filename: `release.yml`
46
+
47
+ After trusted publishing works once, disable token-based publishing in npm package settings and revoke any old npm automation tokens.
48
+
49
+ ## Google Cloud Setup
50
+
51
+ 1. Create a project in the [Google Cloud Console](https://console.cloud.google.com/).
52
+ 2. Enable the **Google Drive API**.
53
+ 3. Create an **OAuth 2.0 Client ID** (application type: Desktop).
54
+ 4. Download the credentials JSON and save it as `credentials.json` in your
55
+ project root (or set `GOOGLE_DRIVE_CREDENTIALS_PATH`).
56
+
57
+ Keep `credentials.json` and `token.json` local only. Do not commit them to GitHub.
58
+
59
+ ## Quick Start
60
+
61
+ ```bash
62
+ # Authenticate and verify access
63
+ aethel auth
64
+
65
+ # Initialize a sync workspace
66
+ aethel init --local-path ./workspace
67
+
68
+ # Optional: target a specific Drive folder
69
+ aethel init --local-path ./workspace \
70
+ --drive-folder <folder-id> \
71
+ --drive-folder-name "My Project"
72
+
73
+ # Check status and sync
74
+ aethel status
75
+ aethel diff --side all
76
+ aethel add --all
77
+ aethel commit -m "initial sync"
78
+ aethel pull -m "pull"
79
+ aethel push -m "push"
80
+ aethel log -n 10
81
+ ```
82
+
83
+ ## Commands
84
+
85
+ | Command | Description |
86
+ |---------|-------------|
87
+ | `auth` | Run OAuth flow, create `token.json`, verify Drive access |
88
+ | `init` | Initialize a local sync workspace |
89
+ | `status` | Show workspace status (local vs remote changes) |
90
+ | `diff` | Show detailed file differences |
91
+ | `add` | Stage changes for commit |
92
+ | `reset` | Unstage changes |
93
+ | `commit` | Execute staged sync operations |
94
+ | `pull` | Fetch remote changes and commit |
95
+ | `push` | Stage and push local changes |
96
+ | `log` | Show sync history |
97
+ | `fetch` | Refresh remote state without applying changes |
98
+ | `resolve` | Resolve conflicts by choosing local, remote, or both |
99
+ | `ignore` | List, test, or create `.aethelignore` patterns |
100
+ | `show` | Show a saved snapshot |
101
+ | `restore` | Restore files from the last snapshot |
102
+ | `rm` | Remove local files and stage remote deletion |
103
+ | `mv` | Move or rename local files |
104
+ | `clean` | List and optionally trash/delete accessible Drive files |
105
+ | `dedupe-folders` | Detect and merge duplicate remote folders |
106
+ | `tui` | Launch interactive terminal UI |
107
+
108
+ ## TUI
109
+
110
+ ```bash
111
+ aethel tui
112
+ ```
113
+
114
+ Dual-pane file browser with local filesystem on the left and Google Drive on
115
+ the right.
116
+
117
+ | Key | Action |
118
+ |-----|--------|
119
+ | `Tab` | Switch focus between Local / Drive pane |
120
+ | `Left` / `Right` | Navigate up / into directories |
121
+ | `u` | Upload selected local file or folder to current Drive directory |
122
+ | `s` | Batch sync local folder contents to current Drive directory |
123
+ | `U` | Upload from a manually entered local path |
124
+ | `n` | Rename selected local item |
125
+ | `x` | Delete selected local item |
126
+ | `Space` | Toggle selection in Drive pane |
127
+ | `t` / `d` | Trash / permanently delete selected Drive items |
128
+ | `/` | Filter by name |
129
+
130
+ ## Deduplication
131
+
132
+ When duplicate folders accumulate from multi-device conflicts, run:
133
+
134
+ ```bash
135
+ # Dry run — report duplicates without changing anything
136
+ aethel dedupe-folders
137
+
138
+ # Execute — merge duplicates and trash empty losers
139
+ aethel dedupe-folders --execute
140
+ ```
141
+
142
+ The deduplication engine processes folders deepest-first to guarantee
143
+ single-pass convergence, caches child state in memory to minimize API calls,
144
+ and runs independent merge groups in parallel. Paths matching `.aethelignore`
145
+ are excluded automatically.
146
+
147
+ ## Ignore Patterns
148
+
149
+ Create a `.aethelignore` file (gitignore syntax) in your workspace root to
150
+ exclude paths from sync and deduplication:
151
+
152
+ ```gitignore
153
+ .venv/
154
+ node_modules/
155
+ __pycache__/
156
+ .idea/
157
+ .vscode/
158
+ dist/
159
+ build/
160
+ ```
161
+
162
+ A default `.aethelignore` is created on `init`.
163
+
164
+ ## Environment Variables
165
+
166
+ | Variable | Default | Description |
167
+ |----------|---------|-------------|
168
+ | `GOOGLE_DRIVE_CREDENTIALS_PATH` | `credentials.json` | Path to OAuth credentials |
169
+ | `GOOGLE_DRIVE_TOKEN_PATH` | `token.json` | Path to cached OAuth token |
170
+ | `AETHEL_DRIVE_CONCURRENCY` | `40` | Max concurrent Drive API requests |
171
+
172
+ ## Architecture
173
+
174
+ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for module structure and data
175
+ flow.
176
+
177
+ ## Publishing
178
+
179
+ ```bash
180
+ npm test
181
+ npm run pack:check
182
+ npm publish
183
+ ```
184
+
185
+ For a GitHub release, push the tagged version and create a release that matches
186
+ the published npm version.
187
+
188
+ ## License
189
+
190
+ [MIT](LICENSE)
@@ -0,0 +1,237 @@
1
+ # Aethel Architecture
2
+
3
+ ## 1. Project Positioning
4
+
5
+ Aethel is a synchronization tool that uses Google Drive as its remote storage. It exposes two interface layers:
6
+
7
+ - CLI: provides a Git-like workflow with `auth`, `init`, `status`, `diff`, `add`, `commit`, `pull`, and `push`.
8
+ - TUI: provides an interactive dual-pane interface for local files and Google Drive.
9
+
10
+ The core design is not a live mirror between local storage and Drive. Instead, synchronization is managed through a `snapshot + diff + staging + execute` pipeline.
11
+
12
+ ## 2. Module Layers
13
+
14
+ ### 2.1 Entry Layer
15
+
16
+ - `src/cli.js`
17
+ - Command-line entry point
18
+ - Parses commands, assembles workspace state, and invokes core sync capabilities
19
+ - `src/tui/index.js`
20
+ - Starts the Ink TUI
21
+ - `src/tui/app.js`
22
+ - Implements dual-pane file browsing, filtering, uploads, deletion, and interactive operations
23
+
24
+ ### 2.2 Core Capability Layer
25
+
26
+ - `src/core/auth.js`
27
+ - OAuth authentication
28
+ - Creates and reads `token.json`
29
+ - `src/core/drive-api.js`
30
+ - Google Drive API wrapper
31
+ - Listing, downloading, uploading, deleting, folder creation, and batch operations
32
+ - `src/core/local-fs.js`
33
+ - Local filesystem operations
34
+ - `src/core/snapshot.js`
35
+ - Scans local files, computes md5 hashes, and builds snapshots
36
+ - `src/core/diff.js`
37
+ - Compares snapshot / remote / local state and produces change sets
38
+ - `src/core/staging.js`
39
+ - Manages the staging area
40
+ - `src/core/sync.js`
41
+ - Executes staged operations
42
+ - `src/core/config.js`
43
+ - Manages `.aethel/` state files
44
+ - `src/core/ignore.js`
45
+ - Manages `.aethelignore`
46
+ - `src/core/remote-cache.js`
47
+ - Short-lived cache for remote listings
48
+
49
+ ### 2.3 State Storage Layer
50
+
51
+ After workspace initialization, the project root contains:
52
+
53
+ ```text
54
+ .aethel/
55
+ config.json
56
+ index.json
57
+ .hash-cache.json
58
+ snapshots/
59
+ latest.json
60
+ history/
61
+ ```
62
+
63
+ - `config.json`: sync root configuration
64
+ - `index.json`: currently staged operations
65
+ - `.hash-cache.json`: local file hash cache
66
+ - `snapshots/latest.json`: baseline state after the most recent successful sync
67
+ - `snapshots/history/`: archived older snapshots
68
+
69
+ ## 3. Core Data Flow
70
+
71
+ ```mermaid
72
+ flowchart LR
73
+ User["User"]
74
+ CLI["CLI / TUI"]
75
+ Auth["auth.js"]
76
+ Config["config.js"]
77
+ Local["snapshot.js + local-fs.js"]
78
+ Cache["remote-cache.js"]
79
+ Drive["drive-api.js"]
80
+ Diff["diff.js"]
81
+ Stage["staging.js"]
82
+ Sync["sync.js"]
83
+ State[".aethel state"]
84
+ Google["Google Drive"]
85
+ Disk["Local Files"]
86
+
87
+ User --> CLI
88
+ CLI --> Auth
89
+ CLI --> Config
90
+ CLI --> Local
91
+ CLI --> Cache
92
+ CLI --> Drive
93
+ Local --> Disk
94
+ Cache --> State
95
+ Config --> State
96
+ Drive --> Google
97
+ Local --> Diff
98
+ Drive --> Diff
99
+ State --> Diff
100
+ Diff --> Stage
101
+ Stage --> State
102
+ Stage --> Sync
103
+ Sync --> Disk
104
+ Sync --> Google
105
+ Sync --> State
106
+ ```
107
+
108
+ ## 4. Synchronization Model
109
+
110
+ ### 4.1 Baseline
111
+
112
+ Aethel uses `snapshot` as the shared baseline:
113
+
114
+ - `snapshot.files`: remote file information from the last sync
115
+ - `snapshot.localFiles`: local file information from the last sync
116
+
117
+ That means the system does not compare only "local vs remote". It also asks:
118
+
119
+ - Has Drive changed since the last sync?
120
+ - Has local state changed since the last sync?
121
+ - Did both sides modify the same path?
122
+
123
+ ### 4.2 Diff Categories
124
+
125
+ `src/core/diff.js` classifies changes as:
126
+
127
+ - `remote_added`
128
+ - `remote_modified`
129
+ - `remote_deleted`
130
+ - `local_added`
131
+ - `local_modified`
132
+ - `local_deleted`
133
+ - `conflict`
134
+
135
+ It also provides default suggested actions for each category:
136
+
137
+ - Drive added/modified -> `download`
138
+ - Drive deleted -> `delete_local`
139
+ - Local added/modified -> `upload`
140
+ - Local deleted -> `delete_remote`
141
+ - Both sides changed the same path -> `conflict`
142
+
143
+ ### 4.3 Execution Model
144
+
145
+ `commit` is not a Git commit. It executes the synchronization actions currently staged:
146
+
147
+ 1. Read `index.json`
148
+ 2. Route each entry by action as `download / upload / delete_local / delete_remote`
149
+ 3. Execute local and remote operations
150
+ 4. Clear staged entries
151
+ 5. Rebuild the snapshot and write the new state to `.aethel/snapshots/latest.json`
152
+
153
+ ## 5. Relationship Between TUI and CLI
154
+
155
+ Both interfaces share the same core modules. The difference is only the interaction surface:
156
+
157
+ - CLI is oriented toward predictable, scriptable sync workflows
158
+ - TUI is oriented toward manual inspection, fast operations, and hands-on Drive / Local management
159
+
160
+ The TUI is not a separate synchronization engine. It directly calls `drive-api.js` and `local-fs.js` for interactive file management.
161
+
162
+ ## 6. Recommended Workflow
163
+
164
+ ### 6.1 Initial Workspace Setup
165
+
166
+ ```bash
167
+ npm install
168
+ npm run auth
169
+ node src/cli.js init --local-path ./workspace --drive-folder <drive-folder-id>
170
+ ```
171
+
172
+ Use this when:
173
+
174
+ - Starting a new project
175
+ - Creating a new sync root
176
+ - Intentionally syncing only a specific Drive folder
177
+
178
+ ### 6.2 Day-to-Day Sync Workflow
179
+
180
+ ```bash
181
+ node src/cli.js status
182
+ node src/cli.js diff --side all
183
+ node src/cli.js add --all
184
+ node src/cli.js commit -m "sync"
185
+ ```
186
+
187
+ This is the most reasonable standard flow because:
188
+
189
+ 1. `status` gives you the overall state first
190
+ 2. `diff` shows details and conflicts
191
+ 3. `add --all` stages the default suggested actions
192
+ 4. `commit` performs the actual synchronization
193
+
194
+ This preserves a manual confirmation point and helps avoid pushing a bad state to Drive or overwriting local files by mistake.
195
+
196
+ ### 6.3 When Conflicts Occur
197
+
198
+ Recommended flow:
199
+
200
+ 1. Run `status` or `diff`
201
+ 2. Identify `conflict` entries
202
+ 3. Decide explicitly whether to keep local, keep remote, or keep both
203
+ 4. Then stage and commit
204
+
205
+ Why:
206
+
207
+ - In Aethel, a conflict means both sides changed the same path after the snapshot
208
+ - This should not be resolved automatically because it can cause silent overwrites
209
+
210
+ ### 6.4 When Manual Inspection Is Needed
211
+
212
+ ```bash
213
+ npm run tui
214
+ ```
215
+
216
+ The TUI is useful for:
217
+
218
+ - Browsing the Drive structure
219
+ - Selecting items and deleting them
220
+ - Uploading files or entire folders directly from local storage
221
+ - Finding files quickly with filters
222
+
223
+ If the goal is traceable, repeatable synchronization, the CLI workflow should still be the default choice.
224
+
225
+ ## 7. Recommended Practices
226
+
227
+ - Use a single Drive root folder as the workspace remote root instead of syncing the entire My Drive directly.
228
+ - Run `status` or `diff` before every `commit`.
229
+ - Treat `.aethelignore` as a synchronization boundary control file, not just a UI filter.
230
+ - Keep `.hash-cache.json` for large workspaces to avoid recomputing all md5 values every time.
231
+ - Prefer the TUI or a dry run for risky operations before executing them for real.
232
+
233
+ ## 8. Future Improvements
234
+
235
+ - Split CLI commands and state transitions into a service layer to reduce how much workflow logic is aggregated in `src/cli.js`.
236
+ - Add documentation for the snapshot schema and index schema.
237
+ - Add automated tests, especially around diff/conflict handling and the sync executor.
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "aethel",
3
+ "version": "0.1.0",
4
+ "description": "Git-style Google Drive sync CLI with interactive TUI",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/aethel/aethel.git"
10
+ },
11
+ "homepage": "https://github.com/aethel/aethel#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/aethel/aethel/issues"
14
+ },
15
+ "keywords": [
16
+ "google-drive",
17
+ "sync",
18
+ "cli",
19
+ "tui",
20
+ "backup",
21
+ "drive",
22
+ "file-manager"
23
+ ],
24
+ "bin": {
25
+ "aethel": "./src/cli.js"
26
+ },
27
+ "files": [
28
+ "src/",
29
+ "LICENSE",
30
+ "README.md",
31
+ "CHANGELOG.md",
32
+ "docs/ARCHITECTURE.md",
33
+ ".env.example"
34
+ ],
35
+ "scripts": {
36
+ "start": "node src/cli.js",
37
+ "auth": "node src/cli.js auth",
38
+ "clean": "node src/cli.js clean",
39
+ "install:cli": "bash scripts/install.sh",
40
+ "pack:check": "npm pack --dry-run",
41
+ "prepublishOnly": "npm test && npm run pack:check",
42
+ "test": "node --test",
43
+ "tui": "node src/cli.js tui"
44
+ },
45
+ "dependencies": {
46
+ "chalk": "^5.3.0",
47
+ "commander": "^12.1.0",
48
+ "googleapis": "^140.0.1",
49
+ "ignore": "^7.0.5",
50
+ "ink": "^5.1.0",
51
+ "ink-select-input": "^6.0.0",
52
+ "ink-spinner": "^5.0.0",
53
+ "ink-text-input": "^6.0.0",
54
+ "open": "^10.1.0",
55
+ "react": "^18.3.1"
56
+ },
57
+ "engines": {
58
+ "node": ">=18"
59
+ }
60
+ }