File size: 5,592 Bytes
09e8c1e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
"""Command-line entry point for the NACC node server.

Currently a stub that just prints configuration info; to be extended with
actual MCP server startup logic.
"""

from __future__ import annotations

import argparse
import json
from pathlib import Path
import sys

from .filesystem import list_files
from .config import load_node_config
from .tools import NodeServer


def _build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="NACC node MCP server")
    subparsers = parser.add_subparsers(dest="command")

    serve = subparsers.add_parser("serve", help="Start the MCP node server")
    serve.add_argument("--host", default="0.0.0.0", help="Host/IP to bind (default 0.0.0.0)")
    serve.add_argument("--port", type=int, default=8765, help="Port to listen on")
    serve.add_argument(
        "--config",
        type=str,
        default="node-config.yml",
        help="Path to node configuration file",
    )
    serve.add_argument(
        "--dry-run",
        action="store_true",
        help="Validate configuration and exit without starting the server",
    )

    init_parser = subparsers.add_parser("init", help="Initialize a new node and generate pairing code")

    list_parser = subparsers.add_parser("list-files", help="List files on this node")
    list_parser.add_argument("--path", default=".", help="Path to inspect")
    list_parser.add_argument(
        "--recursive",
        action="store_true",
        help="Recurse into subdirectories",
    )
    list_parser.add_argument(
        "--filter",
        dest="pattern",
        default=None,
        help="Glob filter applied to relative paths",
    )
    list_parser.add_argument(
        "--with-hash",
        action="store_true",
        help="Compute sha256 for files (slower)",
    )
    list_parser.add_argument(
        "--config",
        type=str,
        default=None,
        help="Optional node-config file to scope the path to the node root",
    )

    return parser


def main(argv: list[str] | None = None) -> None:
    parser = _build_parser()
    parsed_args = list(argv) if argv is not None else sys.argv[1:]
    if not parsed_args or parsed_args[0].startswith("-"):
        parsed_args = ["serve", *parsed_args]
    args = parser.parse_args(parsed_args)

    if args.command == "list-files":
        root_override: Path | None = None
        target_path = Path(args.path)
        if args.config:
            config = load_node_config(args.config)
            root_override = config.root_dir
            if not target_path.is_absolute():
                target_path = (root_override / target_path).resolve()
        files = list_files(
            target_path,
            recursive=args.recursive,
            pattern=args.pattern,
            include_hash=args.with_hash,
            root=str(root_override) if root_override else None,
        )
        payload = {"files": [file.to_dict() for file in files], "count": len(files)}
        print(json.dumps(payload, indent=2))
        return

    if args.command == "init":
        import uuid
        from .pairing import create_session
        
        # Generate defaults
        node_id = str(uuid.uuid4())
        root_dir = Path.cwd().resolve()
        
        # Create config structure
        config_data = {
            "node_id": node_id,
            "root_dir": str(root_dir),
            "display_name": f"Node-{node_id[:8]}",
            "tags": ["generic"],
            "allowed_commands": ["python", "ls", "cat", "echo", "grep"],
            "sync_targets": {}
        }
        
        # Generate pairing code
        session = create_session(node_id)
        
        # Save config
        output_path = Path("node-config.yml")
        if output_path.exists():
            print(f"Config file {output_path} already exists. Skipping creation.")
        else:
            import yaml
            with open(output_path, "w") as f:
                yaml.dump(config_data, f)
            print(f"Created {output_path}")

        print("\n" + "="*40)
        print(f"πŸš€ Node Initialized: {config_data['display_name']}")
        print(f"πŸ†” Node ID: {node_id}")
        print(f"πŸ”‘ PAIRING CODE: {session.code}")
        print("="*40)
        print("\nRun this on your orchestrator to pair:")
        print(f"  nacc-orchestrator register-node {session.code} --ip <THIS_NODE_IP>")
        print("\n(Note: The code is for display only in this version. Use the ID/IP for manual registration if needed.)")
        return

    if args.command in (None, "serve"):
        config_path = getattr(args, "config", "node-config.yml")
        config = load_node_config(config_path)
        if getattr(args, "dry_run", False):
            summary = {
                "node_id": config.node_id,
                "root_dir": str(config.root_dir),
                "tags": config.tags,
                "allowed_commands": config.allowed_commands,
                "sync_targets": {key: str(value) for key, value in config.sync_targets.items()},
            }
            print(json.dumps(summary, indent=2))
            return

        host = getattr(args, "host", "0.0.0.0")
        port = getattr(args, "port", 8765)
        server = NodeServer(config, host=host, port=port)
        try:
            server.serve_forever()
        except KeyboardInterrupt:  # pragma: no cover - manual interrupt
            print("\n[nacc-node] shutdown requested")
            server.shutdown()
        return

    parser.error(f"Unknown command: {args.command}")


if __name__ == "__main__":  # pragma: no cover
    main()