slidesync 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Hails
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.
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: slidesync
3
+ Version: 0.1.0
4
+ Summary: Bidirectional sync between a Slidev markdown deck and Google Slides as native, editable objects
5
+ Author-email: Daniel Hails <slidesync@hails.info>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/DJRHails/slidesync
8
+ Project-URL: Bug Tracker, https://github.com/DJRHails/slidesync/issues
9
+ Keywords: slidev,google-slides,markdown,presentations,slides
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: google-api-python-client>=2
14
+ Requires-Dist: google-auth>=2
15
+ Requires-Dist: python-frontmatter>=1
16
+ Requires-Dist: loguru>=0.7
17
+ Dynamic: license-file
18
+
19
+ # slidesync
20
+
21
+ Bidirectional sync between a [Slidev](https://sli.dev) markdown deck and **Google
22
+ Slides** — as native, editable objects (title/body/bullets/tables/positioned
23
+ images, brand-styled text boxes), not pasted screenshots.
24
+
25
+ Version: 0.1.0
26
+
27
+ ```bash
28
+ uvx slidesync --help # run without installing
29
+ pip install slidesync # or install the CLI + library
30
+ ```
31
+
32
+ ## Why
33
+
34
+ Exporting a deck to images gives you something you can't edit; pasting markdown
35
+ by hand gives you something you can't version. `slidesync` keeps a `.slidev.md`
36
+ file as the source of truth and renders it into **real** Slides objects, so the
37
+ result stays fully editable in Google Slides — and `pull` reconstructs the
38
+ markdown back from those objects, so the loop is reversible.
39
+
40
+ - **`push`** — markdown → Slides (idempotent upsert, never a blind append).
41
+ - **`pull`** — Slides → markdown (handles multi-text-box and externally-authored
42
+ decks, bullet nesting, tables, images, and speaker notes).
43
+ - **`roundtrip`** — push a sample to a scratch deck, pull it back, assert the two
44
+ are semantically identical, delete the scratch deck.
45
+
46
+ ## Auth (no setup)
47
+
48
+ Auth is **borrowed from the [`gog`](https://github.com/) CLI** — no separate
49
+ OAuth client. `slidesync` reads the client id/secret from
50
+ `~/Library/Application Support/gogcli/credentials.json` and the refresh token via
51
+ `gog auth tokens export`, then mints a short-lived access token. The stored token
52
+ already carries the `slides` + `drive` scopes; the Slides API must be enabled on
53
+ the gog Cloud project. Override the account with `--account` or
54
+ `$SLIDESYNC_ACCOUNT`. (Currently macOS-only — it reads gog's macOS Application
55
+ Support path.)
56
+
57
+ ## Commands
58
+
59
+ | Command | Purpose |
60
+ |---------|---------|
61
+ | `slidesync push <file.slidev.md> [--deck ID] [--new "Title"] [--anchor SLIDE] [--prune] [--force]` | markdown → Slides |
62
+ | `slidesync pull <deckId> --out <file.md> [--all]` | Slides → markdown (`--all` includes non-managed slides) |
63
+ | `slidesync roundtrip [--keep]` | self-test: push a sample, pull, assert identical |
64
+ | `slidesync layouts <deckId>` | list a deck's theme layouts + placeholders |
65
+ | `slidesync make-templates <deckId>` | inject branded `{{token}}` template slides |
66
+
67
+ `push` resolves the target deck from (in order) `--deck`, `--new`, or a top-level
68
+ `deck:` frontmatter key. Relative image paths resolve against the markdown file's
69
+ directory.
70
+
71
+ ```bash
72
+ slidesync push deck.slidev.md # targets `deck:` frontmatter
73
+ slidesync push deck.slidev.md --new "Talk"
74
+ slidesync pull <id> --out deck.slidev.md
75
+ slidesync roundtrip
76
+ ```
77
+
78
+ ## Idempotent sync (upsert)
79
+
80
+ Each managed slide is created with `objectId = s2g_<keyHash>_<contentHash>`.
81
+ `keyHash` = per-slide `id:` frontmatter, else title slug, else index (survives
82
+ edits/reorders); `contentHash` is over a canonical render, so push → pull → push
83
+ is a no-op. Diff per run: identical hash → skip; same key, new content → replace;
84
+ new key → create. Removed slides are **kept** unless `--prune`. **Only `s2g_`
85
+ slides are ever touched** — hand-authored slides are invisible to the sync. A
86
+ hidden `<!-- s2g {...} -->` marker in speaker notes carries the human id, image
87
+ path, and template vars so `pull` can recover them.
88
+
89
+ ## Markdown dialect
90
+
91
+ Top-level frontmatter: `theme:`, `deck:`. Slides separated by `---`; each slide
92
+ may have its own frontmatter (`id:`, `template:`, `layout:`).
93
+
94
+ - `# h1` = headline, `## h2` above an `# h1` = kicker; a lone `##` is the title.
95
+ - Bullets `-`/`*`; ordered `1.` (nest with 2-space indent). Inline
96
+ `**bold**` / `*italic*` / `` `code` `` / `[link](url)`. GFM tables.
97
+ `![alt](path)` images (uploaded to Drive; `alt` becomes the accessibility
98
+ description, round-tripped on pull). Blank lines preserved as spacing.
99
+ `<!-- notes -->` become speaker notes.
100
+ - **Internal links:** `[text](#slide-id)` becomes a native Slides link to the
101
+ slide whose `id:` (or title slug) is `slide-id`.
102
+
103
+ ### Built-in brand kit (IBM Plex; red `#C0392B` kicker)
104
+
105
+ Select per slide via `template:` — native styled boxes, no in-deck templates:
106
+ `dark`/`title`, `appendix`, `question`/`label`, `topic`, `content`,
107
+ `graph`/`full` (single full-bleed image), `prompt`/`code` (verbatim monospace).
108
+ Slides with no `template:` fall back to a generative path (section /
109
+ title+body / table / image) that also brands the background + IBM Plex.
110
+
111
+ ### Custom slides (diagrams) — pull-authoritative
112
+
113
+ Give a slide a fenced ```` ```gslides ```` block holding literal Slides API
114
+ requests (use `__PAGE__` for the slide page id). Sync is **pull-authoritative /
115
+ push-if-missing**: the Slides copy is the source of truth — `push` only creates
116
+ the slide when missing, `pull` captures the live drawing back into the block.
117
+
118
+ ## Development
119
+
120
+ ```bash
121
+ uv sync
122
+ uv run pytest -q # offline tests (no network/auth)
123
+ ```
124
+
125
+ Releases publish to PyPI via Trusted Publishing (OIDC) on a `v*.*.*` tag — see
126
+ `.github/workflows/release.yml`. Bump with `uvx bumpver update --patch`.
127
+
128
+ ## Caveats
129
+
130
+ - Slidev-only constructs (`<v-clicks>`, `<div grid>`, CSS) are flattened/stripped
131
+ — this is a content mapper, not a CSS renderer.
132
+ - On `pull`, the slide model holds a single image, so a slide with multiple
133
+ images keeps the first; image `contentUrl`s from foreign decks are ephemeral.
134
+
135
+ ## License
136
+
137
+ MIT © Daniel Hails
@@ -0,0 +1,119 @@
1
+ # slidesync
2
+
3
+ Bidirectional sync between a [Slidev](https://sli.dev) markdown deck and **Google
4
+ Slides** — as native, editable objects (title/body/bullets/tables/positioned
5
+ images, brand-styled text boxes), not pasted screenshots.
6
+
7
+ Version: 0.1.0
8
+
9
+ ```bash
10
+ uvx slidesync --help # run without installing
11
+ pip install slidesync # or install the CLI + library
12
+ ```
13
+
14
+ ## Why
15
+
16
+ Exporting a deck to images gives you something you can't edit; pasting markdown
17
+ by hand gives you something you can't version. `slidesync` keeps a `.slidev.md`
18
+ file as the source of truth and renders it into **real** Slides objects, so the
19
+ result stays fully editable in Google Slides — and `pull` reconstructs the
20
+ markdown back from those objects, so the loop is reversible.
21
+
22
+ - **`push`** — markdown → Slides (idempotent upsert, never a blind append).
23
+ - **`pull`** — Slides → markdown (handles multi-text-box and externally-authored
24
+ decks, bullet nesting, tables, images, and speaker notes).
25
+ - **`roundtrip`** — push a sample to a scratch deck, pull it back, assert the two
26
+ are semantically identical, delete the scratch deck.
27
+
28
+ ## Auth (no setup)
29
+
30
+ Auth is **borrowed from the [`gog`](https://github.com/) CLI** — no separate
31
+ OAuth client. `slidesync` reads the client id/secret from
32
+ `~/Library/Application Support/gogcli/credentials.json` and the refresh token via
33
+ `gog auth tokens export`, then mints a short-lived access token. The stored token
34
+ already carries the `slides` + `drive` scopes; the Slides API must be enabled on
35
+ the gog Cloud project. Override the account with `--account` or
36
+ `$SLIDESYNC_ACCOUNT`. (Currently macOS-only — it reads gog's macOS Application
37
+ Support path.)
38
+
39
+ ## Commands
40
+
41
+ | Command | Purpose |
42
+ |---------|---------|
43
+ | `slidesync push <file.slidev.md> [--deck ID] [--new "Title"] [--anchor SLIDE] [--prune] [--force]` | markdown → Slides |
44
+ | `slidesync pull <deckId> --out <file.md> [--all]` | Slides → markdown (`--all` includes non-managed slides) |
45
+ | `slidesync roundtrip [--keep]` | self-test: push a sample, pull, assert identical |
46
+ | `slidesync layouts <deckId>` | list a deck's theme layouts + placeholders |
47
+ | `slidesync make-templates <deckId>` | inject branded `{{token}}` template slides |
48
+
49
+ `push` resolves the target deck from (in order) `--deck`, `--new`, or a top-level
50
+ `deck:` frontmatter key. Relative image paths resolve against the markdown file's
51
+ directory.
52
+
53
+ ```bash
54
+ slidesync push deck.slidev.md # targets `deck:` frontmatter
55
+ slidesync push deck.slidev.md --new "Talk"
56
+ slidesync pull <id> --out deck.slidev.md
57
+ slidesync roundtrip
58
+ ```
59
+
60
+ ## Idempotent sync (upsert)
61
+
62
+ Each managed slide is created with `objectId = s2g_<keyHash>_<contentHash>`.
63
+ `keyHash` = per-slide `id:` frontmatter, else title slug, else index (survives
64
+ edits/reorders); `contentHash` is over a canonical render, so push → pull → push
65
+ is a no-op. Diff per run: identical hash → skip; same key, new content → replace;
66
+ new key → create. Removed slides are **kept** unless `--prune`. **Only `s2g_`
67
+ slides are ever touched** — hand-authored slides are invisible to the sync. A
68
+ hidden `<!-- s2g {...} -->` marker in speaker notes carries the human id, image
69
+ path, and template vars so `pull` can recover them.
70
+
71
+ ## Markdown dialect
72
+
73
+ Top-level frontmatter: `theme:`, `deck:`. Slides separated by `---`; each slide
74
+ may have its own frontmatter (`id:`, `template:`, `layout:`).
75
+
76
+ - `# h1` = headline, `## h2` above an `# h1` = kicker; a lone `##` is the title.
77
+ - Bullets `-`/`*`; ordered `1.` (nest with 2-space indent). Inline
78
+ `**bold**` / `*italic*` / `` `code` `` / `[link](url)`. GFM tables.
79
+ `![alt](path)` images (uploaded to Drive; `alt` becomes the accessibility
80
+ description, round-tripped on pull). Blank lines preserved as spacing.
81
+ `<!-- notes -->` become speaker notes.
82
+ - **Internal links:** `[text](#slide-id)` becomes a native Slides link to the
83
+ slide whose `id:` (or title slug) is `slide-id`.
84
+
85
+ ### Built-in brand kit (IBM Plex; red `#C0392B` kicker)
86
+
87
+ Select per slide via `template:` — native styled boxes, no in-deck templates:
88
+ `dark`/`title`, `appendix`, `question`/`label`, `topic`, `content`,
89
+ `graph`/`full` (single full-bleed image), `prompt`/`code` (verbatim monospace).
90
+ Slides with no `template:` fall back to a generative path (section /
91
+ title+body / table / image) that also brands the background + IBM Plex.
92
+
93
+ ### Custom slides (diagrams) — pull-authoritative
94
+
95
+ Give a slide a fenced ```` ```gslides ```` block holding literal Slides API
96
+ requests (use `__PAGE__` for the slide page id). Sync is **pull-authoritative /
97
+ push-if-missing**: the Slides copy is the source of truth — `push` only creates
98
+ the slide when missing, `pull` captures the live drawing back into the block.
99
+
100
+ ## Development
101
+
102
+ ```bash
103
+ uv sync
104
+ uv run pytest -q # offline tests (no network/auth)
105
+ ```
106
+
107
+ Releases publish to PyPI via Trusted Publishing (OIDC) on a `v*.*.*` tag — see
108
+ `.github/workflows/release.yml`. Bump with `uvx bumpver update --patch`.
109
+
110
+ ## Caveats
111
+
112
+ - Slidev-only constructs (`<v-clicks>`, `<div grid>`, CSS) are flattened/stripped
113
+ — this is a content mapper, not a CSS renderer.
114
+ - On `pull`, the slide model holds a single image, so a slide with multiple
115
+ images keeps the first; image `contentUrl`s from foreign decks are ephemeral.
116
+
117
+ ## License
118
+
119
+ MIT © Daniel Hails
@@ -0,0 +1,52 @@
1
+ [project]
2
+ name = "slidesync"
3
+ version = "0.1.0"
4
+ authors = [
5
+ { name = "Daniel Hails", email = "slidesync@hails.info" },
6
+ ]
7
+ description = "Bidirectional sync between a Slidev markdown deck and Google Slides as native, editable objects"
8
+ requires-python = ">=3.10"
9
+ dynamic = ["dependencies", "readme"]
10
+ license = { text = "MIT" }
11
+ keywords = ["slidev", "google-slides", "markdown", "presentations", "slides"]
12
+
13
+ [project.scripts]
14
+ slidesync = "slidesync:main"
15
+
16
+ [project.urls]
17
+ "Homepage" = "https://github.com/DJRHails/slidesync"
18
+ "Bug Tracker" = "https://github.com/DJRHails/slidesync/issues"
19
+
20
+ [build-system]
21
+ requires = ["setuptools", "wheel"]
22
+ build-backend = "setuptools.build_meta"
23
+
24
+ [tool.setuptools.dynamic]
25
+ dependencies = { file = ["requirements.txt"] }
26
+ readme = { file = ["README.md"], content-type = "text/markdown" }
27
+
28
+ [tool.setuptools.packages.find]
29
+ include = ["slidesync*"]
30
+
31
+ [dependency-groups]
32
+ dev = ["pytest>=8"]
33
+
34
+ [tool.bumpver]
35
+ current_version = "0.1.0"
36
+ version_pattern = "MAJOR.MINOR.PATCH"
37
+ commit_message = "bump version {old_version} -> {new_version}"
38
+ commit = true
39
+ tag = true
40
+ push = true
41
+
42
+ [tool.bumpver.file_patterns]
43
+ "pyproject.toml" = [
44
+ 'current_version = "{version}"',
45
+ '^version = "{version}"',
46
+ ]
47
+ "slidesync/__init__.py" = [
48
+ '__version__ = "{version}"',
49
+ ]
50
+ "README.md" = [
51
+ "{version}",
52
+ ]
@@ -0,0 +1,4 @@
1
+ google-api-python-client>=2
2
+ google-auth>=2
3
+ python-frontmatter>=1
4
+ loguru>=0.7
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,48 @@
1
+ """slidesync — bidirectional Slidev markdown <-> Google Slides sync.
2
+
3
+ `push` builds **native** Slides objects (title/body placeholders, bullets,
4
+ tables, positioned images), so the result stays editable rather than a flat
5
+ image; `pull` reconstructs Slidev markdown from those native objects;
6
+ `roundtrip` proves the loop is stable. Auth is borrowed from the `gog` CLI.
7
+
8
+ Library usage::
9
+
10
+ from slidesync import get_services, load_slides, push, pull_slides, write_slidev
11
+
12
+ slides_api, drive = get_services()
13
+ push(slides_api, drive, deck_id, load_slides(Path("deck.slidev.md")),
14
+ anchor=None, prune=False)
15
+
16
+ CLI: ``slidesync push|pull|roundtrip|layouts|make-templates`` (see ``--help``).
17
+ """
18
+
19
+ from slidesync._sync import (
20
+ Para,
21
+ Run,
22
+ Slide,
23
+ build_slides,
24
+ get_services,
25
+ load_slides,
26
+ main,
27
+ pull_slides,
28
+ push,
29
+ split_slides,
30
+ write_slidev,
31
+ )
32
+
33
+ __version__ = "0.1.0"
34
+
35
+ __all__ = [
36
+ "Para",
37
+ "Run",
38
+ "Slide",
39
+ "__version__",
40
+ "build_slides",
41
+ "get_services",
42
+ "load_slides",
43
+ "main",
44
+ "pull_slides",
45
+ "push",
46
+ "split_slides",
47
+ "write_slidev",
48
+ ]