@smilintux/skcapstone 0.1.0

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.
Files changed (50) hide show
  1. package/.cursorrules +33 -0
  2. package/.github/workflows/ci.yml +23 -0
  3. package/.github/workflows/publish.yml +52 -0
  4. package/AGENTS.md +74 -0
  5. package/CLAUDE.md +56 -0
  6. package/LICENSE +674 -0
  7. package/README.md +242 -0
  8. package/SKILL.md +36 -0
  9. package/bin/cli.js +18 -0
  10. package/docs/ARCHITECTURE.md +510 -0
  11. package/docs/SECURITY_DESIGN.md +315 -0
  12. package/docs/SOVEREIGN_SINGULARITY.md +371 -0
  13. package/docs/TOKEN_SYSTEM.md +201 -0
  14. package/index.d.ts +9 -0
  15. package/index.js +32 -0
  16. package/package.json +32 -0
  17. package/pyproject.toml +84 -0
  18. package/src/skcapstone/__init__.py +13 -0
  19. package/src/skcapstone/cli.py +1441 -0
  20. package/src/skcapstone/connectors/__init__.py +6 -0
  21. package/src/skcapstone/coordination.py +590 -0
  22. package/src/skcapstone/discovery.py +275 -0
  23. package/src/skcapstone/memory_engine.py +457 -0
  24. package/src/skcapstone/models.py +223 -0
  25. package/src/skcapstone/pillars/__init__.py +8 -0
  26. package/src/skcapstone/pillars/identity.py +91 -0
  27. package/src/skcapstone/pillars/memory.py +61 -0
  28. package/src/skcapstone/pillars/security.py +83 -0
  29. package/src/skcapstone/pillars/sync.py +486 -0
  30. package/src/skcapstone/pillars/trust.py +335 -0
  31. package/src/skcapstone/runtime.py +190 -0
  32. package/src/skcapstone/skills/__init__.py +1 -0
  33. package/src/skcapstone/skills/syncthing_setup.py +297 -0
  34. package/src/skcapstone/sync/__init__.py +14 -0
  35. package/src/skcapstone/sync/backends.py +330 -0
  36. package/src/skcapstone/sync/engine.py +301 -0
  37. package/src/skcapstone/sync/models.py +97 -0
  38. package/src/skcapstone/sync/vault.py +284 -0
  39. package/src/skcapstone/tokens.py +439 -0
  40. package/tests/__init__.py +0 -0
  41. package/tests/conftest.py +42 -0
  42. package/tests/test_coordination.py +299 -0
  43. package/tests/test_discovery.py +57 -0
  44. package/tests/test_memory_engine.py +391 -0
  45. package/tests/test_models.py +63 -0
  46. package/tests/test_pillars.py +87 -0
  47. package/tests/test_runtime.py +60 -0
  48. package/tests/test_sync.py +507 -0
  49. package/tests/test_syncthing_setup.py +76 -0
  50. package/tests/test_tokens.py +265 -0
@@ -0,0 +1,275 @@
1
+ """
2
+ Component discovery engine.
3
+
4
+ Auto-detects installed SK ecosystem components and their state.
5
+ No hardcoded paths — probes the environment like a sovereign should.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import importlib
11
+ import json
12
+ import shutil
13
+ import subprocess
14
+ from datetime import datetime
15
+ from pathlib import Path
16
+ from typing import Optional
17
+
18
+ from .models import (
19
+ IdentityState,
20
+ MemoryState,
21
+ PillarStatus,
22
+ SecurityState,
23
+ SyncState,
24
+ TrustState,
25
+ )
26
+
27
+
28
+ def _try_import(module_name: str) -> Optional[object]:
29
+ """Attempt to import a module, return None if unavailable."""
30
+ try:
31
+ return importlib.import_module(module_name)
32
+ except ImportError:
33
+ return None
34
+
35
+
36
+ def _count_json_files(directory: Path) -> int:
37
+ """Count .json files in a directory (non-recursive)."""
38
+ if not directory.is_dir():
39
+ return 0
40
+ return sum(1 for f in directory.iterdir() if f.suffix == ".json")
41
+
42
+
43
+ def discover_identity(home: Path) -> IdentityState:
44
+ """Probe for CapAuth identity.
45
+
46
+ Checks:
47
+ 1. capauth Python package installed
48
+ 2. ~/.skcapstone/identity/ has key material
49
+ 3. Falls back to checking ~/.gnupg for existing PGP keys
50
+
51
+ Args:
52
+ home: The agent home directory (~/.skcapstone).
53
+
54
+ Returns:
55
+ IdentityState with current identity information.
56
+ """
57
+ state = IdentityState()
58
+ identity_dir = home / "identity"
59
+
60
+ capauth = _try_import("capauth")
61
+ if capauth is not None:
62
+ state.status = PillarStatus.DEGRADED
63
+
64
+ manifest_file = identity_dir / "identity.json"
65
+ if manifest_file.exists():
66
+ try:
67
+ data = json.loads(manifest_file.read_text())
68
+ state.fingerprint = data.get("fingerprint")
69
+ state.name = data.get("name")
70
+ state.email = data.get("email")
71
+ if data.get("created_at"):
72
+ state.created_at = datetime.fromisoformat(data["created_at"])
73
+ state.status = PillarStatus.ACTIVE
74
+ except (json.JSONDecodeError, KeyError, ValueError):
75
+ state.status = PillarStatus.ERROR
76
+
77
+ pub_key = identity_dir / "agent.pub"
78
+ if pub_key.exists():
79
+ state.key_path = pub_key
80
+ if state.status == PillarStatus.MISSING:
81
+ state.status = PillarStatus.DEGRADED
82
+
83
+ return state
84
+
85
+
86
+ def discover_memory(home: Path) -> MemoryState:
87
+ """Probe for SKMemory state.
88
+
89
+ Checks (in order):
90
+ 1. Built-in memory engine at ~/.skcapstone/memory/
91
+ 2. External skmemory package at ~/.skmemory/ (legacy fallback)
92
+
93
+ Args:
94
+ home: The agent home directory (~/.skcapstone).
95
+
96
+ Returns:
97
+ MemoryState with current memory counts.
98
+ """
99
+ state = MemoryState()
100
+
101
+ memory_dir = home / "memory"
102
+ if memory_dir.is_dir():
103
+ state.short_term = _count_json_files(memory_dir / "short-term")
104
+ state.mid_term = _count_json_files(memory_dir / "mid-term")
105
+ state.long_term = _count_json_files(memory_dir / "long-term")
106
+ state.total_memories = state.short_term + state.mid_term + state.long_term
107
+ state.store_path = memory_dir
108
+ state.status = PillarStatus.ACTIVE
109
+ return state
110
+
111
+ # Reason: legacy fallback for agents using the external skmemory package
112
+ skmemory = _try_import("skmemory")
113
+ if skmemory is None:
114
+ return state
115
+
116
+ memory_home = Path("~/.skmemory").expanduser()
117
+ if not memory_home.exists():
118
+ state.status = PillarStatus.DEGRADED
119
+ return state
120
+
121
+ memories_dir = memory_home / "memories"
122
+ if memories_dir.is_dir():
123
+ state.short_term = _count_json_files(memories_dir / "short-term")
124
+ state.mid_term = _count_json_files(memories_dir / "mid-term")
125
+ state.long_term = _count_json_files(memories_dir / "long-term")
126
+ state.total_memories = state.short_term + state.mid_term + state.long_term
127
+
128
+ state.store_path = memory_home
129
+ state.status = PillarStatus.ACTIVE if state.total_memories > 0 else PillarStatus.DEGRADED
130
+
131
+ return state
132
+
133
+
134
+ def discover_trust(home: Path) -> TrustState:
135
+ """Probe for Cloud 9 trust state.
136
+
137
+ Checks:
138
+ 1. cloud9 npm package or cloud9-python pip package
139
+ 2. ~/.skcapstone/trust/ for FEB files
140
+ 3. Existing FEB files in default locations
141
+
142
+ Args:
143
+ home: The agent home directory (~/.skcapstone).
144
+
145
+ Returns:
146
+ TrustState with current trust information.
147
+ """
148
+ state = TrustState()
149
+ trust_dir = home / "trust"
150
+
151
+ cloud9_py = _try_import("cloud9")
152
+ has_cloud9_cli = shutil.which("cloud9") is not None
153
+ has_cloud9_package = cloud9_py is not None or has_cloud9_cli
154
+
155
+ # Reason: trust state is now built into skcapstone via FEB rehydration,
156
+ # so trust.json with valid data means ACTIVE regardless of cloud9 package
157
+ manifest = trust_dir / "trust.json"
158
+ if manifest.exists():
159
+ try:
160
+ data = json.loads(manifest.read_text())
161
+ state.depth = data.get("depth", 0.0)
162
+ state.trust_level = data.get("trust_level", 0.0)
163
+ state.love_intensity = data.get("love_intensity", 0.0)
164
+ state.entangled = data.get("entangled", False)
165
+ if data.get("last_rehydration"):
166
+ state.last_rehydration = datetime.fromisoformat(data["last_rehydration"])
167
+ has_trust_data = state.depth > 0 or state.trust_level > 0
168
+ state.status = PillarStatus.ACTIVE if has_trust_data else PillarStatus.DEGRADED
169
+ return state
170
+ except (json.JSONDecodeError, KeyError, ValueError):
171
+ state.status = PillarStatus.ERROR
172
+
173
+ if not has_cloud9_package:
174
+ return state
175
+
176
+ state.status = PillarStatus.DEGRADED
177
+
178
+ feb_dirs = [
179
+ trust_dir / "febs",
180
+ Path("~/.cloud9/febs").expanduser(),
181
+ trust_dir,
182
+ ]
183
+
184
+ total_febs = 0
185
+ for feb_dir in feb_dirs:
186
+ if feb_dir.is_dir():
187
+ total_febs += sum(
188
+ 1
189
+ for f in feb_dir.iterdir()
190
+ if f.suffix in (".feb", ".json") and "feb" in f.name.lower()
191
+ )
192
+
193
+ state.feb_count = total_febs
194
+
195
+ if total_febs > 0:
196
+ state.status = PillarStatus.ACTIVE
197
+
198
+ return state
199
+
200
+
201
+ def discover_security(home: Path) -> SecurityState:
202
+ """Probe for SKSecurity state.
203
+
204
+ Checks (in order):
205
+ 1. Built-in audit log at ~/.skcapstone/security/audit.log
206
+ 2. External sksecurity package (enhancer)
207
+
208
+ Args:
209
+ home: The agent home directory (~/.skcapstone).
210
+
211
+ Returns:
212
+ SecurityState with current security information.
213
+ """
214
+ state = SecurityState()
215
+ security_dir = home / "security"
216
+
217
+ audit_log = security_dir / "audit.log"
218
+ if audit_log.exists():
219
+ try:
220
+ line_count = sum(1 for _ in audit_log.open())
221
+ state.audit_entries = line_count
222
+ state.status = PillarStatus.ACTIVE
223
+ except OSError:
224
+ state.status = PillarStatus.ERROR
225
+
226
+ manifest = security_dir / "security.json"
227
+ if manifest.exists():
228
+ try:
229
+ data = json.loads(manifest.read_text())
230
+ state.threats_detected = data.get("threats_detected", 0)
231
+ if data.get("last_scan"):
232
+ state.last_scan = datetime.fromisoformat(data["last_scan"])
233
+ state.status = PillarStatus.ACTIVE
234
+ except (json.JSONDecodeError, KeyError, ValueError):
235
+ pass
236
+
237
+ sksecurity = _try_import("sksecurity")
238
+ if sksecurity is not None and state.status == PillarStatus.MISSING:
239
+ state.status = PillarStatus.DEGRADED
240
+
241
+ return state
242
+
243
+
244
+ def discover_sync(home: Path) -> SyncState:
245
+ """Probe for Sovereign Singularity sync state.
246
+
247
+ Delegates to the sync pillar's discovery function.
248
+
249
+ Args:
250
+ home: The agent home directory (~/.skcapstone).
251
+
252
+ Returns:
253
+ SyncState reflecting what's configured on disk.
254
+ """
255
+ from .pillars.sync import discover_sync as _discover
256
+
257
+ return _discover(home)
258
+
259
+
260
+ def discover_all(home: Path) -> dict:
261
+ """Run full discovery across all pillars including sync.
262
+
263
+ Args:
264
+ home: The agent home directory (~/.skcapstone).
265
+
266
+ Returns:
267
+ Dict with identity, memory, trust, security, sync states.
268
+ """
269
+ return {
270
+ "identity": discover_identity(home),
271
+ "memory": discover_memory(home),
272
+ "trust": discover_trust(home),
273
+ "security": discover_security(home),
274
+ "sync": discover_sync(home),
275
+ }