docmost-cli 0.4.0__tar.gz → 0.6.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.
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/.claude/skills/docmost/SKILL.md +18 -4
- docmost_cli-0.6.0/.github/workflows/ci.yml +139 -0
- docmost_cli-0.6.0/.github/workflows/release.yml +224 -0
- docmost_cli-0.6.0/CHANGELOG.md +249 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/CLAUDE.md +53 -3
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/PKG-INFO +43 -13
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/README.md +42 -11
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/SPECIFICATION.md +184 -47
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/man/man1/docmost-cli-attachment.1 +37 -3
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/man/man1/docmost-cli-comment.1 +37 -3
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/man/man1/docmost-cli-config.1 +32 -2
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/man/man1/docmost-cli-page.1 +141 -15
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/man/man1/docmost-cli-search.1 +30 -5
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/man/man1/docmost-cli-space.1 +37 -3
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/man/man1/docmost-cli-sync.1 +22 -11
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/man/man1/docmost-cli-user.1 +4 -1
- docmost_cli-0.6.0/man/man1/docmost-cli-workspace.1 +105 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/man/man1/docmost-cli.1 +54 -3
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/pyproject.toml +6 -2
- docmost_cli-0.6.0/release-notes.md +62 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/__init__.py +1 -1
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/api/attachments.py +6 -2
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/api/auth.py +22 -4
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/api/client.py +6 -4
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/api/comments.py +13 -3
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/api/pages.py +271 -53
- docmost_cli-0.6.0/src/docmost_cli/api/pagination.py +298 -0
- docmost_cli-0.6.0/src/docmost_cli/api/position.py +287 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/api/spaces.py +23 -7
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/api/users.py +4 -4
- docmost_cli-0.6.0/src/docmost_cli/cli/_list_opts.py +248 -0
- docmost_cli-0.6.0/src/docmost_cli/cli/attachment.py +53 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/cli/comment.py +32 -8
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/cli/config_cmd.py +66 -14
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/cli/main.py +52 -3
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/cli/page.py +191 -32
- docmost_cli-0.6.0/src/docmost_cli/cli/search.py +56 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/cli/space.py +30 -7
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/cli/sync_cmd.py +21 -2
- docmost_cli-0.6.0/src/docmost_cli/cli/user.py +30 -0
- docmost_cli-0.6.0/src/docmost_cli/cli/workspace.py +66 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/config/settings.py +3 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/config/store.py +17 -12
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/convert/prosemirror_to_md.py +10 -2
- docmost_cli-0.6.0/src/docmost_cli/models/__init__.py +5 -0
- docmost_cli-0.6.0/src/docmost_cli/models/common.py +47 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/output/__init__.py +6 -0
- docmost_cli-0.6.0/src/docmost_cli/output/formatter.py +144 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/sync/diff.py +1 -1
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/sync/manifest.py +8 -8
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/sync/push.py +114 -53
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_api/test_auth.py +54 -0
- docmost_cli-0.6.0/tests/test_api/test_pages.py +504 -0
- docmost_cli-0.6.0/tests/test_api/test_pagination.py +249 -0
- docmost_cli-0.6.0/tests/test_api/test_position.py +127 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_api/test_spaces.py +67 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_cli/test_comment_commands.py +42 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_cli/test_config_commands.py +31 -0
- docmost_cli-0.6.0/tests/test_cli/test_list_fields.py +139 -0
- docmost_cli-0.6.0/tests/test_cli/test_main.py +60 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_cli/test_page_commands.py +222 -5
- docmost_cli-0.6.0/tests/test_cli/test_user_commands.py +55 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_cli/test_workspace_commands.py +50 -10
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_config/test_store.py +35 -0
- docmost_cli-0.6.0/tests/test_docs/test_man_pages.py +52 -0
- docmost_cli-0.6.0/tests/test_output/test_formatter.py +131 -0
- docmost_cli-0.6.0/tests/test_sync/__init__.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_sync/test_push.py +103 -92
- docmost_cli-0.4.0/CHANGELOG.md +0 -102
- docmost_cli-0.4.0/man/man1/docmost-cli-workspace.1 +0 -68
- docmost_cli-0.4.0/src/docmost_cli/api/pagination.py +0 -94
- docmost_cli-0.4.0/src/docmost_cli/cli/attachment.py +0 -30
- docmost_cli-0.4.0/src/docmost_cli/cli/search.py +0 -33
- docmost_cli-0.4.0/src/docmost_cli/cli/user.py +0 -25
- docmost_cli-0.4.0/src/docmost_cli/cli/workspace.py +0 -40
- docmost_cli-0.4.0/src/docmost_cli/models/__init__.py +0 -3
- docmost_cli-0.4.0/src/docmost_cli/models/common.py +0 -3
- docmost_cli-0.4.0/src/docmost_cli/output/formatter.py +0 -85
- docmost_cli-0.4.0/tests/test_api/test_pages.py +0 -289
- docmost_cli-0.4.0/tests/test_api/test_pagination.py +0 -62
- docmost_cli-0.4.0/tests/test_cli/test_user_commands.py +0 -28
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/.claude/skills/docmost/examples.md +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/.gitignore +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/LICENSE +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/__main__.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/api/__init__.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/api/search.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/api/workspace.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/cli/__init__.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/config/__init__.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/convert/__init__.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/output/tree.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/py.typed +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/sync/__init__.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/sync/frontmatter.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/src/docmost_cli/sync/pull.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/__init__.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/conftest.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/code_block.json +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/code_block.md +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/empty_doc.json +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/empty_doc.md +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/headings_and_text.json +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/headings_and_text.md +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/lists.json +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/lists.md +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/marks.json +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/marks.md +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/simple_paragraph.json +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/simple_paragraph.md +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/table.json +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/fixtures/table.md +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_api/__init__.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_api/test_attachments.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_api/test_client.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_api/test_comments.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_api/test_search.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_api/test_users.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_api/test_workspace.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_cli/__init__.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_cli/test_attachment_commands.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_cli/test_search_commands.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_cli/test_space_commands.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_cli/test_sync_commands.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_config/__init__.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_config/test_settings.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_convert/__init__.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_convert/test_prosemirror_to_md.py +0 -0
- {docmost_cli-0.4.0/tests/test_output → docmost_cli-0.6.0/tests/test_docs}/__init__.py +0 -0
- {docmost_cli-0.4.0/tests/test_sync → docmost_cli-0.6.0/tests/test_output}/__init__.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_output/test_tree.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_sync/test_diff.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_sync/test_frontmatter.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_sync/test_manifest.py +0 -0
- {docmost_cli-0.4.0 → docmost_cli-0.6.0}/tests/test_sync/test_pull.py +0 -0
|
@@ -96,19 +96,33 @@ docmost-cli page export <page-id> --format md
|
|
|
96
96
|
|
|
97
97
|
1. Get current content: `docmost-cli page get <page-id>`
|
|
98
98
|
2. Modify the Markdown content as needed
|
|
99
|
-
3. Update: `docmost-cli page update <page-id> --
|
|
100
|
-
4.
|
|
99
|
+
3. Update: `docmost-cli page update <page-id> --file updated.md`
|
|
100
|
+
4. Content updates are applied in place — the page keeps its ID, slug, history
|
|
101
|
+
and inbound links. Works on Community and Enterprise alike, from Docmost
|
|
102
|
+
v0.71. Use `--append` / `--prepend` to add without replacing.
|
|
101
103
|
|
|
102
104
|
## Output Format
|
|
103
105
|
|
|
104
106
|
- **Content commands** (`page get`): Raw Markdown to stdout — directly usable
|
|
105
|
-
- **List commands** (`--json`): JSON array
|
|
107
|
+
- **List commands** (`--json`): JSON array of **complete server objects** — every
|
|
108
|
+
field the API returned, not a subset. For pages that means `id`, `slugId`,
|
|
109
|
+
`title`, `icon`, `position`, `parentPageId`, `spaceId`, `creatorId`,
|
|
110
|
+
`lastUpdatedById`, `isLocked`, `createdAt`, `updatedAt` and more. Page
|
|
111
|
+
*content* is not included; fetch it with `page get <id>`.
|
|
112
|
+
Pagination is followed automatically, so the array is complete; add
|
|
113
|
+
`--envelope` if you need `hasNextPage` / `nextCursor`
|
|
114
|
+
- **Narrow the output** with `--fields id,title,updatedAt` when you only need a
|
|
115
|
+
few keys — worth doing to keep context small, since `--json` is now much
|
|
116
|
+
larger. It works in JSON and table mode, and an unknown name errors with the
|
|
117
|
+
list of fields the server actually returned
|
|
118
|
+
- **Single-item commands** (`user me`, `workspace info`): add `--json` for the
|
|
119
|
+
complete object
|
|
106
120
|
- **Write commands** (`page create`, `delete`): Page ID to stdout, confirmation to stderr
|
|
107
121
|
- **Always use `--json`** for list/search commands when you need to parse the output programmatically
|
|
108
122
|
|
|
109
123
|
## Tips
|
|
110
124
|
|
|
111
|
-
- Use `--json`
|
|
125
|
+
- Use `--json` on list commands to get parseable output, plus `--fields` to keep it small
|
|
112
126
|
- Page IDs are UUIDs like `019a2a69-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
|
113
127
|
- Space slugs are lowercase alphanumeric (e.g., `engineering`, `devtest`)
|
|
114
128
|
- Use `-y` flag to skip confirmation prompts for automated workflows
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# Uses only first-party actions, and installs with pip rather than uv. The
|
|
4
|
+
# project prefers uv locally (see CLAUDE.md), but this runner could not resolve
|
|
5
|
+
# `astral-sh/setup-uv@v9` at all, and pip is the documented fallback — a CI file
|
|
6
|
+
# that reliably runs beats a marginally faster one.
|
|
7
|
+
#
|
|
8
|
+
# Pins are the current majors, which target Node 24. Node 20 is deprecated and
|
|
9
|
+
# is being force-migrated on the runners, so checkout@v4 / setup-python@v5 would
|
|
10
|
+
# have had a shelf life.
|
|
11
|
+
|
|
12
|
+
on:
|
|
13
|
+
push:
|
|
14
|
+
branches: [main]
|
|
15
|
+
pull_request:
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
|
|
20
|
+
concurrency:
|
|
21
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
22
|
+
cancel-in-progress: true
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
test:
|
|
26
|
+
name: test (py${{ matrix.python-version }})
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
strategy:
|
|
29
|
+
# Report every interpreter, rather than hiding 3.13 behind a 3.11 failure.
|
|
30
|
+
fail-fast: false
|
|
31
|
+
matrix:
|
|
32
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v7
|
|
35
|
+
|
|
36
|
+
- uses: actions/setup-python@v6
|
|
37
|
+
with:
|
|
38
|
+
python-version: ${{ matrix.python-version }}
|
|
39
|
+
cache: pip
|
|
40
|
+
cache-dependency-path: pyproject.toml
|
|
41
|
+
|
|
42
|
+
- name: Install
|
|
43
|
+
run: |
|
|
44
|
+
python -m pip install --upgrade pip
|
|
45
|
+
python -m pip install -e ".[dev]"
|
|
46
|
+
|
|
47
|
+
- name: Verify the interpreter matches the matrix
|
|
48
|
+
# Without this, a misconfigured setup step would silently run every
|
|
49
|
+
# matrix entry on the runner's default Python and report three passes
|
|
50
|
+
# for one version.
|
|
51
|
+
run: |
|
|
52
|
+
python - <<'PY'
|
|
53
|
+
import sys
|
|
54
|
+
actual = "{}.{}".format(*sys.version_info[:2])
|
|
55
|
+
expected = "${{ matrix.python-version }}"
|
|
56
|
+
assert actual == expected, f"expected Python {expected}, got {actual}"
|
|
57
|
+
print("Python", sys.version)
|
|
58
|
+
PY
|
|
59
|
+
|
|
60
|
+
- name: Test
|
|
61
|
+
run: python -m pytest tests/ -q
|
|
62
|
+
|
|
63
|
+
quality:
|
|
64
|
+
name: lint and type-check
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v7
|
|
68
|
+
|
|
69
|
+
- uses: actions/setup-python@v6
|
|
70
|
+
with:
|
|
71
|
+
python-version: "3.11"
|
|
72
|
+
cache: pip
|
|
73
|
+
cache-dependency-path: pyproject.toml
|
|
74
|
+
|
|
75
|
+
- name: Install
|
|
76
|
+
run: |
|
|
77
|
+
python -m pip install --upgrade pip
|
|
78
|
+
python -m pip install -e ".[dev]"
|
|
79
|
+
|
|
80
|
+
- name: Ruff lint
|
|
81
|
+
run: python -m ruff check src/ tests/
|
|
82
|
+
|
|
83
|
+
- name: Ruff format
|
|
84
|
+
run: python -m ruff format --check src/ tests/
|
|
85
|
+
|
|
86
|
+
# Runs once, not per matrix entry: pyproject pins mypy's
|
|
87
|
+
# python_version = "3.11", so the result is interpreter-independent.
|
|
88
|
+
- name: Mypy (strict)
|
|
89
|
+
run: python -m mypy src/
|
|
90
|
+
|
|
91
|
+
build:
|
|
92
|
+
name: build
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
steps:
|
|
95
|
+
- uses: actions/checkout@v7
|
|
96
|
+
|
|
97
|
+
- uses: actions/setup-python@v6
|
|
98
|
+
with:
|
|
99
|
+
python-version: "3.11"
|
|
100
|
+
|
|
101
|
+
- name: Build sdist and wheel
|
|
102
|
+
run: |
|
|
103
|
+
python -m pip install --upgrade pip build
|
|
104
|
+
python -m build
|
|
105
|
+
|
|
106
|
+
- name: Verify the built version matches __version__
|
|
107
|
+
# The version is dynamic: hatch reads it out of src/docmost_cli/__init__.py.
|
|
108
|
+
# If that regex ever stops matching, the build silently produces the wrong
|
|
109
|
+
# version rather than failing.
|
|
110
|
+
run: |
|
|
111
|
+
python - <<'PY'
|
|
112
|
+
import pathlib, re
|
|
113
|
+
|
|
114
|
+
source = pathlib.Path("src/docmost_cli/__init__.py").read_text()
|
|
115
|
+
match = re.search(r'__version__ = "([^"]+)"', source)
|
|
116
|
+
assert match, "no __version__ in src/docmost_cli/__init__.py"
|
|
117
|
+
expected = match.group(1)
|
|
118
|
+
|
|
119
|
+
built = sorted(p.name for p in pathlib.Path("dist").glob("*.whl"))
|
|
120
|
+
assert built, "no wheel produced"
|
|
121
|
+
assert any(f"-{expected}-" in name for name in built), (
|
|
122
|
+
f"__version__ is {expected} but built {built}"
|
|
123
|
+
)
|
|
124
|
+
print(f"version {expected} OK: {built}")
|
|
125
|
+
PY
|
|
126
|
+
|
|
127
|
+
- name: Verify the man pages ship in the wheel
|
|
128
|
+
# They are installed via [tool.hatch.build.targets.wheel.shared-data],
|
|
129
|
+
# which breaks quietly if the path ever moves.
|
|
130
|
+
run: |
|
|
131
|
+
python - <<'PY'
|
|
132
|
+
import pathlib, zipfile
|
|
133
|
+
|
|
134
|
+
wheel = next(iter(pathlib.Path("dist").glob("*.whl")))
|
|
135
|
+
names = zipfile.ZipFile(wheel).namelist()
|
|
136
|
+
pages = [n for n in names if n.endswith(".1") and "share/man/man1" in n]
|
|
137
|
+
assert len(pages) == 10, f"expected 10 man pages in the wheel, found {len(pages)}"
|
|
138
|
+
print(f"{len(pages)} man pages OK")
|
|
139
|
+
PY
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI and cuts a GitHub Release when a v* tag is pushed.
|
|
4
|
+
#
|
|
5
|
+
# Authentication is PyPI Trusted Publishing (OIDC) — no API token is stored in
|
|
6
|
+
# this repository. One-time setup on pypi.org, under the project's
|
|
7
|
+
# "Publishing" settings, add a trusted publisher:
|
|
8
|
+
#
|
|
9
|
+
# Owner: glinhard
|
|
10
|
+
# Repository: docmost-cli
|
|
11
|
+
# Workflow name: release.yml
|
|
12
|
+
# Environment: pypi
|
|
13
|
+
#
|
|
14
|
+
# Dry runs: trigger this workflow manually (Actions -> Release -> Run workflow)
|
|
15
|
+
# to exercise the whole chain — version guard, build, artifact checks, and
|
|
16
|
+
# action resolution — without uploading anything anywhere.
|
|
17
|
+
|
|
18
|
+
on:
|
|
19
|
+
push:
|
|
20
|
+
tags: ["v*"]
|
|
21
|
+
workflow_dispatch:
|
|
22
|
+
inputs:
|
|
23
|
+
dry-run:
|
|
24
|
+
description: "Build and verify only; do not publish"
|
|
25
|
+
type: boolean
|
|
26
|
+
default: true
|
|
27
|
+
|
|
28
|
+
permissions:
|
|
29
|
+
contents: read
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
verify:
|
|
33
|
+
name: verify and build
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v7
|
|
37
|
+
|
|
38
|
+
- uses: actions/setup-python@v6
|
|
39
|
+
with:
|
|
40
|
+
python-version: "3.11"
|
|
41
|
+
cache: pip
|
|
42
|
+
cache-dependency-path: pyproject.toml
|
|
43
|
+
|
|
44
|
+
- name: Install
|
|
45
|
+
run: |
|
|
46
|
+
python -m pip install --upgrade pip build twine
|
|
47
|
+
python -m pip install -e ".[dev]"
|
|
48
|
+
|
|
49
|
+
- name: Full test suite
|
|
50
|
+
# The tag is the last chance to catch a regression before it is
|
|
51
|
+
# published immutably: PyPI does not allow re-uploading a version.
|
|
52
|
+
run: python -m pytest tests/ -q
|
|
53
|
+
|
|
54
|
+
- name: Verify the tag matches __version__
|
|
55
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
56
|
+
run: |
|
|
57
|
+
python - <<'PY'
|
|
58
|
+
import os, pathlib, re
|
|
59
|
+
|
|
60
|
+
source = pathlib.Path("src/docmost_cli/__init__.py").read_text()
|
|
61
|
+
version = re.search(r'__version__ = "([^"]+)"', source).group(1)
|
|
62
|
+
tag = os.environ["GITHUB_REF_NAME"]
|
|
63
|
+
assert tag == f"v{version}", f"tag {tag} does not match __version__ {version}"
|
|
64
|
+
print(f"tag {tag} matches __version__ {version}")
|
|
65
|
+
PY
|
|
66
|
+
|
|
67
|
+
- name: Verify this version is not already on PyPI
|
|
68
|
+
# PyPI rejects a duplicate upload anyway; failing here gives a clearer
|
|
69
|
+
# message than a 400 from the publish step.
|
|
70
|
+
run: |
|
|
71
|
+
python - <<'PY'
|
|
72
|
+
import json, pathlib, re, urllib.request
|
|
73
|
+
|
|
74
|
+
source = pathlib.Path("src/docmost_cli/__init__.py").read_text()
|
|
75
|
+
version = re.search(r'__version__ = "([^"]+)"', source).group(1)
|
|
76
|
+
try:
|
|
77
|
+
with urllib.request.urlopen(
|
|
78
|
+
"https://pypi.org/pypi/docmost-cli/json", timeout=30
|
|
79
|
+
) as response:
|
|
80
|
+
released = set(json.load(response)["releases"])
|
|
81
|
+
except Exception as exc: # noqa: BLE001 - never block a release on this
|
|
82
|
+
print(f"could not reach PyPI ({exc}); skipping the duplicate check")
|
|
83
|
+
else:
|
|
84
|
+
assert version not in released, (
|
|
85
|
+
f"{version} is already on PyPI; bump __version__ before tagging"
|
|
86
|
+
)
|
|
87
|
+
print(f"{version} is not yet on PyPI (has {sorted(released)})")
|
|
88
|
+
PY
|
|
89
|
+
|
|
90
|
+
- name: Build sdist and wheel
|
|
91
|
+
run: python -m build
|
|
92
|
+
|
|
93
|
+
- name: Check the distributions
|
|
94
|
+
run: python -m twine check dist/*
|
|
95
|
+
|
|
96
|
+
- name: Verify the wheel contents
|
|
97
|
+
run: |
|
|
98
|
+
python - <<'PY'
|
|
99
|
+
import pathlib, re, zipfile
|
|
100
|
+
|
|
101
|
+
source = pathlib.Path("src/docmost_cli/__init__.py").read_text()
|
|
102
|
+
version = re.search(r'__version__ = "([^"]+)"', source).group(1)
|
|
103
|
+
|
|
104
|
+
wheel = next(iter(pathlib.Path("dist").glob("*.whl")))
|
|
105
|
+
assert f"-{version}-" in wheel.name, f"built {wheel.name}, expected {version}"
|
|
106
|
+
|
|
107
|
+
names = zipfile.ZipFile(wheel).namelist()
|
|
108
|
+
pages = [n for n in names if n.endswith(".1") and "share/man/man1" in n]
|
|
109
|
+
assert len(pages) == 10, f"expected 10 man pages, found {len(pages)}"
|
|
110
|
+
|
|
111
|
+
metadata = next(n for n in names if n.endswith("METADATA"))
|
|
112
|
+
text = zipfile.ZipFile(wheel).read(metadata).decode()
|
|
113
|
+
assert "License-Expression: AGPL-3.0-or-later" in text, "missing licence expression"
|
|
114
|
+
assert "Classifier: License ::" not in text, (
|
|
115
|
+
"deprecated License :: classifier is back; PEP 639 replaces it "
|
|
116
|
+
"with the license expression"
|
|
117
|
+
)
|
|
118
|
+
print(f"{wheel.name}: {len(pages)} man pages, licence metadata OK")
|
|
119
|
+
PY
|
|
120
|
+
|
|
121
|
+
# upload@v7 and download@v8 are a matched pair: v7 uploads files directly
|
|
122
|
+
# instead of zipping them, and v8 is the major that stopped trying to
|
|
123
|
+
# un-zip a download that was never a zip. Bump them together.
|
|
124
|
+
- uses: actions/upload-artifact@v7
|
|
125
|
+
with:
|
|
126
|
+
name: dist
|
|
127
|
+
path: dist/
|
|
128
|
+
|
|
129
|
+
publish:
|
|
130
|
+
name: publish to PyPI
|
|
131
|
+
needs: verify
|
|
132
|
+
runs-on: ubuntu-latest
|
|
133
|
+
# The job itself also runs on a manual dispatch, so a dry run resolves and
|
|
134
|
+
# downloads every action here — including the third-party publish action.
|
|
135
|
+
# That matters: `astral-sh/setup-uv` turned out not to resolve on these
|
|
136
|
+
# runners at all, and a release is the worst moment to discover that.
|
|
137
|
+
environment:
|
|
138
|
+
name: pypi
|
|
139
|
+
url: https://pypi.org/project/docmost-cli/
|
|
140
|
+
permissions:
|
|
141
|
+
id-token: write # required for Trusted Publishing
|
|
142
|
+
steps:
|
|
143
|
+
- uses: actions/download-artifact@v8
|
|
144
|
+
with:
|
|
145
|
+
name: dist
|
|
146
|
+
path: dist/
|
|
147
|
+
|
|
148
|
+
- name: Publish
|
|
149
|
+
# Uploading is gated on the ref being a tag, not on the dry-run input.
|
|
150
|
+
# A dispatch always runs against a branch, so no manual invocation can
|
|
151
|
+
# publish, whatever inputs it is given.
|
|
152
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
153
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
154
|
+
|
|
155
|
+
- name: Dry run summary
|
|
156
|
+
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
157
|
+
run: |
|
|
158
|
+
echo "Dry run: actions resolved and artifacts built, nothing published."
|
|
159
|
+
echo "Push a v* tag to publish for real."
|
|
160
|
+
ls -l dist/
|
|
161
|
+
|
|
162
|
+
github-release:
|
|
163
|
+
name: GitHub Release
|
|
164
|
+
needs: publish
|
|
165
|
+
runs-on: ubuntu-latest
|
|
166
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
167
|
+
permissions:
|
|
168
|
+
contents: write # required to create the release
|
|
169
|
+
steps:
|
|
170
|
+
- uses: actions/checkout@v7
|
|
171
|
+
|
|
172
|
+
- uses: actions/download-artifact@v8
|
|
173
|
+
with:
|
|
174
|
+
name: dist
|
|
175
|
+
path: dist/
|
|
176
|
+
|
|
177
|
+
- name: Extract this version's CHANGELOG section
|
|
178
|
+
run: |
|
|
179
|
+
python - <<'PY'
|
|
180
|
+
import os, pathlib, re
|
|
181
|
+
|
|
182
|
+
version = os.environ["GITHUB_REF_NAME"].removeprefix("v")
|
|
183
|
+
changelog = pathlib.Path("CHANGELOG.md").read_text()
|
|
184
|
+
# Everything from this version's heading up to the next "## " heading.
|
|
185
|
+
match = re.search(
|
|
186
|
+
rf"^## {re.escape(version)}.*?$(.*?)(?=^## )",
|
|
187
|
+
changelog,
|
|
188
|
+
re.MULTILINE | re.DOTALL,
|
|
189
|
+
)
|
|
190
|
+
body = match.group(1).strip() if match else f"See CHANGELOG.md for {version}."
|
|
191
|
+
pathlib.Path("release-notes.md").write_text(body + "\n")
|
|
192
|
+
print(body[:500])
|
|
193
|
+
PY
|
|
194
|
+
|
|
195
|
+
- name: Create or update the release
|
|
196
|
+
# The tag can arrive two ways: `git push origin v0.6.0`, or the GitHub
|
|
197
|
+
# web UI's "Draft a new release" flow, which creates the tag *and* the
|
|
198
|
+
# release before this job runs. Handle both, so neither route ends in a
|
|
199
|
+
# red job.
|
|
200
|
+
env:
|
|
201
|
+
GH_TOKEN: ${{ github.token }}
|
|
202
|
+
run: |
|
|
203
|
+
set -euo pipefail
|
|
204
|
+
tag="$GITHUB_REF_NAME"
|
|
205
|
+
|
|
206
|
+
if gh release view "$tag" >/dev/null 2>&1; then
|
|
207
|
+
echo "Release $tag already exists; attaching artifacts."
|
|
208
|
+
gh release upload "$tag" dist/* --clobber
|
|
209
|
+
|
|
210
|
+
# Only fill in notes if whoever created it left them empty —
|
|
211
|
+
# never overwrite notes a human wrote by hand.
|
|
212
|
+
if [ -z "$(gh release view "$tag" --json body --jq '.body' | tr -d '[:space:]')" ]; then
|
|
213
|
+
echo "Release body is empty; filling it from CHANGELOG.md."
|
|
214
|
+
gh release edit "$tag" --notes-file release-notes.md
|
|
215
|
+
else
|
|
216
|
+
echo "Release already has notes; leaving them alone."
|
|
217
|
+
fi
|
|
218
|
+
else
|
|
219
|
+
echo "Creating release $tag."
|
|
220
|
+
gh release create "$tag" \
|
|
221
|
+
--title "$tag" \
|
|
222
|
+
--notes-file release-notes.md \
|
|
223
|
+
dist/*
|
|
224
|
+
fi
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.6.0 (2026-07-27)
|
|
4
|
+
|
|
5
|
+
### Behavior changes
|
|
6
|
+
|
|
7
|
+
- **`--json` on list commands is now lossless.** It used to project each item
|
|
8
|
+
onto the same hardcoded column list the Rich table uses, silently discarding
|
|
9
|
+
every other field the server returned — `page list --json` dropped `slugId`,
|
|
10
|
+
`spaceId`, `creatorId`, `lastUpdatedById`, `position`, `isLocked`,
|
|
11
|
+
`createdAt` and `contributorIds`. Each element is now the complete server
|
|
12
|
+
object. The table is unchanged: its column list was always a display choice.
|
|
13
|
+
Page *content* is not part of the list payload (Docmost omits it from
|
|
14
|
+
`baseFields`), so listings stay small.
|
|
15
|
+
- **`--json` no longer invents keys.** A column the server omitted used to be
|
|
16
|
+
emitted as `null`; a missing field is now simply absent. An explicitly named
|
|
17
|
+
`--fields` entry still yields `null`, which keeps projections rectangular.
|
|
18
|
+
- `page list --tree --json` and flat `--json` now agree on shape — the tree path
|
|
19
|
+
already emitted complete objects.
|
|
20
|
+
- `page list --tree` now actually rejects `--page-size`, which its own error
|
|
21
|
+
message has claimed since 0.5.0 while never checking it.
|
|
22
|
+
- Table cells render `None` as blank instead of the literal string `None`
|
|
23
|
+
(visible in `page list`'s `parentPageId` column for every root page).
|
|
24
|
+
- `config show --json` emits `no_session_cache` as a real JSON boolean. The
|
|
25
|
+
table still shows it.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- `--fields a,b,c` on all eight list commands: projects the JSON output *and*
|
|
30
|
+
replaces the table's columns, so the table is configurable for the first time.
|
|
31
|
+
Names are validated against the fields the server actually returned — an
|
|
32
|
+
unknown name is a usage error listing what is available, because a typo would
|
|
33
|
+
otherwise yield a silent full-length column of nulls.
|
|
34
|
+
- `--fields` under `page list --tree --json`, projecting every node recursively
|
|
35
|
+
while always preserving the nested `children` array. It requires `--json`,
|
|
36
|
+
since the indented tree view has no columns to replace.
|
|
37
|
+
- `--json` on `workspace info`, `user me` and `config show`, which previously had
|
|
38
|
+
no machine-readable output at all. The first two emit the complete server
|
|
39
|
+
object and also accept `--fields`. `config show --json` masks `api_key` and
|
|
40
|
+
`password` exactly as the table does.
|
|
41
|
+
- `print_json()` in `output/`, now the single JSON writer — `page list --tree
|
|
42
|
+
--json` and `page get --raw` route through it, which also gives `--raw` the
|
|
43
|
+
`default=str` fallback it was missing.
|
|
44
|
+
- `emit_item()` in `cli/_list_opts.py`, the single-item mirror of `emit_list()`.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- `mypy src/` is clean under `strict = true`; it had been carrying 23 errors.
|
|
49
|
+
Two were real defects rather than annotation noise: `extract_id()` was declared
|
|
50
|
+
`-> str` but returned whatever the server put in `"id"`, so a numeric id would
|
|
51
|
+
have propagated a non-string page ID into request bodies; and `_node_heading()`
|
|
52
|
+
computed `"#" * level` from an unvalidated nested `.get()`, so a ProseMirror
|
|
53
|
+
document with a string or null heading level raised `TypeError` mid-render.
|
|
54
|
+
- `config show` masking is now produced in exactly one place, so no future
|
|
55
|
+
renderer can add a path that forgets to mask.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- `print_table()` gained a keyword-only `fields` argument; `columns` no longer
|
|
60
|
+
affects JSON output.
|
|
61
|
+
- Corrected `SPECIFICATION.md` §7.2, whose sample payload showed a projected
|
|
62
|
+
three-key object and documented a field named `updated` that has never existed
|
|
63
|
+
(the code emits `updatedAt`), and its stale `print_table` signature.
|
|
64
|
+
- Removed two README rows for things that were never implemented: a
|
|
65
|
+
`space get` command and a `space list --detail` flag. Corrected `search` to
|
|
66
|
+
`search query`.
|
|
67
|
+
|
|
68
|
+
## 0.5.0 (2026-07-26)
|
|
69
|
+
|
|
70
|
+
Fixes six gaps reported against Docmost Community 0.95, verified against the
|
|
71
|
+
upstream v0.95.0 server source.
|
|
72
|
+
|
|
73
|
+
### Behavior changes
|
|
74
|
+
|
|
75
|
+
- **List commands now return every result.** Pagination is followed
|
|
76
|
+
automatically instead of stopping at the server's first page. `--limit` is now
|
|
77
|
+
a cap on the *total* across pages rather than a per-request limit, so
|
|
78
|
+
`--limit 200` fetches two pages of 100 instead of failing with HTTP 400.
|
|
79
|
+
- **`sync push` no longer recreates pages silently.** On a server too old to
|
|
80
|
+
update content in place it aborts with an explanation; pass `--allow-recreate`
|
|
81
|
+
to opt into the old delete+recreate behavior, which prints a warning naming the
|
|
82
|
+
affected page count.
|
|
83
|
+
- **`page move --position` takes `first`, `last`, or an ordering key** instead of
|
|
84
|
+
an integer, and defaults to `first`. `POSITION_FIRST` (`"aaaaa"`) is gone — it
|
|
85
|
+
never meant "first" anyway: in ASCII `'a' > '0'`, so it sorted *after* every key
|
|
86
|
+
Docmost's own generator produces.
|
|
87
|
+
- `move_page()`'s `position` argument is now required (the server requires it).
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- **Pagination metadata was silently discarded.** `get_cursor()` looked for
|
|
92
|
+
`data.cursor`, but Docmost returns `data.meta.nextCursor`, so it returned `None`
|
|
93
|
+
every time — which is why the auto-follow documented since 0.2.0 never actually
|
|
94
|
+
ran and `--cursor` was a write-only flag.
|
|
95
|
+
- **`page children` capped at 20.** It sent no `limit` or `cursor` to
|
|
96
|
+
`/pages/sidebar-pages` and got the server default.
|
|
97
|
+
- **`page move --parent` returned HTTP 400 without `--position`.** Docmost's
|
|
98
|
+
`MovePageDto` declares `position` as required (`@MinLength(5) @MaxLength(12)`);
|
|
99
|
+
the CLI dropped it from the body when unset.
|
|
100
|
+
- **Content updates failed with a misleading message.** The CLI POSTed to
|
|
101
|
+
`/pages/content/update`, an endpoint that does not exist in Docmost, and read
|
|
102
|
+
the 404 as an Enterprise-only restriction. Content now goes through
|
|
103
|
+
`POST /pages/update`, which Docmost applies server-side through its
|
|
104
|
+
collaboration gateway. The page keeps its ID, slug, history, comments and
|
|
105
|
+
inbound links — no WebSocket or Yjs client needed. Requires Docmost v0.71+.
|
|
106
|
+
- **Space slug resolution failed past the first page of spaces**, reporting a
|
|
107
|
+
misleading `Space '<slug>' not found.` Now follows pagination, stopping at the
|
|
108
|
+
first match.
|
|
109
|
+
- **`sync pull` fetched an incomplete tree** on spaces with more root pages or
|
|
110
|
+
children than one server page, silently producing a partial local copy.
|
|
111
|
+
- **`sync push` lost the manifest when a phase aborted**, so the next push
|
|
112
|
+
re-created every already-created page as a duplicate. The manifest is now saved
|
|
113
|
+
in a `finally` block.
|
|
114
|
+
- `update_page_content` only intercepted HTTP 404; a 405 or 403 fell through to a
|
|
115
|
+
bare "Unexpected error".
|
|
116
|
+
|
|
117
|
+
### Added
|
|
118
|
+
|
|
119
|
+
- `--version` / `-V` and a `version` subcommand. With `--verbose`, `version` also
|
|
120
|
+
reports the Python version and platform. The version now has a single source of
|
|
121
|
+
truth (`src/docmost_cli/__init__.py`), with `pyproject.toml` deriving it.
|
|
122
|
+
- `--no-session-cache` global flag, `DOCMOST_NO_SESSION_CACHE` environment
|
|
123
|
+
variable and `no_session_cache` config key: the session JWT stays in memory and
|
|
124
|
+
is never read from or written to `~/.cache/docmost-cli/session.json`.
|
|
125
|
+
- `docmost-cli config logout` deletes an existing cached session token.
|
|
126
|
+
- `--limit`, `--page-size`, `--cursor`, `--no-follow` and `--envelope` on all
|
|
127
|
+
eight list commands. `--json` still emits a bare array; `--envelope` wraps it as
|
|
128
|
+
`{"items": [...], "meta": {...}}` carrying `hasNextPage` and `nextCursor`.
|
|
129
|
+
- `page update --append` / `--prepend`.
|
|
130
|
+
- `page move --root` to move a nested page to the space root.
|
|
131
|
+
- `page list --tree --json` outputs the nested tree as JSON (it was previously
|
|
132
|
+
ignored).
|
|
133
|
+
- `api/position.py`: base62 fractional indexing ported from the reference
|
|
134
|
+
algorithm, with jitter to satisfy Docmost's 5-character minimum. Keys stay valid
|
|
135
|
+
for Docmost's own JS generator, so dragging a sibling next to a CLI-placed page
|
|
136
|
+
in the web editor still works.
|
|
137
|
+
- `models/common.py`: `PaginationMeta` and `PaginatedResult` pydantic models.
|
|
138
|
+
- Guards against a server that ignores `cursor`: pagination stops when a cursor or
|
|
139
|
+
a page repeats, and warns, rather than looping forever.
|
|
140
|
+
|
|
141
|
+
### Changed
|
|
142
|
+
|
|
143
|
+
- `--tree` now rejects the pagination flags instead of silently ignoring them.
|
|
144
|
+
- `print_table()` gained a keyword-only `meta` argument; `print_table` and the new
|
|
145
|
+
`print_warning` are exported from `docmost_cli.output`.
|
|
146
|
+
- Corrected `SPECIFICATION.md` §5.2/§5.3 (response envelope shape, `/pages/move`
|
|
147
|
+
requirements, the non-existent content endpoint) and the `--position` docs in
|
|
148
|
+
the man pages. A new doc test keeps the man-page `.TH` versions in sync.
|
|
149
|
+
|
|
150
|
+
## 0.4.0 (2026-03-22)
|
|
151
|
+
|
|
152
|
+
- Add `sync pull` command: download all pages from a space to local Markdown files with YAML frontmatter
|
|
153
|
+
- Add `sync push` command: upload local changes to server (create, update, move pages)
|
|
154
|
+
- Add `sync status` command: show changes between local files and last-pulled state
|
|
155
|
+
- Edition-aware content updates: Enterprise uses REST content update (preserves page ID), Community uses safe create-then-delete (new page created and verified before old page removed)
|
|
156
|
+
- Flat directory layout with `.docmost-manifest.json` tracking sync state and SHA-256 content hashes
|
|
157
|
+
- `--dry-run` flag on push to preview changes without executing
|
|
158
|
+
- `--delete` flag on push to remove server pages not found locally (opt-in safety)
|
|
159
|
+
- `--force` flag on pull to overwrite existing synced data
|
|
160
|
+
- Topological sort ensures parent pages are created before children
|
|
161
|
+
- ID remapping: when Community edition forces new page IDs, frontmatter and manifest are updated automatically
|
|
162
|
+
- 105 new tests (96 sync module + 9 CLI integration)
|
|
163
|
+
|
|
164
|
+
## 0.3.1 (2026-03-22)
|
|
165
|
+
|
|
166
|
+
- Fix `page list --tree` and `page children` on Community edition: use `/pages/sidebar-pages` instead of `/pages/children` (404 on v0.70.3)
|
|
167
|
+
- Tree recursion: only fetch children for pages with `hasChildren: true` (eliminates ~40 wasted API calls on a 50-page space)
|
|
168
|
+
- Narrow error handling in tree builder (auth failures no longer silently swallowed)
|
|
169
|
+
- Reduce API calls in `page get` (fetch page info once, not twice)
|
|
170
|
+
- Remove raw HTTP call from CLI layer (use API function instead)
|
|
171
|
+
- Deduplicate UTF-8 stdio reconfigure into `_ensure_utf8_stdio()`
|
|
172
|
+
- Use `build_body` helper consistently in all API functions
|
|
173
|
+
- Remove dead code: unused models, unused parameter
|
|
174
|
+
|
|
175
|
+
## 0.3.0 (2026-03-22)
|
|
176
|
+
|
|
177
|
+
- Add `parentPageId` to `page list --json` output columns
|
|
178
|
+
- Add `parent_id` to `page get --meta` YAML frontmatter
|
|
179
|
+
- Add `--icon` flag to `page update` command
|
|
180
|
+
- Add recursive tree fallback: `page list --tree` fills in missing children
|
|
181
|
+
- Add "See also" cross-references to all page command help text
|
|
182
|
+
- Fix `page children` and `--tree` on Community edition: use `/pages/sidebar-pages` instead of `/pages/children` (404 on v0.70.3)
|
|
183
|
+
- Fix emoji/Unicode crash: move UTF-8 encoding to correct entry point
|
|
184
|
+
- Fix `--parent` on `page create`: use fractional index position string
|
|
185
|
+
- Fix `--content` escape sequences: `\n` and `\t` now work as newline/tab
|
|
186
|
+
|
|
187
|
+
## 0.2.4 (2026-03-22)
|
|
188
|
+
|
|
189
|
+
- Fix emoji/Unicode crash for real: move UTF-8 reconfigure from `__main__.py` to `cli/main.py` (the actual entry point used by `docmost-cli` script bypasses `__main__.py`)
|
|
190
|
+
|
|
191
|
+
## 0.2.3 (2026-03-22)
|
|
192
|
+
|
|
193
|
+
- Fix `--parent` on `page create`: send fractional index position string (Docmost requires 5-12 char string, not integer)
|
|
194
|
+
- Fix emoji crash on all Windows terminals: remove `isatty()` guard, always reconfigure to UTF-8 on Windows
|
|
195
|
+
- Fix `--content` escape sequences: `\n` and `\t` now interpreted as actual newline/tab
|
|
196
|
+
- Position parameter on `page move` changed from `int` to `str` (fractional index format)
|
|
197
|
+
|
|
198
|
+
## 0.2.2 (2026-03-22)
|
|
199
|
+
|
|
200
|
+
- Fix `--parent` on `page create` silently ignored (import endpoint ignores parentPageId; now calls move_page as fallback)
|
|
201
|
+
- Fix `--help` crash on Windows (`OSError` from Rich's LegacyWindowsRenderer on cp1252 consoles)
|
|
202
|
+
- Fix `page get --meta` crash when page content contains emoji (✅❌⚠️📊)
|
|
203
|
+
- Reconfigure stdout/stderr to UTF-8 at startup on Windows interactive terminals
|
|
204
|
+
|
|
205
|
+
## 0.2.1 (2026-03-22)
|
|
206
|
+
|
|
207
|
+
- Fix tree view crash on Windows with emoji page icons (cp1252 encoding)
|
|
208
|
+
- Fix silent Enterprise endpoint probe leaking error messages on Community edition
|
|
209
|
+
- Fix API endpoints discovered during live integration testing:
|
|
210
|
+
- `/spaces/list` → `/spaces`
|
|
211
|
+
- `/comments/list` → `/comments`
|
|
212
|
+
- `/pages/export` format `md` → `markdown`, response is ZIP not JSON
|
|
213
|
+
- Auth token extracted from `authToken` cookie (not `token`)
|
|
214
|
+
- Comment content JSON-stringified for API
|
|
215
|
+
- Consolidate duplicated code: shared `extract_items`, `extract_id`, `build_body` helpers
|
|
216
|
+
- Remove 6 dead stub files (-84 lines)
|
|
217
|
+
- Add `post_raw()` to DocmostClient for binary/probe responses
|
|
218
|
+
- Fix double file read in page import
|
|
219
|
+
- Add Claude Code skill (`/docmost`) for wiki interaction
|
|
220
|
+
- Prepare for PyPI: py.typed marker, CHANGELOG, dependency upper bounds, classifiers
|
|
221
|
+
|
|
222
|
+
## 0.2.0 (2026-03-22)
|
|
223
|
+
|
|
224
|
+
- Retry with exponential backoff for transient errors (429, 5xx)
|
|
225
|
+
- `--verbose` HTTP debug logging (request/response to stderr)
|
|
226
|
+
- Page duplicate, copy, children, history, export, import commands
|
|
227
|
+
- Tree view (`--tree`) for page listing
|
|
228
|
+
- Workspace info/members, user me, attachment search commands
|
|
229
|
+
- Pagination auto-follow with safety guard (max 1000 iterations)
|
|
230
|
+
- ProseMirror-to-Markdown converter (all block nodes and marks)
|
|
231
|
+
- Claude Code skill (`/docmost`) for wiki interaction
|
|
232
|
+
- Comprehensive README with command reference
|
|
233
|
+
- MIT LICENSE file
|
|
234
|
+
- Session cache file permissions (0600)
|
|
235
|
+
- 175 tests, 0 lint errors
|
|
236
|
+
|
|
237
|
+
## 0.1.0 (2026-03-22)
|
|
238
|
+
|
|
239
|
+
- Initial release
|
|
240
|
+
- Project scaffolding with typer CLI framework
|
|
241
|
+
- Configuration system with TOML profiles and environment variable overrides
|
|
242
|
+
- HTTP client with API key (Enterprise) and session (Community) auth auto-detection
|
|
243
|
+
- Page CRUD: create (via import endpoint), read, update, delete, move
|
|
244
|
+
- Space list, create, update
|
|
245
|
+
- Comment list, create, update (ProseMirror JSON wrapping)
|
|
246
|
+
- Full-text search with space filtering
|
|
247
|
+
- Output helpers enforcing stdout/stderr separation
|
|
248
|
+
- Edition-agnostic design (Community + Enterprise)
|
|
249
|
+
- 50 tests with pytest + pytest-httpx
|