openmymemo 0.2.4__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.
- openmymemo/__init__.py +3 -0
- openmymemo/__main__.py +3 -0
- openmymemo/appfiles.py +87 -0
- openmymemo/cli.py +905 -0
- openmymemo/clients.py +1018 -0
- openmymemo/cloud.py +1382 -0
- openmymemo/codex.py +147 -0
- openmymemo/connectors.py +246 -0
- openmymemo/context.py +268 -0
- openmymemo/mcp_server.py +249 -0
- openmymemo/mdfile.py +371 -0
- openmymemo/proposals.py +474 -0
- openmymemo/snippets.py +93 -0
- openmymemo/static/assets/index-B3p4vYgC.js +2 -0
- openmymemo/static/assets/index-Bh2BAn9N.css +1 -0
- openmymemo/static/index.html +14 -0
- openmymemo/static/logo.png +0 -0
- openmymemo/textutil.py +130 -0
- openmymemo/topics.py +195 -0
- openmymemo/transfer.py +273 -0
- openmymemo/vault.py +875 -0
- openmymemo/web.py +666 -0
- openmymemo-0.2.4.dist-info/METADATA +229 -0
- openmymemo-0.2.4.dist-info/RECORD +27 -0
- openmymemo-0.2.4.dist-info/WHEEL +4 -0
- openmymemo-0.2.4.dist-info/entry_points.txt +2 -0
- openmymemo-0.2.4.dist-info/licenses/LICENSE +201 -0
openmymemo/__init__.py
ADDED
openmymemo/__main__.py
ADDED
openmymemo/appfiles.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Writing other apps' files: their MCP configs (clients.py) and Codex's hooks.json (codex.py).
|
|
2
|
+
|
|
3
|
+
Like ``vault._atomic_write`` a file is replaced through a temporary file and a rename, so the app never reads
|
|
4
|
+
half a file. Because these files are the person's and not ours, a replacement also keeps the file's
|
|
5
|
+
permissions from its first byte (some hold API keys), writes through a symlink to the real file (dotfile
|
|
6
|
+
repositories), and the file as it was before OpenMyMemo first changed it is kept next to it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import contextlib
|
|
12
|
+
import os
|
|
13
|
+
import secrets
|
|
14
|
+
import shutil
|
|
15
|
+
import stat
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
BACKUP_SUFFIX = ".openmymemo-backup"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def replace(path: Path, text: str) -> None:
|
|
22
|
+
"""Atomically replace a file, keeping its permissions; a symlink keeps pointing at the real file."""
|
|
23
|
+
target = path.resolve() if path.is_symlink() else path
|
|
24
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
try:
|
|
26
|
+
mode = target.stat().st_mode & 0o777
|
|
27
|
+
except FileNotFoundError:
|
|
28
|
+
mode = None
|
|
29
|
+
data = text.encode("utf-8")
|
|
30
|
+
tmp = target.with_name(f".{target.name}.{secrets.token_hex(4)}.tmp")
|
|
31
|
+
# Born with the file's own permissions (private for a new file): a config holding API keys is never
|
|
32
|
+
# readable by others, not even for a moment.
|
|
33
|
+
fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600 if mode is None else mode)
|
|
34
|
+
try:
|
|
35
|
+
with os.fdopen(fd, "wb") as f:
|
|
36
|
+
if mode is not None and hasattr(os, "fchmod"):
|
|
37
|
+
os.fchmod(f.fileno(), mode) # exactly, whatever the umask
|
|
38
|
+
f.write(data)
|
|
39
|
+
f.flush()
|
|
40
|
+
os.fsync(f.fileno())
|
|
41
|
+
os.replace(tmp, target)
|
|
42
|
+
except BaseException:
|
|
43
|
+
tmp.unlink(missing_ok=True)
|
|
44
|
+
raise
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _make_private(path: Path) -> None:
|
|
48
|
+
"""chmod 0600 a regular file that others may read. Opened with O_NOFOLLOW, so a symlink (which isn't ours)
|
|
49
|
+
is never followed, not even one swapped in meanwhile. Windows has no such modes: nothing to do there."""
|
|
50
|
+
if not hasattr(os, "O_NOFOLLOW") or not hasattr(os, "fchmod"):
|
|
51
|
+
return
|
|
52
|
+
with contextlib.suppress(OSError): # not ours, gone, or a symlink: left as it is
|
|
53
|
+
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | getattr(os, "O_NONBLOCK", 0))
|
|
54
|
+
try:
|
|
55
|
+
st = os.fstat(fd)
|
|
56
|
+
if stat.S_ISREG(st.st_mode) and stat.S_IMODE(st.st_mode) & 0o077:
|
|
57
|
+
os.fchmod(fd, 0o600)
|
|
58
|
+
finally:
|
|
59
|
+
os.close(fd)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def backup(path: Path) -> None:
|
|
63
|
+
"""Keep the file as it was before OpenMyMemo first changed it, once: ``<file>.openmymemo-backup``.
|
|
64
|
+
|
|
65
|
+
Written private to a temporary file and renamed into place only when complete, so the copy of a file
|
|
66
|
+
holding API keys is never readable by others, and a copy cut short is never taken for the backup. A copy
|
|
67
|
+
left by an older version, which took the original's permissions, is made private here too."""
|
|
68
|
+
copy = path.with_name(path.name + BACKUP_SUFFIX)
|
|
69
|
+
if copy.exists() or copy.is_symlink():
|
|
70
|
+
_make_private(copy)
|
|
71
|
+
return
|
|
72
|
+
if not path.exists():
|
|
73
|
+
return
|
|
74
|
+
source = path.resolve()
|
|
75
|
+
tmp = copy.with_name(f".{copy.name}.{secrets.token_hex(4)}.tmp")
|
|
76
|
+
fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
77
|
+
try:
|
|
78
|
+
with os.fdopen(fd, "wb") as f, open(source, "rb") as src:
|
|
79
|
+
shutil.copyfileobj(src, f)
|
|
80
|
+
f.flush()
|
|
81
|
+
os.fsync(f.fileno())
|
|
82
|
+
shutil.copystat(source, tmp) # the original's times, now that it is all there
|
|
83
|
+
os.chmod(tmp, 0o600) # but readable by this user only, whatever the original allows (PRIVACY.md)
|
|
84
|
+
os.replace(tmp, copy)
|
|
85
|
+
except BaseException:
|
|
86
|
+
tmp.unlink(missing_ok=True)
|
|
87
|
+
raise
|