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,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()
|