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,1225 +0,0 @@
1
- #!/usr/bin/env python3
2
- """CLI commands for nucleus-mcp."""
3
-
4
- import os
5
- import json
6
- import sys
7
- import shutil
8
- from datetime import datetime
9
- from pathlib import Path
10
- import argparse
11
-
12
- # ============================================================================
13
- # TEMPLATE: DEFAULT (Full structure)
14
- # ============================================================================
15
- DEFAULT_STATE = {
16
- "version": "1.0.0",
17
- "current_sprint": {
18
- "name": "Sprint 1",
19
- "focus": "Getting Started with Nucleus",
20
- "started_at": None
21
- },
22
- "top_3_leverage_actions": [
23
- "Set up your first agent",
24
- "Configure triggers",
25
- "Connect to Claude Desktop"
26
- ]
27
- }
28
-
29
- DEFAULT_TRIGGERS = {
30
- "version": "1.0.0",
31
- "triggers": [
32
- {
33
- "event_type": "task_completed",
34
- "target_agent": "synthesizer",
35
- "emitter_filter": None
36
- },
37
- {
38
- "event_type": "research_done",
39
- "target_agent": "architect",
40
- "emitter_filter": ["researcher"]
41
- }
42
- ]
43
- }
44
-
45
- SAMPLE_AGENT = '''# {agent_name} Agent
46
-
47
- ## Role
48
- Define what this agent does.
49
-
50
- ## Responsibilities
51
- - Task 1
52
- - Task 2
53
-
54
- ## Triggers
55
- Activated when: [define trigger conditions]
56
-
57
- ## Output Format
58
- Describe expected output format.
59
- '''
60
-
61
- # ============================================================================
62
- # ONBOARDING: Instructional Seed Tasks
63
- # ============================================================================
64
-
65
- def get_default_tasks():
66
- """Generate instructional seed tasks with current timestamps."""
67
- now = datetime.utcnow().isoformat() + "Z"
68
- return [
69
- {
70
- "id": "onboard-1",
71
- "description": "Welcome! Ask your AI: 'What is my current focus?'",
72
- "status": "READY",
73
- "priority": 1,
74
- "blocked_by": [],
75
- "required_skills": [],
76
- "claimed_by": None,
77
- "source": "nucleus-init",
78
- "escalation_reason": None,
79
- "created_at": now,
80
- "updated_at": now
81
- },
82
- {
83
- "id": "onboard-2",
84
- "description": "Try: 'Show me all tasks' to see your task queue",
85
- "status": "PENDING",
86
- "priority": 2,
87
- "blocked_by": ["onboard-1"],
88
- "required_skills": [],
89
- "claimed_by": None,
90
- "source": "nucleus-init",
91
- "escalation_reason": None,
92
- "created_at": now,
93
- "updated_at": now
94
- },
95
- {
96
- "id": "onboard-3",
97
- "description": "Create your first real task: 'Add a task: [your task here]'",
98
- "status": "PENDING",
99
- "priority": 3,
100
- "blocked_by": ["onboard-2"],
101
- "required_skills": [],
102
- "claimed_by": None,
103
- "source": "nucleus-init",
104
- "escalation_reason": None,
105
- "created_at": now,
106
- "updated_at": now
107
- }
108
- ]
109
-
110
- BRAIN_README = '''# 🧠 Your Nucleus Control Plane
111
-
112
- This folder contains your AI assistant's persistent memory.
113
- Generated by Nucleus v0.5.0
114
-
115
- ## Structure
116
-
117
- | Folder | Purpose |
118
- |--------|---------|
119
- | `ledger/` | Event logs, task history |
120
- | `sessions/` | Saved session states |
121
- | `artifacts/` | Generated documents |
122
- | `slots/` | Agent slot registry |
123
- | `memory/` | Engram storage (Memory) |
124
-
125
- ## Quick Commands
126
-
127
- - `brain_session_start()` — Start your session
128
- - `brain_save_session()` — Save current context
129
- - `brain_resume_session()` — Resume previous session
130
- - `brain_list_tasks()` — See pending tasks
131
-
132
- ## Need Help?
133
-
134
- - Docs: [https://github.com/eidetic-works/nucleus-mcp](https://github.com/eidetic-works/nucleus-mcp)
135
- - Issues: [https://github.com/eidetic-works/nucleus-mcp/issues](https://github.com/eidetic-works/nucleus-mcp/issues)
136
- '''
137
-
138
- # ============================================================================
139
- # TEMPLATE: SOLO (Minimal structure for solo founders)
140
- # ============================================================================
141
- SOLO_STATE = {
142
- "version": "1.0.0",
143
- "mode": "solo",
144
- "current_focus": "Getting started with Nucleus",
145
- "tasks": []
146
- }
147
-
148
- SOLO_THREAD_REGISTRY = '''# Thread Registry
149
-
150
- > **Protocol:** Agents check this file on activation to know their role.
151
- > **Stable Anchor:** Thread ID (UUID) never changes, even when IDE renames the thread.
152
-
153
- ---
154
-
155
- ## Active Threads
156
-
157
- | Thread ID | Role | Focus |
158
- |:----------|:-----|:------|
159
- | *(Add your threads here)* | | |
160
-
161
- ---
162
-
163
- ## How to Use
164
-
165
- 1. **Find your thread ID** in your IDE's artifact path (e.g., `.gemini/antigravity/brain/<thread-id>/`)
166
- 2. **Add a row** to the table above with your role
167
- 3. **Agent self-identifies** on next activation by reading this registry
168
-
169
- ---
170
-
171
- ## Example
172
-
173
- | Thread ID | Role | Focus |
174
- |:----------|:-----|:------|
175
- | `7c654df4-b83e-...` | Lead Systems Architect | Infrastructure, MCP |
176
- | `853a0b7e-9052-...` | Synthesizer | Product work, orchestration |
177
- '''
178
-
179
- SOLO_CONTEXT = '''# Project Context
180
-
181
- > This file provides context to all AI agents working on your project.
182
-
183
- ## Company/Project
184
- - **Name:** [Your Project Name]
185
- - **Description:** [What you're building]
186
-
187
- ## Technical Stack
188
- - **Backend:**
189
- - **Frontend:**
190
- - **Database:**
191
-
192
- ## Current Priorities
193
- 1.
194
- 2.
195
- 3.
196
- '''
197
-
198
-
199
- def init_brain_default(brain_path: Path) -> bool:
200
- """Initialize with default (full) template."""
201
- dirs = [
202
- brain_path / "ledger",
203
- brain_path / "sessions",
204
- brain_path / "slots",
205
- brain_path / "artifacts" / "research",
206
- brain_path / "artifacts" / "strategy",
207
- brain_path / "agents",
208
- brain_path / "memory",
209
- ]
210
-
211
- for d in dirs:
212
- d.mkdir(parents=True, exist_ok=True)
213
- print(f" 📁 Created {d}")
214
-
215
- (brain_path / "ledger" / "state.json").write_text(
216
- json.dumps(DEFAULT_STATE, indent=2)
217
- )
218
- print(" 📄 Created ledger/state.json")
219
-
220
- (brain_path / "ledger" / "triggers.json").write_text(
221
- json.dumps(DEFAULT_TRIGGERS, indent=2)
222
- )
223
- print(" 📄 Created ledger/triggers.json")
224
-
225
- (brain_path / "ledger" / "events.jsonl").write_text("")
226
- print(" 📄 Created ledger/events.jsonl")
227
-
228
- # Seed instructional tasks
229
- (brain_path / "ledger" / "tasks.json").write_text(
230
- json.dumps(get_default_tasks(), indent=2)
231
- )
232
- print(" ✅ Created ledger/tasks.json (3 onboarding tasks)")
233
-
234
- # In-brain README
235
- (brain_path / "README.md").write_text(BRAIN_README)
236
- print(" 📖 Created README.md")
237
-
238
- (brain_path / "agents" / "synthesizer.md").write_text(
239
- SAMPLE_AGENT.format(agent_name="Synthesizer")
240
- )
241
- print(" 🤖 Created agents/synthesizer.md")
242
-
243
- (brain_path / "memory" / "context.md").write_text(
244
- "# Project Context\n\nDescribe your project here.\n"
245
- )
246
- print(" 📝 Created memory/context.md")
247
-
248
- return True
249
-
250
-
251
- def init_brain_solo(brain_path: Path) -> bool:
252
- """Initialize with solo (minimal) template."""
253
- dirs = [
254
- brain_path / "ledger",
255
- brain_path / "sessions",
256
- brain_path / "slots",
257
- brain_path / "meta",
258
- brain_path / "memory",
259
- ]
260
-
261
- for d in dirs:
262
- d.mkdir(parents=True, exist_ok=True)
263
- print(f" 📁 Created {d}")
264
-
265
- (brain_path / "ledger" / "state.json").write_text(
266
- json.dumps(SOLO_STATE, indent=2)
267
- )
268
- print(" 📄 Created ledger/state.json (solo mode)")
269
-
270
- (brain_path / "meta" / "thread_registry.md").write_text(SOLO_THREAD_REGISTRY)
271
- print(" 📋 Created meta/thread_registry.md")
272
-
273
- (brain_path / "memory" / "context.md").write_text(SOLO_CONTEXT)
274
- print(" 📝 Created memory/context.md")
275
-
276
- # Seed instructional tasks
277
- (brain_path / "ledger" / "tasks.json").write_text(
278
- json.dumps(get_default_tasks(), indent=2)
279
- )
280
- print(" ✅ Created ledger/tasks.json (3 onboarding tasks)")
281
-
282
- # In-brain README
283
- (brain_path / "README.md").write_text(BRAIN_README)
284
- print(" 📖 Created README.md")
285
-
286
- return True
287
-
288
-
289
-
290
- def init_brain(path: str = ".brain", template: str = "default"):
291
- """Initialize a new .brain directory structure."""
292
- brain_path = Path(path)
293
-
294
- if brain_path.exists():
295
- # Count files to detect active brain
296
- file_count = len(list(brain_path.rglob("*")))
297
-
298
- if file_count > 10:
299
- # This looks like an active brain - extra protection
300
- print(f"⚠️ {path}/ contains {file_count} files!")
301
- print(" This looks like an ACTIVE brain with real content.")
302
- print(" Overwriting is NOT recommended.")
303
- print()
304
- response = input("Type 'BACKUP-AND-OVERWRITE' to confirm (or anything else to abort): ")
305
- if response != 'BACKUP-AND-OVERWRITE':
306
- print("✅ Aborted. Your brain is safe.")
307
- return False
308
- else:
309
- print(f"⚠️ Directory {path} already exists.")
310
- response = input("Overwrite? (y/N): ")
311
- if response.lower() != 'y':
312
- print("Aborted.")
313
- return False
314
-
315
- # ALWAYS backup before overwrite
316
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
317
- backup_path = Path(f"{path}.backup.{timestamp}")
318
- print(f"📦 Creating backup at {backup_path}...")
319
- shutil.copytree(brain_path, backup_path)
320
- print(f" ✅ Backup complete ({file_count} items saved)")
321
-
322
- # Now safe to remove old brain
323
- shutil.rmtree(brain_path)
324
-
325
- print(f"🧠 Initializing Nucleus Control Plane at {path}/ (template: {template})...")
326
-
327
- # Route to template-specific init
328
- if template == "solo":
329
- init_brain_solo(brain_path)
330
- else:
331
- init_brain_default(brain_path)
332
-
333
- print("\n✅ Nucleus Control Plane initialized!")
334
-
335
- # Generate config and auto-configure
336
- abs_path = str(brain_path.absolute())
337
- nucleus_config = {
338
- "command": "python3",
339
- "args": ["-m", "mcp_server_nucleus"],
340
- "env": {"NUCLEAR_BRAIN_PATH": abs_path}
341
- }
342
-
343
- # Attempt auto-configuration for Claude Desktop
344
- claude_config_path = Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"
345
- auto_configured = False
346
-
347
- if claude_config_path.exists():
348
- try:
349
- print("\n🔍 Found Claude Desktop config...")
350
- backup_path = claude_config_path.with_suffix(".json.bak")
351
- if not backup_path.exists():
352
- shutil.copy2(claude_config_path, backup_path)
353
- print(f" 📦 Created backup at {backup_path}")
354
-
355
- with open(claude_config_path, 'r') as f:
356
- config_data = json.load(f)
357
-
358
- if "mcpServers" not in config_data:
359
- config_data["mcpServers"] = {}
360
-
361
- if "nucleus" not in config_data["mcpServers"]:
362
- config_data["mcpServers"]["nucleus"] = nucleus_config
363
-
364
- with open(claude_config_path, 'w') as f:
365
- json.dump(config_data, f, indent=2)
366
-
367
- print(" ✅ Auto-configured 'nucleus' in Claude Desktop settings!")
368
- auto_configured = True
369
- else:
370
- print(" ℹ️ 'nucleus' already configured in Claude Desktop.")
371
- auto_configured = True
372
-
373
- except Exception as e:
374
- print(f" ⚠️ Could not auto-configure: {e}")
375
-
376
- if not auto_configured:
377
- config_snippet = f'''"nucleus": {{
378
- "command": "python3",
379
- "args": ["-m", "mcp_server_nucleus"],
380
- "env": {{
381
- "NUCLEAR_BRAIN_PATH": "{abs_path}"
382
- }}
383
- }}'''
384
-
385
- print("\n" + "="*60)
386
- print("📋 COPY THIS into your AI client's config:")
387
- print("="*60)
388
- print()
389
- print(config_snippet)
390
- print()
391
- print("="*60)
392
- print("\n📍 Config file locations:")
393
- print(" Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json")
394
- print(" Cursor: ~/.cursor/mcp.json")
395
- print(" Windsurf: ~/.codeium/windsurf/mcp_config.json")
396
-
397
- print("\n" + "="*60)
398
- print("🚀 NEXT STEPS")
399
- print("="*60)
400
- print("\n1. Restart your AI Client (Claude Desktop, Cursor, etc.)")
401
- print("2. Try these prompts:")
402
- print(" • \"What is my current focus?\"")
403
- print(" • \"Show me all tasks\"")
404
- print(" • \"Add a task: Build landing page\"")
405
- print("\n📚 Docs: https://github.com/eidetic-works/nucleus-mcp")
406
-
407
- return True
408
-
409
-
410
- def main():
411
- """CLI entry point."""
412
- parser = argparse.ArgumentParser(
413
- prog='nucleus',
414
- description='Nucleus Control Plane CLI - Manage your AI coordination system'
415
- )
416
-
417
- subparsers = parser.add_subparsers(dest='cli_command', help='Available commands')
418
-
419
- # ============================================================
420
- # INIT COMMAND
421
- # ============================================================
422
- init_parser = subparsers.add_parser('init', help='Initialize a new .brain directory')
423
- init_parser.add_argument(
424
- 'path',
425
- nargs='?',
426
- default='.brain',
427
- help='Path to create .brain directory (default: .brain)'
428
- )
429
- init_parser.add_argument(
430
- '-t', '--template',
431
- choices=['default', 'solo'],
432
- default='default',
433
- help='Template to use: default (full) or solo (minimal)'
434
- )
435
-
436
- # ============================================================
437
- # INSTALL COMMAND
438
- # ============================================================
439
- install_parser = subparsers.add_parser('install', help='Install an agent from a .nuke artifact')
440
- install_parser.add_argument('path', help='Path to the .nuke artifact file')
441
-
442
-
443
- # ============================================================
444
- # DEPTH COMMANDS (ADHD Accommodation)
445
- # ============================================================
446
- depth_parser = subparsers.add_parser('depth', help='Track conversation depth (ADHD guardrail)')
447
- depth_subparsers = depth_parser.add_subparsers(dest='depth_action', help='Depth actions')
448
-
449
- # nucleus depth show
450
- depth_subparsers.add_parser('show', help='Show current depth indicator')
451
-
452
- # nucleus depth up [--to=N]
453
- depth_up = depth_subparsers.add_parser('up', help='Come back up one level')
454
- depth_up.add_argument('--to', type=int, help='Go up to specific level (optional)')
455
-
456
- # nucleus depth reset
457
- depth_subparsers.add_parser('reset', help='Reset to root level')
458
-
459
- # nucleus depth max N
460
- depth_max = depth_subparsers.add_parser('max', help='Set max safe depth')
461
- depth_max.add_argument('level', type=int, help='Max safe depth (1-10)')
462
-
463
- # nucleus depth push TOPIC
464
- depth_push = depth_subparsers.add_parser('push', help='Go deeper into a topic')
465
- depth_push.add_argument('topic', help='Topic you are diving into')
466
-
467
- # nucleus depth map
468
- depth_subparsers.add_parser('map', help='Show visual exploration map')
469
-
470
- # ============================================================
471
- # FEATURES COMMANDS (Feature Map)
472
- # ============================================================
473
- features_parser = subparsers.add_parser('features', help='Manage product feature map')
474
- features_subparsers = features_parser.add_subparsers(dest='features_action', help='Feature actions')
475
-
476
- # nucleus features list [--product=X] [--status=X]
477
- features_list = features_subparsers.add_parser('list', help='List all features')
478
- features_list.add_argument('--product', help='Filter by product (gentlequest/nucleus)')
479
- features_list.add_argument('--status', help='Filter by status')
480
-
481
- # nucleus features test <id>
482
- features_test = features_subparsers.add_parser('test', help='Show test instructions for a feature')
483
- features_test.add_argument('id', help='Feature ID to get test instructions for')
484
-
485
- # nucleus features search <query>
486
- features_search = features_subparsers.add_parser('search', help='Search features')
487
- features_search.add_argument('query', help='Search query')
488
-
489
- # nucleus features proof <id>
490
- features_proof = features_subparsers.add_parser('proof', help='Show proof document for a feature')
491
- features_proof.add_argument('id', help='Feature ID to show proof for')
492
-
493
- # nucleus sessions - Session management commands
494
- sessions_parser = subparsers.add_parser('sessions', help='Session management commands')
495
- sessions_subparsers = sessions_parser.add_subparsers(dest='sessions_action')
496
-
497
- # nucleus sessions list
498
- sessions_subparsers.add_parser('list', help='List all saved sessions')
499
-
500
- # nucleus sessions save <context>
501
- sessions_save = sessions_subparsers.add_parser('save', help='Save current session')
502
- sessions_save.add_argument('context', help='Context name (e.g., "Nucleus v0.5.0")')
503
- sessions_save.add_argument('--task', help='Current active task')
504
-
505
- # nucleus sessions resume [id]
506
- sessions_resume = sessions_subparsers.add_parser('resume', help='Resume a saved session')
507
- sessions_resume.add_argument('id', nargs='?', help='Session ID to resume (defaults to most recent)')
508
-
509
- # --- STATUS SUBCOMMAND (SATELLITE VIEW) ---
510
- status_parser = subparsers.add_parser('status', help='Show unified satellite view of the brain')
511
- status_parser.add_argument('--minimal', action='store_true', help='Show minimal view (depth only)')
512
- status_parser.add_argument('--sprint', action='store_true', help='Show sprint view (includes tasks)')
513
- status_parser.add_argument('--full', action='store_true', help='Show full view (includes session)')
514
-
515
- # --- CONSOLIDATE SUBCOMMAND ---
516
- consolidate_parser = subparsers.add_parser('consolidate', help='Brain consolidation and cleanup operations')
517
- consolidate_subparsers = consolidate_parser.add_subparsers(dest='consolidate_action', help='Consolidation commands')
518
-
519
- # nucleus consolidate archive
520
- consolidate_subparsers.add_parser('archive', help='Archive .resolved.* backup files to clean up brain folder')
521
-
522
- # nucleus consolidate propose
523
- consolidate_subparsers.add_parser('propose', help='Detect redundant artifacts and generate merge proposals')
524
-
525
- # nucleus consolidate status
526
- consolidate_subparsers.add_parser('status', help='Show consolidation status and archive info')
527
-
528
- # ============================================================
529
- # SEARCH COMMAND
530
- # ============================================================
531
- search_parser = subparsers.add_parser('search', help='Search for agents in the registry')
532
- search_parser.add_argument('query', help='Search query (name, tag, or description)')
533
-
534
- # ============================================================
535
- # MOUNT COMMAND
536
- # ============================================================
537
- mount_parser = subparsers.add_parser('mount', help='Manage external MCP mounts')
538
- mount_subparsers = mount_parser.add_subparsers(dest='mount_action', help='Mount actions')
539
-
540
- # nucleus mount add <id> [options]
541
- mount_add = mount_subparsers.add_parser('add', help='Add a new mount')
542
- mount_add.add_argument('id', help='Unique mount ID')
543
- mount_add.add_argument('--transport', choices=['stdio', 'sse'], default='stdio', help='Transport type')
544
- mount_add.add_argument('--command', help='Command to execute (stdio)')
545
- mount_add.add_argument('--args', nargs='*', help='Arguments for command (stdio)')
546
- mount_add.add_argument('--env', nargs='*', help='Environment variables (KEY=VALUE)')
547
- mount_add.add_argument('--url', help='URL for SSE transport')
548
-
549
- # nucleus mount list
550
- mount_subparsers.add_parser('list', help='List all mounts')
551
-
552
- # nucleus mount remove <id>
553
- mount_remove = mount_subparsers.add_parser('remove', help='Remove a mount')
554
- mount_remove.add_argument('id', help='Mount ID to remove')
555
-
556
- args = parser.parse_args()
557
-
558
- if args.cli_command == 'init':
559
- init_brain(args.path, args.template)
560
- elif args.cli_command == 'install':
561
- handle_install_command(args)
562
- elif args.cli_command == 'mount':
563
- handle_mount_command(args)
564
- elif args.cli_command == 'status':
565
- handle_status_command(args)
566
- elif args.cli_command == 'depth':
567
- handle_depth_command(args)
568
- elif args.cli_command == 'features':
569
- handle_features_command(args)
570
- elif args.cli_command == 'sessions':
571
- handle_sessions_command(args)
572
- elif args.cli_command == 'consolidate':
573
- handle_consolidate_command(args)
574
- elif args.cli_command == 'search':
575
- handle_search_command(args)
576
- elif args.cli_command == 'schema':
577
- handle_schema_command(args)
578
- elif args.cli_command is None:
579
- # No command given, show help
580
- parser.print_help()
581
- else:
582
- print(f"Unknown command: {args.cli_command}")
583
- parser.print_help()
584
-
585
-
586
- def handle_depth_command(args):
587
- """Handle depth subcommands."""
588
- # Import the core functions from __init__
589
- try:
590
- from . import _depth_show, _depth_pop, _depth_reset, _depth_set_max, _depth_push, _generate_depth_map
591
- except ImportError:
592
- # Direct import for testing
593
- try:
594
- from mcp_server_nucleus import _depth_show, _depth_pop, _depth_reset, _depth_set_max, _depth_push, _generate_depth_map
595
- except ImportError:
596
- print("Error: Could not import depth functions. Make sure NUCLEAR_BRAIN_PATH is set.")
597
- return
598
-
599
- if args.depth_action == 'show' or args.depth_action is None:
600
- result = _depth_show()
601
- if "error" in result:
602
- print(f"❌ Error: {result['error']}")
603
- return
604
-
605
- print()
606
- print("╔═══════════════════════════════════════════════════════════╗")
607
- print(f"║ {result['indicator']:^59} ║")
608
- print("╟───────────────────────────────────────────────────────────╢")
609
- print(f"║ Status: {result['status']:<51} ║")
610
- print("╟───────────────────────────────────────────────────────────╢")
611
- if result['breadcrumbs']:
612
- print(f"║ Path: {result['breadcrumbs'][:53]:<53} ║")
613
- else:
614
- print("║ Path: (root level) ║")
615
- print("╟───────────────────────────────────────────────────────────╢")
616
- print("║ Tree: ║")
617
- for line in result['tree'].split('\n')[:5]: # Max 5 lines
618
- print(f"║ {line:<56} ║")
619
- print("╚═══════════════════════════════════════════════════════════╝")
620
- print()
621
- print("Commands: nucleus depth up | nucleus depth reset | nucleus depth push <topic>")
622
-
623
- elif args.depth_action == 'up':
624
- to_level = getattr(args, 'to', None)
625
- if to_level is not None:
626
- # Pop multiple times to reach target level
627
- result = _depth_show()
628
- current = result.get('current_depth', 0)
629
- pops_needed = current - to_level
630
- if pops_needed <= 0:
631
- print(f"Already at or below level {to_level}")
632
- return
633
- for _ in range(pops_needed):
634
- result = _depth_pop()
635
- else:
636
- result = _depth_pop()
637
-
638
- if "error" in result:
639
- print(f"❌ Error: {result['error']}")
640
- return
641
-
642
- print(result.get('message', '✅ Popped up one level'))
643
- print(f" {result['indicator']}")
644
- if result.get('breadcrumbs'):
645
- print(f" Path: {result['breadcrumbs']}")
646
-
647
- elif args.depth_action == 'reset':
648
- result = _depth_reset()
649
- if "error" in result:
650
- print(f"❌ Error: {result['error']}")
651
- return
652
- print(result['message'])
653
- print(f" New session: {result['session_id']}")
654
-
655
- elif args.depth_action == 'max':
656
- result = _depth_set_max(args.level)
657
- if "error" in result:
658
- print(f"❌ Error: {result['error']}")
659
- return
660
- print(f"✅ {result['message']}")
661
- print(f" {result['indicator']}")
662
-
663
- elif args.depth_action == 'push':
664
- result = _depth_push(args.topic)
665
- if "error" in result:
666
- print(f"❌ Error: {result['error']}")
667
- return
668
- print(f"📍 Diving into: {args.topic}")
669
- print(f" {result['indicator']}")
670
- if result.get('warning'):
671
- print(f" {result['warning']}")
672
- print(f" Path: {result['breadcrumbs']}")
673
-
674
- elif args.depth_action == 'map':
675
- result = _generate_depth_map()
676
- if "error" in result:
677
- print(f"❌ Error: {result['error']}")
678
- return
679
- print(f"🗺️ {result['message']}")
680
- print(f" Path: {result.get('path', '(root)')}")
681
- print()
682
- print("─" * 50)
683
- print(result['mermaid'])
684
- print("─" * 50)
685
- print()
686
- print("💡 Copy the mermaid code block to visualize in any markdown viewer!")
687
-
688
- else:
689
- # Show depth help
690
- print("Usage: nucleus depth <action>")
691
- print()
692
- print("Actions:")
693
- print(" show Show current depth indicator")
694
- print(" up Come back up one level")
695
- print(" reset Reset to root level")
696
- print(" max N Set max safe depth")
697
- print(" push X Go deeper into topic X")
698
- print(" map Show visual exploration map")
699
-
700
-
701
- def handle_features_command(args):
702
- """Handle features subcommands."""
703
- # Import feature functions from main module
704
- from . import _list_features, _get_feature, _search_features, _get_proof
705
-
706
- if args.features_action == 'list':
707
- result = _list_features(args.product, args.status, None)
708
- if "error" in result:
709
- print(f"❌ Error: {result['error']}")
710
- return
711
-
712
- features = result.get("features", [])
713
- if not features:
714
- print("📋 No features found.")
715
- if args.product or args.status:
716
- print(" Try removing filters to see all features.")
717
- return
718
-
719
- # Group by product
720
- by_product = {}
721
- for f in features:
722
- prod = f.get("product", "unknown")
723
- by_product.setdefault(prod, []).append(f)
724
-
725
- print(f"\n📋 Features ({len(features)} total)")
726
- print("=" * 60)
727
-
728
- for product, prod_features in by_product.items():
729
- print(f"\n{product.upper()} ({len(prod_features)} features):")
730
- print("-" * 40)
731
- for f in prod_features:
732
- status_icon = {
733
- "production": "✅",
734
- "released": "✅",
735
- "development": "🚧",
736
- "staged": "🔄",
737
- "broken": "❌",
738
- "deprecated": "⚠️"
739
- }.get(f.get("status"), "❓")
740
-
741
- validated = f.get("validation_result")
742
- val_icon = "✅" if validated == "passed" else "❌" if validated == "failed" else "⏳"
743
-
744
- print(f" {status_icon} {f.get('name'):<30} v{f.get('version'):<8} {val_icon}")
745
-
746
- print()
747
-
748
- elif args.features_action == 'test':
749
- result = _get_feature(args.id)
750
- if "error" in result:
751
- print(f"❌ Error: {result['error']}")
752
- return
753
-
754
- f = result.get("feature", {})
755
- print(f"\n🧪 How to Test: {f.get('name')}")
756
- print("=" * 60)
757
- print(f"\n📝 Description: {f.get('description')}")
758
- print(f"📦 Product: {f.get('product')}")
759
- print(f"🏷️ Version: {f.get('version')}")
760
- print(f"📊 Status: {f.get('status')}")
761
-
762
- print("\n📋 Test Steps:")
763
- for i, step in enumerate(f.get("how_to_test", []), 1):
764
- print(f" {i}. {step}")
765
-
766
- print("\n✅ Expected Result:")
767
- print(f" {f.get('expected_result')}")
768
-
769
- if f.get("deployed_url"):
770
- print(f"\n🌐 URL: {f.get('deployed_url')}")
771
-
772
- validated = f.get("validation_result")
773
- last_val = f.get("last_validated")
774
- if validated:
775
- val_icon = "✅" if validated == "passed" else "❌"
776
- print(f"\n📅 Last Validated: {last_val} - {val_icon} {validated.upper()}")
777
- else:
778
- print("\n⏳ Not yet validated")
779
-
780
- print()
781
-
782
- elif args.features_action == 'search':
783
- result = _search_features(args.query)
784
- if "error" in result:
785
- print(f"❌ Error: {result['error']}")
786
- return
787
-
788
- features = result.get("features", [])
789
- if not features:
790
- print(f"🔍 No features found matching '{args.query}'")
791
- return
792
-
793
- print(f"\n🔍 Search results for '{args.query}' ({len(features)} matches)")
794
- print("-" * 50)
795
- for f in features:
796
- print(f" • {f.get('name')} ({f.get('product')}) - {f.get('description')[:50]}...")
797
- print()
798
-
799
- elif args.features_action == 'proof':
800
- result = _get_proof(args.id)
801
- if "error" in result:
802
- print(f"❌ Error: {result['error']}")
803
- return
804
-
805
- if not result.get("exists"):
806
- print(f"📋 {result.get('message')}")
807
- print()
808
- print("💡 Generate a proof with:")
809
- print(f" In Claude: brain_generate_proof(feature_id='{args.id}', thinking='...')")
810
- return
811
-
812
- print(result.get("content", ""))
813
-
814
- else:
815
- # Show features help
816
- print("Usage: nucleus features <action>")
817
- print()
818
-
819
-
820
- def handle_mount_command(args):
821
- """Handle mount subcommands."""
822
- brain_path = Path(os.environ.get("NUCLEAR_BRAIN_PATH", ".brain")).resolve()
823
- if not brain_path.exists():
824
- # Try to find .brain in current dir or parents
825
- cwd = Path.cwd()
826
- if (cwd / ".brain").exists():
827
- brain_path = cwd / ".brain"
828
-
829
- mounts_file = brain_path / "mounts.json"
830
-
831
- if args.mount_action == 'add':
832
- if not mounts_file.exists():
833
- mounts = {}
834
- else:
835
- try:
836
- mounts = json.loads(mounts_file.read_text())
837
- except Exception:
838
- mounts = {}
839
-
840
- config = {
841
- "transport": args.transport,
842
- "status": "configured" # CLI configure only
843
- }
844
-
845
- if args.transport == 'stdio':
846
- if not args.command:
847
- print("❌ Error: --command is required for stdio transport")
848
- return
849
- config["command"] = args.command
850
- config["args"] = args.args or []
851
-
852
- env_vars = {}
853
- if args.env:
854
- for e in args.env:
855
- if '=' in e:
856
- k, v = e.split('=', 1)
857
- env_vars[k] = v
858
- if env_vars:
859
- config["env"] = env_vars
860
-
861
- elif args.transport == 'sse':
862
- if not args.url:
863
- print("❌ Error: --url is required for sse transport")
864
- return
865
- config["url"] = args.url
866
-
867
- mounts[args.id] = config
868
- mounts_file.write_text(json.dumps(mounts, indent=2))
869
- print(f"✅ Mount '{args.id}' added to configuration.")
870
- print(" Restart Nucleus to activate.")
871
-
872
- elif args.mount_action == 'remove':
873
- if not mounts_file.exists():
874
- print("❌ No mounts configured.")
875
- return
876
-
877
- try:
878
- mounts = json.loads(mounts_file.read_text())
879
- except Exception:
880
- print("❌ Failed to parse mounts file.")
881
- return
882
-
883
- if args.id in mounts:
884
- del mounts[args.id]
885
- mounts_file.write_text(json.dumps(mounts, indent=2))
886
- print(f"✅ Mount '{args.id}' removed.")
887
- print(" Restart Nucleus to apply.")
888
- else:
889
- print(f"❌ Mount '{args.id}' not found.")
890
-
891
- elif args.mount_action == 'list':
892
- if not mounts_file.exists():
893
- print("No mounts configured.")
894
- return
895
-
896
- try:
897
- mounts = json.loads(mounts_file.read_text())
898
- except Exception:
899
- print("❌ Failed to parse mounts file.")
900
- return
901
-
902
- print(f"\n🔌 Configured Mounts ({len(mounts)})")
903
- print("=" * 60)
904
- for mid, cfg in mounts.items():
905
- t = cfg.get("transport")
906
- details = f"{cfg.get('command')} {' '.join(cfg.get('args', []))}" if t == 'stdio' else cfg.get('url')
907
- print(f" • {mid:<15} [{t}] {details}")
908
- print()
909
-
910
- else:
911
- print("Usage: nucleus mount <add|remove|list>")
912
- print("Actions:")
913
- print(" list List all features")
914
- print(" list --product=X Filter by product")
915
- print(" list --status=X Filter by status")
916
- print(" test <id> Show test instructions")
917
- print(" search <query> Search features")
918
- print(" proof <id> Show proof document")
919
-
920
-
921
- def handle_sessions_command(args):
922
- """Handle sessions subcommands."""
923
- # Import session functions from main module
924
- from . import _list_sessions, _save_session, _resume_session
925
-
926
- if args.sessions_action == 'list':
927
- result = _list_sessions()
928
- if "error" in result:
929
- print(f"❌ Error: {result['error']}")
930
- return
931
-
932
- sessions = result.get("sessions", [])
933
- if not sessions:
934
- print("📋 No saved sessions found.")
935
- return
936
-
937
- print(f"\n📋 Saved Sessions ({len(sessions)})")
938
- print("=" * 60)
939
- for s in sessions:
940
- print(f"• {s['timestamp']} - {s.get('context', 'Unknown Context')}")
941
- print(f" ID: {s['id']}")
942
- print(f" Task: {s.get('active_task', 'None')}")
943
- print("-" * 40)
944
- print()
945
-
946
- elif args.sessions_action == 'save':
947
- result = _save_session(
948
- args.context,
949
- active_task=args.task,
950
- pending_decisions=[],
951
- breadcrumbs=[],
952
- next_steps=[]
953
- )
954
- if "error" in result:
955
- print(f"❌ Error: {result['error']}")
956
- return
957
- print(f"✅ Session saved: {result['session_id']}")
958
-
959
- elif args.sessions_action == 'resume':
960
- result = _resume_session(args.id)
961
- if "error" in result:
962
- print(f"❌ Error: {result['error']}")
963
- return
964
- print("✅ Session resumed")
965
- print(f"Context: {result.get('context')}")
966
- print(f"Task: {result.get('active_task')}")
967
-
968
-
969
- def handle_install_command(args):
970
- """Handle install command."""
971
- # Import Installer lazily
972
- from .runtime.installer import Installer
973
-
974
- try:
975
- nuke_path = Path(args.path)
976
- if not nuke_path.exists():
977
- print(f"❌ Error: File not found: {nuke_path}")
978
- return
979
-
980
- # Initialize installer with environment brain path or default
981
- brain_path = Path(os.environ.get("NUCLEAR_BRAIN_PATH", ".brain"))
982
-
983
- installer = Installer(brain_path)
984
- print(f"📦 Installing agent from {nuke_path}...")
985
-
986
- manifest = installer.install_from_file(nuke_path)
987
-
988
- print("\n✅ Installation Complete!")
989
- print(f" Agent: {manifest.agent.name} ({manifest.agent.id})")
990
- print(f" Version: {manifest.agent.version}")
991
-
992
- caps = [c.scope.value for c in manifest.capabilities]
993
- if caps:
994
- print(f" Capabilities: {', '.join(caps)}")
995
- else:
996
- print(" Capabilities: None declared")
997
-
998
- except Exception as e:
999
- print(f"\n❌ Installation Failed: {e}")
1000
- # import traceback
1001
- # traceback.print_exc()
1002
-
1003
-
1004
-
1005
-
1006
- def handle_search_command(args):
1007
- """Handle agent search."""
1008
- from pathlib import Path
1009
- # Local imports to avoid circular deps during init
1010
- try:
1011
- from .runtime.team import TeamManager
1012
- from .runtime.registry import RegistryClient
1013
- except ImportError:
1014
- # Fallback if running from source without install
1015
- sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
1016
- from mcp_server_nucleus.runtime.team import TeamManager
1017
- from mcp_server_nucleus.runtime.registry import RegistryClient
1018
-
1019
- brain_path = Path(os.environ.get("NUCLEAR_BRAIN_PATH", ".brain"))
1020
-
1021
- # 1. Resolve Registry URL
1022
- team_mgr = TeamManager(brain_path)
1023
- registry_url = team_mgr.get_registry_url()
1024
-
1025
- print(f"🔍 Searching registry: {registry_url or 'Default Public'}...")
1026
-
1027
- # 2. Init Client
1028
- if registry_url:
1029
- client = RegistryClient(registry_url=registry_url)
1030
- else:
1031
- client = RegistryClient()
1032
-
1033
- # 3. Search
1034
- try:
1035
- # In a real CLI verify_client logic, we'd want to handle errors if registry is unreachable
1036
- # For Phase 57, we assume success or catch
1037
- client.fetch_index()
1038
- results = client.search(args.query)
1039
-
1040
- if not results:
1041
- print("No agents found.")
1042
- return
1043
-
1044
- print(f"\nFound {len(results)} agents:")
1045
- print("=" * 60)
1046
- for agent in results:
1047
- print(f" • {agent.name} ({agent.id})")
1048
- print(f" v{agent.latest_version} | {agent.description}")
1049
- print(f" Tags: {', '.join(agent.tags)}")
1050
- print(f" Repo: {agent.repo_url}")
1051
- print()
1052
-
1053
- except Exception as e:
1054
- print(f"❌ Error during search: {e}")
1055
-
1056
-
1057
-
1058
- def handle_consolidate_command(args):
1059
- """Handle consolidate subcommands."""
1060
- # Import the core functions from __init__
1061
- try:
1062
- from . import _archive_resolved_files, _get_archive_path
1063
- except ImportError:
1064
- try:
1065
- from mcp_server_nucleus import _archive_resolved_files, _get_archive_path
1066
- except ImportError:
1067
- print("Error: Could not import consolidation functions. Make sure NUCLEAR_BRAIN_PATH is set.")
1068
- return
1069
-
1070
- if args.consolidate_action == 'archive':
1071
- print("🧹 Archiving resolved backup files...")
1072
- print()
1073
-
1074
- result = _archive_resolved_files()
1075
-
1076
- if not result.get("success"):
1077
- print(f"❌ Error: {result.get('error', 'Unknown error')}")
1078
- return
1079
-
1080
- files_moved = result.get("files_moved", 0)
1081
- archive_path = result.get("archive_path", "")
1082
-
1083
- if files_moved == 0:
1084
- print("✅ No backup files to archive. Brain folder is already clean!")
1085
- return
1086
-
1087
- print(f"✅ Archived {files_moved} files!")
1088
- print(f" Location: {archive_path}")
1089
- print()
1090
- print("📁 Moved files (sample):")
1091
- for f in result.get("moved_files", [])[:5]:
1092
- print(f" • {f}")
1093
- if files_moved > 5:
1094
- print(f" ... and {files_moved - 5} more")
1095
- print()
1096
- print("💡 To recover files, move them back from the archive folder.")
1097
-
1098
- elif args.consolidate_action == 'propose':
1099
- try:
1100
- from . import _generate_merge_proposals
1101
- except ImportError:
1102
- try:
1103
- from mcp_server_nucleus import _generate_merge_proposals
1104
- except ImportError:
1105
- print("Error: Could not import proposal functions.")
1106
- return
1107
-
1108
- print("🔍 Scanning brain for redundant artifacts...")
1109
- print()
1110
-
1111
- result = _generate_merge_proposals()
1112
-
1113
- if not result.get("success"):
1114
- print(f"❌ Error: {result.get('error', 'Unknown error')}")
1115
- return
1116
-
1117
- total = result.get("total_proposals", 0)
1118
- summary = result.get("summary", {})
1119
-
1120
- if total == 0:
1121
- print("✅ Brain is clean! No consolidation proposals found.")
1122
- return
1123
-
1124
- # Print summary table
1125
- print(f"📋 Found {total} consolidation proposals:")
1126
- print()
1127
- print(f" Versioned Duplicates: {summary.get('versioned_duplicates', 0)}")
1128
- print(f" Related Series: {summary.get('related_series', 0)}")
1129
- print(f" Stale Files (30d): {summary.get('stale_files', 0)}")
1130
- print(f" Archive Candidates: {summary.get('archive_candidates', 0)}")
1131
- print()
1132
-
1133
- # Print full proposal document
1134
- print("─" * 60)
1135
- print(result.get("proposal_text", ""))
1136
-
1137
- elif args.consolidate_action == 'status':
1138
- try:
1139
- archive_path = _get_archive_path()
1140
- resolved_archive = archive_path / "resolved"
1141
-
1142
- if not resolved_archive.exists():
1143
- print("📊 Consolidation Status")
1144
- print()
1145
- print(" Archive: Not yet created")
1146
- print(" Run: nucleus consolidate archive")
1147
- return
1148
-
1149
- # Count files in archive
1150
- archived_count = len(list(resolved_archive.glob("*")))
1151
-
1152
- print("📊 Consolidation Status")
1153
- print()
1154
- print(f" Archive path: {resolved_archive}")
1155
- print(f" Archived files: {archived_count}")
1156
- print()
1157
- print("💡 Run 'nucleus consolidate archive' to archive new backup files.")
1158
-
1159
- except Exception as e:
1160
- print(f"❌ Error: {e}")
1161
-
1162
- else:
1163
- # Show consolidate help
1164
- print("Usage: nucleus consolidate <action>")
1165
- print()
1166
- print("Actions:")
1167
- print(" archive Archive .resolved.* backup files (safe, reversible)")
1168
- print(" status Show consolidation status and archive info")
1169
-
1170
-
1171
- def handle_status_command(args):
1172
- """Handle nucleus status command (Satellite View)."""
1173
- from mcp_server_nucleus import (
1174
- _get_satellite_view,
1175
- _format_satellite_cli
1176
- )
1177
-
1178
- # Determine detail level from flags
1179
- if args.minimal:
1180
- detail_level = "minimal"
1181
- elif args.sprint:
1182
- detail_level = "sprint"
1183
- elif args.full:
1184
- detail_level = "full"
1185
- else:
1186
- detail_level = "standard"
1187
-
1188
- try:
1189
- view = _get_satellite_view(detail_level)
1190
- output = _format_satellite_cli(view)
1191
- print(output)
1192
- except Exception as e:
1193
- print(f"❌ Error getting satellite view: {e}")
1194
- print()
1195
- print("Make sure NUCLEAR_BRAIN_PATH is set correctly.")
1196
-
1197
-
1198
-
1199
-
1200
-
1201
-
1202
- def handle_schema_command(args):
1203
- """Handle schema export command."""
1204
- import asyncio
1205
- from .runtime.schema_gen import generate_tool_schema, export_schema_to_file
1206
-
1207
- # We need an initialized MCP instance
1208
- try:
1209
- from . import mcp
1210
- except ImportError:
1211
- print("Error: Could not import mcp instance. Ensure you are in the package root.")
1212
- return
1213
-
1214
- async def run_gen():
1215
- tool_names = await mcp.get_tools()
1216
- print(f"🔍 Generating schema for {len(tool_names)} tools...")
1217
- schema = await generate_tool_schema(mcp)
1218
- export_schema_to_file(schema, args.output)
1219
- print(f"✅ Schema exported to: {args.output}")
1220
-
1221
- asyncio.run(run_gen())
1222
-
1223
-
1224
- if __name__ == "__main__":
1225
- main()