github-sync-agent 0.1.0__py3-none-any.whl → 0.1.2__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.
- github_sync/__init__.py +1 -1
- github_sync/agent.py +26 -20
- github_sync/cli.py +9 -3
- github_sync/config.py +10 -6
- github_sync/setup.py +14 -2
- github_sync_agent-0.1.2.dist-info/METADATA +108 -0
- github_sync_agent-0.1.2.dist-info/RECORD +10 -0
- github_sync_agent-0.1.0.dist-info/METADATA +0 -132
- github_sync_agent-0.1.0.dist-info/RECORD +0 -10
- {github_sync_agent-0.1.0.dist-info → github_sync_agent-0.1.2.dist-info}/WHEEL +0 -0
- {github_sync_agent-0.1.0.dist-info → github_sync_agent-0.1.2.dist-info}/entry_points.txt +0 -0
- {github_sync_agent-0.1.0.dist-info → github_sync_agent-0.1.2.dist-info}/top_level.txt +0 -0
github_sync/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.2"
|
github_sync/agent.py
CHANGED
|
@@ -153,19 +153,23 @@ def sync_project(path: Path, cfg: dict, dry_run: bool) -> bool:
|
|
|
153
153
|
# ---------------------------------------------------------------------------
|
|
154
154
|
|
|
155
155
|
def discover_projects(cfg: dict, project_name: str | None = None) -> list[Path]:
|
|
156
|
-
|
|
156
|
+
roots = [Path(r) for r in cfg["projects_root"]]
|
|
157
157
|
exclude = set(cfg.get("exclude", []))
|
|
158
158
|
|
|
159
159
|
if project_name:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
for root in roots:
|
|
161
|
+
path = root / project_name
|
|
162
|
+
if path.is_dir():
|
|
163
|
+
return [path]
|
|
164
|
+
searched = ", ".join(str(r) for r in roots)
|
|
165
|
+
raise ValueError(f"Project '{project_name}' not found in: {searched}")
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
projects = []
|
|
168
|
+
for root in roots:
|
|
169
|
+
for p in sorted(root.iterdir()):
|
|
170
|
+
if p.is_dir() and p.name not in exclude and not p.name.startswith("."):
|
|
171
|
+
projects.append(p)
|
|
172
|
+
return projects
|
|
169
173
|
|
|
170
174
|
|
|
171
175
|
# ---------------------------------------------------------------------------
|
|
@@ -173,20 +177,22 @@ def discover_projects(cfg: dict, project_name: str | None = None) -> list[Path]:
|
|
|
173
177
|
# ---------------------------------------------------------------------------
|
|
174
178
|
|
|
175
179
|
def get_status(cfg: dict) -> list[dict]:
|
|
176
|
-
|
|
180
|
+
roots = [Path(r) for r in cfg["projects_root"]]
|
|
177
181
|
exclude = set(cfg.get("exclude", []))
|
|
178
182
|
github_user = cfg["github_user"]
|
|
179
183
|
rows = []
|
|
180
184
|
|
|
181
|
-
for
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
185
|
+
for root in roots:
|
|
186
|
+
for path in sorted(root.iterdir()):
|
|
187
|
+
if not path.is_dir() or path.name in exclude or path.name.startswith("."):
|
|
188
|
+
continue
|
|
189
|
+
rows.append({
|
|
190
|
+
"project": path.name,
|
|
191
|
+
"root": str(root),
|
|
192
|
+
"git": is_git_repo(path),
|
|
193
|
+
"remote": is_git_repo(path) and has_remote(path),
|
|
194
|
+
"changes": has_uncommitted_changes(path) if is_git_repo(path) else False,
|
|
195
|
+
"on_github": github_repo_exists(github_user, path.name),
|
|
196
|
+
})
|
|
191
197
|
|
|
192
198
|
return rows
|
github_sync/cli.py
CHANGED
|
@@ -66,7 +66,9 @@ def sync(ctx: click.Context, project: str | None, dry_run: bool) -> None:
|
|
|
66
66
|
click.echo(f"Error: {exc}", err=True)
|
|
67
67
|
sys.exit(1)
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
roots = cfg['projects_root']
|
|
70
|
+
roots_str = roots[0] if len(roots) == 1 else f"{len(roots)} roots"
|
|
71
|
+
logging.info(f"Syncing {len(projects)} project(s) from {roots_str} → github.com/{cfg['github_user']}")
|
|
70
72
|
|
|
71
73
|
results = []
|
|
72
74
|
for path in projects:
|
|
@@ -95,16 +97,20 @@ def status(ctx: click.Context) -> None:
|
|
|
95
97
|
cfg = ctx.obj["cfg"]
|
|
96
98
|
rows = agent.get_status(cfg)
|
|
97
99
|
|
|
98
|
-
|
|
100
|
+
roots = cfg['projects_root']
|
|
101
|
+
click.echo(f"\nProjects root : {roots[0] if len(roots) == 1 else str(roots)}")
|
|
99
102
|
click.echo(f"GitHub user : {cfg['github_user']}\n")
|
|
100
103
|
|
|
101
|
-
header = f"{'Project':<25} {'Git':<6} {'Remote':<8} {'Changes':<10} {'On GitHub'}"
|
|
104
|
+
header = f"{'Project':<25} {'Root':<30} {'Git':<6} {'Remote':<8} {'Changes':<10} {'On GitHub'}"
|
|
102
105
|
click.echo(header)
|
|
103
106
|
click.echo("─" * len(header))
|
|
104
107
|
|
|
105
108
|
for row in rows:
|
|
109
|
+
# Show only the last part of root to keep table readable
|
|
110
|
+
root_short = Path(row['root']).name
|
|
106
111
|
click.echo(
|
|
107
112
|
f"{row['project']:<25} "
|
|
113
|
+
f"{root_short:<30} "
|
|
108
114
|
f"{'✓' if row['git'] else '✗':<6} "
|
|
109
115
|
f"{'✓' if row['remote'] else '✗':<8} "
|
|
110
116
|
f"{'dirty' if row['changes'] else 'clean':<10} "
|
github_sync/config.py
CHANGED
|
@@ -26,10 +26,14 @@ def _apply_defaults(cfg: dict) -> None:
|
|
|
26
26
|
cfg.setdefault("exclude", [])
|
|
27
27
|
cfg.setdefault("log_file", str(Path.home() / ".local" / "share" / "github-sync" / "github-sync.log"))
|
|
28
28
|
cfg.setdefault("log_level", "INFO")
|
|
29
|
-
# Expand ~ in projects_root
|
|
30
|
-
if "projects_root" in cfg:
|
|
31
|
-
cfg["projects_root"] = str(Path(cfg["projects_root"]).expanduser())
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
if not cfg.get("github_user"):
|
|
31
|
+
raise ValueError("Missing required config key: 'github_user'")
|
|
32
|
+
|
|
33
|
+
# Normalize projects_root to a list of expanded absolute paths
|
|
34
|
+
raw = cfg.get("projects_root")
|
|
35
|
+
if not raw:
|
|
36
|
+
raise ValueError("Missing required config key: 'projects_root'")
|
|
37
|
+
if isinstance(raw, str):
|
|
38
|
+
raw = [raw]
|
|
39
|
+
cfg["projects_root"] = [str(Path(p).expanduser().resolve()) for p in raw]
|
github_sync/setup.py
CHANGED
|
@@ -112,8 +112,20 @@ CONFIG_TEMPLATE = """\
|
|
|
112
112
|
|
|
113
113
|
github_user: {github_user}
|
|
114
114
|
|
|
115
|
-
#
|
|
116
|
-
|
|
115
|
+
# Directories to scan for projects.
|
|
116
|
+
# Every subdirectory inside each root is treated as a project to sync.
|
|
117
|
+
# You can specify one path (string) or multiple paths (list):
|
|
118
|
+
#
|
|
119
|
+
# Single path:
|
|
120
|
+
# projects_root: ~/projects
|
|
121
|
+
#
|
|
122
|
+
# Multiple paths:
|
|
123
|
+
# projects_root:
|
|
124
|
+
# - ~/projects
|
|
125
|
+
# - ~/work
|
|
126
|
+
# - /opt/company-code
|
|
127
|
+
projects_root:
|
|
128
|
+
- {projects_root}
|
|
117
129
|
|
|
118
130
|
# Daily schedule time (used by the systemd timer / cron)
|
|
119
131
|
schedule_time: "02:00"
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: github-sync-agent
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Sync all your local projects to GitHub automatically
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/yuvalavni/Agents
|
|
7
|
+
Project-URL: Repository, https://github.com/yuvalavni/Agents
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/yuvalavni/Agents/issues
|
|
9
|
+
Keywords: github,git,sync,backup,automation,cli
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: pyyaml>=6.0
|
|
18
|
+
Requires-Dist: click>=8.0
|
|
19
|
+
|
|
20
|
+
# GitHub Sync Agent
|
|
21
|
+
|
|
22
|
+
Automatically syncs every local project directory to its own GitHub repository.
|
|
23
|
+
|
|
24
|
+
For each project it will:
|
|
25
|
+
1. `git init` if not already a repo
|
|
26
|
+
2. Create the GitHub repo if it doesn't exist yet
|
|
27
|
+
3. Ensure the remote uses SSH
|
|
28
|
+
4. Stage + commit any uncommitted changes
|
|
29
|
+
5. Push to `origin`
|
|
30
|
+
|
|
31
|
+
Runs daily via a systemd user timer, and on-demand via CLI.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install github-sync-agent
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## First-time setup
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
github-sync init
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The wizard will:
|
|
48
|
+
- Check `git` and `gh` are installed
|
|
49
|
+
- Authenticate with GitHub (`gh auth login`)
|
|
50
|
+
- Ask which folder to scan for projects
|
|
51
|
+
- Set up your SSH key automatically
|
|
52
|
+
- Write a config file to `~/.config/github-sync/config.yaml`
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Sync all projects
|
|
60
|
+
github-sync sync
|
|
61
|
+
|
|
62
|
+
# Sync a single project
|
|
63
|
+
github-sync sync --project my-project
|
|
64
|
+
|
|
65
|
+
# Preview without making changes
|
|
66
|
+
github-sync sync --dry-run
|
|
67
|
+
|
|
68
|
+
# Show status of all projects
|
|
69
|
+
github-sync status
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Configuration (`~/.config/github-sync/config.yaml`)
|
|
75
|
+
|
|
76
|
+
Generated by `github-sync init`. You can edit it at any time.
|
|
77
|
+
|
|
78
|
+
| Key | Default | Description |
|
|
79
|
+
|-----|---------|-------------|
|
|
80
|
+
| `github_user` | _(detected from gh auth)_ | Your GitHub username |
|
|
81
|
+
| `projects_root` | `~/projects` | Scans all subdirectories here |
|
|
82
|
+
| `schedule_time` | `02:00` | Daily run time |
|
|
83
|
+
| `default_visibility` | `private` | `private` or `public` for new repos |
|
|
84
|
+
| `auto_commit_message` | `chore: auto-sync` | Commit message for auto-commits |
|
|
85
|
+
| `exclude` | `[]` | Directory names to skip |
|
|
86
|
+
| `log_file` | `~/.local/share/github-sync/github-sync.log` | Log file path |
|
|
87
|
+
| `log_level` | `INFO` | `DEBUG` / `INFO` / `WARNING` / `ERROR` |
|
|
88
|
+
|
|
89
|
+
### Excluding a project
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
exclude:
|
|
93
|
+
- SomeProjectToSkip
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Requirements
|
|
99
|
+
|
|
100
|
+
- Python 3.10+
|
|
101
|
+
- `git` installed
|
|
102
|
+
- `gh` (GitHub CLI) installed — [installation guide](https://cli.github.com)
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
github_sync/__init__.py,sha256=YvuYzWnKtqBb-IqG8HAu-nhIYAsgj9Vmc_b9o7vO-js,22
|
|
2
|
+
github_sync/agent.py,sha256=2WnJCwupDwT3C9bNZLoj2CAvZW-eoqZChqFTk4wNRKw,6692
|
|
3
|
+
github_sync/cli.py,sha256=ndmfTQhK4KbtVEjXWuOg4RLs3UVFpgb0rZAf_YZFWgs,9336
|
|
4
|
+
github_sync/config.py,sha256=BQrSW7DyhcZB6gZnDKkc-W6u1vtD-l76Rn_gW8Egypk,1444
|
|
5
|
+
github_sync/setup.py,sha256=2ZO0ZCCxQ_LyVSdQbWrObwjjBQCDLqdu_IHplUqW0qU,4720
|
|
6
|
+
github_sync_agent-0.1.2.dist-info/METADATA,sha256=u4dfGfpiUf-TQgtldf043Wj1NSZFvTy8fJG2bYj4dFo,2644
|
|
7
|
+
github_sync_agent-0.1.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
8
|
+
github_sync_agent-0.1.2.dist-info/entry_points.txt,sha256=-LmJUTvRzj5cvyS-dEWdipouBj3dgkW6hqrTz1GmyGw,53
|
|
9
|
+
github_sync_agent-0.1.2.dist-info/top_level.txt,sha256=fPYTA83USK1N4JwfouV8zcejgWfWmqRAKQVDCAT8a5I,12
|
|
10
|
+
github_sync_agent-0.1.2.dist-info/RECORD,,
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: github-sync-agent
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Sync all your local projects to GitHub automatically
|
|
5
|
-
Author: yuvalavni
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/yuvalavni/Agents
|
|
8
|
-
Project-URL: Repository, https://github.com/yuvalavni/Agents
|
|
9
|
-
Keywords: github,git,sync,backup,automation,cli
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
-
Classifier: Environment :: Console
|
|
14
|
-
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
15
|
-
Requires-Python: >=3.10
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
Requires-Dist: pyyaml>=6.0
|
|
18
|
-
Requires-Dist: click>=8.0
|
|
19
|
-
|
|
20
|
-
# GitHub Sync Agent
|
|
21
|
-
|
|
22
|
-
Automatically syncs every project under `/home/yuval/projects` to GitHub (`github.com/yuvalavni`).
|
|
23
|
-
|
|
24
|
-
For each project it will:
|
|
25
|
-
1. `git init` if not already a repo
|
|
26
|
-
2. Create the GitHub repo if it doesn't exist yet
|
|
27
|
-
3. Ensure the remote uses SSH
|
|
28
|
-
4. Stage + commit any uncommitted changes
|
|
29
|
-
5. Push to `origin`
|
|
30
|
-
|
|
31
|
-
Runs daily at **02:00** via a systemd user timer, and on-demand via CLI.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## Directory layout
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
agents/github-sync/
|
|
39
|
-
├── github_sync_agent.py # Main script
|
|
40
|
-
├── config.yaml # GitHub user, projects root, schedule, exclusions
|
|
41
|
-
├── requirements.txt
|
|
42
|
-
├── install.sh # One-time setup
|
|
43
|
-
├── systemd/
|
|
44
|
-
│ ├── github-sync.service
|
|
45
|
-
│ └── github-sync.timer
|
|
46
|
-
└── logs/
|
|
47
|
-
└── github-sync.log
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## Quick start
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
cd /home/yuval/projects/Agents/agents/github-sync
|
|
56
|
-
|
|
57
|
-
# 1. Install (once)
|
|
58
|
-
bash install.sh
|
|
59
|
-
|
|
60
|
-
# 2. Dry run
|
|
61
|
-
.venv/bin/python github_sync_agent.py --dry-run
|
|
62
|
-
|
|
63
|
-
# 3. Sync for real
|
|
64
|
-
.venv/bin/python github_sync_agent.py
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## Configuration (`config.yaml`)
|
|
70
|
-
|
|
71
|
-
| Key | Default | Description |
|
|
72
|
-
|-----|---------|-------------|
|
|
73
|
-
| `github_user` | `yuvalavni` | GitHub username |
|
|
74
|
-
| `projects_root` | `/home/yuval/projects` | Scans all subdirectories here |
|
|
75
|
-
| `schedule_time` | `02:00` | Daily run time |
|
|
76
|
-
| `default_visibility` | `private` | `private` or `public` for new repos |
|
|
77
|
-
| `auto_commit_message` | `chore: auto-sync` | Commit message for auto-commits |
|
|
78
|
-
| `exclude` | `[]` | Directory names to skip |
|
|
79
|
-
| `log_file` | `logs/github-sync.log` | Log file path |
|
|
80
|
-
| `log_level` | `INFO` | `DEBUG` / `INFO` / `WARNING` / `ERROR` |
|
|
81
|
-
|
|
82
|
-
### Excluding a project
|
|
83
|
-
|
|
84
|
-
```yaml
|
|
85
|
-
exclude:
|
|
86
|
-
- SomePrivateProject
|
|
87
|
-
- TempExperiment
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## CLI reference
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
# Sync all projects
|
|
96
|
-
.venv/bin/python github_sync_agent.py
|
|
97
|
-
|
|
98
|
-
# Sync a single project
|
|
99
|
-
.venv/bin/python github_sync_agent.py --project Agents
|
|
100
|
-
|
|
101
|
-
# Preview without making any changes
|
|
102
|
-
.venv/bin/python github_sync_agent.py --dry-run
|
|
103
|
-
|
|
104
|
-
# Show status of all projects (git, remote, changes, GitHub)
|
|
105
|
-
.venv/bin/python github_sync_agent.py --status
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## Systemd
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
# Trigger immediately
|
|
114
|
-
systemctl --user start github-sync.service
|
|
115
|
-
|
|
116
|
-
# Check next scheduled run
|
|
117
|
-
systemctl --user list-timers github-sync.timer
|
|
118
|
-
|
|
119
|
-
# View live logs
|
|
120
|
-
journalctl --user -u github-sync.service -f
|
|
121
|
-
|
|
122
|
-
# Disable daily timer
|
|
123
|
-
systemctl --user disable --now github-sync.timer
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
## Prerequisites
|
|
129
|
-
|
|
130
|
-
- `git` installed
|
|
131
|
-
- `gh` installed and authenticated (`gh auth status`)
|
|
132
|
-
- SSH key added to GitHub (`gh ssh-key list`)
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
github_sync/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
2
|
-
github_sync/agent.py,sha256=E21j6AoXXnmtEWKV6LjECMhyfQ78PY9ifyWBKUga3Xk,6391
|
|
3
|
-
github_sync/cli.py,sha256=GFakiYck02S7ZxsKfVqO_bi2Gc427B87kAgzvMUpKLs,9004
|
|
4
|
-
github_sync/config.py,sha256=zywBw0AkDzk1wHamyJ-sbss3DMAXWq7fZeoDKR1fE_s,1306
|
|
5
|
-
github_sync/setup.py,sha256=keV0bBTgADASGBOCmd6Jv8eXqIg_g0uxbBIYpCHaMxA,4461
|
|
6
|
-
github_sync_agent-0.1.0.dist-info/METADATA,sha256=JZR8uNJHQpRgUSfM0ikh_vyHSlNC5cwuAh3rTE5XNUw,3234
|
|
7
|
-
github_sync_agent-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
8
|
-
github_sync_agent-0.1.0.dist-info/entry_points.txt,sha256=-LmJUTvRzj5cvyS-dEWdipouBj3dgkW6hqrTz1GmyGw,53
|
|
9
|
-
github_sync_agent-0.1.0.dist-info/top_level.txt,sha256=fPYTA83USK1N4JwfouV8zcejgWfWmqRAKQVDCAT8a5I,12
|
|
10
|
-
github_sync_agent-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|