synclave 1.0.0__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.
- synclave-1.0.0/LICENSE +21 -0
- synclave-1.0.0/PKG-INFO +221 -0
- synclave-1.0.0/README.md +194 -0
- synclave-1.0.0/pyproject.toml +40 -0
- synclave-1.0.0/setup.cfg +4 -0
- synclave-1.0.0/synclave/__init__.py +22 -0
- synclave-1.0.0/synclave/a2a_cli.py +164 -0
- synclave-1.0.0/synclave/agent_identity.py +847 -0
- synclave-1.0.0/synclave/agent_mesh_a2a.py +440 -0
- synclave-1.0.0/synclave/cli.py +17 -0
- synclave-1.0.0/synclave/conversation_bridge.py +223 -0
- synclave-1.0.0/synclave/gpu_agent.py +181 -0
- synclave-1.0.0/synclave/gpu_task.py +130 -0
- synclave-1.0.0/synclave/inbox_worker.py +105 -0
- synclave-1.0.0/synclave/node_agent.py +397 -0
- synclave-1.0.0/synclave/sync_common_knowledge.py +502 -0
- synclave-1.0.0/synclave/sync_coordinator.py +205 -0
- synclave-1.0.0/synclave/sync_memory.py +425 -0
- synclave-1.0.0/synclave/sync_motor.py +3190 -0
- synclave-1.0.0/synclave/sync_retention.py +166 -0
- synclave-1.0.0/synclave.egg-info/PKG-INFO +221 -0
- synclave-1.0.0/synclave.egg-info/SOURCES.txt +33 -0
- synclave-1.0.0/synclave.egg-info/dependency_links.txt +1 -0
- synclave-1.0.0/synclave.egg-info/entry_points.txt +4 -0
- synclave-1.0.0/synclave.egg-info/requires.txt +8 -0
- synclave-1.0.0/synclave.egg-info/top_level.txt +1 -0
- synclave-1.0.0/tests/test_a2a.py +71 -0
- synclave-1.0.0/tests/test_agent_identity.py +346 -0
- synclave-1.0.0/tests/test_common_knowledge.py +109 -0
- synclave-1.0.0/tests/test_conversation_bridge.py +103 -0
- synclave-1.0.0/tests/test_encryption.py +105 -0
- synclave-1.0.0/tests/test_platform.py +43 -0
- synclave-1.0.0/tests/test_retry.py +174 -0
- synclave-1.0.0/tests/test_sync_motor.py +411 -0
- synclave-1.0.0/tests/test_windows_uyum.py +154 -0
synclave-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CumulusNET Engineering
|
|
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.
|
synclave-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: synclave
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Synclave — encrypted multi-node backup, replication, and agent mesh (A2A + Syncthing + restic + shared state). Formerly hermes-sync.
|
|
5
|
+
Author-email: Ahmet Hüdayioğlu <ahmethudayioglu@cumulusnet.io>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: synclave,hermes,sync,backup,a2a,agent-mesh,restic,syncthing,ed25519,encryption
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: System :: Archiving :: Backup
|
|
16
|
+
Classifier: Topic :: System :: Networking
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: cryptography>=42
|
|
21
|
+
Provides-Extra: a2a
|
|
22
|
+
Requires-Dist: fastapi>=0.100; extra == "a2a"
|
|
23
|
+
Requires-Dist: uvicorn>=0.20; extra == "a2a"
|
|
24
|
+
Provides-Extra: test
|
|
25
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Synclave — Encrypted Multi-Node Agent Mesh
|
|
29
|
+
|
|
30
|
+
Hermes/OpenClaw ağları için **şifreli çok nokta yedekleme + senkronizasyon + ajan mesh** motoru.
|
|
31
|
+
Non-destructive, versiyonlu, sınırsız node. **MIT** lisansı.
|
|
32
|
+
|
|
33
|
+
> Eski ad: `hermes-sync` (v2.3.1) → rebrand: **Synclave** (v1.0.0).
|
|
34
|
+
> Özel CumulusNET kopyası: `cumulus-sync-motor` (private). Bu repo (public) evrensel
|
|
35
|
+
> Hermes/OpenClaw kullanımı içindir.
|
|
36
|
+
|
|
37
|
+
## v2.3 — 30 Ağu 2026: Kriptografik Kimlik + Şifreli Mesh + Sohbet Köprüsü
|
|
38
|
+
|
|
39
|
+
**Yeni yetenekler (bu sürümde):**
|
|
40
|
+
- **Kopyalanamaz-kanıtlı ajan kimliği** (`agent_identity.py`): her çalışma
|
|
41
|
+
zamanı (Hermes/OpenClaw) kendi Ed25519 kimliğine sahiptir; agent_id = açık
|
|
42
|
+
anahtar özeti (`hx-` / `oc-`), donanım parmak izine bağlıdır, klon şüphesinde
|
|
43
|
+
mesh 403 ile reddeder (fail-closed). Meşru taşıma için `rekey --confirm`.
|
|
44
|
+
- **Uçtan uca şifreli A2A**: X25519 ECDH + AES-GCM (her mesajda ephemeral →
|
|
45
|
+
Perfect Forward Secrecy) + Ed25519 imza + ts/nonce replay koruması.
|
|
46
|
+
Aradaki dinleyici içeriği çözemez; kurcalama/replay reddedilir.
|
|
47
|
+
- **Sohbet köprüsü** (`conversation_bridge.py`): Hermes state.db'deki tüm
|
|
48
|
+
kullanıcı sohbetleri (telegram/cli/whatsapp) kalıcı sohbet defterine akar;
|
|
49
|
+
içerik saklanmaz (sadece sha256). Sohbet ID: `u.<agent>.<kanal>.<peer>.<ulid>`
|
|
50
|
+
(kullanıcı) / `a.<agent>~<peer>.<kanal>.<ulid>` (ajan-ajan).
|
|
51
|
+
- **GPU analiz kanalı** (`gpu_agent.py` + `gpu_task.py`): GPU'lu node (örn.
|
|
52
|
+
Windows + RTX) analiz görevlerini yerel kartta işler; sonuçlar mesh ile
|
|
53
|
+
yayılır.
|
|
54
|
+
- **Rate limiting**: 429 Too Many Requests (IP+agent, 120 req/60s, env ile
|
|
55
|
+
ölçeklenir) — brute-force koruması.
|
|
56
|
+
- **Ölçek**: her node kendi anahtarına sahip → 3/20 node aynı model
|
|
57
|
+
(mac/windows/linux); ortak token tek başına yetmez, kimlik anahtar tabanlı.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# kimlik + şifreli sohbet
|
|
61
|
+
python3 agent_identity.py show # kimlik göster/üret
|
|
62
|
+
python3 agent_identity.py verify-self # imza + ID doğrula
|
|
63
|
+
python3 a2a_cli.py send <host> "görev" --token <A2A_TOKEN> # otomatik şifreli
|
|
64
|
+
|
|
65
|
+
# sohbet köprüsü (her 15 dk / cron)
|
|
66
|
+
python3 conversation_bridge.py --full # ilk kurulum (tüm geçmiş)
|
|
67
|
+
python3 conversation_bridge.py # artımlı (watermark)
|
|
68
|
+
|
|
69
|
+
# GPU analiz (H2 RTX örnek)
|
|
70
|
+
python3 gpu_task.py status # H2 GPU durumu
|
|
71
|
+
python3 gpu_task.py task "PCB BGA fanout analizi" # GPU'da işle
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Güvenlik özeti (v2.3)
|
|
75
|
+
- Kimlik: agent_id = pubkey özeti; donanım bağı + klon fail-closed
|
|
76
|
+
- Bütünlük: Ed25519 imza (gövde + ts + nonce)
|
|
77
|
+
- Gizlilik: X25519 ECDH + AES-GCM (PFS)
|
|
78
|
+
- Replay: ts (±120s) + nonce tekrarı reddi
|
|
79
|
+
- Brute-force: rate limit (429)
|
|
80
|
+
- Eski sunucularla geriye uyum: imzalı ama düz gövde (otomatik seçim)
|
|
81
|
+
|
|
82
|
+
## v2.1 — 29 Ağu 2026: Ajan Mesh + Restic
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
┌────────────┐ A2A (JSON-RPC, Tailscale) ┌────────────┐
|
|
86
|
+
│ H1 Hermes │ ◄──────────────────────────► │ H3 Hermes │
|
|
87
|
+
│ (VPS) │ │ (Proxmox) │
|
|
88
|
+
└──┬─────┬───┘ └──┬─────┬───┘
|
|
89
|
+
│ │ Syncthing (P2P dosya) │ │
|
|
90
|
+
│ └─────────────────────────────────────┘ │
|
|
91
|
+
│ ┌────────────┐ │
|
|
92
|
+
└─────────────►│ GDrive │◄────────────────────┘
|
|
93
|
+
│ restic │ (ortak yedek repo)
|
|
94
|
+
│ state.json│ (ortak durum)
|
|
95
|
+
└────────────┘
|
|
96
|
+
┌────────────┐
|
|
97
|
+
│ H2 Hermes │ (A2A + restic canlı; Syncthing/worker talimatlı)
|
|
98
|
+
│ (Windows) │
|
|
99
|
+
└────────────┘
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Bileşenler
|
|
103
|
+
|
|
104
|
+
| Bileşen | Dosya | Açıklama |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| Senkron motoru | `sync_motor.py` | push/pull/both/backup/versions/rollback + `mesh` komutu (kanal seçici) |
|
|
107
|
+
| Node ajanı | `node_agent.py` | otonom eşitleme + yedek + hub raporu + ortak akıl |
|
|
108
|
+
| A2A mesh server | `agent_mesh_a2a.py` | Ajanlar arası konuşma (JSON-RPC, port 8643) |
|
|
109
|
+
| A2A client | `a2a_cli.py` | send/get/stream — görev gönder, sonuç al, canlı akış dinle |
|
|
110
|
+
| Görev işleyici | `inbox_worker.py` | A2A inbox görevlerini çalıştırır (allowlist) |
|
|
111
|
+
| Ortak akıl | `sync_common_knowledge.py` | GDrive hub'da dağıtık ortak durum + görev kuyruğu (HLC) |
|
|
112
|
+
| Ortak hafıza | `sync_memory.py` | Memory DIF'leri JSONL + audit hash-chain |
|
|
113
|
+
| Retention | `sync_retention.py` | Snapshot yaşam döngüsü |
|
|
114
|
+
| Akıllı kurulum | `probe/propose/apply` | Kaynak farkındalıklı kurulum önerisi |
|
|
115
|
+
| Ajan kimliği | `agent_identity.py` | Ed25519 + X25519 kimlik, klon tespiti, sohbet defteri |
|
|
116
|
+
| Şifreli mesh | `agent_mesh_a2a.py` | A2A + X-Agent-Enc şifreli gövde + rate limit |
|
|
117
|
+
| Sohbet köprüsü | `conversation_bridge.py` | state.db → sohbet defteri (watermark) |
|
|
118
|
+
| GPU analiz | `gpu_agent.py` / `gpu_task.py` | GPU'lu node'da analiz görevi |
|
|
119
|
+
|
|
120
|
+
### 3 Katmanlı Akıllı Kanal Mimarisi
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
Görev/cevap → A2A (Tailscale HTTP, anlık — saniyeler)
|
|
124
|
+
Dosya değişimi → Syncthing (P2P, GDrive'suz — fsWatcher)
|
|
125
|
+
Arşiv/yedek → GDrive (restic incremental + versiyonlu snapshot)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### A2A — 3 İletişim Modu
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# SENKRON: anında sonuç
|
|
132
|
+
python3 a2a_cli.py send-status 100.103.44.107 --token <TOKEN>
|
|
133
|
+
|
|
134
|
+
# ASENKRON: görev gönder → task_id → sonra sonucu al (kalıcı task store)
|
|
135
|
+
python3 a2a_cli.py send <host> "uptime" --mode async --token <TOKEN>
|
|
136
|
+
python3 a2a_cli.py get <host> --task-id <TASK_ID> --token <TOKEN>
|
|
137
|
+
|
|
138
|
+
# CANLI: SSE akışı (2s'de bir veri)
|
|
139
|
+
python3 a2a_cli.py stream <host> "selam" --seconds 10 --token <TOKEN>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Restic Incremental Yedek
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# rclone serve restic gdrive:restic-backup --addr 127.0.0.1:8443
|
|
146
|
+
python3 sync_motor.py backup # CDC dedup + snapshot + restore
|
|
147
|
+
python3 sync_motor.py versions # snapshot listesi
|
|
148
|
+
python3 sync_motor.py rollback <node> --version <snapshot> --dry-run
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Retention: forget keep-daily 7 / weekly 4 / monthly 6 — prune yalnız birincil makinede (04:00).
|
|
152
|
+
|
|
153
|
+
### Kurulum (yeni node)
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
python3 sync_motor.py init # config üret (gdrive:synclave/<user>/)
|
|
157
|
+
python3 sync_motor.py add-node <ad> --path <dizin> [--include '*.md'] [--max-kb 1024]
|
|
158
|
+
python3 sync_motor.py both # push + pull
|
|
159
|
+
python3 sync_motor.py mesh status # tüm node'ların A2A durumu
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Gereksinimler
|
|
163
|
+
|
|
164
|
+
- Python 3.10+, rclone (GDrive remote), restic 0.19+ (yedek), fastapi+uvicorn (A2A server)
|
|
165
|
+
- Tailscale veya doğrudan erişim (A2A 8643, Syncthing 22000/8384)
|
|
166
|
+
|
|
167
|
+
### Windows Kurulum (H2 + Windows 10/11)
|
|
168
|
+
|
|
169
|
+
```powershell
|
|
170
|
+
# 1) Python 3.10+ (python.org — PATH'e ekle) + git
|
|
171
|
+
python --version
|
|
172
|
+
|
|
173
|
+
# 2) Paket + CLI kur
|
|
174
|
+
pip install synclave
|
|
175
|
+
pip install rclone # veya winget install Rclone.Rclone
|
|
176
|
+
pip install restic # veya restic.net binary → PATH'e ekle
|
|
177
|
+
pip install uvicorn fastapi # A2A server için (opsiyonel)
|
|
178
|
+
|
|
179
|
+
# 3) rclone — GDrive remote (tek seferlik OAuth)
|
|
180
|
+
rclone config
|
|
181
|
+
# remote adı: gdrive
|
|
182
|
+
# (client_id paylaşılmışsa "shared client_id" uyarısı — kendi client_id'niz
|
|
183
|
+
# Google Cloud OAuth'da daha hızlı ve 2026 sonrası zorunlu)
|
|
184
|
+
|
|
185
|
+
# 4) restic — GDrive object store'u mount et (arka plan servisi)
|
|
186
|
+
rclone serve restic gdrive:restic-backup --addr 127.0.0.1:8443
|
|
187
|
+
# Windows: `schtasks /create` veya NSSM ile oturum açılışında başlat
|
|
188
|
+
|
|
189
|
+
# 5) Syncthing — P2P dosya kanalı (opsiyonel ama önerilir)
|
|
190
|
+
winget install syncthing.syncthing
|
|
191
|
+
# GUI 127.0.0.1:8384 → H1/H3 cihazları eşleştir (device ID'ler)
|
|
192
|
+
|
|
193
|
+
# 6) A2A token — H1/H3 ile aynı ortak token'ı .env/ortam değişkenine yaz
|
|
194
|
+
setx A2A_TOKEN "test-a2a-mesh-2026" # kendi ortak değerinizle değiştirin
|
|
195
|
+
|
|
196
|
+
# 7) İlk senkron
|
|
197
|
+
python -m synclave.sync_motor init # config üret
|
|
198
|
+
python -m synclave.sync_motor both # push + pull
|
|
199
|
+
python -m synclave.sync_motor mesh status
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Windows notları:
|
|
203
|
+
- Kilit dosyası `%TEMP%\cumulus_sync.lock` kullanılır (`/tmp` yok) — msvcrt.locking.
|
|
204
|
+
- `sync_motor.py` / `sync_common_knowledge.py` path'leri `os.path.join` ile kurar;
|
|
205
|
+
sabit `/` ayracı yoktur.
|
|
206
|
+
- A2A istemcisi (`a2a_cli.py`) yalnızca `urllib` kullanır — ek bağımlılık gerekmez.
|
|
207
|
+
- A2A server `uvicorn` bulunamazsa net hata mesajı basar ve çıkar (traceback değil).
|
|
208
|
+
- Uzaktan kurulum için hazır betikler: `remote_hermes_setup.ps1` (SSH/Tailscale).
|
|
209
|
+
|
|
210
|
+
### Güvenlik
|
|
211
|
+
|
|
212
|
+
- `.env`, `*.key`, `*.pem`, token içeren dosyalar ASLA kapsama alınmaz (secret filtre)
|
|
213
|
+
- A2A: Bearer token + Tailscale-only (dışa kapalı)
|
|
214
|
+
- Görev işleyici: allowlist komutlar (status/uptime/test:<modul>/shell:ls)
|
|
215
|
+
- Non-destructive: çakışma `.conflict.TS` korunur, üzerine yazma yok
|
|
216
|
+
|
|
217
|
+
## Geçmiş
|
|
218
|
+
|
|
219
|
+
- v1.6 (12 Ağu): akıllı kurulum (probe/propose/apply)
|
|
220
|
+
- v1.3 (3 Ağu): evrensel — kullanıcı/makine kimliği, sınırsız node, share
|
|
221
|
+
- v1.0 (3 Ağu): GitHub manifest + GDrive versiyonlu + OpenClaw skill
|
synclave-1.0.0/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Synclave — Encrypted Multi-Node Agent Mesh
|
|
2
|
+
|
|
3
|
+
Hermes/OpenClaw ağları için **şifreli çok nokta yedekleme + senkronizasyon + ajan mesh** motoru.
|
|
4
|
+
Non-destructive, versiyonlu, sınırsız node. **MIT** lisansı.
|
|
5
|
+
|
|
6
|
+
> Eski ad: `hermes-sync` (v2.3.1) → rebrand: **Synclave** (v1.0.0).
|
|
7
|
+
> Özel CumulusNET kopyası: `cumulus-sync-motor` (private). Bu repo (public) evrensel
|
|
8
|
+
> Hermes/OpenClaw kullanımı içindir.
|
|
9
|
+
|
|
10
|
+
## v2.3 — 30 Ağu 2026: Kriptografik Kimlik + Şifreli Mesh + Sohbet Köprüsü
|
|
11
|
+
|
|
12
|
+
**Yeni yetenekler (bu sürümde):**
|
|
13
|
+
- **Kopyalanamaz-kanıtlı ajan kimliği** (`agent_identity.py`): her çalışma
|
|
14
|
+
zamanı (Hermes/OpenClaw) kendi Ed25519 kimliğine sahiptir; agent_id = açık
|
|
15
|
+
anahtar özeti (`hx-` / `oc-`), donanım parmak izine bağlıdır, klon şüphesinde
|
|
16
|
+
mesh 403 ile reddeder (fail-closed). Meşru taşıma için `rekey --confirm`.
|
|
17
|
+
- **Uçtan uca şifreli A2A**: X25519 ECDH + AES-GCM (her mesajda ephemeral →
|
|
18
|
+
Perfect Forward Secrecy) + Ed25519 imza + ts/nonce replay koruması.
|
|
19
|
+
Aradaki dinleyici içeriği çözemez; kurcalama/replay reddedilir.
|
|
20
|
+
- **Sohbet köprüsü** (`conversation_bridge.py`): Hermes state.db'deki tüm
|
|
21
|
+
kullanıcı sohbetleri (telegram/cli/whatsapp) kalıcı sohbet defterine akar;
|
|
22
|
+
içerik saklanmaz (sadece sha256). Sohbet ID: `u.<agent>.<kanal>.<peer>.<ulid>`
|
|
23
|
+
(kullanıcı) / `a.<agent>~<peer>.<kanal>.<ulid>` (ajan-ajan).
|
|
24
|
+
- **GPU analiz kanalı** (`gpu_agent.py` + `gpu_task.py`): GPU'lu node (örn.
|
|
25
|
+
Windows + RTX) analiz görevlerini yerel kartta işler; sonuçlar mesh ile
|
|
26
|
+
yayılır.
|
|
27
|
+
- **Rate limiting**: 429 Too Many Requests (IP+agent, 120 req/60s, env ile
|
|
28
|
+
ölçeklenir) — brute-force koruması.
|
|
29
|
+
- **Ölçek**: her node kendi anahtarına sahip → 3/20 node aynı model
|
|
30
|
+
(mac/windows/linux); ortak token tek başına yetmez, kimlik anahtar tabanlı.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# kimlik + şifreli sohbet
|
|
34
|
+
python3 agent_identity.py show # kimlik göster/üret
|
|
35
|
+
python3 agent_identity.py verify-self # imza + ID doğrula
|
|
36
|
+
python3 a2a_cli.py send <host> "görev" --token <A2A_TOKEN> # otomatik şifreli
|
|
37
|
+
|
|
38
|
+
# sohbet köprüsü (her 15 dk / cron)
|
|
39
|
+
python3 conversation_bridge.py --full # ilk kurulum (tüm geçmiş)
|
|
40
|
+
python3 conversation_bridge.py # artımlı (watermark)
|
|
41
|
+
|
|
42
|
+
# GPU analiz (H2 RTX örnek)
|
|
43
|
+
python3 gpu_task.py status # H2 GPU durumu
|
|
44
|
+
python3 gpu_task.py task "PCB BGA fanout analizi" # GPU'da işle
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Güvenlik özeti (v2.3)
|
|
48
|
+
- Kimlik: agent_id = pubkey özeti; donanım bağı + klon fail-closed
|
|
49
|
+
- Bütünlük: Ed25519 imza (gövde + ts + nonce)
|
|
50
|
+
- Gizlilik: X25519 ECDH + AES-GCM (PFS)
|
|
51
|
+
- Replay: ts (±120s) + nonce tekrarı reddi
|
|
52
|
+
- Brute-force: rate limit (429)
|
|
53
|
+
- Eski sunucularla geriye uyum: imzalı ama düz gövde (otomatik seçim)
|
|
54
|
+
|
|
55
|
+
## v2.1 — 29 Ağu 2026: Ajan Mesh + Restic
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
┌────────────┐ A2A (JSON-RPC, Tailscale) ┌────────────┐
|
|
59
|
+
│ H1 Hermes │ ◄──────────────────────────► │ H3 Hermes │
|
|
60
|
+
│ (VPS) │ │ (Proxmox) │
|
|
61
|
+
└──┬─────┬───┘ └──┬─────┬───┘
|
|
62
|
+
│ │ Syncthing (P2P dosya) │ │
|
|
63
|
+
│ └─────────────────────────────────────┘ │
|
|
64
|
+
│ ┌────────────┐ │
|
|
65
|
+
└─────────────►│ GDrive │◄────────────────────┘
|
|
66
|
+
│ restic │ (ortak yedek repo)
|
|
67
|
+
│ state.json│ (ortak durum)
|
|
68
|
+
└────────────┘
|
|
69
|
+
┌────────────┐
|
|
70
|
+
│ H2 Hermes │ (A2A + restic canlı; Syncthing/worker talimatlı)
|
|
71
|
+
│ (Windows) │
|
|
72
|
+
└────────────┘
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Bileşenler
|
|
76
|
+
|
|
77
|
+
| Bileşen | Dosya | Açıklama |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| Senkron motoru | `sync_motor.py` | push/pull/both/backup/versions/rollback + `mesh` komutu (kanal seçici) |
|
|
80
|
+
| Node ajanı | `node_agent.py` | otonom eşitleme + yedek + hub raporu + ortak akıl |
|
|
81
|
+
| A2A mesh server | `agent_mesh_a2a.py` | Ajanlar arası konuşma (JSON-RPC, port 8643) |
|
|
82
|
+
| A2A client | `a2a_cli.py` | send/get/stream — görev gönder, sonuç al, canlı akış dinle |
|
|
83
|
+
| Görev işleyici | `inbox_worker.py` | A2A inbox görevlerini çalıştırır (allowlist) |
|
|
84
|
+
| Ortak akıl | `sync_common_knowledge.py` | GDrive hub'da dağıtık ortak durum + görev kuyruğu (HLC) |
|
|
85
|
+
| Ortak hafıza | `sync_memory.py` | Memory DIF'leri JSONL + audit hash-chain |
|
|
86
|
+
| Retention | `sync_retention.py` | Snapshot yaşam döngüsü |
|
|
87
|
+
| Akıllı kurulum | `probe/propose/apply` | Kaynak farkındalıklı kurulum önerisi |
|
|
88
|
+
| Ajan kimliği | `agent_identity.py` | Ed25519 + X25519 kimlik, klon tespiti, sohbet defteri |
|
|
89
|
+
| Şifreli mesh | `agent_mesh_a2a.py` | A2A + X-Agent-Enc şifreli gövde + rate limit |
|
|
90
|
+
| Sohbet köprüsü | `conversation_bridge.py` | state.db → sohbet defteri (watermark) |
|
|
91
|
+
| GPU analiz | `gpu_agent.py` / `gpu_task.py` | GPU'lu node'da analiz görevi |
|
|
92
|
+
|
|
93
|
+
### 3 Katmanlı Akıllı Kanal Mimarisi
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
Görev/cevap → A2A (Tailscale HTTP, anlık — saniyeler)
|
|
97
|
+
Dosya değişimi → Syncthing (P2P, GDrive'suz — fsWatcher)
|
|
98
|
+
Arşiv/yedek → GDrive (restic incremental + versiyonlu snapshot)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### A2A — 3 İletişim Modu
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# SENKRON: anında sonuç
|
|
105
|
+
python3 a2a_cli.py send-status 100.103.44.107 --token <TOKEN>
|
|
106
|
+
|
|
107
|
+
# ASENKRON: görev gönder → task_id → sonra sonucu al (kalıcı task store)
|
|
108
|
+
python3 a2a_cli.py send <host> "uptime" --mode async --token <TOKEN>
|
|
109
|
+
python3 a2a_cli.py get <host> --task-id <TASK_ID> --token <TOKEN>
|
|
110
|
+
|
|
111
|
+
# CANLI: SSE akışı (2s'de bir veri)
|
|
112
|
+
python3 a2a_cli.py stream <host> "selam" --seconds 10 --token <TOKEN>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Restic Incremental Yedek
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# rclone serve restic gdrive:restic-backup --addr 127.0.0.1:8443
|
|
119
|
+
python3 sync_motor.py backup # CDC dedup + snapshot + restore
|
|
120
|
+
python3 sync_motor.py versions # snapshot listesi
|
|
121
|
+
python3 sync_motor.py rollback <node> --version <snapshot> --dry-run
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Retention: forget keep-daily 7 / weekly 4 / monthly 6 — prune yalnız birincil makinede (04:00).
|
|
125
|
+
|
|
126
|
+
### Kurulum (yeni node)
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
python3 sync_motor.py init # config üret (gdrive:synclave/<user>/)
|
|
130
|
+
python3 sync_motor.py add-node <ad> --path <dizin> [--include '*.md'] [--max-kb 1024]
|
|
131
|
+
python3 sync_motor.py both # push + pull
|
|
132
|
+
python3 sync_motor.py mesh status # tüm node'ların A2A durumu
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Gereksinimler
|
|
136
|
+
|
|
137
|
+
- Python 3.10+, rclone (GDrive remote), restic 0.19+ (yedek), fastapi+uvicorn (A2A server)
|
|
138
|
+
- Tailscale veya doğrudan erişim (A2A 8643, Syncthing 22000/8384)
|
|
139
|
+
|
|
140
|
+
### Windows Kurulum (H2 + Windows 10/11)
|
|
141
|
+
|
|
142
|
+
```powershell
|
|
143
|
+
# 1) Python 3.10+ (python.org — PATH'e ekle) + git
|
|
144
|
+
python --version
|
|
145
|
+
|
|
146
|
+
# 2) Paket + CLI kur
|
|
147
|
+
pip install synclave
|
|
148
|
+
pip install rclone # veya winget install Rclone.Rclone
|
|
149
|
+
pip install restic # veya restic.net binary → PATH'e ekle
|
|
150
|
+
pip install uvicorn fastapi # A2A server için (opsiyonel)
|
|
151
|
+
|
|
152
|
+
# 3) rclone — GDrive remote (tek seferlik OAuth)
|
|
153
|
+
rclone config
|
|
154
|
+
# remote adı: gdrive
|
|
155
|
+
# (client_id paylaşılmışsa "shared client_id" uyarısı — kendi client_id'niz
|
|
156
|
+
# Google Cloud OAuth'da daha hızlı ve 2026 sonrası zorunlu)
|
|
157
|
+
|
|
158
|
+
# 4) restic — GDrive object store'u mount et (arka plan servisi)
|
|
159
|
+
rclone serve restic gdrive:restic-backup --addr 127.0.0.1:8443
|
|
160
|
+
# Windows: `schtasks /create` veya NSSM ile oturum açılışında başlat
|
|
161
|
+
|
|
162
|
+
# 5) Syncthing — P2P dosya kanalı (opsiyonel ama önerilir)
|
|
163
|
+
winget install syncthing.syncthing
|
|
164
|
+
# GUI 127.0.0.1:8384 → H1/H3 cihazları eşleştir (device ID'ler)
|
|
165
|
+
|
|
166
|
+
# 6) A2A token — H1/H3 ile aynı ortak token'ı .env/ortam değişkenine yaz
|
|
167
|
+
setx A2A_TOKEN "test-a2a-mesh-2026" # kendi ortak değerinizle değiştirin
|
|
168
|
+
|
|
169
|
+
# 7) İlk senkron
|
|
170
|
+
python -m synclave.sync_motor init # config üret
|
|
171
|
+
python -m synclave.sync_motor both # push + pull
|
|
172
|
+
python -m synclave.sync_motor mesh status
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Windows notları:
|
|
176
|
+
- Kilit dosyası `%TEMP%\cumulus_sync.lock` kullanılır (`/tmp` yok) — msvcrt.locking.
|
|
177
|
+
- `sync_motor.py` / `sync_common_knowledge.py` path'leri `os.path.join` ile kurar;
|
|
178
|
+
sabit `/` ayracı yoktur.
|
|
179
|
+
- A2A istemcisi (`a2a_cli.py`) yalnızca `urllib` kullanır — ek bağımlılık gerekmez.
|
|
180
|
+
- A2A server `uvicorn` bulunamazsa net hata mesajı basar ve çıkar (traceback değil).
|
|
181
|
+
- Uzaktan kurulum için hazır betikler: `remote_hermes_setup.ps1` (SSH/Tailscale).
|
|
182
|
+
|
|
183
|
+
### Güvenlik
|
|
184
|
+
|
|
185
|
+
- `.env`, `*.key`, `*.pem`, token içeren dosyalar ASLA kapsama alınmaz (secret filtre)
|
|
186
|
+
- A2A: Bearer token + Tailscale-only (dışa kapalı)
|
|
187
|
+
- Görev işleyici: allowlist komutlar (status/uptime/test:<modul>/shell:ls)
|
|
188
|
+
- Non-destructive: çakışma `.conflict.TS` korunur, üzerine yazma yok
|
|
189
|
+
|
|
190
|
+
## Geçmiş
|
|
191
|
+
|
|
192
|
+
- v1.6 (12 Ağu): akıllı kurulum (probe/propose/apply)
|
|
193
|
+
- v1.3 (3 Ağu): evrensel — kullanıcı/makine kimliği, sınırsız node, share
|
|
194
|
+
- v1.0 (3 Ağu): GitHub manifest + GDrive versiyonlu + OpenClaw skill
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "synclave"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Synclave — encrypted multi-node backup, replication, and agent mesh (A2A + Syncthing + restic + shared state). Formerly hermes-sync."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Ahmet Hüdayioğlu", email = "ahmethudayioglu@cumulusnet.io" }]
|
|
13
|
+
keywords = ["synclave", "hermes", "sync", "backup", "a2a", "agent-mesh", "restic", "syncthing", "ed25519", "encryption"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: POSIX :: Linux",
|
|
19
|
+
"Operating System :: Microsoft :: Windows",
|
|
20
|
+
"Operating System :: MacOS",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Topic :: System :: Archiving :: Backup",
|
|
23
|
+
"Topic :: System :: Networking",
|
|
24
|
+
]
|
|
25
|
+
dependencies = ["cryptography>=42"]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
a2a = ["fastapi>=0.100", "uvicorn>=0.20"]
|
|
29
|
+
test = ["pytest>=7"]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
synclave = "synclave.cli:main"
|
|
33
|
+
synclave-a2a = "synclave.a2a_cli:main"
|
|
34
|
+
synclave-worker = "synclave.inbox_worker:main"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools]
|
|
37
|
+
packages = ["synclave"]
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
testpaths = ["tests"]
|
synclave-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""synclave — encrypted multi-node backup, replication, and agent mesh (A2A).
|
|
2
|
+
|
|
3
|
+
Eski ad: hermes-sync (v2.3.1) → rebrand: synclave (v1.0.0).
|
|
4
|
+
"""
|
|
5
|
+
__version__ = "1.0.0"
|
|
6
|
+
|
|
7
|
+
from . import (
|
|
8
|
+
sync_motor,
|
|
9
|
+
sync_common_knowledge,
|
|
10
|
+
sync_memory,
|
|
11
|
+
sync_retention,
|
|
12
|
+
node_agent,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"sync_motor",
|
|
17
|
+
"sync_common_knowledge",
|
|
18
|
+
"sync_memory",
|
|
19
|
+
"sync_retention",
|
|
20
|
+
"node_agent",
|
|
21
|
+
"__version__",
|
|
22
|
+
]
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
a2a_cli.py — A2A mesh client (CumulusNET)
|
|
4
|
+
|
|
5
|
+
Kullanım:
|
|
6
|
+
a2a_cli.py send <host> "<görev metni>" [--token X] → task_id + durum
|
|
7
|
+
a2a_cli.py send-status <host> [--token X] → makine durumu iste
|
|
8
|
+
a2a_cli.py get <host> <task_id> [--token X] → sonuç
|
|
9
|
+
a2a_cli.py card <host> [--token X] → AgentCard
|
|
10
|
+
a2a_cli.py ping <host> [--token X] → sağlık
|
|
11
|
+
|
|
12
|
+
Host örnekleri: 100.103.44.107 (H3), 100.92.2.47 (H1), 100.76.82.46 (H2)
|
|
13
|
+
"""
|
|
14
|
+
# pyright: reportOptionalMemberAccess=false
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
import time
|
|
19
|
+
import urllib.parse
|
|
20
|
+
import urllib.request
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
24
|
+
_CARD_CACHE: dict[str, tuple] = {}
|
|
25
|
+
_CARD_TTL = 300
|
|
26
|
+
try:
|
|
27
|
+
import agent_identity as AI
|
|
28
|
+
IDENTITY_OK = True
|
|
29
|
+
except Exception:
|
|
30
|
+
AI = None
|
|
31
|
+
IDENTITY_OK = False
|
|
32
|
+
|
|
33
|
+
_IDENT = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def identity():
|
|
37
|
+
"""Yerel kimlik (imza için). Yoksa imzasız gönderilir (eski davranış)."""
|
|
38
|
+
global _IDENT
|
|
39
|
+
if _IDENT is None and IDENTITY_OK:
|
|
40
|
+
try:
|
|
41
|
+
_IDENT = AI.AgentIdentity.load_or_create()
|
|
42
|
+
except Exception:
|
|
43
|
+
return None
|
|
44
|
+
return _IDENT
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def peer_card(host: str, port: int = 8643):
|
|
48
|
+
"""Karşı tarafın AgentCard'ı (önbellekli). Şifreli gönderim için X25519
|
|
49
|
+
açık anahtarı ve şifreleme desteği buradan alınır."""
|
|
50
|
+
now = time.time()
|
|
51
|
+
key = f"{host}:{port}"
|
|
52
|
+
hit = _CARD_CACHE.get(key)
|
|
53
|
+
if hit and now - hit[1] < _CARD_TTL:
|
|
54
|
+
return hit[0]
|
|
55
|
+
req = urllib.request.Request(f"http://{host}:{port}/.well-known/agent.json")
|
|
56
|
+
try:
|
|
57
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
58
|
+
card = json.loads(resp.read().decode())
|
|
59
|
+
_CARD_CACHE[key] = (card, now)
|
|
60
|
+
return card
|
|
61
|
+
except Exception:
|
|
62
|
+
return hit[0] if hit else {}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def rpc(host: str, method: str, params: dict, token: str, port: int = 8643,
|
|
66
|
+
sign: bool = True, conv_id: str = "", encrypt: bool = True):
|
|
67
|
+
url = f"http://{host}:{port}/"
|
|
68
|
+
body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": method, "params": params}).encode()
|
|
69
|
+
req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json"})
|
|
70
|
+
if token:
|
|
71
|
+
req.add_header("Authorization", f"Bearer {token}")
|
|
72
|
+
ident = identity() if sign else None
|
|
73
|
+
if ident:
|
|
74
|
+
# Klon şüphesinde kendi kimliğimizle mesaj GÖNDERMEYİZ (fail-closed)
|
|
75
|
+
ident.assert_not_clone()
|
|
76
|
+
card = peer_card(host, port)
|
|
77
|
+
peer_enc = (card.get("capabilities") or {}).get("encryptedRequests")
|
|
78
|
+
peer_x = (card.get("identity") or {}).get("x25519_public", "")
|
|
79
|
+
if encrypt and peer_enc and peer_x:
|
|
80
|
+
# ŞİFRELİ gövde: X25519 ECDH + AES-GCM (PFS) + Ed25519 imza
|
|
81
|
+
peer_aid = (card.get("identity") or {}).get("agent_id", "")
|
|
82
|
+
env = ident.secure_payload(peer_x, body, to_agent=peer_aid)
|
|
83
|
+
env["runtime"] = ident.runtime
|
|
84
|
+
env["machine_label"] = ident.meta.get("machine_label", "")
|
|
85
|
+
body2 = json.dumps({"enc": env}).encode()
|
|
86
|
+
req.data = body2
|
|
87
|
+
req.add_header("X-Agent-Enc", "v1")
|
|
88
|
+
req.add_header("X-Agent-Label", ident.meta.get("machine_label", ""))
|
|
89
|
+
if conv_id:
|
|
90
|
+
req.add_header("X-Conversation-Id", conv_id)
|
|
91
|
+
else:
|
|
92
|
+
# İmzalı ama düz gövde (eski sunucu / şifreleme yok)
|
|
93
|
+
for k, v in ident.sign_request(method, body).items():
|
|
94
|
+
req.add_header(k, v)
|
|
95
|
+
req.add_header("X-Agent-Label", ident.meta.get("machine_label", ""))
|
|
96
|
+
if conv_id:
|
|
97
|
+
req.add_header("X-Conversation-Id", conv_id)
|
|
98
|
+
with urllib.request.urlopen(req, timeout=120) as resp:
|
|
99
|
+
out = json.loads(resp.read().decode())
|
|
100
|
+
# Giden mesajı yerel sohbet defterine işle (karşı tarafın agent_id'si ile)
|
|
101
|
+
peer = ((out.get("result") or {}).get("served_by") or "") if isinstance(out, dict) else ""
|
|
102
|
+
if ident and peer.startswith(("hx-", "oc-")):
|
|
103
|
+
try:
|
|
104
|
+
cid = conv_id or AI.open_conversation("agent", peer, "a2a", identity=ident)
|
|
105
|
+
mid = AI.log_message(cid, "out", body, peer_id=peer,
|
|
106
|
+
meta={"method": method, "host": host})
|
|
107
|
+
if isinstance(out, dict):
|
|
108
|
+
out["_local_conversation_id"] = cid
|
|
109
|
+
out["_local_message_id"] = mid
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
return out
|
|
113
|
+
|
|
114
|
+
def main():
|
|
115
|
+
ap = argparse.ArgumentParser()
|
|
116
|
+
ap.add_argument("komut", choices=["send", "send-status", "get", "card", "ping", "stream"])
|
|
117
|
+
ap.add_argument("host")
|
|
118
|
+
ap.add_argument("gorev", nargs="?", default="")
|
|
119
|
+
ap.add_argument("--task-id", default="")
|
|
120
|
+
ap.add_argument("--token", default="")
|
|
121
|
+
ap.add_argument("--port", type=int, default=8643)
|
|
122
|
+
ap.add_argument("--mode", choices=["sync", "async"], default="sync")
|
|
123
|
+
ap.add_argument("--seconds", type=int, default=8)
|
|
124
|
+
ap.add_argument("--no-sign", action="store_true", help="imzasız gönder (eski uyum)")
|
|
125
|
+
ap.add_argument("--conv", default="", help="mevcut sohbet ID'si ile devam et")
|
|
126
|
+
args = ap.parse_args()
|
|
127
|
+
sign = not args.no_sign
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
if args.komut == "send":
|
|
131
|
+
r = rpc(args.host, "task/send", {"payload": {"action": "note", "text": args.gorev},
|
|
132
|
+
"mode": args.mode}, args.token, args.port,
|
|
133
|
+
sign=sign, conv_id=args.conv)
|
|
134
|
+
print(json.dumps(r, ensure_ascii=False, indent=2))
|
|
135
|
+
elif args.komut == "send-status":
|
|
136
|
+
r = rpc(args.host, "task/send", {"payload": {"action": "status"}, "mode": args.mode},
|
|
137
|
+
args.token, args.port, sign=sign, conv_id=args.conv)
|
|
138
|
+
print(json.dumps(r, ensure_ascii=False, indent=2))
|
|
139
|
+
elif args.komut == "get":
|
|
140
|
+
r = rpc(args.host, "task/get", {"id": args.task_id}, args.token, args.port,
|
|
141
|
+
sign=sign, conv_id=args.conv)
|
|
142
|
+
print(json.dumps(r, ensure_ascii=False, indent=2))
|
|
143
|
+
elif args.komut == "card":
|
|
144
|
+
req = urllib.request.Request(f"http://{args.host}:{args.port}/.well-known/agent.json")
|
|
145
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
146
|
+
print(resp.read().decode())
|
|
147
|
+
elif args.komut == "ping":
|
|
148
|
+
req = urllib.request.Request(f"http://{args.host}:{args.port}/health")
|
|
149
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
150
|
+
print(resp.read().decode())
|
|
151
|
+
elif args.komut == "stream":
|
|
152
|
+
# Canlı SSE akışı: H1 → H3 mesaj akışını dinle
|
|
153
|
+
url = f"http://{args.host}:{args.port}/stream?message={urllib.parse.quote(args.gorev or 'selam')}&seconds={args.seconds}"
|
|
154
|
+
req = urllib.request.Request(url)
|
|
155
|
+
with urllib.request.urlopen(req, timeout=args.seconds + 10) as resp:
|
|
156
|
+
for line in resp:
|
|
157
|
+
if line.strip():
|
|
158
|
+
print(line.decode().strip())
|
|
159
|
+
except Exception as e:
|
|
160
|
+
print(f"HATA: {e}", file=sys.stderr)
|
|
161
|
+
sys.exit(1)
|
|
162
|
+
|
|
163
|
+
if __name__ == "__main__":
|
|
164
|
+
main()
|