mdview-cli 0.1.0__tar.gz → 1.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.
Files changed (28) hide show
  1. mdview_cli-1.1.0/.github/workflows/release.yml +53 -0
  2. mdview_cli-1.1.0/.gitignore +6 -0
  3. mdview_cli-1.1.0/PKG-INFO +170 -0
  4. mdview_cli-1.1.0/README.md +152 -0
  5. {mdview_cli-0.1.0 → mdview_cli-1.1.0}/pyproject.toml +3 -3
  6. mdview_cli-1.1.0/src/mdview_cli/__init__.py +0 -0
  7. {mdview_cli-0.1.0 → mdview_cli-1.1.0}/src/mdview_cli/api.py +28 -11
  8. {mdview_cli-0.1.0 → mdview_cli-1.1.0}/src/mdview_cli/cli.py +188 -25
  9. {mdview_cli-0.1.0 → mdview_cli-1.1.0}/src/mdview_cli/state.py +12 -7
  10. mdview_cli-1.1.0/src/mdview_cli.egg-info/PKG-INFO +170 -0
  11. {mdview_cli-0.1.0 → mdview_cli-1.1.0}/src/mdview_cli.egg-info/SOURCES.txt +7 -1
  12. mdview_cli-1.1.0/src/mdview_cli.egg-info/scm_file_list.json +17 -0
  13. mdview_cli-1.1.0/src/mdview_cli.egg-info/scm_version.json +8 -0
  14. mdview_cli-1.1.0/tests/e2e/test_free_tools.py +75 -0
  15. mdview_cli-1.1.0/tests/test_api.py +61 -0
  16. mdview_cli-1.1.0/tests/test_cli.py +371 -0
  17. mdview_cli-0.1.0/PKG-INFO +0 -63
  18. mdview_cli-0.1.0/README.md +0 -45
  19. mdview_cli-0.1.0/src/mdview_cli/__init__.py +0 -1
  20. mdview_cli-0.1.0/src/mdview_cli.egg-info/PKG-INFO +0 -63
  21. mdview_cli-0.1.0/tests/test_cli.py +0 -185
  22. {mdview_cli-0.1.0 → mdview_cli-1.1.0}/LICENSE +0 -0
  23. {mdview_cli-0.1.0 → mdview_cli-1.1.0}/setup.cfg +0 -0
  24. {mdview_cli-0.1.0 → mdview_cli-1.1.0}/src/mdview_cli/config.py +0 -0
  25. {mdview_cli-0.1.0 → mdview_cli-1.1.0}/src/mdview_cli.egg-info/dependency_links.txt +0 -0
  26. {mdview_cli-0.1.0 → mdview_cli-1.1.0}/src/mdview_cli.egg-info/entry_points.txt +0 -0
  27. {mdview_cli-0.1.0 → mdview_cli-1.1.0}/src/mdview_cli.egg-info/requires.txt +0 -0
  28. {mdview_cli-0.1.0 → mdview_cli-1.1.0}/src/mdview_cli.egg-info/top_level.txt +0 -0
@@ -0,0 +1,53 @@
1
+ name: CLI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ branches:
8
+ - main
9
+ pull_request:
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ matrix:
16
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
17
+ steps:
18
+ - uses: actions/checkout@v7
19
+ - uses: actions/setup-python@v6
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+ - run: python -m pip install -e '.[test]'
23
+ - run: python -m pytest tests/test_cli.py
24
+
25
+ e2e-free-tools:
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v7
29
+ - uses: actions/setup-python@v6
30
+ with:
31
+ python-version: "3.12"
32
+ - run: python -m pip install -e '.[test]'
33
+ - run: python -m pytest tests/e2e
34
+
35
+ publish:
36
+ if: startsWith(github.ref, 'refs/tags/v')
37
+ needs: [test, e2e-free-tools]
38
+ runs-on: ubuntu-latest
39
+ environment: pypi
40
+ permissions:
41
+ id-token: write
42
+ steps:
43
+ - uses: actions/checkout@v7
44
+ with:
45
+ fetch-depth: 0
46
+ - uses: actions/setup-python@v6
47
+ with:
48
+ python-version: "3.12"
49
+ - run: python -m pip install build
50
+ - run: python -m build
51
+ - uses: pypa/gh-action-pypi-publish@release/v1
52
+ with:
53
+ packages-dir: dist/
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ .pytest_cache/
4
+ build/
5
+ dist/
6
+ .venv/
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: mdview-cli
3
+ Version: 1.1.0
4
+ Summary: Iterate on Markdown and verify every revision with mdview.io
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://mdview.io
7
+ Project-URL: Repository, https://github.com/gogainda/mdview-cli
8
+ Project-URL: Issues, https://github.com/gogainda/mdview-cli/issues
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: click>=8.1
13
+ Requires-Dist: httpx>=0.27
14
+ Requires-Dist: platformdirs>=4.0
15
+ Provides-Extra: test
16
+ Requires-Dist: pytest>=8.0; extra == "test"
17
+ Dynamic: license-file
18
+
19
+ # mdv — preview Markdown files from your terminal
20
+
21
+ `mdv` renders a Markdown file the way it should look — headings, tables, code,
22
+ Mermaid diagrams — and opens it in your browser. One command, no account, no
23
+ configuration.
24
+
25
+ ```bash
26
+ uv tool install mdview-cli # or: pipx install mdview-cli, pip install mdview-cli
27
+ mdv notes.md
28
+ ```
29
+
30
+ Homebrew users:
31
+
32
+ ```bash
33
+ brew install gogainda/tap/mdview-cli
34
+ ```
35
+
36
+ That's the whole workflow: `mdv FILE.md` publishes the file to
37
+ [mdview.io](https://mdview.io), opens the rendered page, and prints a link you
38
+ can send to anyone. Anonymous previews last 30 days and take files up to 2 MB.
39
+
40
+ ## Two ways to preview
41
+
42
+ **Shareable preview (default).** `mdv FILE.md` is shorthand for `mdv preview
43
+ FILE.md`. The title comes from the first `#` heading, or pass your own:
44
+
45
+ ```bash
46
+ mdv notes.md
47
+ mdv preview notes.md --title "Design notes"
48
+ mdv preview notes.md --json # print the share URLs as JSON, don't open a browser
49
+ ```
50
+
51
+ **Local-only preview.** `mdv open` uploads nothing: it packs the file into the
52
+ URL itself and rendering happens entirely in your browser. Use it for drafts
53
+ that shouldn't leave your machine.
54
+
55
+ ```bash
56
+ mdv open notes.md
57
+ ```
58
+
59
+ ## Iterating on one document
60
+
61
+ Previews are throwaway: every run mints a new link. When you keep working on
62
+ the same file — a spec, an architecture doc, a report — you want the opposite:
63
+ one stable URL whose content follows your edits, and a check that every
64
+ revision still renders cleanly.
65
+
66
+ Set up a free mdview.io CLI token once, then it's one command per iteration:
67
+
68
+ ```bash
69
+ mdv keys set # once (or set MDVIEW_TOKEN in CI)
70
+ mdv sync spec.md # publish this revision, verify it, auto-repair broken diagrams
71
+ ```
72
+
73
+ While you iterate, the document lives at its **private** `/p/` page — only
74
+ you, signed in, can see it, and the URL stays the same forever. Every sync
75
+ shows the fixed, renderable version there. Broken Mermaid diagrams are
76
+ repaired automatically and the fix is written back into your file (the
77
+ original is kept as a timestamped backup); only documents that can't be
78
+ repaired exit `1`.
79
+
80
+ When — and only when — you decide to publish, make it public; and grab a PDF
81
+ at any point:
82
+
83
+ ```bash
84
+ mdv share spec.md # mint the public /s/ link (from then on it follows your syncs)
85
+ mdv share spec.md --slug my-doc # …or with a custom /s/my-doc slug
86
+ mdv export spec.md # render the current revision to spec.pdf
87
+ ```
88
+
89
+ Or do both steps at once — sync, auto-repair, and share in one call:
90
+
91
+ ```bash
92
+ mdv publish spec.md --slug my-doc # sync + repair + share, atomically
93
+ ```
94
+
95
+ Useful around the loop:
96
+
97
+ ```bash
98
+ mdv sync spec.md --no-fix # fast sync: skip auto-repair, just report failures
99
+ mdv export spec.md --no-sync # PDF of the last synced revision; local edits stay private
100
+ mdv list # all saved documents with their URLs
101
+ mdv status spec.md # document ID, public state, last sync, local edits pending?
102
+ mdv unlink spec.md # forget the file↔document link; the document stays online
103
+ ```
104
+
105
+ Nothing is public until you run `mdv share`. After that, the public page
106
+ serves the last synced revision — mid-edit local changes still stay private
107
+ until you sync. `mdv fix FILE --local` repairs a file in place without
108
+ creating a saved document (free accounts share the daily Quick Fix limit;
109
+ Pro is unlimited).
110
+
111
+ ### Example session
112
+
113
+ A real session, working on a doc whose Mermaid diagrams start out broken:
114
+
115
+ ```console
116
+ $ mdv sync spec.md # iterate — the broken flowchart is auto-repaired (~30s)
117
+ Document: 8be0be9198400443
118
+ Private: https://mdview.io/p/8be0be9198400443
119
+ Renderable: yes (1 diagrams, 0 tables)
120
+
121
+ $ vim spec.md # …add two sections, each with a (broken) diagram…
122
+ $ mdv sync spec.md # same doc, all three diagrams repaired and verified
123
+ Document: 8be0be9198400443
124
+ Private: https://mdview.io/p/8be0be9198400443
125
+ Renderable: yes (3 diagrams, 0 tables)
126
+
127
+ $ mdv share spec.md # happy with it — make it public
128
+ https://mdview.io/s/9a2b8fb1
129
+
130
+ $ mdv export spec.md # …and hand over a PDF
131
+ PDF: spec.pdf
132
+ ```
133
+
134
+ Two commands did all the work: `mdv sync` after every editing round, and
135
+ `mdv share` once at the end. The fixes were written back into `spec.md`
136
+ (originals in timestamped backups, so you can review what the repair
137
+ changed — worth a glance before sharing, since a repair is guaranteed to
138
+ render but not to read your mind), the private URL never changed, and the
139
+ public link didn't exist until it was asked for.
140
+
141
+ **When this flow fits:** a document that lives longer than one paste — specs,
142
+ architecture docs, reports, runbooks, agent-written docs you refine over
143
+ hours or days. Use it when reviewers follow one link that must always show
144
+ the latest good revision, when a PDF has to be producible at any moment, or
145
+ in CI as a docs gate (`mdv sync --no-fix` exits `1` on broken diagrams
146
+ without editing anything). For a one-off snapshot, `mdv preview` is enough;
147
+ for reading a local file without uploading it, use `mdv open`.
148
+
149
+ ## Reference
150
+
151
+ | Command | Token | What it does |
152
+ |---|---|---|
153
+ | `mdv FILE.md` / `mdv preview` | no | Publish anonymously, open the rendered page (30-day link) |
154
+ | `mdv open` | no | Render locally in the browser; nothing is uploaded |
155
+ | `mdv sync` | yes | Update a saved document at its private /p/ URL, verify + auto-repair |
156
+ | `mdv share` | yes | Make a synced document public at its /s/ URL, optionally with `--slug` |
157
+ | `mdv publish` | yes | Sync + auto-repair + share in one call |
158
+ | `mdv verify` | yes | Re-check rendering of a synced document |
159
+ | `mdv fix` | yes | Repair broken Mermaid diagrams, with a local backup |
160
+ | `mdv export` | yes | Export a synced document to PDF |
161
+ | `mdv list` | yes | List saved documents |
162
+ | `mdv status` | yes | Show a synced document's ID, public state, slug, and whether local edits are unsynced |
163
+ | `mdv unlink` | no | Remove the local file↔document association |
164
+ | `mdv keys set/list/path/unset` | — | Manage the stored CLI token |
165
+
166
+ Exit codes: `0` success, `1` document doesn't render cleanly, `2` usage error,
167
+ `3` mdview.io error.
168
+
169
+ Environment: `MDVIEW_TOKEN` supplies the token without storing it (ideal for
170
+ CI); `MDVIEW_BASE_URL` points the CLI at another server.
@@ -0,0 +1,152 @@
1
+ # mdv — preview Markdown files from your terminal
2
+
3
+ `mdv` renders a Markdown file the way it should look — headings, tables, code,
4
+ Mermaid diagrams — and opens it in your browser. One command, no account, no
5
+ configuration.
6
+
7
+ ```bash
8
+ uv tool install mdview-cli # or: pipx install mdview-cli, pip install mdview-cli
9
+ mdv notes.md
10
+ ```
11
+
12
+ Homebrew users:
13
+
14
+ ```bash
15
+ brew install gogainda/tap/mdview-cli
16
+ ```
17
+
18
+ That's the whole workflow: `mdv FILE.md` publishes the file to
19
+ [mdview.io](https://mdview.io), opens the rendered page, and prints a link you
20
+ can send to anyone. Anonymous previews last 30 days and take files up to 2 MB.
21
+
22
+ ## Two ways to preview
23
+
24
+ **Shareable preview (default).** `mdv FILE.md` is shorthand for `mdv preview
25
+ FILE.md`. The title comes from the first `#` heading, or pass your own:
26
+
27
+ ```bash
28
+ mdv notes.md
29
+ mdv preview notes.md --title "Design notes"
30
+ mdv preview notes.md --json # print the share URLs as JSON, don't open a browser
31
+ ```
32
+
33
+ **Local-only preview.** `mdv open` uploads nothing: it packs the file into the
34
+ URL itself and rendering happens entirely in your browser. Use it for drafts
35
+ that shouldn't leave your machine.
36
+
37
+ ```bash
38
+ mdv open notes.md
39
+ ```
40
+
41
+ ## Iterating on one document
42
+
43
+ Previews are throwaway: every run mints a new link. When you keep working on
44
+ the same file — a spec, an architecture doc, a report — you want the opposite:
45
+ one stable URL whose content follows your edits, and a check that every
46
+ revision still renders cleanly.
47
+
48
+ Set up a free mdview.io CLI token once, then it's one command per iteration:
49
+
50
+ ```bash
51
+ mdv keys set # once (or set MDVIEW_TOKEN in CI)
52
+ mdv sync spec.md # publish this revision, verify it, auto-repair broken diagrams
53
+ ```
54
+
55
+ While you iterate, the document lives at its **private** `/p/` page — only
56
+ you, signed in, can see it, and the URL stays the same forever. Every sync
57
+ shows the fixed, renderable version there. Broken Mermaid diagrams are
58
+ repaired automatically and the fix is written back into your file (the
59
+ original is kept as a timestamped backup); only documents that can't be
60
+ repaired exit `1`.
61
+
62
+ When — and only when — you decide to publish, make it public; and grab a PDF
63
+ at any point:
64
+
65
+ ```bash
66
+ mdv share spec.md # mint the public /s/ link (from then on it follows your syncs)
67
+ mdv share spec.md --slug my-doc # …or with a custom /s/my-doc slug
68
+ mdv export spec.md # render the current revision to spec.pdf
69
+ ```
70
+
71
+ Or do both steps at once — sync, auto-repair, and share in one call:
72
+
73
+ ```bash
74
+ mdv publish spec.md --slug my-doc # sync + repair + share, atomically
75
+ ```
76
+
77
+ Useful around the loop:
78
+
79
+ ```bash
80
+ mdv sync spec.md --no-fix # fast sync: skip auto-repair, just report failures
81
+ mdv export spec.md --no-sync # PDF of the last synced revision; local edits stay private
82
+ mdv list # all saved documents with their URLs
83
+ mdv status spec.md # document ID, public state, last sync, local edits pending?
84
+ mdv unlink spec.md # forget the file↔document link; the document stays online
85
+ ```
86
+
87
+ Nothing is public until you run `mdv share`. After that, the public page
88
+ serves the last synced revision — mid-edit local changes still stay private
89
+ until you sync. `mdv fix FILE --local` repairs a file in place without
90
+ creating a saved document (free accounts share the daily Quick Fix limit;
91
+ Pro is unlimited).
92
+
93
+ ### Example session
94
+
95
+ A real session, working on a doc whose Mermaid diagrams start out broken:
96
+
97
+ ```console
98
+ $ mdv sync spec.md # iterate — the broken flowchart is auto-repaired (~30s)
99
+ Document: 8be0be9198400443
100
+ Private: https://mdview.io/p/8be0be9198400443
101
+ Renderable: yes (1 diagrams, 0 tables)
102
+
103
+ $ vim spec.md # …add two sections, each with a (broken) diagram…
104
+ $ mdv sync spec.md # same doc, all three diagrams repaired and verified
105
+ Document: 8be0be9198400443
106
+ Private: https://mdview.io/p/8be0be9198400443
107
+ Renderable: yes (3 diagrams, 0 tables)
108
+
109
+ $ mdv share spec.md # happy with it — make it public
110
+ https://mdview.io/s/9a2b8fb1
111
+
112
+ $ mdv export spec.md # …and hand over a PDF
113
+ PDF: spec.pdf
114
+ ```
115
+
116
+ Two commands did all the work: `mdv sync` after every editing round, and
117
+ `mdv share` once at the end. The fixes were written back into `spec.md`
118
+ (originals in timestamped backups, so you can review what the repair
119
+ changed — worth a glance before sharing, since a repair is guaranteed to
120
+ render but not to read your mind), the private URL never changed, and the
121
+ public link didn't exist until it was asked for.
122
+
123
+ **When this flow fits:** a document that lives longer than one paste — specs,
124
+ architecture docs, reports, runbooks, agent-written docs you refine over
125
+ hours or days. Use it when reviewers follow one link that must always show
126
+ the latest good revision, when a PDF has to be producible at any moment, or
127
+ in CI as a docs gate (`mdv sync --no-fix` exits `1` on broken diagrams
128
+ without editing anything). For a one-off snapshot, `mdv preview` is enough;
129
+ for reading a local file without uploading it, use `mdv open`.
130
+
131
+ ## Reference
132
+
133
+ | Command | Token | What it does |
134
+ |---|---|---|
135
+ | `mdv FILE.md` / `mdv preview` | no | Publish anonymously, open the rendered page (30-day link) |
136
+ | `mdv open` | no | Render locally in the browser; nothing is uploaded |
137
+ | `mdv sync` | yes | Update a saved document at its private /p/ URL, verify + auto-repair |
138
+ | `mdv share` | yes | Make a synced document public at its /s/ URL, optionally with `--slug` |
139
+ | `mdv publish` | yes | Sync + auto-repair + share in one call |
140
+ | `mdv verify` | yes | Re-check rendering of a synced document |
141
+ | `mdv fix` | yes | Repair broken Mermaid diagrams, with a local backup |
142
+ | `mdv export` | yes | Export a synced document to PDF |
143
+ | `mdv list` | yes | List saved documents |
144
+ | `mdv status` | yes | Show a synced document's ID, public state, slug, and whether local edits are unsynced |
145
+ | `mdv unlink` | no | Remove the local file↔document association |
146
+ | `mdv keys set/list/path/unset` | — | Manage the stored CLI token |
147
+
148
+ Exit codes: `0` success, `1` document doesn't render cleanly, `2` usage error,
149
+ `3` mdview.io error.
150
+
151
+ Environment: `MDVIEW_TOKEN` supplies the token without storing it (ideal for
152
+ CI); `MDVIEW_BASE_URL` points the CLI at another server.
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["setuptools>=69"]
2
+ requires = ["setuptools>=69", "setuptools-scm>=8"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
@@ -29,8 +29,8 @@ mdv = "mdview_cli.cli:cli"
29
29
  [tool.setuptools.packages.find]
30
30
  where = ["src"]
31
31
 
32
- [tool.setuptools.dynamic]
33
- version = {attr = "mdview_cli.__version__"}
32
+ [tool.setuptools_scm]
33
+ fallback_version = "0.0.0"
34
34
 
35
35
  [tool.pytest.ini_options]
36
36
  testpaths = ["tests"]
File without changes
@@ -1,5 +1,10 @@
1
+ import time
2
+
1
3
  import httpx
2
4
 
5
+ BUSY_RETRY_ATTEMPTS = 4
6
+ BUSY_RETRY_DELAY = 1.0
7
+
3
8
 
4
9
  class ApiError(RuntimeError):
5
10
  def __init__(self, message: str, status_code: int | None = None):
@@ -16,18 +21,24 @@ class MdviewApi:
16
21
  )
17
22
 
18
23
  def request(self, method: str, path: str, **kwargs):
19
- try:
20
- response = self.client.request(method, path, **kwargs)
21
- except httpx.HTTPError as error:
22
- raise ApiError(f"Could not reach mdview.io: {error}") from error
23
- if response.is_error:
24
+ delay = BUSY_RETRY_DELAY
25
+ for attempt in range(BUSY_RETRY_ATTEMPTS):
24
26
  try:
25
- body = response.json()
26
- message = body.get("message") or body.get("error") or response.reason_phrase
27
- except ValueError:
28
- message = response.reason_phrase
29
- raise ApiError(str(message), response.status_code)
30
- return response
27
+ response = self.client.request(method, path, **kwargs)
28
+ except httpx.HTTPError as error:
29
+ raise ApiError(f"Could not reach mdview.io: {error}") from error
30
+ if response.is_error:
31
+ try:
32
+ body = response.json()
33
+ message = body.get("message") or body.get("error") or response.reason_phrase
34
+ except ValueError:
35
+ message = response.reason_phrase
36
+ if "busy" in str(message).lower() and attempt < BUSY_RETRY_ATTEMPTS - 1:
37
+ time.sleep(delay)
38
+ delay *= 2
39
+ continue
40
+ raise ApiError(str(message), response.status_code)
41
+ return response
31
42
 
32
43
  def create(self, title, content):
33
44
  return self.request("POST", "/api/documents", json={"title": title, "content": content}).json()
@@ -44,6 +55,9 @@ class MdviewApi:
44
55
  def share(self, document_id):
45
56
  return self.request("POST", f"/api/documents/{document_id}/share", json={}).json()
46
57
 
58
+ def set_slug(self, document_id, slug):
59
+ return self.request("PATCH", f"/api/documents/{document_id}/share/slug", json={"slug": slug}).json()
60
+
47
61
  def verify(self, document_id, status=False):
48
62
  suffix = "/status" if status else ""
49
63
  return self.request("GET", f"/api/documents/{document_id}/verify{suffix}").json()
@@ -51,6 +65,9 @@ class MdviewApi:
51
65
  def fix(self, document_id):
52
66
  return self.request("POST", f"/api/documents/{document_id}/fix/diagrams", json={}).json()
53
67
 
68
+ def fix_markdown(self, content):
69
+ return self.request("POST", "/api/documents/fix/diagrams", json={"content": content}).json()
70
+
54
71
  def export_pdf(self, document_id):
55
72
  return self.request("GET", f"/api/documents/{document_id}/export/pdf").content
56
73