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.
- package/package.json +2 -4
- package/.github/workflows/ci.yml +0 -72
- package/.smithery/shttp/module.js +0 -3
- package/.smithery/shttp/module.js.map +0 -7
- package/.smithery/stdio/index.cjs +0 -74
- package/.smithery/stdio/index.cjs.map +0 -7
- package/CHANGELOG.md +0 -65
- package/CODE_OF_CONDUCT.md +0 -34
- package/COMPARISON.md +0 -20
- package/CONTRIBUTING.md +0 -137
- package/DEVELOPMENT.md +0 -109
- package/LICENSE +0 -21
- package/README.md +0 -319
- package/ROADMAP.md +0 -24
- package/SECURITY.md +0 -88
- package/docs/COMMUNITY_FAQ.md +0 -48
- package/docs/COMPETITIVE_ANALYSIS.md +0 -90
- package/docs/DEMO_SCRIPT.md +0 -86
- package/docs/ENTERPRISE.md +0 -169
- package/docs/FAQ.md +0 -228
- package/docs/QUICK_START.md +0 -155
- package/docs/README.md +0 -67
- package/docs/RECURSIVE_SNAP_DEMO.gif +0 -0
- package/docs/social-preview-hq.png +0 -0
- package/docs/social-preview.png +0 -0
- package/examples/basic_usage.py +0 -84
- package/package.json.real +0 -24
- package/pyproject.toml +0 -50
- package/registry-server.json +0 -20
- package/scripts/demo_governance.py +0 -109
- package/scripts/sse_bridge.py +0 -87
- package/scripts/verify_windows_paths.py +0 -48
- package/server.json +0 -20
- package/src/mcp_server_nucleus/__init__.py +0 -8583
- package/src/mcp_server_nucleus/__main__.py +0 -9
- package/src/mcp_server_nucleus/cli.py +0 -1225
- package/src/mcp_server_nucleus/commitment_core.py +0 -146
- package/src/mcp_server_nucleus/commitment_ledger.py +0 -841
- package/src/mcp_server_nucleus/core/__init__.py +0 -6
- package/src/mcp_server_nucleus/core/orchestrator.py +0 -9
- package/src/mcp_server_nucleus/core/tool_registration_impl.py +0 -99
- package/src/mcp_server_nucleus/daemon/safety/lock.py +0 -121
- package/src/mcp_server_nucleus/deployment/render_integration.py +0 -4
- package/src/mcp_server_nucleus/hypervisor/__init__.py +0 -0
- package/src/mcp_server_nucleus/hypervisor/injector.py +0 -83
- package/src/mcp_server_nucleus/hypervisor/locker.py +0 -130
- package/src/mcp_server_nucleus/hypervisor/watchdog.py +0 -71
- package/src/mcp_server_nucleus/runtime/__init__.py +0 -58
- package/src/mcp_server_nucleus/runtime/agent.py +0 -517
- package/src/mcp_server_nucleus/runtime/agent_pool.py +0 -797
- package/src/mcp_server_nucleus/runtime/agents/base.py +0 -34
- package/src/mcp_server_nucleus/runtime/agents/oracle/manifest.json +0 -28
- package/src/mcp_server_nucleus/runtime/auth.py +0 -98
- package/src/mcp_server_nucleus/runtime/autopilot.py +0 -1057
- package/src/mcp_server_nucleus/runtime/broker.py +0 -126
- package/src/mcp_server_nucleus/runtime/budget.py +0 -153
- package/src/mcp_server_nucleus/runtime/capabilities/base.py +0 -20
- package/src/mcp_server_nucleus/runtime/capabilities/brain_ops.py +0 -253
- package/src/mcp_server_nucleus/runtime/capabilities/code_ops.py +0 -192
- package/src/mcp_server_nucleus/runtime/capabilities/depth_tracker.py +0 -81
- package/src/mcp_server_nucleus/runtime/capabilities/feature_map.py +0 -274
- package/src/mcp_server_nucleus/runtime/capabilities/marketing_engine.py +0 -205
- package/src/mcp_server_nucleus/runtime/capabilities/memory_ops.py +0 -79
- package/src/mcp_server_nucleus/runtime/capabilities/proof_system.py +0 -127
- package/src/mcp_server_nucleus/runtime/capabilities/render_ops.py +0 -80
- package/src/mcp_server_nucleus/runtime/capabilities/render_poller_cap.py +0 -266
- package/src/mcp_server_nucleus/runtime/capabilities/self_healing.py +0 -110
- package/src/mcp_server_nucleus/runtime/capabilities/strategy.py +0 -138
- package/src/mcp_server_nucleus/runtime/capabilities/synthesizer.py +0 -134
- package/src/mcp_server_nucleus/runtime/capabilities/web_ops.py +0 -94
- package/src/mcp_server_nucleus/runtime/common.py +0 -134
- package/src/mcp_server_nucleus/runtime/context_manager.py +0 -342
- package/src/mcp_server_nucleus/runtime/context_rules.json +0 -86
- package/src/mcp_server_nucleus/runtime/crdt_task_store.py +0 -349
- package/src/mcp_server_nucleus/runtime/critic.py +0 -150
- package/src/mcp_server_nucleus/runtime/daemon.py +0 -142
- package/src/mcp_server_nucleus/runtime/dashboard.py +0 -1087
- package/src/mcp_server_nucleus/runtime/depth_ops.py +0 -339
- package/src/mcp_server_nucleus/runtime/event_ops.py +0 -103
- package/src/mcp_server_nucleus/runtime/event_stream.py +0 -213
- package/src/mcp_server_nucleus/runtime/export.py +0 -64
- package/src/mcp_server_nucleus/runtime/factory.py +0 -594
- package/src/mcp_server_nucleus/runtime/federation.py +0 -1040
- package/src/mcp_server_nucleus/runtime/fetcher.py +0 -92
- package/src/mcp_server_nucleus/runtime/file_monitor.py +0 -208
- package/src/mcp_server_nucleus/runtime/firestore_bridge.py +0 -91
- package/src/mcp_server_nucleus/runtime/gcloud_ops.py +0 -186
- package/src/mcp_server_nucleus/runtime/hooks.py +0 -246
- package/src/mcp_server_nucleus/runtime/identity/gatekeeper.py +0 -106
- package/src/mcp_server_nucleus/runtime/identity/keygen.py +0 -147
- package/src/mcp_server_nucleus/runtime/identity/manifest.py +0 -62
- package/src/mcp_server_nucleus/runtime/identity/trust.py +0 -27
- package/src/mcp_server_nucleus/runtime/inspector.py +0 -74
- package/src/mcp_server_nucleus/runtime/installer.py +0 -81
- package/src/mcp_server_nucleus/runtime/ipc_auth.py +0 -377
- package/src/mcp_server_nucleus/runtime/lifecycle.py +0 -129
- package/src/mcp_server_nucleus/runtime/llm_client.py +0 -408
- package/src/mcp_server_nucleus/runtime/llm_tiers.json +0 -88
- package/src/mcp_server_nucleus/runtime/locking.py +0 -228
- package/src/mcp_server_nucleus/runtime/loops/fixer.py +0 -110
- package/src/mcp_server_nucleus/runtime/memory.py +0 -158
- package/src/mcp_server_nucleus/runtime/mounter.py +0 -210
- package/src/mcp_server_nucleus/runtime/mounter_ops.py +0 -250
- package/src/mcp_server_nucleus/runtime/nuke_protocol.py +0 -147
- package/src/mcp_server_nucleus/runtime/orchestrator.py +0 -370
- package/src/mcp_server_nucleus/runtime/orchestrator_unified.py +0 -263
- package/src/mcp_server_nucleus/runtime/orchestrator_v3.py +0 -617
- package/src/mcp_server_nucleus/runtime/plugin_loader.py +0 -84
- package/src/mcp_server_nucleus/runtime/policy.py +0 -117
- package/src/mcp_server_nucleus/runtime/profiling.py +0 -219
- package/src/mcp_server_nucleus/runtime/prometheus.py +0 -286
- package/src/mcp_server_nucleus/runtime/proposals.py +0 -101
- package/src/mcp_server_nucleus/runtime/publisher.py +0 -109
- package/src/mcp_server_nucleus/runtime/pulse.py +0 -516
- package/src/mcp_server_nucleus/runtime/registry.py +0 -82
- package/src/mcp_server_nucleus/runtime/render_ops.py +0 -83
- package/src/mcp_server_nucleus/runtime/schema_gen.py +0 -140
- package/src/mcp_server_nucleus/runtime/session_ops.py +0 -224
- package/src/mcp_server_nucleus/runtime/stdio_server.py +0 -633
- package/src/mcp_server_nucleus/runtime/storage.py +0 -83
- package/src/mcp_server_nucleus/runtime/strategy.py +0 -91
- package/src/mcp_server_nucleus/runtime/swarm.py +0 -170
- package/src/mcp_server_nucleus/runtime/sync_ops.py +0 -878
- package/src/mcp_server_nucleus/runtime/task_ingestion.py +0 -1020
- package/src/mcp_server_nucleus/runtime/task_ops.py +0 -449
- package/src/mcp_server_nucleus/runtime/task_scheduler.py +0 -488
- package/src/mcp_server_nucleus/runtime/team.py +0 -59
- package/src/mcp_server_nucleus/runtime/triggers.py +0 -237
- package/src/mcp_server_nucleus/runtime/vector_store.py +0 -125
- package/src/mcp_server_nucleus/runtime/watcher.py +0 -112
- package/src/mcp_server_nucleus/tool_tiers.py +0 -243
- package/tests/__init__.py +0 -1
- package/tests/conftest.py +0 -42
- package/tests/test_core.py +0 -235
- package/tests/test_search.py +0 -69
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
GitFetcher: The Supply Line.
|
|
3
|
-
Securely clones and checks out specific commits of Agent Repositories.
|
|
4
|
-
|
|
5
|
-
Strategic Role:
|
|
6
|
-
- "The Truck": Moves bytes from GitHub to Nucleus.
|
|
7
|
-
- "Immutable": Always checks out a specific hash (The Stamp).
|
|
8
|
-
- "Secure": Validates URLs and prevents command injection.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import logging
|
|
12
|
-
import shutil
|
|
13
|
-
import subprocess
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
|
|
16
|
-
logger = logging.getLogger("GIT_FETCHER")
|
|
17
|
-
|
|
18
|
-
class GitFetcher:
|
|
19
|
-
def __init__(self):
|
|
20
|
-
pass
|
|
21
|
-
|
|
22
|
-
def fetch(self, url: str, destination: Path, commit_hash: str) -> Path:
|
|
23
|
-
"""
|
|
24
|
-
Clones a repo to destination and checks out a specific commit.
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
url: Git URL (https://github.com/... or file://...)
|
|
28
|
-
destination: Local path to install to.
|
|
29
|
-
commit_hash: The SHA-1 hash to checkout.
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
Path to the checked out directory (destination).
|
|
33
|
-
"""
|
|
34
|
-
destination = Path(destination)
|
|
35
|
-
|
|
36
|
-
logger.info(f"🚚 Fetching {url} -> {destination} @ {commit_hash[:7]}")
|
|
37
|
-
|
|
38
|
-
# 1. Cleanup target if exists
|
|
39
|
-
if destination.exists():
|
|
40
|
-
shutil.rmtree(destination)
|
|
41
|
-
destination.mkdir(parents=True)
|
|
42
|
-
|
|
43
|
-
try:
|
|
44
|
-
# 2. Clone (No checkout initially for speed/safety)
|
|
45
|
-
# We use subprocess directly to avoid external lib dependencies like GitPython
|
|
46
|
-
# for this core component.
|
|
47
|
-
|
|
48
|
-
# Note: We clone to a temp folder inside destination or directly?
|
|
49
|
-
# Git won't clone into non-empty dir usually.
|
|
50
|
-
# But we just cleared it.
|
|
51
|
-
|
|
52
|
-
subprocess.check_call(
|
|
53
|
-
["git", "clone", "--no-checkout", url, "."],
|
|
54
|
-
cwd=destination,
|
|
55
|
-
stdout=subprocess.DEVNULL,
|
|
56
|
-
stderr=subprocess.PIPE
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
# 3. Checkout Specific Hash
|
|
60
|
-
subprocess.check_call(
|
|
61
|
-
["git", "checkout", commit_hash],
|
|
62
|
-
cwd=destination,
|
|
63
|
-
stdout=subprocess.DEVNULL,
|
|
64
|
-
stderr=subprocess.PIPE
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
# 4. Verify Head (Paranoid Check)
|
|
68
|
-
current_hash = subprocess.check_output(
|
|
69
|
-
["git", "rev-parse", "HEAD"],
|
|
70
|
-
cwd=destination
|
|
71
|
-
).decode().strip()
|
|
72
|
-
|
|
73
|
-
if current_hash != commit_hash:
|
|
74
|
-
raise ValueError(f"Hash Mismatch! Requested {commit_hash}, got {current_hash}")
|
|
75
|
-
|
|
76
|
-
# 5. Cleanup .git folder?
|
|
77
|
-
# Phase 57 Spec says "The Stamp" is the commit hash.
|
|
78
|
-
# We might want to keep .git for provenance or remove it for "frozen" artifact.
|
|
79
|
-
# NukePacker wraps the RESULT. So this is just a staging step.
|
|
80
|
-
# Let's clean up .git to ensure it's a pure artifact source.
|
|
81
|
-
shutil.rmtree(destination / ".git")
|
|
82
|
-
|
|
83
|
-
logger.info("✅ Fetch & Checkout Verified.")
|
|
84
|
-
return destination
|
|
85
|
-
|
|
86
|
-
except subprocess.CalledProcessError as e:
|
|
87
|
-
# Capture stderr for better error
|
|
88
|
-
raise RuntimeError(f"Git Command Failed: {e}")
|
|
89
|
-
except Exception as e:
|
|
90
|
-
if destination.exists():
|
|
91
|
-
shutil.rmtree(destination)
|
|
92
|
-
raise e
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
File Monitor - Phase 50 Implementation
|
|
3
|
-
======================================
|
|
4
|
-
Watches the .brain folder for changes and emits events.
|
|
5
|
-
|
|
6
|
-
This is the core of "Native Sync" (Tiers 1-3 in NUCLEUS_PRODUCT_SPECS.md).
|
|
7
|
-
When a file changes, we notify all connected MCP clients.
|
|
8
|
-
|
|
9
|
-
Usage:
|
|
10
|
-
monitor = FileMonitor(brain_path)
|
|
11
|
-
monitor.start()
|
|
12
|
-
# ... later
|
|
13
|
-
monitor.stop()
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
import os
|
|
17
|
-
import threading
|
|
18
|
-
import logging
|
|
19
|
-
from pathlib import Path
|
|
20
|
-
from typing import Callable, Optional, List
|
|
21
|
-
from dataclasses import dataclass, field
|
|
22
|
-
from datetime import datetime
|
|
23
|
-
|
|
24
|
-
try:
|
|
25
|
-
from watchdog.observers import Observer
|
|
26
|
-
from watchdog.events import FileSystemEventHandler, FileSystemEvent
|
|
27
|
-
WATCHDOG_AVAILABLE = True
|
|
28
|
-
except ImportError:
|
|
29
|
-
WATCHDOG_AVAILABLE = False
|
|
30
|
-
Observer = None
|
|
31
|
-
FileSystemEventHandler = object
|
|
32
|
-
FileSystemEvent = None
|
|
33
|
-
|
|
34
|
-
logger = logging.getLogger("nucleus.file_monitor")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@dataclass
|
|
38
|
-
class FileChangeEvent:
|
|
39
|
-
"""Represents a file change in the brain folder."""
|
|
40
|
-
event_type: str # created, modified, deleted, moved
|
|
41
|
-
path: str
|
|
42
|
-
timestamp: datetime = field(default_factory=datetime.now)
|
|
43
|
-
is_directory: bool = False
|
|
44
|
-
|
|
45
|
-
def to_dict(self) -> dict:
|
|
46
|
-
return {
|
|
47
|
-
"event_type": self.event_type,
|
|
48
|
-
"path": self.path,
|
|
49
|
-
"timestamp": self.timestamp.isoformat(),
|
|
50
|
-
"is_directory": self.is_directory
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class BrainEventHandler(FileSystemEventHandler):
|
|
55
|
-
"""Handles file system events in the .brain folder."""
|
|
56
|
-
|
|
57
|
-
def __init__(self, callback: Callable[[FileChangeEvent], None]):
|
|
58
|
-
self.callback = callback
|
|
59
|
-
self._debounce_cache: dict = {}
|
|
60
|
-
self._debounce_ms = 100 # Ignore duplicate events within 100ms
|
|
61
|
-
|
|
62
|
-
def _should_ignore(self, path: str) -> bool:
|
|
63
|
-
"""Ignore temporary files, caches, and hidden files."""
|
|
64
|
-
basename = os.path.basename(path)
|
|
65
|
-
ignored_patterns = [
|
|
66
|
-
'.DS_Store',
|
|
67
|
-
'__pycache__',
|
|
68
|
-
'.git',
|
|
69
|
-
'*.pyc',
|
|
70
|
-
'*.swp',
|
|
71
|
-
'*.tmp',
|
|
72
|
-
'.resolved.', # Archive backup files
|
|
73
|
-
]
|
|
74
|
-
for pattern in ignored_patterns:
|
|
75
|
-
if pattern.startswith('*'):
|
|
76
|
-
if basename.endswith(pattern[1:]):
|
|
77
|
-
return True
|
|
78
|
-
elif pattern in basename:
|
|
79
|
-
return True
|
|
80
|
-
return False
|
|
81
|
-
|
|
82
|
-
def _debounce(self, path: str) -> bool:
|
|
83
|
-
"""Return True if we should process this event (not debounced)."""
|
|
84
|
-
now = datetime.now()
|
|
85
|
-
key = path
|
|
86
|
-
if key in self._debounce_cache:
|
|
87
|
-
last_time = self._debounce_cache[key]
|
|
88
|
-
if (now - last_time).total_seconds() * 1000 < self._debounce_ms:
|
|
89
|
-
return False
|
|
90
|
-
self._debounce_cache[key] = now
|
|
91
|
-
return True
|
|
92
|
-
|
|
93
|
-
def _emit(self, event_type: str, event: FileSystemEvent):
|
|
94
|
-
"""Emit a file change event."""
|
|
95
|
-
if self._should_ignore(event.src_path):
|
|
96
|
-
return
|
|
97
|
-
if not self._debounce(event.src_path):
|
|
98
|
-
return
|
|
99
|
-
|
|
100
|
-
change = FileChangeEvent(
|
|
101
|
-
event_type=event_type,
|
|
102
|
-
path=event.src_path,
|
|
103
|
-
is_directory=event.is_directory
|
|
104
|
-
)
|
|
105
|
-
logger.debug(f"File change detected: {change.event_type} -> {change.path}")
|
|
106
|
-
self.callback(change)
|
|
107
|
-
|
|
108
|
-
def on_created(self, event):
|
|
109
|
-
self._emit("created", event)
|
|
110
|
-
|
|
111
|
-
def on_modified(self, event):
|
|
112
|
-
self._emit("modified", event)
|
|
113
|
-
|
|
114
|
-
def on_deleted(self, event):
|
|
115
|
-
self._emit("deleted", event)
|
|
116
|
-
|
|
117
|
-
def on_moved(self, event):
|
|
118
|
-
self._emit("moved", event)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
class FileMonitor:
|
|
122
|
-
"""
|
|
123
|
-
Monitors the .brain folder for file changes.
|
|
124
|
-
|
|
125
|
-
This enables "push" notifications to MCP clients when files change,
|
|
126
|
-
solving the sync problem across Chats/IDEs/CLI.
|
|
127
|
-
"""
|
|
128
|
-
|
|
129
|
-
def __init__(self, brain_path: str, on_change: Optional[Callable[[FileChangeEvent], None]] = None):
|
|
130
|
-
self.brain_path = Path(brain_path)
|
|
131
|
-
self.on_change = on_change or self._default_handler
|
|
132
|
-
self._observer: Optional[Observer] = None
|
|
133
|
-
self._running = False
|
|
134
|
-
self._event_queue: List[FileChangeEvent] = []
|
|
135
|
-
self._lock = threading.Lock()
|
|
136
|
-
|
|
137
|
-
if not WATCHDOG_AVAILABLE:
|
|
138
|
-
logger.warning("watchdog library not installed. File monitoring disabled.")
|
|
139
|
-
|
|
140
|
-
def _default_handler(self, event: FileChangeEvent):
|
|
141
|
-
"""Default handler just logs and queues events."""
|
|
142
|
-
with self._lock:
|
|
143
|
-
self._event_queue.append(event)
|
|
144
|
-
# Keep queue bounded
|
|
145
|
-
if len(self._event_queue) > 100:
|
|
146
|
-
self._event_queue = self._event_queue[-50:]
|
|
147
|
-
|
|
148
|
-
def start(self) -> bool:
|
|
149
|
-
"""Start watching the brain folder."""
|
|
150
|
-
if not WATCHDOG_AVAILABLE:
|
|
151
|
-
logger.error("Cannot start file monitor: watchdog not installed.")
|
|
152
|
-
return False
|
|
153
|
-
|
|
154
|
-
if self._running:
|
|
155
|
-
logger.warning("File monitor already running.")
|
|
156
|
-
return True
|
|
157
|
-
|
|
158
|
-
if not self.brain_path.exists():
|
|
159
|
-
logger.warning(f"Brain path does not exist: {self.brain_path}")
|
|
160
|
-
return False
|
|
161
|
-
|
|
162
|
-
try:
|
|
163
|
-
handler = BrainEventHandler(self.on_change)
|
|
164
|
-
self._observer = Observer()
|
|
165
|
-
self._observer.schedule(handler, str(self.brain_path), recursive=True)
|
|
166
|
-
self._observer.start()
|
|
167
|
-
self._running = True
|
|
168
|
-
logger.info(f"📡 File monitor started: {self.brain_path}")
|
|
169
|
-
return True
|
|
170
|
-
except Exception as e:
|
|
171
|
-
logger.error(f"Failed to start file monitor: {e}")
|
|
172
|
-
return False
|
|
173
|
-
|
|
174
|
-
def stop(self):
|
|
175
|
-
"""Stop watching the brain folder."""
|
|
176
|
-
if self._observer and self._running:
|
|
177
|
-
self._observer.stop()
|
|
178
|
-
self._observer.join(timeout=2)
|
|
179
|
-
self._running = False
|
|
180
|
-
logger.info("📡 File monitor stopped.")
|
|
181
|
-
|
|
182
|
-
def get_pending_events(self) -> List[FileChangeEvent]:
|
|
183
|
-
"""Get and clear pending events."""
|
|
184
|
-
with self._lock:
|
|
185
|
-
events = self._event_queue.copy()
|
|
186
|
-
self._event_queue.clear()
|
|
187
|
-
return events
|
|
188
|
-
|
|
189
|
-
@property
|
|
190
|
-
def is_running(self) -> bool:
|
|
191
|
-
return self._running
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
# Singleton instance for global access
|
|
195
|
-
_global_monitor: Optional[FileMonitor] = None
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def get_file_monitor() -> Optional[FileMonitor]:
|
|
199
|
-
"""Get the global file monitor instance."""
|
|
200
|
-
return _global_monitor
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def init_file_monitor(brain_path: str, on_change: Optional[Callable] = None) -> FileMonitor:
|
|
204
|
-
"""Initialize and return the global file monitor."""
|
|
205
|
-
global _global_monitor
|
|
206
|
-
if _global_monitor is None:
|
|
207
|
-
_global_monitor = FileMonitor(brain_path, on_change)
|
|
208
|
-
return _global_monitor
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Nucleus Firestore Bridge
|
|
3
|
-
========================
|
|
4
|
-
Bridges the Local Brain (CLI) with the Cloud Brain (Orchestrator) via Firestore.
|
|
5
|
-
|
|
6
|
-
Logic:
|
|
7
|
-
- When an event is emitted locally, we push it to the 'nucleus-events' collection.
|
|
8
|
-
- This allows the Cloud Orchestrator to see local events (like "Task Added").
|
|
9
|
-
|
|
10
|
-
Location: mcp_server_nucleus/runtime/firestore_bridge.py
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import logging
|
|
14
|
-
from typing import Dict, Any
|
|
15
|
-
|
|
16
|
-
try:
|
|
17
|
-
from google.cloud import firestore
|
|
18
|
-
HAS_FIRESTORE = True
|
|
19
|
-
except ImportError:
|
|
20
|
-
HAS_FIRESTORE = False
|
|
21
|
-
|
|
22
|
-
logger = logging.getLogger(__name__)
|
|
23
|
-
|
|
24
|
-
# Configuration
|
|
25
|
-
PROJECT_ID = "gen-lang-client-0894185576"
|
|
26
|
-
COLLECTION_NAME = "nucleus-events"
|
|
27
|
-
|
|
28
|
-
class FirestoreBridge:
|
|
29
|
-
def __init__(self, project_id: str = PROJECT_ID):
|
|
30
|
-
self.project_id = project_id
|
|
31
|
-
self.client = None
|
|
32
|
-
self.enabled = False
|
|
33
|
-
|
|
34
|
-
# Only enable if we have the library and credentials (or can get them)
|
|
35
|
-
if HAS_FIRESTORE:
|
|
36
|
-
try:
|
|
37
|
-
# This will use Application Default Credentials (gcloud auth application-default login)
|
|
38
|
-
self.client = firestore.Client(project=project_id)
|
|
39
|
-
self.enabled = True
|
|
40
|
-
except Exception as e:
|
|
41
|
-
logger.debug(f"Firestore Bridge disabled (no creds): {e}")
|
|
42
|
-
|
|
43
|
-
def push_event(self, event: Dict[str, Any]) -> bool:
|
|
44
|
-
"""Push a local event to the cloud."""
|
|
45
|
-
if not self.enabled or not self.client:
|
|
46
|
-
return False
|
|
47
|
-
|
|
48
|
-
try:
|
|
49
|
-
# key by event_id for idempotency
|
|
50
|
-
event_id = event.get("event_id")
|
|
51
|
-
if not event_id:
|
|
52
|
-
return False
|
|
53
|
-
|
|
54
|
-
doc_ref = self.client.collection(COLLECTION_NAME).document(event_id)
|
|
55
|
-
doc_ref.set(event)
|
|
56
|
-
return True
|
|
57
|
-
except Exception as e:
|
|
58
|
-
logger.warning(f"Failed to push event to cloud: {e}")
|
|
59
|
-
return False
|
|
60
|
-
|
|
61
|
-
def list_cloud_tasks(self, limit: int = 50) -> list:
|
|
62
|
-
"""Fetch tasks from the cloud 'nucleus-tasks' collection."""
|
|
63
|
-
if not self.enabled or not self.client:
|
|
64
|
-
return []
|
|
65
|
-
|
|
66
|
-
try:
|
|
67
|
-
# We assume cloud tasks are in 'nucleus-tasks'
|
|
68
|
-
# Ordered by priority (asc) and created_at (desc)
|
|
69
|
-
docs = self.client.collection("nucleus-tasks") \
|
|
70
|
-
.order_by("priority", direction=firestore.Query.ASCENDING) \
|
|
71
|
-
.limit(limit) \
|
|
72
|
-
.stream()
|
|
73
|
-
|
|
74
|
-
tasks = []
|
|
75
|
-
for doc in docs:
|
|
76
|
-
t = doc.to_dict()
|
|
77
|
-
t["source"] = "cloud" # Mark as cloud source
|
|
78
|
-
tasks.append(t)
|
|
79
|
-
return tasks
|
|
80
|
-
except Exception as e:
|
|
81
|
-
logger.warning(f"Failed to list cloud tasks: {e}")
|
|
82
|
-
return []
|
|
83
|
-
|
|
84
|
-
# Singleton instance
|
|
85
|
-
_bridge = None
|
|
86
|
-
|
|
87
|
-
def get_bridge() -> FirestoreBridge:
|
|
88
|
-
global _bridge
|
|
89
|
-
if _bridge is None:
|
|
90
|
-
_bridge = FirestoreBridge()
|
|
91
|
-
return _bridge
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
GCloud Ops - Phase 49 Implementation
|
|
3
|
-
====================================
|
|
4
|
-
Wraps the local `gcloud` CLI to query infrastructure state.
|
|
5
|
-
|
|
6
|
-
This enables "Smooth across IDEs and CLI" (Tier 4 in NUCLEUS_PRODUCT_SPECS.md).
|
|
7
|
-
The agent becomes the DevOps engineer using YOUR local gcloud auth.
|
|
8
|
-
|
|
9
|
-
Usage:
|
|
10
|
-
ops = GCloudOps()
|
|
11
|
-
status = ops.get_cloud_run_services()
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
import os
|
|
15
|
-
import json
|
|
16
|
-
import subprocess
|
|
17
|
-
import shutil
|
|
18
|
-
import logging
|
|
19
|
-
from typing import Optional, List, Dict, Any
|
|
20
|
-
from dataclasses import dataclass
|
|
21
|
-
|
|
22
|
-
logger = logging.getLogger("nucleus.gcloud_ops")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def find_gcloud() -> Optional[str]:
|
|
26
|
-
"""Find gcloud binary in PATH or common locations."""
|
|
27
|
-
# 1. Check PATH
|
|
28
|
-
gcloud_path = shutil.which("gcloud")
|
|
29
|
-
if gcloud_path:
|
|
30
|
-
return gcloud_path
|
|
31
|
-
|
|
32
|
-
# 2. Check common locations (Mac/Linux)
|
|
33
|
-
common_paths = [
|
|
34
|
-
os.path.expanduser("~/google-cloud-sdk/bin/gcloud"),
|
|
35
|
-
"/usr/local/bin/gcloud",
|
|
36
|
-
"/opt/homebrew/bin/gcloud",
|
|
37
|
-
"/usr/bin/gcloud",
|
|
38
|
-
# Homebrew on Apple Silicon
|
|
39
|
-
"/opt/homebrew/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin/gcloud",
|
|
40
|
-
]
|
|
41
|
-
for p in common_paths:
|
|
42
|
-
if os.path.exists(p):
|
|
43
|
-
return p
|
|
44
|
-
|
|
45
|
-
return None
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@dataclass
|
|
49
|
-
class GCloudResult:
|
|
50
|
-
"""Result of a gcloud command."""
|
|
51
|
-
success: bool
|
|
52
|
-
data: Any = None
|
|
53
|
-
error: Optional[str] = None
|
|
54
|
-
command: str = ""
|
|
55
|
-
|
|
56
|
-
def to_dict(self) -> dict:
|
|
57
|
-
return {
|
|
58
|
-
"success": self.success,
|
|
59
|
-
"data": self.data,
|
|
60
|
-
"error": self.error,
|
|
61
|
-
"command": self.command
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class GCloudOps:
|
|
66
|
-
"""
|
|
67
|
-
Wraps gcloud CLI for infrastructure queries.
|
|
68
|
-
|
|
69
|
-
Uses the user's local gcloud authentication - no API keys needed.
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
def __init__(self, project: Optional[str] = None, region: str = "us-central1"):
|
|
73
|
-
self.gcloud_path = find_gcloud()
|
|
74
|
-
self.project = project or os.environ.get("GCLOUD_PROJECT")
|
|
75
|
-
self.region = region
|
|
76
|
-
|
|
77
|
-
@property
|
|
78
|
-
def is_available(self) -> bool:
|
|
79
|
-
"""Check if gcloud is available."""
|
|
80
|
-
return self.gcloud_path is not None
|
|
81
|
-
|
|
82
|
-
def _run(self, args: List[str], format_json: bool = True) -> GCloudResult:
|
|
83
|
-
"""Run a gcloud command and return result."""
|
|
84
|
-
if not self.is_available:
|
|
85
|
-
return GCloudResult(
|
|
86
|
-
success=False,
|
|
87
|
-
error="gcloud CLI not found. Install: https://cloud.google.com/sdk/docs/install",
|
|
88
|
-
command=" ".join(args)
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
cmd = [self.gcloud_path] + args
|
|
92
|
-
if format_json:
|
|
93
|
-
cmd.extend(["--format", "json"])
|
|
94
|
-
|
|
95
|
-
full_cmd = " ".join(cmd)
|
|
96
|
-
|
|
97
|
-
try:
|
|
98
|
-
result = subprocess.run(
|
|
99
|
-
cmd,
|
|
100
|
-
capture_output=True,
|
|
101
|
-
text=True,
|
|
102
|
-
timeout=30
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
if result.returncode != 0:
|
|
106
|
-
return GCloudResult(
|
|
107
|
-
success=False,
|
|
108
|
-
error=result.stderr.strip() or f"Command failed with exit code {result.returncode}",
|
|
109
|
-
command=full_cmd
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
if format_json and result.stdout.strip():
|
|
113
|
-
try:
|
|
114
|
-
data = json.loads(result.stdout)
|
|
115
|
-
except json.JSONDecodeError:
|
|
116
|
-
data = result.stdout.strip()
|
|
117
|
-
else:
|
|
118
|
-
data = result.stdout.strip()
|
|
119
|
-
|
|
120
|
-
return GCloudResult(success=True, data=data, command=full_cmd)
|
|
121
|
-
|
|
122
|
-
except subprocess.TimeoutExpired:
|
|
123
|
-
return GCloudResult(
|
|
124
|
-
success=False,
|
|
125
|
-
error="Command timed out (30s)",
|
|
126
|
-
command=full_cmd
|
|
127
|
-
)
|
|
128
|
-
except Exception as e:
|
|
129
|
-
return GCloudResult(
|
|
130
|
-
success=False,
|
|
131
|
-
error=str(e),
|
|
132
|
-
command=full_cmd
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
def get_current_project(self) -> GCloudResult:
|
|
136
|
-
"""Get the current active project."""
|
|
137
|
-
return self._run(["config", "get-value", "project"], format_json=False)
|
|
138
|
-
|
|
139
|
-
def get_account(self) -> GCloudResult:
|
|
140
|
-
"""Get the current authenticated account."""
|
|
141
|
-
return self._run(["config", "get-value", "account"], format_json=False)
|
|
142
|
-
|
|
143
|
-
def list_cloud_run_services(self, project: Optional[str] = None) -> GCloudResult:
|
|
144
|
-
"""List Cloud Run services."""
|
|
145
|
-
args = ["run", "services", "list"]
|
|
146
|
-
if project or self.project:
|
|
147
|
-
args.extend(["--project", project or self.project])
|
|
148
|
-
args.extend(["--region", self.region])
|
|
149
|
-
return self._run(args)
|
|
150
|
-
|
|
151
|
-
def get_cloud_run_service(self, service_name: str, project: Optional[str] = None) -> GCloudResult:
|
|
152
|
-
"""Get details of a specific Cloud Run service."""
|
|
153
|
-
args = ["run", "services", "describe", service_name]
|
|
154
|
-
if project or self.project:
|
|
155
|
-
args.extend(["--project", project or self.project])
|
|
156
|
-
args.extend(["--region", self.region])
|
|
157
|
-
return self._run(args)
|
|
158
|
-
|
|
159
|
-
def get_cloud_run_revisions(self, service_name: str, project: Optional[str] = None) -> GCloudResult:
|
|
160
|
-
"""List revisions for a Cloud Run service."""
|
|
161
|
-
args = ["run", "revisions", "list", "--service", service_name]
|
|
162
|
-
if project or self.project:
|
|
163
|
-
args.extend(["--project", project or self.project])
|
|
164
|
-
args.extend(["--region", self.region])
|
|
165
|
-
return self._run(args)
|
|
166
|
-
|
|
167
|
-
def check_auth_status(self) -> Dict[str, Any]:
|
|
168
|
-
"""Check overall gcloud authentication status."""
|
|
169
|
-
return {
|
|
170
|
-
"gcloud_available": self.is_available,
|
|
171
|
-
"gcloud_path": self.gcloud_path,
|
|
172
|
-
"project": self.get_current_project().data if self.is_available else None,
|
|
173
|
-
"account": self.get_account().data if self.is_available else None,
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
# Singleton instance
|
|
178
|
-
_gcloud_ops: Optional[GCloudOps] = None
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def get_gcloud_ops() -> GCloudOps:
|
|
182
|
-
"""Get or create the global GCloudOps instance."""
|
|
183
|
-
global _gcloud_ops
|
|
184
|
-
if _gcloud_ops is None:
|
|
185
|
-
_gcloud_ops = GCloudOps()
|
|
186
|
-
return _gcloud_ops
|