docketeer-git 0.0.6__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,7 @@
1
+ from docketeer_git.backup import backup
2
+
3
+ git_tasks = [backup]
4
+
5
+ task_collections = ["docketeer_git:git_tasks"]
6
+
7
+ __all__ = ["git_tasks", "task_collections"]
@@ -0,0 +1,121 @@
1
+ """Git-backed workspace backup — periodic commit and optional push."""
2
+
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ from datetime import datetime, timedelta
7
+ from pathlib import Path
8
+
9
+ from docket.dependencies import Perpetual
10
+
11
+ from docketeer import environment
12
+ from docketeer.dependencies import EnvironmentStr, WorkspacePath
13
+
14
+ log = logging.getLogger(__name__)
15
+
16
+ BACKUP_INTERVAL = environment.get_timedelta("GIT_BACKUP_INTERVAL", timedelta(minutes=5))
17
+
18
+ DEFAULT_GITIGNORE = """\
19
+ *.lock
20
+ *.tmp
21
+ *.swp
22
+ *~
23
+ __pycache__/
24
+ tmp/
25
+ """
26
+
27
+
28
+ async def _git(
29
+ cwd: Path,
30
+ *args: str,
31
+ env: dict[str, str] | None = None,
32
+ ) -> asyncio.subprocess.Process:
33
+ """Run a git command and return the completed process."""
34
+ proc = await asyncio.create_subprocess_exec(
35
+ "git",
36
+ *args,
37
+ cwd=cwd,
38
+ stdout=asyncio.subprocess.PIPE,
39
+ stderr=asyncio.subprocess.PIPE,
40
+ env={**os.environ, **env} if env else None,
41
+ )
42
+ await proc.wait()
43
+ return proc
44
+
45
+
46
+ async def _git_config(cwd: Path, key: str, value: str) -> None:
47
+ await _git(cwd, "config", key, value)
48
+
49
+
50
+ async def _init_repo(
51
+ workspace: Path,
52
+ *,
53
+ branch: str,
54
+ remote: str,
55
+ author_name: str,
56
+ author_email: str,
57
+ ) -> None:
58
+ """Initialize a git repo in the workspace if one doesn't exist."""
59
+ await _git(workspace, "init", "-b", branch)
60
+ await _git_config(workspace, "user.name", author_name)
61
+ await _git_config(workspace, "user.email", author_email)
62
+
63
+ if remote:
64
+ await _git(workspace, "remote", "add", "origin", remote)
65
+
66
+ gitignore = workspace / ".gitignore"
67
+ if not gitignore.exists():
68
+ gitignore.write_text(DEFAULT_GITIGNORE)
69
+
70
+
71
+ async def _has_changes(workspace: Path) -> bool:
72
+ """Check if the workspace has any uncommitted changes or untracked files."""
73
+ status = await _git(workspace, "status", "--porcelain")
74
+ assert status.stdout is not None
75
+ output = await status.stdout.read()
76
+ return bool(output.strip())
77
+
78
+
79
+ async def backup(
80
+ perpetual: Perpetual = Perpetual(every=BACKUP_INTERVAL, automatic=True),
81
+ workspace: Path = WorkspacePath(),
82
+ remote: str = EnvironmentStr("GIT_REMOTE", ""),
83
+ branch: str = EnvironmentStr("GIT_BRANCH", "main"),
84
+ author_name: str = EnvironmentStr("GIT_AUTHOR_NAME", "Docketeer"),
85
+ author_email: str = EnvironmentStr("GIT_AUTHOR_EMAIL", "docketeer@localhost"),
86
+ ) -> None:
87
+ """Commit workspace changes and optionally push to a remote."""
88
+ if not any(workspace.iterdir()):
89
+ return
90
+
91
+ if not (workspace / ".git").is_dir():
92
+ await _init_repo(
93
+ workspace,
94
+ branch=branch,
95
+ remote=remote,
96
+ author_name=author_name,
97
+ author_email=author_email,
98
+ )
99
+
100
+ if not await _has_changes(workspace):
101
+ return
102
+
103
+ await _git(workspace, "add", ".")
104
+
105
+ author_env = {
106
+ "GIT_AUTHOR_NAME": author_name,
107
+ "GIT_AUTHOR_EMAIL": author_email,
108
+ "GIT_COMMITTER_NAME": author_name,
109
+ "GIT_COMMITTER_EMAIL": author_email,
110
+ }
111
+
112
+ timestamp = datetime.now().astimezone().strftime("%Y-%m-%d %H:%M:%S %z")
113
+ await _git(workspace, "commit", "-m", f"backup: {timestamp}", env=author_env)
114
+ log.info("Workspace backup committed: %s", timestamp)
115
+
116
+ if remote:
117
+ result = await _git(workspace, "push", "-u", "origin", branch)
118
+ if result.returncode != 0:
119
+ assert result.stderr is not None
120
+ stderr = (await result.stderr.read()).decode()
121
+ log.warning("Push failed: %s", stderr)
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: docketeer-git
3
+ Version: 0.0.6
4
+ Summary: Git-backed workspace backup plugin for Docketeer
5
+ Project-URL: Homepage, https://github.com/chrisguidry/docketeer
6
+ Project-URL: Repository, https://github.com/chrisguidry/docketeer
7
+ Project-URL: Issues, https://github.com/chrisguidry/docketeer/issues
8
+ Author-email: Chris Guidry <guid@omg.lol>
9
+ License-Expression: MIT
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Classifier: Topic :: System :: Archiving :: Backup
15
+ Requires-Python: >=3.12
16
+ Requires-Dist: docketeer
17
+ Requires-Dist: pydocket
18
+ Description-Content-Type: text/markdown
19
+
20
+ # docketeer-git
21
+
22
+ Git-backed workspace backup plugin for
23
+ [Docketeer](https://pypi.org/project/docketeer/). Automatically commits the
24
+ agent's workspace (`~/.docketeer/memory/`) to a local git repo on a timer, and
25
+ optionally pushes to a remote for off-machine backup.
26
+
27
+ Install `docketeer-git` alongside `docketeer` and backups start automatically.
28
+ No agent-facing tools are added — the agent doesn't know about backups.
29
+
30
+ ## Configuration
31
+
32
+ | Variable | Default | Description |
33
+ |---|---|---|
34
+ | `DOCKETEER_GIT_BACKUP_INTERVAL` | `PT5M` | How often to check for changes (ISO 8601 duration or seconds) |
35
+ | `DOCKETEER_GIT_REMOTE` | _(empty)_ | Remote URL to push to. No push if unset. |
36
+ | `DOCKETEER_GIT_BRANCH` | `main` | Branch name to use |
37
+ | `DOCKETEER_GIT_AUTHOR_NAME` | `Docketeer` | Git author name for backup commits |
38
+ | `DOCKETEER_GIT_AUTHOR_EMAIL` | `docketeer@localhost` | Git author email for backup commits |
39
+
40
+ ## How it works
41
+
42
+ A periodic docket task checks the workspace for uncommitted changes every 5
43
+ minutes (configurable). If anything changed, it stages everything and commits
44
+ with a timestamped message. If `DOCKETEER_GIT_REMOTE` is set, it pushes after
45
+ each commit. Push failures are logged but don't crash the agent.
46
+
47
+ The git repo is initialized automatically on first run. You can browse the
48
+ history with standard git tools (`git log`, `git diff`, etc.) in the workspace
49
+ directory.
@@ -0,0 +1,6 @@
1
+ docketeer_git/__init__.py,sha256=gB0tgUUEQcJuCOqG-_u91GieS_rsKWkeqNdHs43MBhw,155
2
+ docketeer_git/backup.py,sha256=wv2bR0xgdiCwlgW90LLjhs-srYXbUS2W-0oswQBJuzQ,3514
3
+ docketeer_git-0.0.6.dist-info/METADATA,sha256=rrsuqX4as8yZlBNnZoyQY97Kcl_V4v-F83DolM_Gse0,2155
4
+ docketeer_git-0.0.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
5
+ docketeer_git-0.0.6.dist-info/entry_points.txt,sha256=Hwuh47wBtr58oKO6mwasZtTKy7eIzxCMJl5BEsuF3aI,55
6
+ docketeer_git-0.0.6.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [docketeer.tasks]
2
+ git = docketeer_git:task_collections