margin 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: margin
3
+ Version: 0.2.1
4
+ Summary: Publish a repo's markdown to a self-hosted Outline, keep comments anchored, and bring each new comment back to an AI agent in batches
5
+ Author: Konstantinos Georgiou
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/drkostas/margin
8
+ Keywords: outline,markdown,comments,claude,claude-code,agents,documentation
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Topic :: Documentation
11
+ Classifier: Topic :: Utilities
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Provides-Extra: test
16
+ Requires-Dist: pytest>=8; extra == "test"
17
+ Dynamic: license-file
18
+
19
+ # margin
20
+
21
+ margin publishes the markdown files of your repositories to a self-hosted [Outline](https://github.com/outline/outline) wiki, and brings every comment you leave there back to an AI agent such as Claude Code. You read your docs in a browser or on your phone, select a sentence and comment on it, and the agent answers in the thread and changes the file.
22
+
23
+ Each repository becomes one Outline collection. Folders become parent documents, and a folder's `README.md` is that folder's page.
24
+
25
+ ## What it does
26
+
27
+ - **Publishes changes as small patches.** A comment in Outline is attached to a piece of text. Replacing a whole document through Outline's API removes every one of those attachments, so margin never does that. It works out the smallest changes, applies them one at a time, and checks each result against what it expected.
28
+ - **Publishes the pictures a file shows.** A local image referenced by a relative path (`![The settings page](shots/settings.png)`) is uploaded once as an attachment of its document, so everyone who can read the document sees it. The file keeps its relative path. A changed picture counts as a changed file.
29
+ - **Never settles a disagreement by "newest wins".** margin keeps both sides' text from the last sync. If only the file changed, it patches Outline. If someone edited the document in Outline, or both changed, it leaves both alone and reports the document.
30
+ - **Collects comments two ways.** Outline sends a signed webhook for each new comment. A catch-up poll every few minutes finds anything the webhook missed, for example while the machine was asleep. A comment that arrives both ways is recorded once.
31
+ - **Sends comments in batches.** A burst of comments becomes one message. A batch is delivered after a quiet period (3 minutes by default), never later than a cap after its first comment (15 minutes), and at once when a comment mentions the agent's user.
32
+ - **Keeps the agent out of its own inbox.** Comments written by the agent's user are never recorded, so its replies cannot wake it.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install margin
38
+ ```
39
+
40
+ margin needs Python 3.11 or newer and has no dependencies outside the standard library. You also need an Outline server that you can reach, with two users, yourself and the agent. [`examples/outline`](examples/outline) has a Docker Compose setup for Outline, Redis and a small sign-in provider.
41
+
42
+ ## Configure
43
+
44
+ margin reads `~/.config/margin/config.toml`. Secrets are never written in it. Each secret is the output of a command, for example a password manager or the macOS keychain.
45
+
46
+ ```toml
47
+ [outline]
48
+ url = "https://docs.example.com"
49
+ api_key_command = ["pass", "outline/agent-api-key"] # the agent's key, a normal member
50
+ admin_key_command = ["pass", "outline/admin-api-key"] # used only to create the webhook
51
+
52
+ [intake]
53
+ listen = "127.0.0.1:8771"
54
+ webhook_url = "http://host.docker.internal:8771/outline" # the address Outline posts to
55
+ secret_command = ["pass", "outline/webhook-secret"]
56
+ poll_seconds = 600
57
+
58
+ [batch]
59
+ quiet_seconds = 180
60
+ cap_seconds = 900
61
+
62
+ [notify]
63
+ command = ["my-notifier", "{route}"] # receives each batch as JSON on stdin; {text}, {repo} and {repo_path} are filled in
64
+
65
+ [[repo]]
66
+ name = "my-project" # the Outline collection
67
+ path = "~/code/my-project"
68
+ exclude = ["vendor/**"]
69
+ route = "my-project" # passed to the notifier as {route}
70
+ ```
71
+
72
+ When `[notify]` has no command, margin prints each batch.
73
+
74
+ Outline refuses to send webhooks to private addresses. If margin runs on the same machine as an Outline container, add the container's host gateway address to Outline's `ALLOWED_PRIVATE_IP_ADDRESSES` setting.
75
+
76
+ ## Use
77
+
78
+ ```bash
79
+ margin sync # publish what changed, in every configured repo
80
+ margin sync my-project --full
81
+ margin serve # webhook receiver, catch-up poll, batching and a sync every minute
82
+ margin status # documents that need attention, comments waiting
83
+ margin reply <comment-id> "Changed to five minutes, see the Lifetime section."
84
+ margin resolve <comment-id>
85
+ margin thread <comment-id>
86
+ margin link docs/plan.md # publish these files now and print a Markdown link to each
87
+ margin setup-claude # link the skill, register Outline's MCP, deny its destructive tools
88
+ ```
89
+
90
+ A batch looks like this.
91
+
92
+ ```text
93
+ 2 new comment(s) in Outline.
94
+
95
+ ### Cache design
96
+ my-project/docs/cache.md https://docs.example.com/doc/cache-design-pbS4AcgGK0
97
+ - [393bdced-...] Alex on "least recently used first":
98
+ Please also say what happens when memory is full.
99
+ - [819aea0e-...] Alex on "The cache holds entries":
100
+ Is ten minutes measured from write or from last read?
101
+ ```
102
+
103
+ The notifier also receives every comment as structured JSON, with the document's title, link and repository path.
104
+
105
+ ## Limits
106
+
107
+ - A comment attached to text that the agent then rewrites loses its attachment. The thread stays, with no highlighted text. margin keeps its patches small so that this happens only to text that really changed.
108
+ - Outline re-parses a whole list or quote when a patch has to cross into the next block. Comments inside that block lose their attachment the same way. New content at the end of a document is appended and touches nothing.
109
+ - Edits made in Outline are reported, not copied back to the file. That direction is planned.
110
+
111
+ ## Licence
112
+
113
+ MIT
@@ -0,0 +1,20 @@
1
+ margin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ margin/batch.py,sha256=el8bHtPY0TPz1xllKcaqy7_SgXKijpvtEx2jMSDXnoE,1748
3
+ margin/claude.py,sha256=gZhRE07r9wxKO7LQZQwrvAaEFZfZBlJBcbT5e_0HxcU,3577
4
+ margin/cli.py,sha256=6I-9jZ1ZqhmaX34-TNsmzRrCkoCcGAUGEz-Y1gMqtVQ,13788
5
+ margin/config.py,sha256=ijQ98rl4KycYYIHGxKmRpQ77oUmxt3AKrnSSswl1_R4,8389
6
+ margin/fold.py,sha256=QWwMOzw6T0EFSi8z8Pf78fUjt32DoJhMa2UGepgv-BI,38426
7
+ margin/images.py,sha256=A9c8dqQlAOA4ycV8PU_t6S2rfkb3BZvYkBpJrw-uq4E,3156
8
+ margin/inbox.py,sha256=WeIakbR5wvj9BYmCFt3r2oleuVFVV_AmBgwboYvDa4Y,15312
9
+ margin/outline.py,sha256=85sUXQXBSBwp_hTs_Pkh0w0r-HI3xI7pSwfyKlp4dro,12232
10
+ margin/patches.py,sha256=4GSqKevQkJM6nf-Xi2_rffmqGp-cqPsEOyQSIWeMebk,11837
11
+ margin/pinch_check.py,sha256=zij6y4TpAvalMkbfG5nK8KkadfYPBszXMHiiJ_9vRJ4,18177
12
+ margin/store.py,sha256=B6Ozo5oJZXsI6r7fw5NcdwH7UYPiXLPul13lLhApJa0,8757
13
+ margin/sync.py,sha256=ocPWRPkU8oPSQy_njxsRFxnLhJpqLMWyYsWPgtgRe2o,35710
14
+ margin/skills/margin/SKILL.md,sha256=eXvX5l-SBVsbG7im5AZEX_GiYRlJMZlrv3-V-dv03gE,6279
15
+ margin-0.2.1.dist-info/licenses/LICENSE,sha256=y2yT4XOJ5hjMzENqKJShtqLiwI-IE1PnWqMxbhhL5r8,1078
16
+ margin-0.2.1.dist-info/METADATA,sha256=8rMHTJfTNu0PdY8h1OxbyjiNh0FNqK2epp-wGF6dpbM,5995
17
+ margin-0.2.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
18
+ margin-0.2.1.dist-info/entry_points.txt,sha256=-5w3KEPp-au9A_bOPxtQGR67B9egA10Me2jOSRbXoDs,43
19
+ margin-0.2.1.dist-info/top_level.txt,sha256=u21cuPWaJENEWAYmCFNkRU86jsBUCoVDmgkRuy2U1SY,7
20
+ margin-0.2.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ margin = margin.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Konstantinos Georgiou
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ margin