sheafmd 1.0.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 (207) hide show
  1. sheafmd-1.0.0/.github/workflows/ci.yml +125 -0
  2. sheafmd-1.0.0/.gitignore +5 -0
  3. sheafmd-1.0.0/GLOSSARY.md +105 -0
  4. sheafmd-1.0.0/LICENSE +21 -0
  5. sheafmd-1.0.0/PKG-INFO +163 -0
  6. sheafmd-1.0.0/README.md +150 -0
  7. sheafmd-1.0.0/SPEC.md +1451 -0
  8. sheafmd-1.0.0/pyproject.toml +56 -0
  9. sheafmd-1.0.0/src/sheafmd/__init__.py +48 -0
  10. sheafmd-1.0.0/src/sheafmd/__main__.py +6 -0
  11. sheafmd-1.0.0/src/sheafmd/_pandoc.py +20 -0
  12. sheafmd-1.0.0/src/sheafmd/agent_files/agents-snippet.md +5 -0
  13. sheafmd-1.0.0/src/sheafmd/agent_files/claude-skill.md +65 -0
  14. sheafmd-1.0.0/src/sheafmd/agent_setup.py +633 -0
  15. sheafmd-1.0.0/src/sheafmd/agents/__init__.py +1 -0
  16. sheafmd-1.0.0/src/sheafmd/agents/base.py +100 -0
  17. sheafmd-1.0.0/src/sheafmd/agents/claude_code.py +377 -0
  18. sheafmd-1.0.0/src/sheafmd/agents/detect.py +63 -0
  19. sheafmd-1.0.0/src/sheafmd/agents/generic.py +57 -0
  20. sheafmd-1.0.0/src/sheafmd/agents/prompts.py +45 -0
  21. sheafmd-1.0.0/src/sheafmd/agents/registry.py +42 -0
  22. sheafmd-1.0.0/src/sheafmd/atomic.py +36 -0
  23. sheafmd-1.0.0/src/sheafmd/blocks.py +341 -0
  24. sheafmd-1.0.0/src/sheafmd/charts.py +399 -0
  25. sheafmd-1.0.0/src/sheafmd/cli.py +1013 -0
  26. sheafmd-1.0.0/src/sheafmd/comments.py +618 -0
  27. sheafmd-1.0.0/src/sheafmd/config.py +232 -0
  28. sheafmd-1.0.0/src/sheafmd/diagnostics.py +135 -0
  29. sheafmd-1.0.0/src/sheafmd/edit.py +1834 -0
  30. sheafmd-1.0.0/src/sheafmd/hub/__init__.py +1 -0
  31. sheafmd-1.0.0/src/sheafmd/hub/asktell.py +547 -0
  32. sheafmd-1.0.0/src/sheafmd/hub/auth.py +190 -0
  33. sheafmd-1.0.0/src/sheafmd/hub/client.py +518 -0
  34. sheafmd-1.0.0/src/sheafmd/hub/core.py +482 -0
  35. sheafmd-1.0.0/src/sheafmd/hub/events.py +106 -0
  36. sheafmd-1.0.0/src/sheafmd/hub/handler.py +890 -0
  37. sheafmd-1.0.0/src/sheafmd/hub/index.py +567 -0
  38. sheafmd-1.0.0/src/sheafmd/hub/jobs.py +223 -0
  39. sheafmd-1.0.0/src/sheafmd/hub/live.py +526 -0
  40. sheafmd-1.0.0/src/sheafmd/hub/peer.py +123 -0
  41. sheafmd-1.0.0/src/sheafmd/hub/proxy.py +301 -0
  42. sheafmd-1.0.0/src/sheafmd/hub/recover.py +242 -0
  43. sheafmd-1.0.0/src/sheafmd/hub/roots.py +569 -0
  44. sheafmd-1.0.0/src/sheafmd/hub/sender.py +113 -0
  45. sheafmd-1.0.0/src/sheafmd/hub/server.py +498 -0
  46. sheafmd-1.0.0/src/sheafmd/hub/service.py +280 -0
  47. sheafmd-1.0.0/src/sheafmd/hub/sock.py +118 -0
  48. sheafmd-1.0.0/src/sheafmd/hub/state.py +507 -0
  49. sheafmd-1.0.0/src/sheafmd/images.py +126 -0
  50. sheafmd-1.0.0/src/sheafmd/lifecycle.py +284 -0
  51. sheafmd-1.0.0/src/sheafmd/links.py +124 -0
  52. sheafmd-1.0.0/src/sheafmd/mcp.py +722 -0
  53. sheafmd-1.0.0/src/sheafmd/page/index.css +53 -0
  54. sheafmd-1.0.0/src/sheafmd/page/index.js +78 -0
  55. sheafmd-1.0.0/src/sheafmd/page/page.css +175 -0
  56. sheafmd-1.0.0/src/sheafmd/page/page.js +3101 -0
  57. sheafmd-1.0.0/src/sheafmd/paths.py +233 -0
  58. sheafmd-1.0.0/src/sheafmd/pdf.py +189 -0
  59. sheafmd-1.0.0/src/sheafmd/render.py +1189 -0
  60. sheafmd-1.0.0/src/sheafmd/sanitize.py +182 -0
  61. sheafmd-1.0.0/src/sheafmd/seat.py +52 -0
  62. sheafmd-1.0.0/src/sheafmd/source.py +259 -0
  63. sheafmd-1.0.0/src/sheafmd/theme.py +178 -0
  64. sheafmd-1.0.0/src/sheafmd/themes/plain/theme.css +32 -0
  65. sheafmd-1.0.0/src/sheafmd/themes/plain/theme.toml +1 -0
  66. sheafmd-1.0.0/tests/agent_fakes.py +115 -0
  67. sheafmd-1.0.0/tests/conftest.py +369 -0
  68. sheafmd-1.0.0/tests/corpus/comments-in-every-block.md +50 -0
  69. sheafmd-1.0.0/tests/corpus/crlf.md +25 -0
  70. sheafmd-1.0.0/tests/corpus/empty.md +0 -0
  71. sheafmd-1.0.0/tests/corpus/fences-in-lists.md +47 -0
  72. sheafmd-1.0.0/tests/corpus/fences.md +59 -0
  73. sheafmd-1.0.0/tests/corpus/front-matter.md +29 -0
  74. sheafmd-1.0.0/tests/corpus/headings.md +40 -0
  75. sheafmd-1.0.0/tests/corpus/hr-and-blank.md +39 -0
  76. sheafmd-1.0.0/tests/corpus/indented-code.md +38 -0
  77. sheafmd-1.0.0/tests/corpus/lists.md +59 -0
  78. sheafmd-1.0.0/tests/corpus/minimal.md +3 -0
  79. sheafmd-1.0.0/tests/corpus/no-trailing-newline.md +16 -0
  80. sheafmd-1.0.0/tests/corpus/only-blank.md +3 -0
  81. sheafmd-1.0.0/tests/corpus/paragraphs.md +33 -0
  82. sheafmd-1.0.0/tests/corpus/quotes.md +52 -0
  83. sheafmd-1.0.0/tests/corpus/raw-html.md +44 -0
  84. sheafmd-1.0.0/tests/corpus/tables.md +51 -0
  85. sheafmd-1.0.0/tests/corpus/task-lists.md +37 -0
  86. sheafmd-1.0.0/tests/helpers.py +771 -0
  87. sheafmd-1.0.0/tests/leakguard.py +199 -0
  88. sheafmd-1.0.0/tests/page/conftest.py +177 -0
  89. sheafmd-1.0.0/tests/page/pagehelp.py +289 -0
  90. sheafmd-1.0.0/tests/page/test_page_agent.py +191 -0
  91. sheafmd-1.0.0/tests/page/test_page_boot.py +16 -0
  92. sheafmd-1.0.0/tests/page/test_page_drafts.py +512 -0
  93. sheafmd-1.0.0/tests/page/test_page_index.py +97 -0
  94. sheafmd-1.0.0/tests/page/test_page_keyboard.py +141 -0
  95. sheafmd-1.0.0/tests/page/test_page_launch.py +134 -0
  96. sheafmd-1.0.0/tests/page/test_page_outage.py +1183 -0
  97. sheafmd-1.0.0/tests/page/test_page_proxy.py +56 -0
  98. sheafmd-1.0.0/tests/page/test_page_selection.py +461 -0
  99. sheafmd-1.0.0/tests/page/test_page_waits.py +85 -0
  100. sheafmd-1.0.0/tests/page/test_page_writing.py +4490 -0
  101. sheafmd-1.0.0/tests/test_agent_fakes.py +14 -0
  102. sheafmd-1.0.0/tests/test_agent_files.py +117 -0
  103. sheafmd-1.0.0/tests/test_agents_adapters.py +756 -0
  104. sheafmd-1.0.0/tests/test_agents_detect.py +100 -0
  105. sheafmd-1.0.0/tests/test_anchoring.py +140 -0
  106. sheafmd-1.0.0/tests/test_api.py +694 -0
  107. sheafmd-1.0.0/tests/test_atomic.py +51 -0
  108. sheafmd-1.0.0/tests/test_blocks.py +139 -0
  109. sheafmd-1.0.0/tests/test_charts.py +309 -0
  110. sheafmd-1.0.0/tests/test_ci_scripts.py +404 -0
  111. sheafmd-1.0.0/tests/test_ci_workflow.py +358 -0
  112. sheafmd-1.0.0/tests/test_cli.py +1126 -0
  113. sheafmd-1.0.0/tests/test_cli_comments.py +521 -0
  114. sheafmd-1.0.0/tests/test_cli_diag.py +219 -0
  115. sheafmd-1.0.0/tests/test_cli_hub.py +500 -0
  116. sheafmd-1.0.0/tests/test_cli_list.py +453 -0
  117. sheafmd-1.0.0/tests/test_cli_token.py +226 -0
  118. sheafmd-1.0.0/tests/test_client_hub_stopping.py +99 -0
  119. sheafmd-1.0.0/tests/test_comment_ops.py +62 -0
  120. sheafmd-1.0.0/tests/test_comments_format.py +242 -0
  121. sheafmd-1.0.0/tests/test_config.py +602 -0
  122. sheafmd-1.0.0/tests/test_corpus_comments.py +51 -0
  123. sheafmd-1.0.0/tests/test_diagnostics.py +190 -0
  124. sheafmd-1.0.0/tests/test_edit_corpus.py +76 -0
  125. sheafmd-1.0.0/tests/test_edit_followups.py +1009 -0
  126. sheafmd-1.0.0/tests/test_edit_markers.py +263 -0
  127. sheafmd-1.0.0/tests/test_helpers_exec.py +75 -0
  128. sheafmd-1.0.0/tests/test_helpers_listeners.py +160 -0
  129. sheafmd-1.0.0/tests/test_helpers_numbers.py +19 -0
  130. sheafmd-1.0.0/tests/test_helpers_ports.py +192 -0
  131. sheafmd-1.0.0/tests/test_hub_address.py +46 -0
  132. sheafmd-1.0.0/tests/test_hub_asktell.py +2014 -0
  133. sheafmd-1.0.0/tests/test_hub_auth.py +183 -0
  134. sheafmd-1.0.0/tests/test_hub_handler.py +1247 -0
  135. sheafmd-1.0.0/tests/test_hub_index.py +479 -0
  136. sheafmd-1.0.0/tests/test_hub_index_live.py +1116 -0
  137. sheafmd-1.0.0/tests/test_hub_index_page.py +289 -0
  138. sheafmd-1.0.0/tests/test_hub_jobs.py +429 -0
  139. sheafmd-1.0.0/tests/test_hub_jump.py +358 -0
  140. sheafmd-1.0.0/tests/test_hub_lifecycle.py +326 -0
  141. sheafmd-1.0.0/tests/test_hub_listing.py +708 -0
  142. sheafmd-1.0.0/tests/test_hub_live.py +967 -0
  143. sheafmd-1.0.0/tests/test_hub_peer.py +407 -0
  144. sheafmd-1.0.0/tests/test_hub_recover.py +544 -0
  145. sheafmd-1.0.0/tests/test_hub_roots.py +841 -0
  146. sheafmd-1.0.0/tests/test_hub_rotate.py +415 -0
  147. sheafmd-1.0.0/tests/test_hub_security.py +544 -0
  148. sheafmd-1.0.0/tests/test_hub_server.py +729 -0
  149. sheafmd-1.0.0/tests/test_hub_sock.py +228 -0
  150. sheafmd-1.0.0/tests/test_hub_state.py +356 -0
  151. sheafmd-1.0.0/tests/test_hub_upgrade.py +425 -0
  152. sheafmd-1.0.0/tests/test_images.py +308 -0
  153. sheafmd-1.0.0/tests/test_init.py +1448 -0
  154. sheafmd-1.0.0/tests/test_isolation.py +150 -0
  155. sheafmd-1.0.0/tests/test_leakguard.py +331 -0
  156. sheafmd-1.0.0/tests/test_links.py +187 -0
  157. sheafmd-1.0.0/tests/test_markers_roles.py +290 -0
  158. sheafmd-1.0.0/tests/test_mcp_documents.py +247 -0
  159. sheafmd-1.0.0/tests/test_mcp_http.py +351 -0
  160. sheafmd-1.0.0/tests/test_mcp_pages.py +635 -0
  161. sheafmd-1.0.0/tests/test_mcp_stdio.py +1040 -0
  162. sheafmd-1.0.0/tests/test_mcp_tools.py +492 -0
  163. sheafmd-1.0.0/tests/test_merge.py +516 -0
  164. sheafmd-1.0.0/tests/test_merge_align.py +361 -0
  165. sheafmd-1.0.0/tests/test_merge_gate.py +381 -0
  166. sheafmd-1.0.0/tests/test_merge_markers.py +87 -0
  167. sheafmd-1.0.0/tests/test_mermaid.py +45 -0
  168. sheafmd-1.0.0/tests/test_page_assets.py +236 -0
  169. sheafmd-1.0.0/tests/test_page_toast.py +61 -0
  170. sheafmd-1.0.0/tests/test_page_upgrade.py +88 -0
  171. sheafmd-1.0.0/tests/test_pandoc.py +84 -0
  172. sheafmd-1.0.0/tests/test_paths.py +94 -0
  173. sheafmd-1.0.0/tests/test_pdf.py +295 -0
  174. sheafmd-1.0.0/tests/test_pdf_csp.py +182 -0
  175. sheafmd-1.0.0/tests/test_proc_state.py +60 -0
  176. sheafmd-1.0.0/tests/test_prompts.py +73 -0
  177. sheafmd-1.0.0/tests/test_proxy_config.py +189 -0
  178. sheafmd-1.0.0/tests/test_proxy_contract.py +255 -0
  179. sheafmd-1.0.0/tests/test_proxy_images.py +472 -0
  180. sheafmd-1.0.0/tests/test_proxy_member.py +515 -0
  181. sheafmd-1.0.0/tests/test_proxy_socket.py +184 -0
  182. sheafmd-1.0.0/tests/test_proxy_start.py +215 -0
  183. sheafmd-1.0.0/tests/test_readme.py +101 -0
  184. sheafmd-1.0.0/tests/test_references.py +142 -0
  185. sheafmd-1.0.0/tests/test_remote_box.py +287 -0
  186. sheafmd-1.0.0/tests/test_remote_seat.py +152 -0
  187. sheafmd-1.0.0/tests/test_render.py +422 -0
  188. sheafmd-1.0.0/tests/test_route_access.py +162 -0
  189. sheafmd-1.0.0/tests/test_sanitize.py +429 -0
  190. sheafmd-1.0.0/tests/test_save_roundtrip.py +188 -0
  191. sheafmd-1.0.0/tests/test_service.py +911 -0
  192. sheafmd-1.0.0/tests/test_share_copy.py +963 -0
  193. sheafmd-1.0.0/tests/test_source.py +540 -0
  194. sheafmd-1.0.0/tests/test_spec_page_pins.py +121 -0
  195. sheafmd-1.0.0/tests/test_theme.py +798 -0
  196. sheafmd-1.0.0/tests/test_tokens.py +360 -0
  197. sheafmd-1.0.0/tests/test_tools.py +139 -0
  198. sheafmd-1.0.0/tests/test_windows_gate.py +54 -0
  199. sheafmd-1.0.0/tools/VERIFYING-RENDERED-OUTPUT.md +149 -0
  200. sheafmd-1.0.0/tools/check-rendered-pdf.py +244 -0
  201. sheafmd-1.0.0/tools/ci/_ci.py +111 -0
  202. sheafmd-1.0.0/tools/ci/page_paths.py +91 -0
  203. sheafmd-1.0.0/tools/ci/service_linux.py +161 -0
  204. sheafmd-1.0.0/tools/ci/service_macos.py +129 -0
  205. sheafmd-1.0.0/tools/ci/stall_watch.py +207 -0
  206. sheafmd-1.0.0/tools/wheel_smoke.py +177 -0
  207. sheafmd-1.0.0/uv.lock +640 -0
@@ -0,0 +1,125 @@
1
+ # SPEC 18. A pull request, a push to main and the nightly run run on ubuntu only, and macOS runs only when someone
2
+ # starts the workflow by hand with `macos` ticked: a macOS minute counts ten times.
3
+ # tests/test_ci_workflow.py holds these triggers.
4
+ name: CI
5
+
6
+ on:
7
+ pull_request:
8
+ branches: [main]
9
+ push:
10
+ branches: [main]
11
+ # paths-ignore filters push and pull_request only; a scheduled run always starts.
12
+ schedule:
13
+ - cron: '0 1 * * *'
14
+ workflow_dispatch:
15
+ inputs:
16
+ macos:
17
+ description: Add the macOS jobs (a macOS minute counts ten times)
18
+ type: boolean
19
+ default: false
20
+ python:
21
+ description: Python version to test (all is every version of the matrix)
22
+ type: choice
23
+ options: [all, "3.11", "3.13", "3.14"]
24
+ default: all
25
+
26
+ # A new push to a pull request cancels its earlier run. A run on main is never cancelled once it has
27
+ # started, so every merge to main gets a full verdict. Each run on main has a
28
+ # group of its own, because a group holds one running and one waiting run, and a newer waiting run
29
+ # would replace an older one even with cancel-in-progress false. Merges in a burst therefore run side
30
+ # by side, one job each, and a 3.11 or 3.14 regression shows in the nightly run, not in a merge.
31
+ concurrency:
32
+ group: ci-${{ github.ref == 'refs/heads/main' && github.run_id || github.ref }}
33
+ cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
34
+
35
+ permissions:
36
+ contents: read
37
+
38
+ jobs:
39
+ test:
40
+ name: ${{ matrix.os }} py${{ matrix.python }}
41
+ runs-on: ${{ matrix.os }}
42
+ # A run takes about 25 minutes; 45 still stops a hang.
43
+ timeout-minutes: 45
44
+ strategy:
45
+ fail-fast: false
46
+ matrix:
47
+ os: ${{ fromJSON(github.event_name == 'workflow_dispatch' && inputs.macos && '["ubuntu-latest","macos-latest"]' || '["ubuntu-latest"]') }}
48
+ python: ${{ fromJSON(github.event_name == 'workflow_dispatch' && inputs.python != 'all' && format('["{0}"]', inputs.python) || (github.event_name == 'workflow_dispatch' || github.event_name == 'schedule') && '["3.11","3.13","3.14"]' || '["3.13"]') }}
49
+ steps:
50
+ - uses: actions/checkout@v4
51
+ with:
52
+ # A pull request's checkout is its merge commit; two commits give the base as its first parent.
53
+ fetch-depth: 2
54
+
55
+ - uses: astral-sh/setup-uv@v6
56
+ with:
57
+ python-version: ${{ matrix.python }}
58
+ enable-cache: true
59
+ cache-dependency-glob: uv.lock
60
+
61
+ - name: Install from the lock
62
+ run: uv sync --locked
63
+
64
+ # -v names every test in the log, and tools/ci/stall_watch.py dumps
65
+ # the process table and the pytest process's thread stacks when the output stops for six
66
+ # minutes, then ends the run after ten, so a hung job names itself instead of being cancelled.
67
+ - name: Tests
68
+ run: uv run --no-sync python tools/ci/stall_watch.py -- uv run --no-sync pytest -v --durations=25 -m "not page"
69
+
70
+ # The page tests drive Chromium through Playwright (tests/page) and take about half of a pull
71
+ # request's run. A pull request that changes no path they load skips them, and says so here
72
+ # (tools/ci/page_paths.py holds the list). A push to main, the nightly run and a manual run
73
+ # always run them. Until tests/page exists on main there is nothing to run.
74
+ - name: Decide whether the page tests run
75
+ id: page
76
+ run: uv run --no-sync python tools/ci/page_paths.py
77
+
78
+ - name: Cache the Playwright browsers
79
+ if: hashFiles('tests/page/**') != '' && steps.page.outputs.run == 'true'
80
+ uses: actions/cache@v4
81
+ with:
82
+ path: ${{ runner.os == 'macOS' && '~/Library/Caches/ms-playwright' || '~/.cache/ms-playwright' }}
83
+ key: playwright-${{ runner.os }}-${{ hashFiles('uv.lock') }}
84
+
85
+ - name: Page tests (Playwright, Chromium)
86
+ if: hashFiles('tests/page/**') != '' && steps.page.outputs.run == 'true'
87
+ shell: bash
88
+ run: |
89
+ set -o pipefail
90
+ # --with-deps installs Chromium's system packages through sudo, which GitHub's runners have.
91
+ deps=""
92
+ if [ "$RUNNER_OS" = "Linux" ] && command -v sudo >/dev/null; then deps="--with-deps"; fi
93
+ uv run --no-sync playwright install $deps chromium
94
+ uv run --no-sync python tools/ci/stall_watch.py -- uv run --no-sync pytest -v --durations=25 -m page -rs 2>&1 | tee page-tests.txt
95
+ # A page test that cannot start its browser skips with a reason. Here that is a failure.
96
+ if grep -Eq '[0-9]+ skipped' page-tests.txt; then
97
+ echo "::error::page tests were skipped, so Chromium did not run them"
98
+ exit 1
99
+ fi
100
+
101
+ # A Chromium has closed with no word on a runner. The runner is a container
102
+ # with no dmesg (kernel.dmesg_restrict) and a memory cap of its own, so this reads what it can: the cgroup's
103
+ # own count of OOM kills, the memory and load, and /dev/shm. Nothing here changes the result of the job.
104
+ - name: Host state after a failure
105
+ if: failure() && runner.os == 'Linux'
106
+ shell: bash
107
+ run: |
108
+ free -m || true
109
+ uptime || true
110
+ cat /sys/fs/cgroup/memory.max /sys/fs/cgroup/memory.current /sys/fs/cgroup/memory.events 2>/dev/null || true
111
+ df -h /dev/shm || true
112
+ dmesg 2>/dev/null | tail -50 || true
113
+ journalctl -k --no-pager -n 50 2>/dev/null || true
114
+
115
+ - name: Wheel smoke (build, uv tool install, sheaf version, --share, init cursor, a hub)
116
+ run: uv run --no-sync python tools/wheel_smoke.py
117
+
118
+ # These two install a real user service on the runner. They refuse to run anywhere else.
119
+ - name: User service, systemd
120
+ if: runner.os == 'Linux'
121
+ run: PATH="$PWD/.venv/bin:$PATH" .venv/bin/python tools/ci/service_linux.py
122
+
123
+ - name: User service, launchd
124
+ if: runner.os == 'macOS'
125
+ run: PATH="$PWD/.venv/bin:$PATH" .venv/bin/python tools/ci/service_macos.py
@@ -0,0 +1,5 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ dist/
5
+ .pytest_cache/
@@ -0,0 +1,105 @@
1
+ # sheaf
2
+
3
+ A local working page for a markdown document, shared between a person and their agent. The `.md` stays the source; everything done on the page lands back in it.
4
+
5
+ ## Language
6
+
7
+ **Hub**:
8
+ The one local process that serves every document.
9
+ _Avoid_: server, per-document server
10
+
11
+ **Root**:
12
+ A git repo (or a folder outside git) whose documents the hub serves, named after its folder.
13
+ _Avoid_: project, collection
14
+
15
+ **Core**:
16
+ What every sheaf user gets: rendering, editing, comments, the agent adapter, the plain theme.
17
+
18
+ **Layer**:
19
+ An organisation's additions on top of the core, made of a theme, a root config, and separate programs that use sheaf as a library.
20
+ _Avoid_: plugin, extension
21
+
22
+ **Theme**:
23
+ A folder of data that gives pages their look: stylesheet, fonts, wordmark, PDF footer. `plain` is built in.
24
+ _Avoid_: skin, brand
25
+
26
+ **Root config**:
27
+ The `.sheaf.toml` file at the top of a root, checked in, that sets the root's theme, its "prepared for" name and its link rule.
28
+ _Avoid_: settings, project config
29
+
30
+ **Share copy**:
31
+ A read-only render of a document with every comment removed, for someone outside the conversation.
32
+ _Avoid_: client render, client version, public copy
33
+
34
+ **Link rule**:
35
+ The standard that every page, article, tool or document a text names is a link to its URL. It is checked when a page is rendered and told to the agent.
36
+
37
+ **Diagnostics log**:
38
+ The local record of everything that went wrong on a page or the hub, kept for bug reports.
39
+ _Avoid_: incident log
40
+
41
+ **Comment**:
42
+ A threaded note a person or an agent leaves on a passage, stored in the `.md` right after that passage. Written as `sheaf-comment`; the older `rd-comment` is read as the same thing.
43
+ _Avoid_: annotation, note, marker
44
+
45
+ **Foreign comment**:
46
+ Any HTML comment in the `.md` that sheaf did not write, such as another review tool's comments or a table-of-contents directive. sheaf never changes, moves or drops one, and does not show it as a Comment.
47
+
48
+ **Hub key**:
49
+ The one secret that proves a caller is the person whose hub it is. A browser holds a cookie derived from it, swapped for once; the CLI and the stdio MCP door read it from disk.
50
+ _Avoid_: token, edit token, password
51
+
52
+ **Sign-in nonce**:
53
+ A value the hub makes for the key holder that signs one browser in, once, within 30 seconds. `sheaf open` hands the browser this instead of the hub key.
54
+ _Avoid_: sign-in token, key URL
55
+
56
+ **Loopback mode**:
57
+ The hub as it runs by default: reached on 127.0.0.1 only, by the hub key, a browser cookie or an MCP token.
58
+
59
+ **Remote seat**:
60
+ A person working on the hub's machine over ssh, with no display there, so a browser on their own machine reaches the hub only through a tunnel. Not proxy mode: the hub is the same, only the way to it differs.
61
+ _Avoid_: remote mode, remote host
62
+
63
+ **Proxy mode**:
64
+ The hub served on the origin of another web application, the front, through a reverse proxy on the same machine, with the front's sign-in saying who is asking. Off unless the hub's config switches it on.
65
+ _Avoid_: hosted mode, remote mode
66
+
67
+ **Front**:
68
+ The web application whose origin the hub's pages share in proxy mode, and whose sign-in says who the member is.
69
+ _Avoid_: host app, parent app
70
+
71
+ **Verifier**:
72
+ The small service the reverse proxy asks, in proxy mode, whether a request carries a session of the front, and which member it is. Not part of the hub.
73
+
74
+ **Member**:
75
+ A user of the front whom the hub's config lets in through proxy mode, with the roots they see and the name they sign with. A member has no account on the machine.
76
+ _Avoid_: account, login
77
+
78
+ **Agent adapter**:
79
+ How the hub reaches an agent session: ask it a question, deliver a Tell, read its state, stop it. Each agent offers only what it can really do.
80
+
81
+ **MCP door**:
82
+ How an agent reaches the hub: read a document's threads and its text, reply, resolve, and, with the right to write, make a page and change its text. Any agent that speaks MCP can use it, whether the hub runs on the same machine or on a box it connects to.
83
+ _Avoid_: MCP server, plugin
84
+
85
+ **MCP token**:
86
+ A named, revocable secret that lets one agent use the hub's HTTP MCP door, and nothing else. A token made with `--write` may also read, make and change pages through the door.
87
+ _Avoid_: API key, hub key
88
+
89
+ **Target session**:
90
+ The agent session a document's Ask and Tell reach: the last one that rendered it from an agent.
91
+
92
+ **Ask**:
93
+ A question on a comment thread, answered in the thread by a fork of the target session, which changes nothing.
94
+
95
+ **Tell**:
96
+ A comment sent into the running target session, which acts on it and replies in the thread.
97
+ _Avoid_: send, push
98
+
99
+ **Role**:
100
+ What a reply is, recorded apart from who wrote it: `answer` for an Ask, `action` for what a session did after a Tell.
101
+ _Avoid_: reply type, magic author name
102
+
103
+ **Pin**:
104
+ A version of a document that the hub keeps beside the newest because a page holds its reload on it (`POST /_/pin`), so what the page sends later against that version is still taken. It is not the popover a page fixes on screen.
105
+ _Avoid_: lock, snapshot
sheafmd-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tiroir
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.
sheafmd-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,163 @@
1
+ Metadata-Version: 2.5
2
+ Name: sheafmd
3
+ Version: 1.0.0
4
+ Summary: A working page for a markdown document, shared between a person and their agent.
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: beautifulsoup4>=4.12
9
+ Requires-Dist: fonttools[woff]>=4.50
10
+ Requires-Dist: pillow>=10
11
+ Requires-Dist: pypandoc-binary==1.17
12
+ Description-Content-Type: text/markdown
13
+
14
+ # sheaf
15
+
16
+ A working page for a markdown document, shared between a person and their agent.
17
+ The `.md` stays the source; everything done on the page lands back in it.
18
+
19
+ You read the document in your browser, edit it in place, and comment on passages. Press **Ask** on a
20
+ comment and a copy of the agent session that wrote the document answers in the thread; press
21
+ **Tell** and the running session acts on it and replies. Every edit and every comment is written
22
+ into the `.md`, so the agent reads the same file you do, and `git diff` shows what happened.
23
+
24
+ Three things set it apart:
25
+
26
+ - **A save made against an older file is merged, not refused.** If the agent changed the file
27
+ while you were editing, your save is lined up with it paragraph by paragraph. Only the same
28
+ paragraph changed on both sides is a conflict, and even then nothing is lost.
29
+ - **A share copy with the comments taken out.** `sheaf plan.md --share` writes one self-contained
30
+ HTML file (and, with `--pdf`, a PDF) that you can send to someone outside the conversation.
31
+ - **Ask and Tell from the page, at any time.** The agent does not have to be waiting.
32
+
33
+ ## Install
34
+
35
+ uv tool install sheafmd
36
+
37
+ `pipx install sheafmd` works too. It needs Python 3.11 or later; pandoc comes with it. The command
38
+ is `sheaf`.
39
+
40
+ ## Quick start
41
+
42
+ sheaf plan.md
43
+
44
+ This starts the hub if none runs, opens the page in your browser and prints its address. Select
45
+ text on the page and press **Comment**; edit a paragraph and it is saved to `plan.md`. The hub
46
+ serves every document you open from one local address, `http://127.0.0.1:27419`, and its index
47
+ page lists them.
48
+
49
+ The commands that work on the file alone need no hub:
50
+
51
+ sheaf comments plan.md # the open comments, with their threads
52
+ sheaf reply plan.md c3 "Done." --resolve # answer a comment and resolve it
53
+ sheaf plan.md --share # plan-share.html, no comments, no script
54
+
55
+ `sheaf --help` lists every command.
56
+
57
+ ## Agents
58
+
59
+ sheaf init claude # or codex, gemini, cursor
60
+
61
+ `init` registers sheaf's MCP door with that agent, using the agent's own command (`claude mcp add`,
62
+ `codex mcp add`, `gemini mcp add`; for Cursor it adds an entry to `~/.cursor/mcp.json`). The agent
63
+ can then list the documents with open comments, read a thread, reply and resolve. For Claude Code
64
+ it also installs a skill in `~/.claude/skills/sheaf`. `init` never replaces what it did not write:
65
+ a server called `sheaf` that is set up differently, a skill file of your own, or a file it cannot
66
+ read is left as it is, and it says why and what to do.
67
+
68
+ Ask and Tell from the page work with Claude Code. The other agents reach sheaf through the MCP
69
+ door; adapters for them come later.
70
+
71
+ For an agent that has no skills, or a project that wants the instruction in the repository, put
72
+ this in the project's `AGENTS.md` (sheaf never writes that file):
73
+
74
+ ```
75
+ This project keeps documents in markdown and uses sheaf: `sheaf FILE.md` opens a page for the person and prints its URL, which you give them. Do not render text meant for pasting somewhere else (an email, a post, a commit message, a prompt, a config file): put it in the conversation.
76
+ Before you revise a document, read its comments with `sheaf comments FILE.md`.
77
+ After you handle a comment, reply with `sheaf reply FILE.md ID "what you did" --resolve`; never delete a comment. A message that starts "Tell from sheaf" is a comment to act on: do it, then reply with `--role action`.
78
+ Record what goes wrong with `sheaf diag note "what went wrong"`. Never run `sheaf signin-url` yourself: it prints the hub's key; ask the person to run it.
79
+ Every page, article, tool or document you name is a markdown link to its URL, checked with curl that it answers.
80
+ ```
81
+
82
+ An agent that reaches the hub over HTTP instead gets a named token: `sheaf token add NAME`
83
+ prints it once. A token can read and answer comments. A token made with
84
+ `sheaf token add NAME --write` can also read a page's text, make a page and change its text; it
85
+ keeps every comment in the text, and a change made against an older text is refused.
86
+
87
+ ## Platforms
88
+
89
+ - **Linux** is tested: CI runs the whole test suite on every pull request and every merge to main, on x86-64, with Python
90
+ 3.13, and with Python 3.11, 3.13 and 3.14 every night. On aarch64 sheaf was tried once, by hand, with Python 3.14: the failures it
91
+ showed were fixed, and the whole suite has not been run there again.
92
+ - **macOS** is not tested yet. The code has launchd and `~/Library` paths; nobody has run sheaf on a
93
+ Mac of their own.
94
+ - **Windows** is not supported: use WSL2 (untested). Natively, only `--share`, `comments`,
95
+ `comment`, `reply`, `resolve`, `reopen` and `version` run.
96
+
97
+ ## Keep the hub running
98
+
99
+ The hub starts by itself the first time a command needs it. To keep it running across logins and
100
+ reboots, install it as a user service (never done for you):
101
+
102
+ sheaf service install # systemd on Linux, launchd on macOS
103
+ sheaf service uninstall
104
+
105
+ With the service installed, `sheaf start` and `sheaf restart` go through it. If the hub crashes the
106
+ service starts it again after a growing delay (2 s, up to 60 s on systemd 254 and later) and gives
107
+ up after five starts in five minutes. `sheaf diag` lists every `hub-start`, `hub-exit` and
108
+ `unclean-exit`. Linux is the tested platform. CI installs the service for real on a Linux runner
109
+ and checks that it restarts the hub after a `kill -9` and after an upgrade. The launchd job is
110
+ checked only by tests that pretend to be a Mac: nobody has run it on a Mac of their own yet.
111
+
112
+ After you upgrade sheaf, the next `sheaf` command restarts an idle hub on the new version and open
113
+ pages reload by themselves. If an Ask or Tell is running, it says so and leaves the hub alone: run
114
+ `sheaf restart` when it is done. A page with edits you have not saved, or a comment half written,
115
+ is never reloaded; it tells you instead.
116
+
117
+ ## A hub on a remote box
118
+
119
+ The hub listens on `127.0.0.1` only. To use one on a box you reach over ssh:
120
+
121
+ 1. On the box, install sheaf and run `sheaf service install`. A user service stops when its owner
122
+ logs out unless the admin runs `loginctl enable-linger <you>` once.
123
+ 2. On your machine forward the hub's port to the same port number:
124
+ `ssh -L 27419:127.0.0.1:27419 box`. VS Code Remote-SSH forwards it for you. In an ssh session
125
+ with no display, `sheaf open` prints this command, with your port, user and address, above the
126
+ URL and starts no browser on the box.
127
+ 3. On the box run `sheaf signin-url` and open what it prints in your own browser. That URL holds the
128
+ hub key: open it yourself, never paste it to an agent.
129
+
130
+ Two people on one box each set their own port in `~/.config/sheaf/config.toml` (`port = 27420`) and
131
+ forward their own. The sign-in cookie is named after the hub's port, so a local hub and a
132
+ forwarded one on the same port number would overwrite each other's.
133
+
134
+ ## Signing in again
135
+
136
+ `sheaf rotate` replaces the hub key. Every browser is signed out at once: an open page shows the
137
+ sign-in page (or, with unsaved edits on it, keeps them and says how to sign in again). Sign in
138
+ again with `sheaf open FILE.md`, or with `sheaf signin-url` for a browser on another machine. MCP
139
+ tokens are not affected.
140
+
141
+ ## Security
142
+
143
+ The trust line is your operating-system user: any process running as you is trusted. sheaf defends
144
+ against other websites in your browser, other users on the machine, and hostile content in a
145
+ document you render. The hub listens on loopback only, checks the `Host` and `Origin` of every
146
+ request, and talks only to a browser that holds its key; the key is never in a page, and another
147
+ local user without it gets nothing but the hub's name and version. The sign-in link that
148
+ `sheaf open` hands your browser works once, and on Linux only for a browser of your own user: another
149
+ user who reads it from the process list is refused (macOS cannot check that). Raw HTML in a document is
150
+ sanitised and every page carries a content security policy, also for a page an agent wrote. An
151
+ agent's token reaches the door's tools and nothing else: it cannot use the page's routes, run Ask
152
+ or Tell, or add a folder, and it reads, makes and changes pages only when it was made with
153
+ `--write`. sheaf collects nothing and opens no connection to another machine: its commands, your
154
+ browser and the hub talk over `127.0.0.1`, and Ask and Tell go through your own agent.
155
+
156
+ ---
157
+
158
+ Made by tiroir. A hosted version is coming.
159
+
160
+ ## Licence
161
+
162
+ MIT. The pandoc binary comes from the separate `pypandoc-binary` wheel (GPL); sheaf itself stays
163
+ MIT.
@@ -0,0 +1,150 @@
1
+ # sheaf
2
+
3
+ A working page for a markdown document, shared between a person and their agent.
4
+ The `.md` stays the source; everything done on the page lands back in it.
5
+
6
+ You read the document in your browser, edit it in place, and comment on passages. Press **Ask** on a
7
+ comment and a copy of the agent session that wrote the document answers in the thread; press
8
+ **Tell** and the running session acts on it and replies. Every edit and every comment is written
9
+ into the `.md`, so the agent reads the same file you do, and `git diff` shows what happened.
10
+
11
+ Three things set it apart:
12
+
13
+ - **A save made against an older file is merged, not refused.** If the agent changed the file
14
+ while you were editing, your save is lined up with it paragraph by paragraph. Only the same
15
+ paragraph changed on both sides is a conflict, and even then nothing is lost.
16
+ - **A share copy with the comments taken out.** `sheaf plan.md --share` writes one self-contained
17
+ HTML file (and, with `--pdf`, a PDF) that you can send to someone outside the conversation.
18
+ - **Ask and Tell from the page, at any time.** The agent does not have to be waiting.
19
+
20
+ ## Install
21
+
22
+ uv tool install sheafmd
23
+
24
+ `pipx install sheafmd` works too. It needs Python 3.11 or later; pandoc comes with it. The command
25
+ is `sheaf`.
26
+
27
+ ## Quick start
28
+
29
+ sheaf plan.md
30
+
31
+ This starts the hub if none runs, opens the page in your browser and prints its address. Select
32
+ text on the page and press **Comment**; edit a paragraph and it is saved to `plan.md`. The hub
33
+ serves every document you open from one local address, `http://127.0.0.1:27419`, and its index
34
+ page lists them.
35
+
36
+ The commands that work on the file alone need no hub:
37
+
38
+ sheaf comments plan.md # the open comments, with their threads
39
+ sheaf reply plan.md c3 "Done." --resolve # answer a comment and resolve it
40
+ sheaf plan.md --share # plan-share.html, no comments, no script
41
+
42
+ `sheaf --help` lists every command.
43
+
44
+ ## Agents
45
+
46
+ sheaf init claude # or codex, gemini, cursor
47
+
48
+ `init` registers sheaf's MCP door with that agent, using the agent's own command (`claude mcp add`,
49
+ `codex mcp add`, `gemini mcp add`; for Cursor it adds an entry to `~/.cursor/mcp.json`). The agent
50
+ can then list the documents with open comments, read a thread, reply and resolve. For Claude Code
51
+ it also installs a skill in `~/.claude/skills/sheaf`. `init` never replaces what it did not write:
52
+ a server called `sheaf` that is set up differently, a skill file of your own, or a file it cannot
53
+ read is left as it is, and it says why and what to do.
54
+
55
+ Ask and Tell from the page work with Claude Code. The other agents reach sheaf through the MCP
56
+ door; adapters for them come later.
57
+
58
+ For an agent that has no skills, or a project that wants the instruction in the repository, put
59
+ this in the project's `AGENTS.md` (sheaf never writes that file):
60
+
61
+ ```
62
+ This project keeps documents in markdown and uses sheaf: `sheaf FILE.md` opens a page for the person and prints its URL, which you give them. Do not render text meant for pasting somewhere else (an email, a post, a commit message, a prompt, a config file): put it in the conversation.
63
+ Before you revise a document, read its comments with `sheaf comments FILE.md`.
64
+ After you handle a comment, reply with `sheaf reply FILE.md ID "what you did" --resolve`; never delete a comment. A message that starts "Tell from sheaf" is a comment to act on: do it, then reply with `--role action`.
65
+ Record what goes wrong with `sheaf diag note "what went wrong"`. Never run `sheaf signin-url` yourself: it prints the hub's key; ask the person to run it.
66
+ Every page, article, tool or document you name is a markdown link to its URL, checked with curl that it answers.
67
+ ```
68
+
69
+ An agent that reaches the hub over HTTP instead gets a named token: `sheaf token add NAME`
70
+ prints it once. A token can read and answer comments. A token made with
71
+ `sheaf token add NAME --write` can also read a page's text, make a page and change its text; it
72
+ keeps every comment in the text, and a change made against an older text is refused.
73
+
74
+ ## Platforms
75
+
76
+ - **Linux** is tested: CI runs the whole test suite on every pull request and every merge to main, on x86-64, with Python
77
+ 3.13, and with Python 3.11, 3.13 and 3.14 every night. On aarch64 sheaf was tried once, by hand, with Python 3.14: the failures it
78
+ showed were fixed, and the whole suite has not been run there again.
79
+ - **macOS** is not tested yet. The code has launchd and `~/Library` paths; nobody has run sheaf on a
80
+ Mac of their own.
81
+ - **Windows** is not supported: use WSL2 (untested). Natively, only `--share`, `comments`,
82
+ `comment`, `reply`, `resolve`, `reopen` and `version` run.
83
+
84
+ ## Keep the hub running
85
+
86
+ The hub starts by itself the first time a command needs it. To keep it running across logins and
87
+ reboots, install it as a user service (never done for you):
88
+
89
+ sheaf service install # systemd on Linux, launchd on macOS
90
+ sheaf service uninstall
91
+
92
+ With the service installed, `sheaf start` and `sheaf restart` go through it. If the hub crashes the
93
+ service starts it again after a growing delay (2 s, up to 60 s on systemd 254 and later) and gives
94
+ up after five starts in five minutes. `sheaf diag` lists every `hub-start`, `hub-exit` and
95
+ `unclean-exit`. Linux is the tested platform. CI installs the service for real on a Linux runner
96
+ and checks that it restarts the hub after a `kill -9` and after an upgrade. The launchd job is
97
+ checked only by tests that pretend to be a Mac: nobody has run it on a Mac of their own yet.
98
+
99
+ After you upgrade sheaf, the next `sheaf` command restarts an idle hub on the new version and open
100
+ pages reload by themselves. If an Ask or Tell is running, it says so and leaves the hub alone: run
101
+ `sheaf restart` when it is done. A page with edits you have not saved, or a comment half written,
102
+ is never reloaded; it tells you instead.
103
+
104
+ ## A hub on a remote box
105
+
106
+ The hub listens on `127.0.0.1` only. To use one on a box you reach over ssh:
107
+
108
+ 1. On the box, install sheaf and run `sheaf service install`. A user service stops when its owner
109
+ logs out unless the admin runs `loginctl enable-linger <you>` once.
110
+ 2. On your machine forward the hub's port to the same port number:
111
+ `ssh -L 27419:127.0.0.1:27419 box`. VS Code Remote-SSH forwards it for you. In an ssh session
112
+ with no display, `sheaf open` prints this command, with your port, user and address, above the
113
+ URL and starts no browser on the box.
114
+ 3. On the box run `sheaf signin-url` and open what it prints in your own browser. That URL holds the
115
+ hub key: open it yourself, never paste it to an agent.
116
+
117
+ Two people on one box each set their own port in `~/.config/sheaf/config.toml` (`port = 27420`) and
118
+ forward their own. The sign-in cookie is named after the hub's port, so a local hub and a
119
+ forwarded one on the same port number would overwrite each other's.
120
+
121
+ ## Signing in again
122
+
123
+ `sheaf rotate` replaces the hub key. Every browser is signed out at once: an open page shows the
124
+ sign-in page (or, with unsaved edits on it, keeps them and says how to sign in again). Sign in
125
+ again with `sheaf open FILE.md`, or with `sheaf signin-url` for a browser on another machine. MCP
126
+ tokens are not affected.
127
+
128
+ ## Security
129
+
130
+ The trust line is your operating-system user: any process running as you is trusted. sheaf defends
131
+ against other websites in your browser, other users on the machine, and hostile content in a
132
+ document you render. The hub listens on loopback only, checks the `Host` and `Origin` of every
133
+ request, and talks only to a browser that holds its key; the key is never in a page, and another
134
+ local user without it gets nothing but the hub's name and version. The sign-in link that
135
+ `sheaf open` hands your browser works once, and on Linux only for a browser of your own user: another
136
+ user who reads it from the process list is refused (macOS cannot check that). Raw HTML in a document is
137
+ sanitised and every page carries a content security policy, also for a page an agent wrote. An
138
+ agent's token reaches the door's tools and nothing else: it cannot use the page's routes, run Ask
139
+ or Tell, or add a folder, and it reads, makes and changes pages only when it was made with
140
+ `--write`. sheaf collects nothing and opens no connection to another machine: its commands, your
141
+ browser and the hub talk over `127.0.0.1`, and Ask and Tell go through your own agent.
142
+
143
+ ---
144
+
145
+ Made by tiroir. A hosted version is coming.
146
+
147
+ ## Licence
148
+
149
+ MIT. The pandoc binary comes from the separate `pypandoc-binary` wheel (GPL); sheaf itself stays
150
+ MIT.