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.
- sheafmd-1.0.0/.github/workflows/ci.yml +125 -0
- sheafmd-1.0.0/.gitignore +5 -0
- sheafmd-1.0.0/GLOSSARY.md +105 -0
- sheafmd-1.0.0/LICENSE +21 -0
- sheafmd-1.0.0/PKG-INFO +163 -0
- sheafmd-1.0.0/README.md +150 -0
- sheafmd-1.0.0/SPEC.md +1451 -0
- sheafmd-1.0.0/pyproject.toml +56 -0
- sheafmd-1.0.0/src/sheafmd/__init__.py +48 -0
- sheafmd-1.0.0/src/sheafmd/__main__.py +6 -0
- sheafmd-1.0.0/src/sheafmd/_pandoc.py +20 -0
- sheafmd-1.0.0/src/sheafmd/agent_files/agents-snippet.md +5 -0
- sheafmd-1.0.0/src/sheafmd/agent_files/claude-skill.md +65 -0
- sheafmd-1.0.0/src/sheafmd/agent_setup.py +633 -0
- sheafmd-1.0.0/src/sheafmd/agents/__init__.py +1 -0
- sheafmd-1.0.0/src/sheafmd/agents/base.py +100 -0
- sheafmd-1.0.0/src/sheafmd/agents/claude_code.py +377 -0
- sheafmd-1.0.0/src/sheafmd/agents/detect.py +63 -0
- sheafmd-1.0.0/src/sheafmd/agents/generic.py +57 -0
- sheafmd-1.0.0/src/sheafmd/agents/prompts.py +45 -0
- sheafmd-1.0.0/src/sheafmd/agents/registry.py +42 -0
- sheafmd-1.0.0/src/sheafmd/atomic.py +36 -0
- sheafmd-1.0.0/src/sheafmd/blocks.py +341 -0
- sheafmd-1.0.0/src/sheafmd/charts.py +399 -0
- sheafmd-1.0.0/src/sheafmd/cli.py +1013 -0
- sheafmd-1.0.0/src/sheafmd/comments.py +618 -0
- sheafmd-1.0.0/src/sheafmd/config.py +232 -0
- sheafmd-1.0.0/src/sheafmd/diagnostics.py +135 -0
- sheafmd-1.0.0/src/sheafmd/edit.py +1834 -0
- sheafmd-1.0.0/src/sheafmd/hub/__init__.py +1 -0
- sheafmd-1.0.0/src/sheafmd/hub/asktell.py +547 -0
- sheafmd-1.0.0/src/sheafmd/hub/auth.py +190 -0
- sheafmd-1.0.0/src/sheafmd/hub/client.py +518 -0
- sheafmd-1.0.0/src/sheafmd/hub/core.py +482 -0
- sheafmd-1.0.0/src/sheafmd/hub/events.py +106 -0
- sheafmd-1.0.0/src/sheafmd/hub/handler.py +890 -0
- sheafmd-1.0.0/src/sheafmd/hub/index.py +567 -0
- sheafmd-1.0.0/src/sheafmd/hub/jobs.py +223 -0
- sheafmd-1.0.0/src/sheafmd/hub/live.py +526 -0
- sheafmd-1.0.0/src/sheafmd/hub/peer.py +123 -0
- sheafmd-1.0.0/src/sheafmd/hub/proxy.py +301 -0
- sheafmd-1.0.0/src/sheafmd/hub/recover.py +242 -0
- sheafmd-1.0.0/src/sheafmd/hub/roots.py +569 -0
- sheafmd-1.0.0/src/sheafmd/hub/sender.py +113 -0
- sheafmd-1.0.0/src/sheafmd/hub/server.py +498 -0
- sheafmd-1.0.0/src/sheafmd/hub/service.py +280 -0
- sheafmd-1.0.0/src/sheafmd/hub/sock.py +118 -0
- sheafmd-1.0.0/src/sheafmd/hub/state.py +507 -0
- sheafmd-1.0.0/src/sheafmd/images.py +126 -0
- sheafmd-1.0.0/src/sheafmd/lifecycle.py +284 -0
- sheafmd-1.0.0/src/sheafmd/links.py +124 -0
- sheafmd-1.0.0/src/sheafmd/mcp.py +722 -0
- sheafmd-1.0.0/src/sheafmd/page/index.css +53 -0
- sheafmd-1.0.0/src/sheafmd/page/index.js +78 -0
- sheafmd-1.0.0/src/sheafmd/page/page.css +175 -0
- sheafmd-1.0.0/src/sheafmd/page/page.js +3101 -0
- sheafmd-1.0.0/src/sheafmd/paths.py +233 -0
- sheafmd-1.0.0/src/sheafmd/pdf.py +189 -0
- sheafmd-1.0.0/src/sheafmd/render.py +1189 -0
- sheafmd-1.0.0/src/sheafmd/sanitize.py +182 -0
- sheafmd-1.0.0/src/sheafmd/seat.py +52 -0
- sheafmd-1.0.0/src/sheafmd/source.py +259 -0
- sheafmd-1.0.0/src/sheafmd/theme.py +178 -0
- sheafmd-1.0.0/src/sheafmd/themes/plain/theme.css +32 -0
- sheafmd-1.0.0/src/sheafmd/themes/plain/theme.toml +1 -0
- sheafmd-1.0.0/tests/agent_fakes.py +115 -0
- sheafmd-1.0.0/tests/conftest.py +369 -0
- sheafmd-1.0.0/tests/corpus/comments-in-every-block.md +50 -0
- sheafmd-1.0.0/tests/corpus/crlf.md +25 -0
- sheafmd-1.0.0/tests/corpus/empty.md +0 -0
- sheafmd-1.0.0/tests/corpus/fences-in-lists.md +47 -0
- sheafmd-1.0.0/tests/corpus/fences.md +59 -0
- sheafmd-1.0.0/tests/corpus/front-matter.md +29 -0
- sheafmd-1.0.0/tests/corpus/headings.md +40 -0
- sheafmd-1.0.0/tests/corpus/hr-and-blank.md +39 -0
- sheafmd-1.0.0/tests/corpus/indented-code.md +38 -0
- sheafmd-1.0.0/tests/corpus/lists.md +59 -0
- sheafmd-1.0.0/tests/corpus/minimal.md +3 -0
- sheafmd-1.0.0/tests/corpus/no-trailing-newline.md +16 -0
- sheafmd-1.0.0/tests/corpus/only-blank.md +3 -0
- sheafmd-1.0.0/tests/corpus/paragraphs.md +33 -0
- sheafmd-1.0.0/tests/corpus/quotes.md +52 -0
- sheafmd-1.0.0/tests/corpus/raw-html.md +44 -0
- sheafmd-1.0.0/tests/corpus/tables.md +51 -0
- sheafmd-1.0.0/tests/corpus/task-lists.md +37 -0
- sheafmd-1.0.0/tests/helpers.py +771 -0
- sheafmd-1.0.0/tests/leakguard.py +199 -0
- sheafmd-1.0.0/tests/page/conftest.py +177 -0
- sheafmd-1.0.0/tests/page/pagehelp.py +289 -0
- sheafmd-1.0.0/tests/page/test_page_agent.py +191 -0
- sheafmd-1.0.0/tests/page/test_page_boot.py +16 -0
- sheafmd-1.0.0/tests/page/test_page_drafts.py +512 -0
- sheafmd-1.0.0/tests/page/test_page_index.py +97 -0
- sheafmd-1.0.0/tests/page/test_page_keyboard.py +141 -0
- sheafmd-1.0.0/tests/page/test_page_launch.py +134 -0
- sheafmd-1.0.0/tests/page/test_page_outage.py +1183 -0
- sheafmd-1.0.0/tests/page/test_page_proxy.py +56 -0
- sheafmd-1.0.0/tests/page/test_page_selection.py +461 -0
- sheafmd-1.0.0/tests/page/test_page_waits.py +85 -0
- sheafmd-1.0.0/tests/page/test_page_writing.py +4490 -0
- sheafmd-1.0.0/tests/test_agent_fakes.py +14 -0
- sheafmd-1.0.0/tests/test_agent_files.py +117 -0
- sheafmd-1.0.0/tests/test_agents_adapters.py +756 -0
- sheafmd-1.0.0/tests/test_agents_detect.py +100 -0
- sheafmd-1.0.0/tests/test_anchoring.py +140 -0
- sheafmd-1.0.0/tests/test_api.py +694 -0
- sheafmd-1.0.0/tests/test_atomic.py +51 -0
- sheafmd-1.0.0/tests/test_blocks.py +139 -0
- sheafmd-1.0.0/tests/test_charts.py +309 -0
- sheafmd-1.0.0/tests/test_ci_scripts.py +404 -0
- sheafmd-1.0.0/tests/test_ci_workflow.py +358 -0
- sheafmd-1.0.0/tests/test_cli.py +1126 -0
- sheafmd-1.0.0/tests/test_cli_comments.py +521 -0
- sheafmd-1.0.0/tests/test_cli_diag.py +219 -0
- sheafmd-1.0.0/tests/test_cli_hub.py +500 -0
- sheafmd-1.0.0/tests/test_cli_list.py +453 -0
- sheafmd-1.0.0/tests/test_cli_token.py +226 -0
- sheafmd-1.0.0/tests/test_client_hub_stopping.py +99 -0
- sheafmd-1.0.0/tests/test_comment_ops.py +62 -0
- sheafmd-1.0.0/tests/test_comments_format.py +242 -0
- sheafmd-1.0.0/tests/test_config.py +602 -0
- sheafmd-1.0.0/tests/test_corpus_comments.py +51 -0
- sheafmd-1.0.0/tests/test_diagnostics.py +190 -0
- sheafmd-1.0.0/tests/test_edit_corpus.py +76 -0
- sheafmd-1.0.0/tests/test_edit_followups.py +1009 -0
- sheafmd-1.0.0/tests/test_edit_markers.py +263 -0
- sheafmd-1.0.0/tests/test_helpers_exec.py +75 -0
- sheafmd-1.0.0/tests/test_helpers_listeners.py +160 -0
- sheafmd-1.0.0/tests/test_helpers_numbers.py +19 -0
- sheafmd-1.0.0/tests/test_helpers_ports.py +192 -0
- sheafmd-1.0.0/tests/test_hub_address.py +46 -0
- sheafmd-1.0.0/tests/test_hub_asktell.py +2014 -0
- sheafmd-1.0.0/tests/test_hub_auth.py +183 -0
- sheafmd-1.0.0/tests/test_hub_handler.py +1247 -0
- sheafmd-1.0.0/tests/test_hub_index.py +479 -0
- sheafmd-1.0.0/tests/test_hub_index_live.py +1116 -0
- sheafmd-1.0.0/tests/test_hub_index_page.py +289 -0
- sheafmd-1.0.0/tests/test_hub_jobs.py +429 -0
- sheafmd-1.0.0/tests/test_hub_jump.py +358 -0
- sheafmd-1.0.0/tests/test_hub_lifecycle.py +326 -0
- sheafmd-1.0.0/tests/test_hub_listing.py +708 -0
- sheafmd-1.0.0/tests/test_hub_live.py +967 -0
- sheafmd-1.0.0/tests/test_hub_peer.py +407 -0
- sheafmd-1.0.0/tests/test_hub_recover.py +544 -0
- sheafmd-1.0.0/tests/test_hub_roots.py +841 -0
- sheafmd-1.0.0/tests/test_hub_rotate.py +415 -0
- sheafmd-1.0.0/tests/test_hub_security.py +544 -0
- sheafmd-1.0.0/tests/test_hub_server.py +729 -0
- sheafmd-1.0.0/tests/test_hub_sock.py +228 -0
- sheafmd-1.0.0/tests/test_hub_state.py +356 -0
- sheafmd-1.0.0/tests/test_hub_upgrade.py +425 -0
- sheafmd-1.0.0/tests/test_images.py +308 -0
- sheafmd-1.0.0/tests/test_init.py +1448 -0
- sheafmd-1.0.0/tests/test_isolation.py +150 -0
- sheafmd-1.0.0/tests/test_leakguard.py +331 -0
- sheafmd-1.0.0/tests/test_links.py +187 -0
- sheafmd-1.0.0/tests/test_markers_roles.py +290 -0
- sheafmd-1.0.0/tests/test_mcp_documents.py +247 -0
- sheafmd-1.0.0/tests/test_mcp_http.py +351 -0
- sheafmd-1.0.0/tests/test_mcp_pages.py +635 -0
- sheafmd-1.0.0/tests/test_mcp_stdio.py +1040 -0
- sheafmd-1.0.0/tests/test_mcp_tools.py +492 -0
- sheafmd-1.0.0/tests/test_merge.py +516 -0
- sheafmd-1.0.0/tests/test_merge_align.py +361 -0
- sheafmd-1.0.0/tests/test_merge_gate.py +381 -0
- sheafmd-1.0.0/tests/test_merge_markers.py +87 -0
- sheafmd-1.0.0/tests/test_mermaid.py +45 -0
- sheafmd-1.0.0/tests/test_page_assets.py +236 -0
- sheafmd-1.0.0/tests/test_page_toast.py +61 -0
- sheafmd-1.0.0/tests/test_page_upgrade.py +88 -0
- sheafmd-1.0.0/tests/test_pandoc.py +84 -0
- sheafmd-1.0.0/tests/test_paths.py +94 -0
- sheafmd-1.0.0/tests/test_pdf.py +295 -0
- sheafmd-1.0.0/tests/test_pdf_csp.py +182 -0
- sheafmd-1.0.0/tests/test_proc_state.py +60 -0
- sheafmd-1.0.0/tests/test_prompts.py +73 -0
- sheafmd-1.0.0/tests/test_proxy_config.py +189 -0
- sheafmd-1.0.0/tests/test_proxy_contract.py +255 -0
- sheafmd-1.0.0/tests/test_proxy_images.py +472 -0
- sheafmd-1.0.0/tests/test_proxy_member.py +515 -0
- sheafmd-1.0.0/tests/test_proxy_socket.py +184 -0
- sheafmd-1.0.0/tests/test_proxy_start.py +215 -0
- sheafmd-1.0.0/tests/test_readme.py +101 -0
- sheafmd-1.0.0/tests/test_references.py +142 -0
- sheafmd-1.0.0/tests/test_remote_box.py +287 -0
- sheafmd-1.0.0/tests/test_remote_seat.py +152 -0
- sheafmd-1.0.0/tests/test_render.py +422 -0
- sheafmd-1.0.0/tests/test_route_access.py +162 -0
- sheafmd-1.0.0/tests/test_sanitize.py +429 -0
- sheafmd-1.0.0/tests/test_save_roundtrip.py +188 -0
- sheafmd-1.0.0/tests/test_service.py +911 -0
- sheafmd-1.0.0/tests/test_share_copy.py +963 -0
- sheafmd-1.0.0/tests/test_source.py +540 -0
- sheafmd-1.0.0/tests/test_spec_page_pins.py +121 -0
- sheafmd-1.0.0/tests/test_theme.py +798 -0
- sheafmd-1.0.0/tests/test_tokens.py +360 -0
- sheafmd-1.0.0/tests/test_tools.py +139 -0
- sheafmd-1.0.0/tests/test_windows_gate.py +54 -0
- sheafmd-1.0.0/tools/VERIFYING-RENDERED-OUTPUT.md +149 -0
- sheafmd-1.0.0/tools/check-rendered-pdf.py +244 -0
- sheafmd-1.0.0/tools/ci/_ci.py +111 -0
- sheafmd-1.0.0/tools/ci/page_paths.py +91 -0
- sheafmd-1.0.0/tools/ci/service_linux.py +161 -0
- sheafmd-1.0.0/tools/ci/service_macos.py +129 -0
- sheafmd-1.0.0/tools/ci/stall_watch.py +207 -0
- sheafmd-1.0.0/tools/wheel_smoke.py +177 -0
- 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
|
sheafmd-1.0.0/.gitignore
ADDED
|
@@ -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.
|
sheafmd-1.0.0/README.md
ADDED
|
@@ -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.
|