nucleus-mcp 1.0.7 → 1.2.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 (135) hide show
  1. package/package.json +2 -4
  2. package/.github/workflows/ci.yml +0 -72
  3. package/.smithery/shttp/module.js +0 -3
  4. package/.smithery/shttp/module.js.map +0 -7
  5. package/.smithery/stdio/index.cjs +0 -74
  6. package/.smithery/stdio/index.cjs.map +0 -7
  7. package/CHANGELOG.md +0 -65
  8. package/CODE_OF_CONDUCT.md +0 -34
  9. package/COMPARISON.md +0 -20
  10. package/CONTRIBUTING.md +0 -137
  11. package/DEVELOPMENT.md +0 -109
  12. package/LICENSE +0 -21
  13. package/README.md +0 -319
  14. package/ROADMAP.md +0 -24
  15. package/SECURITY.md +0 -88
  16. package/docs/COMMUNITY_FAQ.md +0 -48
  17. package/docs/COMPETITIVE_ANALYSIS.md +0 -90
  18. package/docs/DEMO_SCRIPT.md +0 -86
  19. package/docs/ENTERPRISE.md +0 -169
  20. package/docs/FAQ.md +0 -228
  21. package/docs/QUICK_START.md +0 -155
  22. package/docs/README.md +0 -67
  23. package/docs/RECURSIVE_SNAP_DEMO.gif +0 -0
  24. package/docs/social-preview-hq.png +0 -0
  25. package/docs/social-preview.png +0 -0
  26. package/examples/basic_usage.py +0 -84
  27. package/package.json.real +0 -24
  28. package/pyproject.toml +0 -50
  29. package/registry-server.json +0 -20
  30. package/scripts/demo_governance.py +0 -109
  31. package/scripts/sse_bridge.py +0 -87
  32. package/scripts/verify_windows_paths.py +0 -48
  33. package/server.json +0 -20
  34. package/src/mcp_server_nucleus/__init__.py +0 -8583
  35. package/src/mcp_server_nucleus/__main__.py +0 -9
  36. package/src/mcp_server_nucleus/cli.py +0 -1225
  37. package/src/mcp_server_nucleus/commitment_core.py +0 -146
  38. package/src/mcp_server_nucleus/commitment_ledger.py +0 -841
  39. package/src/mcp_server_nucleus/core/__init__.py +0 -6
  40. package/src/mcp_server_nucleus/core/orchestrator.py +0 -9
  41. package/src/mcp_server_nucleus/core/tool_registration_impl.py +0 -99
  42. package/src/mcp_server_nucleus/daemon/safety/lock.py +0 -121
  43. package/src/mcp_server_nucleus/deployment/render_integration.py +0 -4
  44. package/src/mcp_server_nucleus/hypervisor/__init__.py +0 -0
  45. package/src/mcp_server_nucleus/hypervisor/injector.py +0 -83
  46. package/src/mcp_server_nucleus/hypervisor/locker.py +0 -130
  47. package/src/mcp_server_nucleus/hypervisor/watchdog.py +0 -71
  48. package/src/mcp_server_nucleus/runtime/__init__.py +0 -58
  49. package/src/mcp_server_nucleus/runtime/agent.py +0 -517
  50. package/src/mcp_server_nucleus/runtime/agent_pool.py +0 -797
  51. package/src/mcp_server_nucleus/runtime/agents/base.py +0 -34
  52. package/src/mcp_server_nucleus/runtime/agents/oracle/manifest.json +0 -28
  53. package/src/mcp_server_nucleus/runtime/auth.py +0 -98
  54. package/src/mcp_server_nucleus/runtime/autopilot.py +0 -1057
  55. package/src/mcp_server_nucleus/runtime/broker.py +0 -126
  56. package/src/mcp_server_nucleus/runtime/budget.py +0 -153
  57. package/src/mcp_server_nucleus/runtime/capabilities/base.py +0 -20
  58. package/src/mcp_server_nucleus/runtime/capabilities/brain_ops.py +0 -253
  59. package/src/mcp_server_nucleus/runtime/capabilities/code_ops.py +0 -192
  60. package/src/mcp_server_nucleus/runtime/capabilities/depth_tracker.py +0 -81
  61. package/src/mcp_server_nucleus/runtime/capabilities/feature_map.py +0 -274
  62. package/src/mcp_server_nucleus/runtime/capabilities/marketing_engine.py +0 -205
  63. package/src/mcp_server_nucleus/runtime/capabilities/memory_ops.py +0 -79
  64. package/src/mcp_server_nucleus/runtime/capabilities/proof_system.py +0 -127
  65. package/src/mcp_server_nucleus/runtime/capabilities/render_ops.py +0 -80
  66. package/src/mcp_server_nucleus/runtime/capabilities/render_poller_cap.py +0 -266
  67. package/src/mcp_server_nucleus/runtime/capabilities/self_healing.py +0 -110
  68. package/src/mcp_server_nucleus/runtime/capabilities/strategy.py +0 -138
  69. package/src/mcp_server_nucleus/runtime/capabilities/synthesizer.py +0 -134
  70. package/src/mcp_server_nucleus/runtime/capabilities/web_ops.py +0 -94
  71. package/src/mcp_server_nucleus/runtime/common.py +0 -134
  72. package/src/mcp_server_nucleus/runtime/context_manager.py +0 -342
  73. package/src/mcp_server_nucleus/runtime/context_rules.json +0 -86
  74. package/src/mcp_server_nucleus/runtime/crdt_task_store.py +0 -349
  75. package/src/mcp_server_nucleus/runtime/critic.py +0 -150
  76. package/src/mcp_server_nucleus/runtime/daemon.py +0 -142
  77. package/src/mcp_server_nucleus/runtime/dashboard.py +0 -1087
  78. package/src/mcp_server_nucleus/runtime/depth_ops.py +0 -339
  79. package/src/mcp_server_nucleus/runtime/event_ops.py +0 -103
  80. package/src/mcp_server_nucleus/runtime/event_stream.py +0 -213
  81. package/src/mcp_server_nucleus/runtime/export.py +0 -64
  82. package/src/mcp_server_nucleus/runtime/factory.py +0 -594
  83. package/src/mcp_server_nucleus/runtime/federation.py +0 -1040
  84. package/src/mcp_server_nucleus/runtime/fetcher.py +0 -92
  85. package/src/mcp_server_nucleus/runtime/file_monitor.py +0 -208
  86. package/src/mcp_server_nucleus/runtime/firestore_bridge.py +0 -91
  87. package/src/mcp_server_nucleus/runtime/gcloud_ops.py +0 -186
  88. package/src/mcp_server_nucleus/runtime/hooks.py +0 -246
  89. package/src/mcp_server_nucleus/runtime/identity/gatekeeper.py +0 -106
  90. package/src/mcp_server_nucleus/runtime/identity/keygen.py +0 -147
  91. package/src/mcp_server_nucleus/runtime/identity/manifest.py +0 -62
  92. package/src/mcp_server_nucleus/runtime/identity/trust.py +0 -27
  93. package/src/mcp_server_nucleus/runtime/inspector.py +0 -74
  94. package/src/mcp_server_nucleus/runtime/installer.py +0 -81
  95. package/src/mcp_server_nucleus/runtime/ipc_auth.py +0 -377
  96. package/src/mcp_server_nucleus/runtime/lifecycle.py +0 -129
  97. package/src/mcp_server_nucleus/runtime/llm_client.py +0 -408
  98. package/src/mcp_server_nucleus/runtime/llm_tiers.json +0 -88
  99. package/src/mcp_server_nucleus/runtime/locking.py +0 -228
  100. package/src/mcp_server_nucleus/runtime/loops/fixer.py +0 -110
  101. package/src/mcp_server_nucleus/runtime/memory.py +0 -158
  102. package/src/mcp_server_nucleus/runtime/mounter.py +0 -210
  103. package/src/mcp_server_nucleus/runtime/mounter_ops.py +0 -250
  104. package/src/mcp_server_nucleus/runtime/nuke_protocol.py +0 -147
  105. package/src/mcp_server_nucleus/runtime/orchestrator.py +0 -370
  106. package/src/mcp_server_nucleus/runtime/orchestrator_unified.py +0 -263
  107. package/src/mcp_server_nucleus/runtime/orchestrator_v3.py +0 -617
  108. package/src/mcp_server_nucleus/runtime/plugin_loader.py +0 -84
  109. package/src/mcp_server_nucleus/runtime/policy.py +0 -117
  110. package/src/mcp_server_nucleus/runtime/profiling.py +0 -219
  111. package/src/mcp_server_nucleus/runtime/prometheus.py +0 -286
  112. package/src/mcp_server_nucleus/runtime/proposals.py +0 -101
  113. package/src/mcp_server_nucleus/runtime/publisher.py +0 -109
  114. package/src/mcp_server_nucleus/runtime/pulse.py +0 -516
  115. package/src/mcp_server_nucleus/runtime/registry.py +0 -82
  116. package/src/mcp_server_nucleus/runtime/render_ops.py +0 -83
  117. package/src/mcp_server_nucleus/runtime/schema_gen.py +0 -140
  118. package/src/mcp_server_nucleus/runtime/session_ops.py +0 -224
  119. package/src/mcp_server_nucleus/runtime/stdio_server.py +0 -633
  120. package/src/mcp_server_nucleus/runtime/storage.py +0 -83
  121. package/src/mcp_server_nucleus/runtime/strategy.py +0 -91
  122. package/src/mcp_server_nucleus/runtime/swarm.py +0 -170
  123. package/src/mcp_server_nucleus/runtime/sync_ops.py +0 -878
  124. package/src/mcp_server_nucleus/runtime/task_ingestion.py +0 -1020
  125. package/src/mcp_server_nucleus/runtime/task_ops.py +0 -449
  126. package/src/mcp_server_nucleus/runtime/task_scheduler.py +0 -488
  127. package/src/mcp_server_nucleus/runtime/team.py +0 -59
  128. package/src/mcp_server_nucleus/runtime/triggers.py +0 -237
  129. package/src/mcp_server_nucleus/runtime/vector_store.py +0 -125
  130. package/src/mcp_server_nucleus/runtime/watcher.py +0 -112
  131. package/src/mcp_server_nucleus/tool_tiers.py +0 -243
  132. package/tests/__init__.py +0 -1
  133. package/tests/conftest.py +0 -42
  134. package/tests/test_core.py +0 -235
  135. package/tests/test_search.py +0 -69
@@ -1,246 +0,0 @@
1
-
2
- """
3
- Strategic Hooks for Nucleus Daemon.
4
- The "Billion Dollar Extensions" for Network Effects, Identity, and Scale.
5
-
6
- Strategic Role:
7
- - Turn the Daemon into a Platform (App Store).
8
- - Turn the Daemon into a Global Node (Insight Exchange).
9
- - Deepen Host Integration (Intimacy).
10
- """
11
-
12
- import json
13
- import logging
14
- import time
15
- import uuid
16
- import hashlib
17
- from typing import List, Optional
18
- from dataclasses import dataclass, asdict
19
- from pathlib import Path
20
-
21
- # --- CRYPTO UPGRADE (Phase 60: Gates Fix) ---
22
- try:
23
- from cryptography.hazmat.primitives.asymmetric import ed25519
24
- from cryptography.hazmat.primitives import serialization
25
- CRYPTO_AVAILABLE = True
26
- except ImportError:
27
- CRYPTO_AVAILABLE = False
28
-
29
- logger = logging.getLogger(__name__)
30
-
31
- # --- 1. The App Store Hook (AgentManifest) ---
32
- @dataclass
33
- class AgentManifest:
34
- """Schema for a Tradeable Agent (The App Store Unit)."""
35
- id: str
36
- name: str
37
- version: str
38
- author_did: str # Decentralized ID of creator
39
- capabilities: List[str]
40
- lifecycle_state: str = "active" # active, archived, tombstone
41
- price_per_invocation_usd: float = 0.0
42
- hash: str = ""
43
- signature: str = ""
44
-
45
- def sign(self, identity_key: 'IdentityKey'):
46
- """Sign the manifest to prove authorship."""
47
- # Canonicalize the data
48
- payload = f"{self.id}:{self.version}:{self.author_did}:{self.lifecycle_state}"
49
- self.signature = identity_key.sign_message(payload)
50
- self.hash = hashlib.sha256(payload.encode()).hexdigest()
51
-
52
- # --- 2. The Insight Hook (InsightExchange) ---
53
- @dataclass
54
- class InsightPacket:
55
- """Schema for Tradeable Information (The Data Unit)."""
56
- topic: str
57
- content_hash: str
58
- value_score: float
59
- source_did: str
60
- timestamp: float
61
- proof: Optional[str] = None # ZK Proof of validity (Future)
62
-
63
- class InsightExchange:
64
- """Protocol for trading Insights between Daemons."""
65
- def __init__(self, brain_path: Path):
66
- self.ledger_path = brain_path / "network" / "insights.jsonl"
67
- self.ledger_path.parent.mkdir(parents=True, exist_ok=True)
68
-
69
- def offer_insight(self, topic: str, content: str, value: float) -> InsightPacket:
70
- """Create an offer to sell info."""
71
- packet = InsightPacket(
72
- topic=topic,
73
- content_hash=hashlib.sha256(content.encode()).hexdigest(),
74
- value_score=value,
75
- source_did="self", # In real impl, use IdentityKey
76
- timestamp=time.time()
77
- )
78
- # Log offers
79
- with open(self.ledger_path, "a") as f:
80
- f.write(json.dumps(asdict(packet)) + "\n")
81
- return packet
82
-
83
- # --- 3. The Grid Hook (RemoteExecutionProtocol) ---
84
- class RemoteExecutionProtocol:
85
- """Interface for offloading work to the Cloud (The Grid)."""
86
- def __init__(self, brain_path: Path):
87
- self.config_path = brain_path / "network" / "grid_config.json"
88
-
89
- def dispatch_container(self, image: str, cmd: List[str], max_budget_usd: float) -> str:
90
- """
91
- Stub: Dispatch a Docker container to a Grid Node (e.g. Cloud Run).
92
- Returns: Execution ID.
93
- """
94
- logger.info(f"🌩️ GRID DISPATCH: {image} (Budget: ${max_budget_usd})")
95
- # In real impl, this calls Google Cloud Run API or a P2P compute market
96
- return f"grid_exec_{uuid.uuid4()}"
97
-
98
- # --- 4. The Host Hook (HostIntimacy) ---
99
- class HostIntimacy:
100
- """
101
- Permission-aware OS Access.
102
- Managing the "Deep Binding" to the user's life (Files, Calendar, etc).
103
- """
104
- def __init__(self):
105
- self.permissions = {
106
- "filesystem": "read-write",
107
- "geolocation": "denied",
108
- "clipboard": "ask",
109
- "biometrics": "unavailable"
110
- }
111
-
112
- def check_permission(self, capability: str) -> bool:
113
- """Centralized permission check."""
114
- return self.permissions.get(capability, "denied") in ["allowed", "read-write", "ask"]
115
-
116
- # --- 5. The Identity Hook (IdentityKey) ---
117
- class IdentityKey:
118
- """
119
- Cryptographic Identity for the Sovereign Node (Ed25519).
120
- The "Soul" of the machine.
121
- """
122
- def __init__(self, brain_path: Path):
123
- self.key_dir = brain_path / "identity"
124
- self.key_dir.mkdir(parents=True, exist_ok=True)
125
- self.private_key_path = self.key_dir / "node.pem"
126
- self.public_key_path = self.key_dir / "node.pub"
127
-
128
- self.did = ""
129
- self._private_key = None
130
- self._load_or_create_identity()
131
-
132
- def _load_or_create_identity(self):
133
- if not CRYPTO_AVAILABLE:
134
- logger.warning("⚠️ 'cryptography' lib not found. Using UUID stub for Identity.")
135
- self.did = f"did:nucleus:stub:{uuid.uuid4()}"
136
- return
137
-
138
- if self.private_key_path.exists():
139
- try:
140
- with open(self.private_key_path, "rb") as f:
141
- self._private_key = serialization.load_pem_private_key(
142
- f.read(), password=None
143
- )
144
- logger.info("🔑 Loaded existing IdentityKey.")
145
- except Exception as e:
146
- logger.error(f"❌ Failed to load IdentityKey: {e}. Generating new one.")
147
- self._generate_new_key()
148
- else:
149
- self._generate_new_key()
150
-
151
- # Derive Public Key and DID
152
- if self._private_key:
153
- public_key = self._private_key.public_key()
154
- pub_bytes = public_key.public_bytes(
155
- encoding=serialization.Encoding.Raw,
156
- format=serialization.PublicFormat.Raw
157
- )
158
- # DID format: did:nucleus:<hex_pub_key>
159
- self.did = f"did:nucleus:{pub_bytes.hex()}"
160
-
161
- # Save public key for strict reference
162
- with open(self.public_key_path, "wb") as f:
163
- f.write(public_key.public_bytes(
164
- encoding=serialization.Encoding.PEM,
165
- format=serialization.PublicFormat.SubjectPublicKeyInfo
166
- ))
167
-
168
- def _generate_new_key(self):
169
- logger.info("✨ Generating new Ed25519 IdentityKey...")
170
- self._private_key = ed25519.Ed25519PrivateKey.generate()
171
-
172
- # Save Private Key
173
- pem = self._private_key.private_bytes(
174
- encoding=serialization.Encoding.PEM,
175
- format=serialization.PrivateFormat.PKCS8,
176
- encryption_algorithm=serialization.NoEncryption()
177
- )
178
- with open(self.private_key_path, "wb") as f:
179
- f.write(pem)
180
-
181
- def sign_message(self, message: str) -> str:
182
- """Sign a payload using Ed25519."""
183
- if not self._private_key:
184
- return f"stub_sig_{uuid.uuid4()}"
185
-
186
- points = message.encode("utf-8")
187
- signature = self._private_key.sign(points)
188
- return signature.hex()
189
-
190
- def verify_signature(self, message: str, signature_hex: str, did: str) -> bool:
191
- """Verify a signature against a DID (Public Key)."""
192
- if not CRYPTO_AVAILABLE or "stub" in did:
193
- return True # Fallback for dev/stub modes
194
-
195
- try:
196
- # Extract Public Key from DID
197
- if not did.startswith("did:nucleus:"):
198
- return False
199
- pub_key_hex = did.split(":")[-1]
200
- pub_key_bytes = bytes.fromhex(pub_key_hex)
201
-
202
- public_key = ed25519.Ed25519PublicKey.from_public_bytes(pub_key_bytes)
203
-
204
- signature_bytes = bytes.fromhex(signature_hex)
205
- public_key.verify(signature_bytes, message.encode("utf-8"))
206
- return True
207
- except Exception as e:
208
- logger.warning(f"⚠️ Signature Verification Failed: {e}")
209
- return False
210
-
211
- # --- 6. The Pulse (AmbientTelemetry) ---
212
- class AmbientTelemetry:
213
- """
214
- Writes status to a "Pulse File" for the HUD/Status Bar to read.
215
- Frequency: High (1s-5s).
216
-
217
- Security (V9 Patch): Uses IdentityKey to sign heartbeats, preventing IPC hijacking.
218
- """
219
- def __init__(self, brain_path: Path, identity: Optional['IdentityKey'] = None):
220
- self.pulse_file = brain_path / "pulse.json"
221
- self.identity = identity
222
-
223
- def beat(self, status: str, active_tasks: int, cpu_usage: float = 0.0):
224
- """Write the heartbeat with a cryptographic pulse signal."""
225
- timestamp = time.time()
226
- data = {
227
- "timestamp": timestamp,
228
- "status": status, # idle, busy, error, sleeping
229
- "tasks": active_tasks,
230
- "cpu": cpu_usage,
231
- "color": "green" if status == "idle" else "gold" if status == "busy" else "red"
232
- }
233
-
234
- # Add Trust Signal (Crypographic Pulse)
235
- if self.identity:
236
- payload = f"{timestamp}:{status}:{active_tasks}"
237
- data["pulse_sig"] = self.identity.sign_message(payload)
238
- data["did"] = self.identity.did
239
-
240
- # Atomic write
241
- try:
242
- temp_file = self.pulse_file.with_suffix(".tmp")
243
- temp_file.write_text(json.dumps(data))
244
- temp_file.replace(self.pulse_file)
245
- except Exception as e:
246
- logger.warning(f"Failed to pulse: {e}")
@@ -1,106 +0,0 @@
1
- """
2
- Gatekeeper: The Permission Broker.
3
- Manages user consent ("CapabilityGrants") for sensitive operations.
4
-
5
- Strategic Role:
6
- - PERSISTENT: Remembers what the user allowed.
7
- - GRANULAR: "Network: google.com" != "Network: *".
8
- - INTERACTIVE: Bridges the gap between Agent Request and User Decision.
9
- """
10
-
11
- import json
12
- import logging
13
- import hashlib
14
- from pathlib import Path
15
- from typing import Dict, Any
16
- from pydantic import BaseModel
17
-
18
- logger = logging.getLogger("GATEKEEPER")
19
-
20
- ACCESS_DENIED = False
21
- ACCESS_GRANTED = True
22
-
23
- class GrantRequest(BaseModel):
24
- agent_id: str
25
- capability: str
26
- params: Dict[str, Any] # e.g. {"domain": "google.com"} or {"path": "/tmp"}
27
-
28
- def fingerprint(self) -> str:
29
- """Unique hash for this request signature"""
30
- # Canonicalize params
31
- import json
32
- param_str = json.dumps(self.params, sort_keys=True)
33
- raw = f"{self.agent_id}|{self.capability}|{param_str}"
34
- return hashlib.sha256(raw.encode()).hexdigest()
35
-
36
- class CapabilityGrant(BaseModel):
37
- request_fingerprint: str
38
- agent_id: str
39
- capability: str
40
- params: Dict[str, Any]
41
- granted_at: str # ISO Timestamp
42
- granted_by: str = "user"
43
-
44
- class Gatekeeper:
45
- def __init__(self, brain_path: Path):
46
- self.brain_path = brain_path
47
- self.ledger_path = brain_path / "ledger" / "permissions.json"
48
- self._cache_grants: Dict[str, CapabilityGrant] = {}
49
- self._load_ledger()
50
-
51
- def _load_ledger(self):
52
- self.ledger_path.parent.mkdir(parents=True, exist_ok=True)
53
- if self.ledger_path.exists():
54
- try:
55
- data = json.loads(self.ledger_path.read_text())
56
- for item in data:
57
- grant = CapabilityGrant(**item)
58
- self._cache_grants[grant.request_fingerprint] = grant
59
- except Exception as e:
60
- logger.error(f"Failed to load permission ledger: {e}")
61
-
62
- def _save_ledger(self):
63
- data = [g.model_dump() for g in self._cache_grants.values()]
64
- self.ledger_path.write_text(json.dumps(data, indent=2))
65
-
66
- def check_permission(self, request: GrantRequest) -> bool:
67
- """
68
- Check if a grant exists for this specific request.
69
- """
70
- fp = request.fingerprint()
71
- if fp in self._cache_grants:
72
- return ACCESS_GRANTED
73
-
74
- # TODO: Implement wildcard support?
75
- # For Phase 57, we do strict equality.
76
-
77
- return ACCESS_DENIED
78
-
79
- def grant_permission(self, request: GrantRequest):
80
- """
81
- Persist a new grant.
82
- """
83
- from datetime import datetime
84
- fp = request.fingerprint()
85
-
86
- grant = CapabilityGrant(
87
- request_fingerprint=fp,
88
- agent_id=request.agent_id,
89
- capability=request.capability,
90
- params=request.params,
91
- granted_at=datetime.now().isoformat()
92
- )
93
-
94
- self._cache_grants[fp] = grant
95
- self._save_ledger()
96
- logger.info(f"Granted permission: {request.agent_id} -> {request.capability} {request.params}")
97
-
98
- def revoke_permission(self, request: GrantRequest):
99
- """
100
- Revoke a grant.
101
- """
102
- fp = request.fingerprint()
103
- if fp in self._cache_grants:
104
- del self._cache_grants[fp]
105
- self._save_ledger()
106
- logger.info(f"Revoked permission: {request.agent_id}")
@@ -1,147 +0,0 @@
1
- import hashlib
2
- import json
3
- import logging
4
- from datetime import datetime, timezone
5
- from pathlib import Path
6
- from typing import NamedTuple, Optional, Dict
7
-
8
- try:
9
- from cryptography.hazmat.primitives import serialization
10
- from cryptography.hazmat.primitives.asymmetric import ed25519
11
- HAS_CRYPTO = True
12
- except ImportError:
13
- HAS_CRYPTO = False
14
-
15
- logger = logging.getLogger("KEY_MANAGER")
16
-
17
- class KeyPair(NamedTuple):
18
- key_id: str
19
- private_key_pem: str
20
- public_key_pem: str
21
-
22
- class KeyManager:
23
- """
24
- Manages Ed25519 Key Generation, Verification, and Persistence for Nucleus.
25
- """
26
- def __init__(self, brain_path: Optional[Path] = None):
27
- """
28
- Args:
29
- brain_path: Optional root path for key storage. If None, runs in stateless mode.
30
- """
31
- if not HAS_CRYPTO:
32
- raise ImportError("cryptography module is required. Install with: pip install cryptography")
33
-
34
- self.brain_path = brain_path
35
- if self.brain_path:
36
- self.keystore_path = self.brain_path / "ledger" / "keystore.json"
37
- self.keystore_path.parent.mkdir(parents=True, exist_ok=True)
38
-
39
- def _load_keystore(self) -> Dict[str, Dict[str, str]]:
40
- if not self.brain_path or not self.keystore_path.exists():
41
- return {}
42
- try:
43
- return json.loads(self.keystore_path.read_text())
44
- except Exception:
45
- return {}
46
-
47
- def _save_keystore(self, store: Dict):
48
- if self.brain_path:
49
- # We don't have locking here yet, assume single user or add locking later
50
- # Ideally use update_brain_file or lock
51
- self.keystore_path.write_text(json.dumps(store, indent=2))
52
-
53
- def generate_key(self, alias: Optional[str] = None) -> str:
54
- """
55
- Generates, Persists, and returns Key ID.
56
- Args:
57
- alias: Optional human readable alias (e.g. 'publisher_main')
58
- """
59
- kp = self.generate_keypair()
60
-
61
- if self.brain_path:
62
- store = self._load_keystore()
63
- store[kp.key_id] = {
64
- "private_key_pem": kp.private_key_pem,
65
- "public_key_pem": kp.public_key_pem,
66
- "alias": alias,
67
- "created_at": datetime.now(timezone.utc).isoformat()
68
- }
69
- if alias:
70
- # Also index by alias for convenience?
71
- # Or just search. Let's just store by ID for now.
72
- pass
73
- self._save_keystore(store)
74
-
75
- return kp.key_id
76
-
77
- def get_key_pair(self, key_id: str) -> Optional[KeyPair]:
78
- """Retrieve key pair from disk by ID."""
79
- if not self.brain_path:
80
- return None
81
-
82
- store = self._load_keystore()
83
- if key_id in store:
84
- data = store[key_id]
85
- return KeyPair(
86
- key_id=key_id,
87
- private_key_pem=data["private_key_pem"],
88
- public_key_pem=data["public_key_pem"]
89
- )
90
- return None
91
-
92
- def generate_keypair(self) -> KeyPair:
93
- """Generates a new Ed25519 keypair and returns PEM strings (Stateless)."""
94
- private_key = ed25519.Ed25519PrivateKey.generate()
95
- public_key = private_key.public_key()
96
-
97
- # Serialize Private Key
98
- private_pem = private_key.private_bytes(
99
- encoding=serialization.Encoding.PEM,
100
- format=serialization.PrivateFormat.PKCS8,
101
- encryption_algorithm=serialization.NoEncryption()
102
- )
103
-
104
- # Serialize Public Key
105
- public_pem = public_key.public_bytes(
106
- encoding=serialization.Encoding.PEM,
107
- format=serialization.PublicFormat.SubjectPublicKeyInfo
108
- )
109
-
110
- # Generate Key ID (Fingerprint of Public Key)
111
- public_der = public_key.public_bytes(
112
- encoding=serialization.Encoding.DER,
113
- format=serialization.PublicFormat.SubjectPublicKeyInfo
114
- )
115
- key_id = hashlib.sha256(public_der).hexdigest()
116
-
117
- return KeyPair(
118
- key_id=key_id,
119
- private_key_pem=private_pem.decode('utf-8'),
120
- public_key_pem=public_pem.decode('utf-8')
121
- )
122
-
123
- def sign(self, private_key_pem: str, data: bytes) -> bytes:
124
- """Signs data with private key PEM. Returns raw bytes signature."""
125
- private_key = serialization.load_pem_private_key(
126
- private_key_pem.encode('utf-8'),
127
- password=None
128
- )
129
- if not isinstance(private_key, ed25519.Ed25519PrivateKey):
130
- raise ValueError("Invalid Key Type. Expected Ed25519PrivateKey.")
131
-
132
- return private_key.sign(data)
133
-
134
- def verify(self, public_key_pem: str, signature: bytes, data: bytes) -> bool:
135
- """Verifies signature with public key PEM. Returns True if valid."""
136
- try:
137
- public_key = serialization.load_pem_public_key(
138
- public_key_pem.encode('utf-8')
139
- )
140
- if not isinstance(public_key, ed25519.Ed25519PublicKey):
141
- raise ValueError("Invalid Key Type. Expected Ed25519PublicKey.")
142
-
143
- public_key.verify(signature, data)
144
- return True
145
- except Exception as e:
146
- logger.warning(f"Verification Failed: {e}")
147
- return False
@@ -1,62 +0,0 @@
1
- from enum import Enum
2
- from pydantic import BaseModel, Field, validator, ValidationError
3
- from typing import List, Optional, Literal, Dict, Any
4
-
5
- class CapabilityScope(str, Enum):
6
- NETWORK = "network"
7
- FILESYSTEM = "filesystem"
8
- MEMORY = "memory"
9
- SHELL = "shell"
10
- STRATEGY = "strategy"
11
- BROWSER = "browser"
12
-
13
- class Capability(BaseModel):
14
- scope: CapabilityScope
15
- reason: str = Field(..., description="Why this capability is needed")
16
-
17
- # Scope-specific fields
18
- domains: Optional[List[str]] = Field(None, description="For network scope: Whitelisted domains")
19
- paths: Optional[List[str]] = Field(None, description="For filesystem/strategy scope: Allowed paths")
20
- mode: Optional[Literal["read", "write", "readwrite", "read_write"]] = Field(None, description="For filesystem/strategy scope: Access mode")
21
-
22
- @validator("domains")
23
- def validate_network_domains(cls, v, values):
24
- if values.get("scope") == CapabilityScope.NETWORK and not v:
25
- raise ValueError("Network capability must specify 'domains' whitelist.")
26
- return v
27
-
28
- @validator("paths")
29
- def validate_fs_paths(cls, v, values):
30
- scope = values.get("scope")
31
- if scope in [CapabilityScope.FILESYSTEM, CapabilityScope.STRATEGY] and not v:
32
- raise ValueError(f"{scope} capability must specify 'paths' whitelist.")
33
- return v
34
-
35
- class AgentIdentity(BaseModel):
36
- id: str = Field(..., pattern=r"^[a-z0-9]+(\.[a-z0-9]+)+$", description="Reverse DNS ID (e.g. nucleus.core.ops)")
37
- name: str
38
- version: str = Field(..., pattern=r"^\d+\.\d+\.\d+$", description="Semantic Version")
39
- description: str
40
- author: str
41
- license: str
42
-
43
- class LifecyclePolicy(BaseModel):
44
- persistence: Literal["session", "persistent", "immortal"] = "session"
45
- cleanup: Literal["strict", "lazy", "none"] = "strict"
46
-
47
- class AgentManifest(BaseModel):
48
- manifest_version: str = "1.0.0"
49
- agent: AgentIdentity
50
- capabilities: List[Capability] = Field(default_factory=list)
51
- lifecycle: LifecyclePolicy = Field(default_factory=LifecyclePolicy)
52
-
53
- class ManifestValidator:
54
- """
55
- Validates Agent Manifests against the strict zero-trust schema.
56
- """
57
- @staticmethod
58
- def validate(manifest_data: Dict[str, Any]) -> AgentManifest:
59
- try:
60
- return AgentManifest(**manifest_data)
61
- except ValidationError as e:
62
- raise ValueError(f"Manifest Validation Failed: {e}")
@@ -1,27 +0,0 @@
1
- from enum import Enum
2
- from pydantic import BaseModel, Field
3
- from typing import Optional
4
-
5
- class TrustLevel(str, Enum):
6
- VERIFIED = "verified" # Signed by Nucleus Root or known partner
7
- COMMUNITY = "community" # Signed by unknown key, but structure valid
8
- UNKNOWN = "unknown" # Unsigned or invalid
9
- MALICIOUS = "malicious" # Specific deny-list
10
-
11
- class TrustProfile(BaseModel):
12
- """
13
- Represents the Identity of an Agent Publisher.
14
- This corresponds to the 'signer' of the .nuke artifact.
15
- """
16
- publisher_id: str = Field(..., description="Unique Key ID (Fingerprint)")
17
- trust_level: TrustLevel = Field(default=TrustLevel.UNKNOWN, description="Current trust status")
18
- label: str = Field(..., description="Human readable name (e.g., 'Antigravity')")
19
- public_key: str = Field(..., description="PEM encoded Public Key")
20
- verification_url: Optional[str] = Field(None, description="URL to verify identity (e.g. GitHub Gist)")
21
-
22
- def to_json(self) -> str:
23
- return self.model_dump_json(indent=2)
24
-
25
- @classmethod
26
- def from_json(cls, json_str: str) -> "TrustProfile":
27
- return cls.model_validate_json(json_str)
@@ -1,74 +0,0 @@
1
- """
2
- Inspector: The Lens.
3
- Provides deep introspection into Agent Manifests for security review.
4
-
5
- Role:
6
- - Renders human-readable reports of what an agent can do.
7
- - Highlights high-risk capabilities (Network, Shell, FS).
8
- - Verifies Trust Chains (Future scope).
9
- """
10
-
11
- from .identity.manifest import AgentManifest, CapabilityScope
12
-
13
- class ManifestViewer:
14
- @staticmethod
15
- def render_report(manifest: AgentManifest) -> str:
16
- """
17
- Generates a formatted text report of the agent's identity and capabilities.
18
- """
19
- agent = manifest.agent
20
- lines = []
21
-
22
- # Header
23
- lines.append("╔═══════════════════════════════════════════════════════════╗")
24
- lines.append(f"║ IDENTITY: {agent.name:<47} ║")
25
- lines.append(f"║ ID: {agent.id:<47} ║")
26
- lines.append(f"║ Version: {agent.version:<47} ║")
27
- lines.append(f"║ License: {agent.license:<47} ║")
28
- lines.append("╟───────────────────────────────────────────────────────────╢")
29
-
30
- # Description
31
- desc_preview = (agent.description[:50] + '..') if len(agent.description) > 50 else agent.description
32
- lines.append(f"║ {desc_preview:<57} ║")
33
- lines.append("╠═══════════════════════════════════════════════════════════╣")
34
-
35
- # Capabilities
36
- if not manifest.capabilities:
37
- lines.append("║ ✅ No Capabilities Requested (Safe) ║")
38
- else:
39
- lines.append("║ ⚠️ CAPABILITIES REQUESTED ║")
40
- lines.append("╟───────────────────────────────────────────────────────────╢")
41
-
42
- for cap in manifest.capabilities:
43
- scope = cap.scope.value.upper()
44
- icon = ManifestViewer._get_icon(cap.scope)
45
-
46
- lines.append(f"║ {icon} {scope:<53} ║")
47
- lines.append(f"║ Reason: {cap.reason[:44]:<44} ║")
48
-
49
- # Details
50
- if cap.scope == CapabilityScope.NETWORK and cap.domains:
51
- lines.append(f"║ Domains: {', '.join(cap.domains)[:43]:<43} ║")
52
-
53
- if cap.scope == CapabilityScope.FILESYSTEM and cap.paths:
54
- mode = getattr(cap, 'mode', 'read')
55
- lines.append(f"║ [{mode.upper()}] Paths: {', '.join(cap.paths)[:38]:<38} ║")
56
-
57
- lines.append("╟───────────────────────────────────────────────────────────╢")
58
-
59
- lines.append("╚═══════════════════════════════════════════════════════════╝")
60
- return "\n".join(lines)
61
-
62
- @staticmethod
63
- def _get_icon(scope: CapabilityScope) -> str:
64
- if scope == CapabilityScope.NETWORK:
65
- return "⚠️ "
66
- if scope == CapabilityScope.SHELL:
67
- return "🚨"
68
- if scope == CapabilityScope.FILESYSTEM:
69
- return "📁"
70
- if scope == CapabilityScope.MEMORY:
71
- return "🧠"
72
- if scope == CapabilityScope.BROWSER:
73
- return "🌐"
74
- return "🔧"