github-sync-agent 0.1.3__tar.gz → 0.1.4__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.
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/PKG-INFO +4 -1
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/README.md +3 -0
- github_sync_agent-0.1.4/github_sync/__init__.py +1 -0
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/github_sync/cli.py +19 -11
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/github_sync/setup.py +145 -11
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/github_sync_agent.egg-info/PKG-INFO +4 -1
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/pyproject.toml +1 -1
- github_sync_agent-0.1.3/github_sync/__init__.py +0 -1
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/github_sync/agent.py +0 -0
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/github_sync/config.py +0 -0
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/github_sync_agent.egg-info/SOURCES.txt +0 -0
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/github_sync_agent.egg-info/dependency_links.txt +0 -0
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/github_sync_agent.egg-info/entry_points.txt +0 -0
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/github_sync_agent.egg-info/requires.txt +0 -0
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/github_sync_agent.egg-info/top_level.txt +0 -0
- {github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github-sync-agent
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Sync all your local projects to GitHub automatically
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/yuvalavni/Agents
|
|
@@ -49,8 +49,11 @@ The wizard will:
|
|
|
49
49
|
- Authenticate with GitHub (`gh auth login`)
|
|
50
50
|
- Ask which folder to scan for projects
|
|
51
51
|
- Set up your SSH key automatically
|
|
52
|
+
- **Verify SSH works** (auto-fixes port 443 and ssh-agent if needed)
|
|
52
53
|
- Write a config file to `~/.config/github-sync/config.yaml`
|
|
53
54
|
|
|
55
|
+
Setup will **not** complete until `ssh -T git@github.com` succeeds.
|
|
56
|
+
|
|
54
57
|
---
|
|
55
58
|
|
|
56
59
|
## Usage
|
|
@@ -30,8 +30,11 @@ The wizard will:
|
|
|
30
30
|
- Authenticate with GitHub (`gh auth login`)
|
|
31
31
|
- Ask which folder to scan for projects
|
|
32
32
|
- Set up your SSH key automatically
|
|
33
|
+
- **Verify SSH works** (auto-fixes port 443 and ssh-agent if needed)
|
|
33
34
|
- Write a config file to `~/.config/github-sync/config.yaml`
|
|
34
35
|
|
|
36
|
+
Setup will **not** complete until `ssh -T git@github.com` succeeds.
|
|
37
|
+
|
|
35
38
|
---
|
|
36
39
|
|
|
37
40
|
## Usage
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.4"
|
|
@@ -207,8 +207,8 @@ def init() -> None:
|
|
|
207
207
|
|
|
208
208
|
click.echo()
|
|
209
209
|
|
|
210
|
-
# ── 6. SSH
|
|
211
|
-
click.echo(click.style(" Setting up SSH
|
|
210
|
+
# ── 6. SSH setup + verification ─────────────────────────────────────────
|
|
211
|
+
click.echo(click.style(" Setting up SSH for GitHub...", bold=True))
|
|
212
212
|
|
|
213
213
|
if not setup.ssh_key_exists():
|
|
214
214
|
click.echo(" No SSH key found — generating one...")
|
|
@@ -220,17 +220,25 @@ def init() -> None:
|
|
|
220
220
|
else:
|
|
221
221
|
click.echo(" ✓ SSH key already exists")
|
|
222
222
|
|
|
223
|
-
setup.
|
|
223
|
+
setup.add_github_host_keys()
|
|
224
224
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
225
|
+
click.echo(" Verifying SSH connection to GitHub...")
|
|
226
|
+
ok, msg, fixes = setup.ensure_github_ssh()
|
|
227
|
+
|
|
228
|
+
for fix in fixes:
|
|
229
|
+
click.echo(f" → applied fix: {fix}")
|
|
230
|
+
|
|
231
|
+
if ok:
|
|
232
|
+
user = setup.ssh_auth_username(msg) or github_user
|
|
233
|
+
click.echo(f" ✓ SSH verified as {user}")
|
|
232
234
|
else:
|
|
233
|
-
click.echo("
|
|
235
|
+
click.echo(click.style(" ✗ SSH verification failed.", fg="red"))
|
|
236
|
+
click.echo(f" {msg}")
|
|
237
|
+
click.echo()
|
|
238
|
+
click.echo(" Manual checks:")
|
|
239
|
+
click.echo(" ssh -T git@github.com")
|
|
240
|
+
click.echo(" gh ssh-key add ~/.ssh/id_ed25519.pub")
|
|
241
|
+
sys.exit(1)
|
|
234
242
|
|
|
235
243
|
click.echo()
|
|
236
244
|
|
|
@@ -3,6 +3,8 @@ Setup helpers used by `github-sync init`.
|
|
|
3
3
|
Checks prerequisites, detects credentials, sets up SSH.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
import platform
|
|
7
|
+
import re
|
|
6
8
|
import shutil
|
|
7
9
|
import subprocess
|
|
8
10
|
from pathlib import Path
|
|
@@ -49,6 +51,16 @@ def get_github_username() -> str | None:
|
|
|
49
51
|
# ---------------------------------------------------------------------------
|
|
50
52
|
|
|
51
53
|
DEFAULT_KEY = Path.home() / ".ssh" / "id_ed25519"
|
|
54
|
+
SSH_CONFIG = Path.home() / ".ssh" / "config"
|
|
55
|
+
|
|
56
|
+
GITHUB_PORT443_BLOCK = """
|
|
57
|
+
# Added by github-sync — use port 443 when port 22 is blocked
|
|
58
|
+
Host github.com
|
|
59
|
+
HostName ssh.github.com
|
|
60
|
+
Port 443
|
|
61
|
+
User git
|
|
62
|
+
IdentityFile ~/.ssh/id_ed25519
|
|
63
|
+
"""
|
|
52
64
|
|
|
53
65
|
|
|
54
66
|
def ssh_key_exists() -> bool:
|
|
@@ -56,6 +68,7 @@ def ssh_key_exists() -> bool:
|
|
|
56
68
|
|
|
57
69
|
|
|
58
70
|
def generate_ssh_key(passphrase: str = "") -> bool:
|
|
71
|
+
Path.home().joinpath(".ssh").mkdir(mode=0o700, exist_ok=True)
|
|
59
72
|
result = subprocess.run(
|
|
60
73
|
["ssh-keygen", "-t", "ed25519", "-C", "github-sync", "-f", str(DEFAULT_KEY), "-N", passphrase],
|
|
61
74
|
capture_output=True, text=True,
|
|
@@ -63,29 +76,101 @@ def generate_ssh_key(passphrase: str = "") -> bool:
|
|
|
63
76
|
return result.returncode == 0
|
|
64
77
|
|
|
65
78
|
|
|
66
|
-
def
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
)
|
|
71
|
-
|
|
79
|
+
def _append_known_hosts(hostname: str, port: int | None = None) -> bool:
|
|
80
|
+
cmd = ["ssh-keyscan", "-t", "ed25519"]
|
|
81
|
+
if port:
|
|
82
|
+
cmd.extend(["-p", str(port)])
|
|
83
|
+
cmd.append(hostname)
|
|
84
|
+
|
|
85
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
86
|
+
if result.returncode != 0 or not result.stdout.strip():
|
|
72
87
|
return False
|
|
88
|
+
|
|
73
89
|
known_hosts = Path.home() / ".ssh" / "known_hosts"
|
|
74
90
|
known_hosts.parent.mkdir(mode=0o700, exist_ok=True)
|
|
75
91
|
existing = known_hosts.read_text() if known_hosts.exists() else ""
|
|
76
|
-
if
|
|
92
|
+
if hostname not in existing:
|
|
77
93
|
with open(known_hosts, "a") as f:
|
|
78
94
|
f.write(result.stdout)
|
|
79
95
|
return True
|
|
80
96
|
|
|
81
97
|
|
|
82
|
-
def
|
|
83
|
-
"""
|
|
98
|
+
def add_github_host_keys() -> None:
|
|
99
|
+
"""Trust GitHub host keys for both port 22 and port 443."""
|
|
100
|
+
_append_known_hosts("github.com")
|
|
101
|
+
_append_known_hosts("ssh.github.com", port=443)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_github_ssh() -> tuple[bool, str]:
|
|
105
|
+
"""Test SSH auth to GitHub. Returns (success, combined output)."""
|
|
84
106
|
result = subprocess.run(
|
|
85
|
-
[
|
|
107
|
+
[
|
|
108
|
+
"ssh", "-T",
|
|
109
|
+
"-o", "BatchMode=yes",
|
|
110
|
+
"-o", "ConnectTimeout=15",
|
|
111
|
+
"-o", "StrictHostKeyChecking=accept-new",
|
|
112
|
+
"git@github.com",
|
|
113
|
+
],
|
|
86
114
|
capture_output=True, text=True,
|
|
87
115
|
)
|
|
88
|
-
|
|
116
|
+
output = (result.stdout + result.stderr).strip()
|
|
117
|
+
ok = "successfully authenticated" in output.lower()
|
|
118
|
+
return ok, output
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def ssh_auth_username(output: str) -> str | None:
|
|
122
|
+
match = re.search(r"Hi ([^!]+)!", output)
|
|
123
|
+
return match.group(1) if match else None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def is_ssh_connection_error(message: str) -> bool:
|
|
127
|
+
lower = message.lower()
|
|
128
|
+
return any(
|
|
129
|
+
phrase in lower
|
|
130
|
+
for phrase in ("connection closed", "connection reset", "timed out", "network is unreachable")
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def is_ssh_auth_error(message: str) -> bool:
|
|
135
|
+
lower = message.lower()
|
|
136
|
+
return "permission denied" in lower or "publickey" in lower
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def has_github_port443_config() -> bool:
|
|
140
|
+
if not SSH_CONFIG.exists():
|
|
141
|
+
return False
|
|
142
|
+
content = SSH_CONFIG.read_text()
|
|
143
|
+
return "ssh.github.com" in content and "443" in content
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def configure_github_ssh_port_443() -> bool:
|
|
147
|
+
"""Configure ~/.ssh/config to reach GitHub over port 443."""
|
|
148
|
+
if has_github_port443_config():
|
|
149
|
+
return True
|
|
150
|
+
|
|
151
|
+
SSH_CONFIG.parent.mkdir(mode=0o700, exist_ok=True)
|
|
152
|
+
with open(SSH_CONFIG, "a") as f:
|
|
153
|
+
f.write(GITHUB_PORT443_BLOCK)
|
|
154
|
+
SSH_CONFIG.chmod(0o600)
|
|
155
|
+
_append_known_hosts("ssh.github.com", port=443)
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def add_ssh_key_to_agent() -> bool:
|
|
160
|
+
"""Load the default key into ssh-agent (required on some macOS setups)."""
|
|
161
|
+
if not DEFAULT_KEY.exists():
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
commands = []
|
|
165
|
+
if platform.system() == "Darwin":
|
|
166
|
+
commands.append(["ssh-add", "--apple-use-keychain", str(DEFAULT_KEY)])
|
|
167
|
+
commands.append(["ssh-add", str(DEFAULT_KEY)])
|
|
168
|
+
|
|
169
|
+
for cmd in commands:
|
|
170
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
171
|
+
if result.returncode == 0:
|
|
172
|
+
return True
|
|
173
|
+
return False
|
|
89
174
|
|
|
90
175
|
|
|
91
176
|
def upload_ssh_key_to_github(title: str = "github-sync") -> bool:
|
|
@@ -99,6 +184,55 @@ def upload_ssh_key_to_github(title: str = "github-sync") -> bool:
|
|
|
99
184
|
return result.returncode == 0
|
|
100
185
|
|
|
101
186
|
|
|
187
|
+
def ssh_key_on_github() -> bool:
|
|
188
|
+
"""Back-compat wrapper."""
|
|
189
|
+
ok, _ = test_github_ssh()
|
|
190
|
+
return ok
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def ensure_github_ssh() -> tuple[bool, str, list[str]]:
|
|
194
|
+
"""
|
|
195
|
+
Verify GitHub SSH works; apply fixes as needed.
|
|
196
|
+
Returns (success, last_message, fixes_applied).
|
|
197
|
+
"""
|
|
198
|
+
fixes: list[str] = []
|
|
199
|
+
|
|
200
|
+
ok, msg = test_github_ssh()
|
|
201
|
+
if ok:
|
|
202
|
+
return True, msg, fixes
|
|
203
|
+
|
|
204
|
+
if is_ssh_connection_error(msg):
|
|
205
|
+
configure_github_ssh_port_443()
|
|
206
|
+
fixes.append("configured port 443")
|
|
207
|
+
ok, msg = test_github_ssh()
|
|
208
|
+
if ok:
|
|
209
|
+
return True, msg, fixes
|
|
210
|
+
|
|
211
|
+
if is_ssh_auth_error(msg):
|
|
212
|
+
if upload_ssh_key_to_github():
|
|
213
|
+
fixes.append("uploaded SSH key")
|
|
214
|
+
ok, msg = test_github_ssh()
|
|
215
|
+
if ok:
|
|
216
|
+
return True, msg, fixes
|
|
217
|
+
|
|
218
|
+
if add_ssh_key_to_agent():
|
|
219
|
+
fixes.append("loaded key into ssh-agent")
|
|
220
|
+
ok, msg = test_github_ssh()
|
|
221
|
+
if ok:
|
|
222
|
+
return True, msg, fixes
|
|
223
|
+
|
|
224
|
+
# Last resort: port 443 + agent together
|
|
225
|
+
if is_ssh_connection_error(msg) and not has_github_port443_config():
|
|
226
|
+
configure_github_ssh_port_443()
|
|
227
|
+
add_ssh_key_to_agent()
|
|
228
|
+
fixes.append("port 443 + ssh-agent")
|
|
229
|
+
ok, msg = test_github_ssh()
|
|
230
|
+
if ok:
|
|
231
|
+
return True, msg, fixes
|
|
232
|
+
|
|
233
|
+
return False, msg, fixes
|
|
234
|
+
|
|
235
|
+
|
|
102
236
|
# ---------------------------------------------------------------------------
|
|
103
237
|
# Config writer
|
|
104
238
|
# ---------------------------------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github-sync-agent
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Sync all your local projects to GitHub automatically
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/yuvalavni/Agents
|
|
@@ -49,8 +49,11 @@ The wizard will:
|
|
|
49
49
|
- Authenticate with GitHub (`gh auth login`)
|
|
50
50
|
- Ask which folder to scan for projects
|
|
51
51
|
- Set up your SSH key automatically
|
|
52
|
+
- **Verify SSH works** (auto-fixes port 443 and ssh-agent if needed)
|
|
52
53
|
- Write a config file to `~/.config/github-sync/config.yaml`
|
|
53
54
|
|
|
55
|
+
Setup will **not** complete until `ssh -T git@github.com` succeeds.
|
|
56
|
+
|
|
54
57
|
---
|
|
55
58
|
|
|
56
59
|
## Usage
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/github_sync_agent.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/github_sync_agent.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{github_sync_agent-0.1.3 → github_sync_agent-0.1.4}/github_sync_agent.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|