Skip to content

Commit 7b92982

Browse files
authored
docs: rewrite README with comprehensive docs (#1)
docs: rewrite README with badges, TOC, and comprehensive sections
1 parent 1f8188f commit 7b92982

14 files changed

Lines changed: 174 additions & 60 deletions

File tree

README.md

Lines changed: 144 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,121 @@
11
# apple-notes-sync
22

3-
A macOS CLI tool that exports Apple Notes to a Git repository as Markdown files, with optional Google Drive sync via rclone.
3+
[![Go](https://img.shields.io/badge/Go-1.26+-00ADD8?logo=go&logoColor=white)](https://go.dev)
4+
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5+
[![Platform: macOS](https://img.shields.io/badge/platform-macOS-brightgreen)](#prerequisites)
6+
[![CI](https://github.com/PyAgni/apple-notes-syncer/actions/workflows/ci.yml/badge.svg)](https://github.com/PyAgni/apple-notes-syncer/actions)
7+
8+
**Seamless, automatic Git backup of your Apple Notes — with optional Google Drive sync.**
9+
One command (or hourly launchd) turns your Notes app into a version-controlled Markdown repo.
10+
11+
## Table of Contents
12+
13+
- [Quick Start](#quick-start)
14+
- [Features](#features)
15+
- [Why apple-notes-sync?](#why-apple-notes-sync)
16+
- [In Action](#in-action)
17+
- [Prerequisites](#prerequisites)
18+
- [Installation](#installation)
19+
- [Configuration](#configuration)
20+
- [Running Manually](#running-manually)
21+
- [Scheduling with launchd](#scheduling-with-launchd)
22+
- [Google Drive Setup](#google-drive-setup)
23+
- [How Renames and Deletions Are Handled](#how-renames-and-deletions-are-handled)
24+
- [Limitations](#limitations)
25+
- [Troubleshooting](#troubleshooting)
26+
- [Alternatives](#alternatives)
27+
- [Contributing](#contributing)
28+
- [License](#license)
29+
30+
## Quick Start
31+
32+
```bash
33+
# 1. Install
34+
go install github.com/PyAgni/apple-notes-syncer/cmd/apple-notes-sync@latest
35+
36+
# 2. Create a Git repo for your notes
37+
mkdir ~/Notes && cd ~/Notes
38+
git init
39+
git remote add origin git@github.com:yourusername/my-notes.git
40+
git commit --allow-empty -m "init"
41+
git push -u origin main
42+
43+
# 3. Configure
44+
cp configs/config.example.yaml ~/.apple-notes-sync.yaml
45+
# Edit repo_path in the config file
46+
47+
# 4. Run
48+
apple-notes-sync --repo-path ~/Notes
49+
50+
# 5. (Optional) Schedule hourly syncs
51+
make launchd
52+
```
53+
54+
Done. Your notes now live in Git with full history.
455

556
## Features
657

758
- Extracts all notes from the macOS Notes app via AppleScript
859
- Converts HTML note bodies to clean Markdown
960
- Mirrors the Notes folder hierarchy into the repository
10-
- Adds YAML front matter (title, dates, account) to each note
61+
- Adds a metadata table (ID, dates, account) to each note
1162
- Commits with timestamped messages and pushes to your remote
1263
- Optionally syncs to Google Drive via rclone
1364
- Cleans up notes that were deleted from Apple Notes
1465
- Configurable via CLI flags, environment variables, or YAML config file
1566

67+
## Why apple-notes-sync?
68+
69+
Apple Notes is great on your Mac and iPhone, but terrible for backups, version history, or migrating to Obsidian/Logseq.
70+
71+
Other exporters are one-shot scripts. This tool gives you **continuous sync**:
72+
73+
- **Automatic Git commits + push** — full version history of every edit
74+
- **Orphan cleanup** — deleted notes disappear from the repo automatically
75+
- **Hourly launchd scheduling** — set it and forget it
76+
- **rclone integration** — Google Drive as a bonus backup layer
77+
- **Folder mirroring** — your Notes folder structure is preserved exactly
78+
79+
> **Note**: This is a one-way export. Edits made to the Markdown files do not flow back to Apple Notes.
80+
81+
## In Action
82+
83+
<!-- TODO: Add demo GIF or screenshot -->
84+
<!-- ![Demo](https://github.com/PyAgni/apple-notes-syncer/raw/main/assets/demo.gif) -->
85+
86+
Example output for a single note:
87+
88+
```markdown
89+
# My Project Ideas
90+
91+
Content of the note converted to clean Markdown...
92+
93+
- Bullet points preserved
94+
- [Links](https://example.com) converted properly
95+
- **Bold** and *italic* formatting kept
96+
97+
---
98+
99+
| ID | Created | Modified | Account | Shared |
100+
|----|---------|----------|---------|--------|
101+
| x-coredata://abc123 | 2026-03-18 16:00:00 | 2026-03-20 09:30:00 | iCloud | No |
102+
```
103+
104+
Repository structure mirrors your Notes folders:
105+
106+
```
107+
~/Notes/
108+
├── Work/
109+
│ ├── Meeting Notes.md
110+
│ └── Project Ideas.md
111+
├── Personal/
112+
│ ├── Travel Plans.md
113+
│ └── Reading List.md
114+
├── Recipes/
115+
│ └── Pasta Carbonara.md
116+
└── ...
117+
```
118+
16119
## Prerequisites
17120

18121
- **macOS** (required — uses AppleScript to access Notes)
@@ -33,20 +136,7 @@ make install
33136
### Using `go install`
34137

35138
```bash
36-
go install github.com/agni/apple-notes-sync/cmd/apple-notes-sync@latest
37-
```
38-
39-
## One-time repo setup
40-
41-
Create and initialize a Git repository for your notes:
42-
43-
```bash
44-
mkdir ~/Notes
45-
cd ~/Notes
46-
git init
47-
git remote add origin git@github.com:yourusername/my-notes.git
48-
git commit --allow-empty -m "init"
49-
git push -u origin main
139+
go install github.com/PyAgni/apple-notes-syncer/cmd/apple-notes-sync@latest
50140
```
51141

52142
## Configuration
@@ -58,6 +148,18 @@ Configuration is loaded from (in order of precedence):
58148
3. YAML config file (`~/.apple-notes-sync.yaml`)
59149
4. Defaults
60150

151+
Minimal config to get started:
152+
153+
```yaml
154+
repo_path: ~/Notes
155+
```
156+
157+
See [`configs/config.example.yaml`](configs/config.example.yaml) for all options. Copy it:
158+
159+
```bash
160+
cp configs/config.example.yaml ~/.apple-notes-sync.yaml
161+
```
162+
61163
### CLI flags
62164

63165
```
@@ -82,15 +184,6 @@ export ANS_RCLONE_REMOTE_NAME=gdrive
82184
export ANS_RCLONE_REMOTE_PATH=AppleNotes
83185
```
84186

85-
### YAML config file
86-
87-
See [`configs/config.example.yaml`](configs/config.example.yaml) for a complete reference. Copy it to get started:
88-
89-
```bash
90-
cp configs/config.example.yaml ~/.apple-notes-sync.yaml
91-
# Edit with your settings
92-
```
93-
94187
<details>
95188
<summary>Full config reference</summary>
96189

@@ -119,7 +212,7 @@ cp configs/config.example.yaml ~/.apple-notes-sync.yaml
119212
| `attachments.max_size_mb` | int | `50` | Max attachment size in MB |
120213
| `attachments.dir` | string | `"_attachments"` | Attachment subdirectory |
121214
| `dry_run` | bool | `false` | Preview mode |
122-
| `front_matter` | bool | `true` | Add YAML front matter |
215+
| `front_matter` | bool | `true` | Add metadata table to notes |
123216
| `clean_orphans` | bool | `true` | Remove deleted notes |
124217
| `timeout` | duration | `120s` | AppleScript timeout |
125218
| `commit_template` | string | see below | Commit message Go template |
@@ -130,7 +223,7 @@ Template fields: `.Timestamp`, `.Written`, `.Total`, `.Skipped`
130223

131224
</details>
132225

133-
## Running manually
226+
## Running Manually
134227

135228
```bash
136229
# Basic run
@@ -169,7 +262,7 @@ Create the log directory:
169262
mkdir -p ~/Library/Logs/apple-notes-sync
170263
```
171264

172-
## Google Drive setup
265+
## Google Drive Setup
173266

174267
1. Install rclone: `brew install rclone`
175268

@@ -198,14 +291,34 @@ rclone:
198291
rclone sync ~/Notes gdrive:AppleNotes --dry-run
199292
```
200293

201-
## How renames and deletions are handled
294+
## How Renames and Deletions Are Handled
202295

203296
- **Renamed notes**: A renamed note appears as a new file and the old filename is removed (if `clean_orphans: true`). This shows as a delete + add in git, which GitHub renders as a rename if content is similar.
204297
- **Deleted notes**: When a note is deleted from Apple Notes, the corresponding `.md` file is removed on the next sync (if `clean_orphans: true`).
205298
- **Moved notes**: Moving a note to a different folder creates the file in the new directory and removes it from the old one.
206299

300+
## Limitations
301+
302+
- **macOS only** — relies on AppleScript to access the Notes app
303+
- **One-way export** — edits to Markdown files do not sync back to Apple Notes
304+
- **AppleScript permissions required** — on first run, macOS will prompt to allow automation access
305+
- **Attachments >50 MB skipped** by default (configurable via `attachments.max_size_mb`)
306+
- **Large note libraries** may take a few minutes on the first sync (AppleScript extraction is the bottleneck)
307+
308+
## Troubleshooting
309+
310+
| Problem | Solution |
311+
|---------|----------|
312+
| AppleScript timeout | Increase `timeout: 300s` in your config |
313+
| Permission denied on Notes | System Settings > Privacy & Security > Automation > allow Terminal (or your terminal app) |
314+
| rclone OAuth expired | Run `rclone config` again to re-authenticate |
315+
| Unicode errors in dates | Already handled — the parser normalizes Unicode whitespace from macOS locales |
316+
| "repo_path is required" | Set `repo_path` in your config file or pass `--repo-path` |
317+
207318
## Contributing
208319

320+
Contributions are welcome! Please open an issue or submit a pull request.
321+
209322
1. Fork the repository
210323
2. Create a feature branch: `git checkout -b my-feature`
211324
3. Make your changes
@@ -217,10 +330,11 @@ rclone sync ~/Notes gdrive:AppleNotes --dry-run
217330
```bash
218331
make build # Build the binary
219332
make test # Run tests with race detector
220-
make check-coverage # Run tests and check coverage 80%
333+
make check-coverage # Run tests and check coverage >= 80%
221334
make lint # Run go vet + staticcheck
222335
make fmt # Format code
223336
make tidy # Tidy go modules
337+
make help # Show all available targets
224338
```
225339

226340
## License

cmd/apple-notes-sync/main.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ import (
1313
"github.com/spf13/cobra"
1414
"go.uber.org/zap"
1515

16-
"github.com/agni/apple-notes-sync/internal/applescript"
17-
"github.com/agni/apple-notes-sync/internal/config"
18-
"github.com/agni/apple-notes-sync/internal/converter"
19-
"github.com/agni/apple-notes-sync/internal/filesystem"
20-
"github.com/agni/apple-notes-sync/internal/gitops"
21-
"github.com/agni/apple-notes-sync/internal/logging"
22-
"github.com/agni/apple-notes-sync/internal/rclone"
23-
"github.com/agni/apple-notes-sync/internal/shell"
24-
"github.com/agni/apple-notes-sync/internal/syncer"
16+
"github.com/PyAgni/apple-notes-syncer/internal/applescript"
17+
"github.com/PyAgni/apple-notes-syncer/internal/config"
18+
"github.com/PyAgni/apple-notes-syncer/internal/converter"
19+
"github.com/PyAgni/apple-notes-syncer/internal/filesystem"
20+
"github.com/PyAgni/apple-notes-syncer/internal/gitops"
21+
"github.com/PyAgni/apple-notes-syncer/internal/logging"
22+
"github.com/PyAgni/apple-notes-syncer/internal/rclone"
23+
"github.com/PyAgni/apple-notes-syncer/internal/shell"
24+
"github.com/PyAgni/apple-notes-syncer/internal/syncer"
2525
)
2626

2727
// Build-time variables set via -ldflags.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module github.com/agni/apple-notes-sync
1+
module github.com/PyAgni/apple-notes-syncer
22

33
go 1.26.1
44

internal/applescript/extractor.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import (
77

88
"go.uber.org/zap"
99

10-
"github.com/agni/apple-notes-sync/internal/model"
11-
"github.com/agni/apple-notes-sync/internal/shell"
10+
"github.com/PyAgni/apple-notes-syncer/internal/model"
11+
"github.com/PyAgni/apple-notes-syncer/internal/shell"
1212
)
1313

1414
//go:embed scripts

internal/applescript/extractor_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99
"github.com/stretchr/testify/require"
1010
"go.uber.org/zap"
1111

12-
"github.com/agni/apple-notes-sync/internal/model"
13-
"github.com/agni/apple-notes-sync/internal/shell"
12+
"github.com/PyAgni/apple-notes-syncer/internal/model"
13+
"github.com/PyAgni/apple-notes-syncer/internal/shell"
1414
)
1515

1616
// MockCommandExecutor is a testify mock for shell.CommandExecutor.

internal/applescript/parser.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"strings"
88
"time"
99

10-
"github.com/agni/apple-notes-sync/internal/model"
10+
"github.com/PyAgni/apple-notes-syncer/internal/model"
1111
)
1212

1313
const (

internal/filesystem/writer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313

1414
"go.uber.org/zap"
1515

16-
"github.com/agni/apple-notes-sync/internal/model"
16+
"github.com/PyAgni/apple-notes-syncer/internal/model"
1717
)
1818

1919
// NoteWriter manages writing notes to the filesystem.

internal/filesystem/writer_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"github.com/stretchr/testify/require"
1313
"go.uber.org/zap"
1414

15-
"github.com/agni/apple-notes-sync/internal/model"
15+
"github.com/PyAgni/apple-notes-syncer/internal/model"
1616
)
1717

1818
func newTestWriter(t *testing.T, subdir string, frontMatter bool) (*FSNoteWriter, string) {

internal/gitops/git.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
"go.uber.org/zap"
1111

12-
"github.com/agni/apple-notes-sync/internal/shell"
12+
"github.com/PyAgni/apple-notes-syncer/internal/shell"
1313
)
1414

1515
// GitClient performs git operations on the notes repository.

internal/gitops/git_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/stretchr/testify/require"
1010
"go.uber.org/zap"
1111

12-
"github.com/agni/apple-notes-sync/internal/shell"
12+
"github.com/PyAgni/apple-notes-syncer/internal/shell"
1313
)
1414

1515
// MockCommandExecutor is a testify mock for shell.CommandExecutor.

0 commit comments

Comments
 (0)