feat(operators): agent markdown drive management via Hatch and SSH runbook
This commit is contained in:
Executable
+430
@@ -0,0 +1,430 @@
|
||||
#!/usr/bin/env python3
|
||||
"""agent_md.py — Access and modify Muse agent .md files via Hatch gateway and SSH.
|
||||
|
||||
Enables operators to inspect, audit, diff, and inject operational DRIVE into
|
||||
Muse agents across the fleet (muse, pip, 646, opm, def, dev).
|
||||
"""
|
||||
|
||||
import difflib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# Ensure muse_cli can be imported
|
||||
sys.path.insert(0, os.path.expanduser("~/.local/lib/python3.14/site-packages"))
|
||||
try:
|
||||
from muse_cli.gateway import Gateway, load_cookies, AuthError, GatewayError
|
||||
except ImportError:
|
||||
Gateway = None
|
||||
|
||||
NETVM_ROOT = Path("/home/super/Projects/NetVM")
|
||||
SHARED_OPERATORS = NETVM_ROOT / "shared" / "operators"
|
||||
VALID_ACCOUNTS = ["muse", "pip", "646", "opm", "def", "dev"]
|
||||
|
||||
TARGET_MD_FILES = [
|
||||
"SOUL.md",
|
||||
"PROACTIVE_PREFERENCES.md",
|
||||
"HEARTBEAT.md",
|
||||
"AGENTS.md",
|
||||
"MEMORY.md",
|
||||
"USER.md",
|
||||
"TOOLS.md",
|
||||
"IDENTITY.md",
|
||||
]
|
||||
|
||||
# Tunnel / Port inventory
|
||||
TUNNEL_PORTS = {
|
||||
"muse-main": {"port": 2224, "terminal": 7681, "user": "muse"},
|
||||
"muse": {"port": 2225, "terminal": 7682, "user": "hatch"},
|
||||
"646": {"port": 2226, "terminal": 7683, "user": "hatch"},
|
||||
"pip": {"port": 2227, "terminal": 7684, "user": "hatch"},
|
||||
"opm": {"port": 2228, "terminal": 7685, "user": "hatch"},
|
||||
"def": {"port": 2229, "terminal": 7686, "user": "hatch"},
|
||||
"dev": {"port": 2230, "terminal": 7687, "user": "hatch"},
|
||||
}
|
||||
|
||||
|
||||
def get_gateway(account: str) -> "Gateway":
|
||||
"""Obtain an authenticated Gateway connection for an account."""
|
||||
if not Gateway:
|
||||
raise RuntimeError("muse_cli.gateway module is not available")
|
||||
conf_dir = Path.home() / ".config" / "muse-cli" / account
|
||||
cfile = conf_dir / "cookies.txt"
|
||||
if not cfile.exists():
|
||||
raise FileNotFoundError(f"No cookies found for account '{account}' at {cfile}")
|
||||
cookies = load_cookies(str(cfile))
|
||||
if not cookies.strip():
|
||||
raise ValueError(f"Cookies file for '{account}' is empty")
|
||||
return Gateway(cookies)
|
||||
|
||||
|
||||
def list_files(account: str, path: str = "") -> list:
|
||||
"""List files in the agent container filesystem via Hatch."""
|
||||
gw = get_gateway(account)
|
||||
res = gw.call_json("fs.list", body={"path": path})
|
||||
return res.get("entries", [])
|
||||
|
||||
|
||||
def read_md(account: str, filename: str, max_bytes: int = 200000) -> dict:
|
||||
"""Read a markdown file from the agent container via Hatch."""
|
||||
gw = get_gateway(account)
|
||||
offset = 0
|
||||
chunks = []
|
||||
chunk_len = min(65536, max_bytes)
|
||||
|
||||
while True:
|
||||
body = {"path": filename, "offset": offset, "len": chunk_len}
|
||||
res = gw.call_json("fs.read", body=body)
|
||||
text = res.get("text", "")
|
||||
if not text and res.get("data_base64"):
|
||||
import base64
|
||||
text = base64.b64decode(res["data_base64"]).decode("utf-8", "replace")
|
||||
chunks.append(text)
|
||||
offset += len(text.encode("utf-8"))
|
||||
if res.get("eof") or offset >= max_bytes or not text:
|
||||
break
|
||||
|
||||
full_text = "".join(chunks)
|
||||
return {
|
||||
"ok": True,
|
||||
"account": account,
|
||||
"filename": filename,
|
||||
"size": len(full_text.encode("utf-8")),
|
||||
"text": full_text,
|
||||
"eof": True,
|
||||
}
|
||||
|
||||
|
||||
def write_md(account: str, filename: str, text: str, overwrite: bool = True, append: bool = False) -> dict:
|
||||
"""Write content to a file in the agent container via Hatch."""
|
||||
gw = get_gateway(account)
|
||||
body = {
|
||||
"path": filename,
|
||||
"overwrite": overwrite,
|
||||
"append": append,
|
||||
"create_parent": True,
|
||||
"text": text,
|
||||
}
|
||||
res = gw.call_json("fs.write", body=body)
|
||||
return {
|
||||
"ok": True,
|
||||
"account": account,
|
||||
"filename": filename,
|
||||
"bytes_written": res.get("bytes_written", len(text.encode("utf-8"))),
|
||||
"path": res.get("path", f"/{filename}"),
|
||||
}
|
||||
|
||||
|
||||
def audit_agents(accounts: list = None) -> dict:
|
||||
"""Audit markdown files and operational DRIVE across fleet agents."""
|
||||
accounts = accounts or VALID_ACCOUNTS
|
||||
results = {}
|
||||
|
||||
for acct in accounts:
|
||||
acct_res = {
|
||||
"account": acct,
|
||||
"connected": False,
|
||||
"files": {},
|
||||
"drive_status": {},
|
||||
"issues": [],
|
||||
"drive_score": 0,
|
||||
}
|
||||
try:
|
||||
gw = get_gateway(acct)
|
||||
acct_res["connected"] = True
|
||||
acct_res["vm_id"] = gw.vm_id
|
||||
|
||||
entries = gw.call_json("fs.list", body={"path": ""}).get("entries", [])
|
||||
entry_map = {e["name"]: e for e in entries}
|
||||
|
||||
for tf in TARGET_MD_FILES:
|
||||
if tf in entry_map:
|
||||
info = entry_map[tf]
|
||||
acct_res["files"][tf] = {
|
||||
"exists": True,
|
||||
"size": info.get("size", 0),
|
||||
"modified": info.get("modifiedAt", ""),
|
||||
}
|
||||
else:
|
||||
acct_res["files"][tf] = {
|
||||
"exists": False,
|
||||
"size": 0,
|
||||
"modified": None,
|
||||
}
|
||||
|
||||
# Analyze DRIVE indicators
|
||||
# 1. HEARTBEAT.md checklist
|
||||
hb_info = acct_res["files"].get("HEARTBEAT.md", {})
|
||||
if not hb_info.get("exists"):
|
||||
acct_res["drive_status"]["heartbeat"] = "MISSING"
|
||||
acct_res["issues"].append("HEARTBEAT.md missing (no recurring checks)")
|
||||
elif hb_info.get("size", 0) <= 120:
|
||||
acct_res["drive_status"]["heartbeat"] = "EMPTY_CHECKLIST"
|
||||
acct_res["issues"].append("HEARTBEAT.md has empty checklist (background runner idle)")
|
||||
else:
|
||||
acct_res["drive_status"]["heartbeat"] = "ACTIVE"
|
||||
acct_res["drive_score"] += 25
|
||||
|
||||
# 2. PROACTIVE_PREFERENCES.md
|
||||
pro_info = acct_res["files"].get("PROACTIVE_PREFERENCES.md", {})
|
||||
if not pro_info.get("exists"):
|
||||
acct_res["drive_status"]["proactive"] = "MISSING"
|
||||
acct_res["issues"].append("PROACTIVE_PREFERENCES.md missing")
|
||||
elif pro_info.get("size", 0) <= 500:
|
||||
acct_res["drive_status"]["proactive"] = "BLANK_TEMPLATE"
|
||||
acct_res["issues"].append("PROACTIVE_PREFERENCES.md unconfigured (never reaches out)")
|
||||
else:
|
||||
acct_res["drive_status"]["proactive"] = "CONFIGURED"
|
||||
acct_res["drive_score"] += 25
|
||||
|
||||
# 3. SOUL.md
|
||||
soul_info = acct_res["files"].get("SOUL.md", {})
|
||||
if not soul_info.get("exists"):
|
||||
acct_res["drive_status"]["soul"] = "MISSING"
|
||||
acct_res["issues"].append("SOUL.md missing")
|
||||
elif soul_info.get("size", 0) <= 850:
|
||||
acct_res["drive_status"]["soul"] = "PASSIVE_STOCK"
|
||||
acct_res["issues"].append("SOUL.md is passive stock template (no operator drive)")
|
||||
else:
|
||||
acct_res["drive_status"]["soul"] = "OPERATOR_SOUL"
|
||||
acct_res["drive_score"] += 25
|
||||
|
||||
# 4. TOOLS.md & USER.md
|
||||
tools_info = acct_res["files"].get("TOOLS.md", {})
|
||||
user_info = acct_res["files"].get("USER.md", {})
|
||||
if tools_info.get("size", 0) > 400 and user_info.get("size", 0) > 400:
|
||||
acct_res["drive_status"]["context"] = "FULL_CONTEXT"
|
||||
acct_res["drive_score"] += 25
|
||||
else:
|
||||
acct_res["drive_status"]["context"] = "PARTIAL_OR_EMPTY"
|
||||
acct_res["issues"].append("TOOLS.md or USER.md missing operational conventions")
|
||||
|
||||
except Exception as e:
|
||||
acct_res["error"] = str(e)
|
||||
acct_res["issues"].append(f"Connection failed: {e}")
|
||||
|
||||
results[acct] = acct_res
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def diff_md(account: str, filename: str) -> dict:
|
||||
"""Compare an agent's container file against the shared operator template."""
|
||||
local_path = SHARED_OPERATORS / filename
|
||||
if not local_path.exists():
|
||||
raise FileNotFoundError(f"Local template {local_path} not found")
|
||||
|
||||
local_content = local_path.read_text(encoding="utf-8")
|
||||
remote_data = read_md(account, filename)
|
||||
remote_content = remote_data.get("text", "")
|
||||
|
||||
diff = list(difflib.unified_diff(
|
||||
remote_content.splitlines(keepends=True),
|
||||
local_content.splitlines(keepends=True),
|
||||
fromfile=f"{account}:{filename} (remote)",
|
||||
tofile=f"shared/operators/{filename} (local)",
|
||||
))
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"account": account,
|
||||
"filename": filename,
|
||||
"identical": len(diff) == 0,
|
||||
"diff": "".join(diff),
|
||||
"remote_size": len(remote_content.encode("utf-8")),
|
||||
"local_size": len(local_content.encode("utf-8")),
|
||||
}
|
||||
|
||||
|
||||
def inject_drive(account: str, force: bool = False) -> dict:
|
||||
"""Inject high-drive operational instructions into the agent's container."""
|
||||
updates = []
|
||||
|
||||
# 1. SOUL.md
|
||||
soul_text = (SHARED_OPERATORS / "SOUL.md").read_text(encoding="utf-8")
|
||||
r_soul = write_md(account, "SOUL.md", soul_text, overwrite=True)
|
||||
updates.append({"file": "SOUL.md", "bytes": r_soul["bytes_written"]})
|
||||
|
||||
# 2. PROACTIVE_PREFERENCES.md
|
||||
pro_text = (SHARED_OPERATORS / "PROACTIVE_PREFERENCES.md").read_text(encoding="utf-8")
|
||||
r_pro = write_md(account, "PROACTIVE_PREFERENCES.md", pro_text, overwrite=True)
|
||||
updates.append({"file": "PROACTIVE_PREFERENCES.md", "bytes": r_pro["bytes_written"]})
|
||||
|
||||
# 3. HEARTBEAT.md
|
||||
hb_text = (SHARED_OPERATORS / "HEARTBEAT.md").read_text(encoding="utf-8")
|
||||
r_hb = write_md(account, "HEARTBEAT.md", hb_text, overwrite=True)
|
||||
updates.append({"file": "HEARTBEAT.md", "bytes": r_hb["bytes_written"]})
|
||||
|
||||
# 4. USER.md
|
||||
user_text = (SHARED_OPERATORS / "USER.md").read_text(encoding="utf-8")
|
||||
r_user = write_md(account, "USER.md", user_text, overwrite=True)
|
||||
updates.append({"file": "USER.md", "bytes": r_user["bytes_written"]})
|
||||
|
||||
# 5. TOOLS.md
|
||||
tools_text = (SHARED_OPERATORS / "TOOLS.md").read_text(encoding="utf-8")
|
||||
r_tools = write_md(account, "TOOLS.md", tools_text, overwrite=True)
|
||||
updates.append({"file": "TOOLS.md", "bytes": r_tools["bytes_written"]})
|
||||
|
||||
# 6. AGENTS.md (preserve existing custom lessons if present)
|
||||
agents_template = (SHARED_OPERATORS / "AGENTS.md").read_text(encoding="utf-8")
|
||||
try:
|
||||
remote_agents = read_md(account, "AGENTS.md").get("text", "")
|
||||
if "## Lessons" in remote_agents and len(remote_agents) > len(agents_template):
|
||||
# Extract custom lessons from remote and merge
|
||||
custom_lessons = remote_agents.split("## Lessons", 1)[1]
|
||||
merged_agents = agents_template.rstrip() + "\n\n## Lessons" + custom_lessons
|
||||
r_agents = write_md(account, "AGENTS.md", merged_agents, overwrite=True)
|
||||
else:
|
||||
r_agents = write_md(account, "AGENTS.md", agents_template, overwrite=True)
|
||||
except Exception:
|
||||
r_agents = write_md(account, "AGENTS.md", agents_template, overwrite=True)
|
||||
updates.append({"file": "AGENTS.md", "bytes": r_agents["bytes_written"]})
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"account": account,
|
||||
"action": "inject_drive",
|
||||
"updates": updates,
|
||||
"message": f"Successfully injected high-drive operator files into {account} container",
|
||||
}
|
||||
|
||||
|
||||
def get_ssh_info(account: str = None) -> dict:
|
||||
"""Return SSH connection coordinates and reverse tunnel configuration."""
|
||||
jump_host = "34.139.37.135"
|
||||
if account:
|
||||
entry = TUNNEL_PORTS.get(account, {"port": 2226, "terminal": 7683, "user": "hatch"})
|
||||
port = entry["port"]
|
||||
user = entry["user"]
|
||||
cmd = f"ssh -o StrictHostKeyChecking=no -p {port} {user}@localhost"
|
||||
proxy_cmd = f"ssh -o StrictHostKeyChecking=no -J super@{jump_host} -p {port} {user}@localhost"
|
||||
return {
|
||||
"account": account,
|
||||
"jump_host": jump_host,
|
||||
"port": port,
|
||||
"container_user": user,
|
||||
"terminal_port": entry.get("terminal"),
|
||||
"direct_from_vm": cmd,
|
||||
"jump_command": proxy_cmd,
|
||||
"cat_example": f"cat file.md | {proxy_cmd} 'cat > /home/hatch/file.md'",
|
||||
}
|
||||
return {
|
||||
"jump_host": jump_host,
|
||||
"tunnels": TUNNEL_PORTS,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="Manage Muse agent .md files via Hatch and SSH")
|
||||
sub = parser.add_subparsers(dest="cmd")
|
||||
|
||||
p_audit = sub.add_parser("audit", help="Audit .md files and DRIVE across all agents")
|
||||
p_audit.add_argument("accounts", nargs="*", help="Optional account filter")
|
||||
p_audit.add_argument("--json", action="store_true", help="Output JSON")
|
||||
|
||||
p_list = sub.add_parser("list", help="List container files via Hatch")
|
||||
p_list.add_argument("account", help="Agent account")
|
||||
p_list.add_argument("path", nargs="?", default="", help="Subdirectory path")
|
||||
|
||||
p_read = sub.add_parser("read", help="Read a markdown file via Hatch")
|
||||
p_read.add_argument("account", help="Agent account")
|
||||
p_read.add_argument("filename", help="Filename (e.g. SOUL.md)")
|
||||
|
||||
p_write = sub.add_parser("write", help="Write a markdown file via Hatch")
|
||||
p_write.add_argument("account", help="Agent account")
|
||||
p_write.add_argument("filename", help="Filename (e.g. SOUL.md)")
|
||||
p_write.add_argument("--content", help="Text content to write")
|
||||
p_write.add_argument("--file", help="Local file to copy content from")
|
||||
|
||||
p_diff = sub.add_parser("diff", help="Diff remote file against shared operator template")
|
||||
p_diff.add_argument("account", help="Agent account")
|
||||
p_diff.add_argument("filename", help="Filename (e.g. SOUL.md)")
|
||||
|
||||
p_drive = sub.add_parser("inject-drive", help="Inject high-drive operator files into agent")
|
||||
p_drive.add_argument("account", help="Agent account")
|
||||
p_drive.add_argument("--force", action="store_true", help="Force overwrite")
|
||||
|
||||
p_sync_all = sub.add_parser("sync-all", help="Inject high-drive files across all active agents")
|
||||
|
||||
p_ssh = sub.add_parser("ssh-info", help="Get SSH tunnel dial-in information")
|
||||
p_ssh.add_argument("account", nargs="?", help="Optional agent account")
|
||||
|
||||
args = parser.parse_args()
|
||||
if not args.cmd:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
if args.cmd == "audit":
|
||||
res = audit_agents(args.accounts or None)
|
||||
if args.json:
|
||||
print(json.dumps(res, indent=2))
|
||||
else:
|
||||
print(f"\n{'='*70}\nMUSE AGENT .MD & DRIVE AUDIT REPORT\n{'='*70}")
|
||||
for acct, d in res.items():
|
||||
if not d.get("connected"):
|
||||
print(f"\n[AGENT {acct.upper()}] ✗ Connection failed: {d.get('error')}")
|
||||
continue
|
||||
score = d.get("drive_score", 0)
|
||||
status_color = "HIGH DRIVE" if score >= 75 else ("PARTIAL" if score >= 50 else "LOW DRIVE / STALE")
|
||||
print(f"\n[AGENT {acct.upper()}] DRIVE Score: {score}/100 ({status_color}) VM: {d.get('vm_id', 'unknown')}")
|
||||
for fname, finfo in d.get("files", {}).items():
|
||||
ex = "✓" if finfo.get("exists") else "✗"
|
||||
sz = f"{finfo.get('size', 0):6} bytes"
|
||||
mod = (finfo.get("modified") or "")[:19]
|
||||
print(f" {ex} {fname:24} {sz} {mod}")
|
||||
if d.get("issues"):
|
||||
print(" Issues:")
|
||||
for iss in d["issues"]:
|
||||
print(f" • {iss}")
|
||||
print(f"\n{'='*70}\n")
|
||||
|
||||
elif args.cmd == "list":
|
||||
entries = list_files(args.account, args.path)
|
||||
print(json.dumps(entries, indent=2))
|
||||
|
||||
elif args.cmd == "read":
|
||||
r = read_md(args.account, args.filename)
|
||||
print(r.get("text", ""))
|
||||
|
||||
elif args.cmd == "write":
|
||||
content = args.content
|
||||
if args.file:
|
||||
content = Path(args.file).read_text(encoding="utf-8")
|
||||
if content is None:
|
||||
print("Error: provide --content or --file", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
res = write_md(args.account, args.filename, content)
|
||||
print(json.dumps(res, indent=2))
|
||||
|
||||
elif args.cmd == "diff":
|
||||
res = diff_md(args.account, args.filename)
|
||||
if res["identical"]:
|
||||
print(f"{args.account}:{args.filename} matches local shared/operators/{args.filename} exactly.")
|
||||
else:
|
||||
print(res["diff"])
|
||||
|
||||
elif args.cmd == "inject-drive":
|
||||
res = inject_drive(args.account, force=args.force)
|
||||
print(json.dumps(res, indent=2))
|
||||
|
||||
elif args.cmd == "sync-all":
|
||||
results = {}
|
||||
for acct in VALID_ACCOUNTS:
|
||||
try:
|
||||
results[acct] = inject_drive(acct)
|
||||
print(f"✓ Injected DRIVE into {acct}")
|
||||
except Exception as e:
|
||||
results[acct] = {"ok": False, "error": str(e)}
|
||||
print(f"✗ Failed {acct}: {e}")
|
||||
|
||||
elif args.cmd == "ssh-info":
|
||||
res = get_ssh_info(args.account)
|
||||
print(json.dumps(res, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+155
-3
@@ -31,6 +31,8 @@ from pathlib import Path
|
||||
|
||||
NETVM_ROOT = Path("/home/super/Projects/NetVM")
|
||||
BIN = NETVM_ROOT / "bin"
|
||||
if str(BIN) not in sys.path:
|
||||
sys.path.insert(0, str(BIN))
|
||||
JOBS_DIR = NETVM_ROOT / "jobs"
|
||||
SYSTEMD_USER = Path.home() / ".config" / "systemd" / "user"
|
||||
CTL_LOG = NETVM_ROOT / "box-ctl.jsonl"
|
||||
@@ -1488,6 +1490,79 @@ def act_ssh_show(name):
|
||||
out(True, name=name, private_key=str(priv_path), public_key=str(pub_path),
|
||||
public_key_content=pub_line, fingerprint=fp)
|
||||
|
||||
|
||||
def act_ssh_ports():
|
||||
"""List container reverse tunnel port mappings."""
|
||||
import agent_md
|
||||
out(True, **agent_md.get_ssh_info())
|
||||
|
||||
|
||||
def act_ssh_info(name):
|
||||
"""Show SSH dial-in command and reverse tunnel coordinates for a specific agent."""
|
||||
import agent_md
|
||||
out(True, **agent_md.get_ssh_info(name))
|
||||
|
||||
|
||||
def act_md_audit(accounts=None):
|
||||
"""Audit markdown files and operational drive across fleet agents."""
|
||||
import agent_md
|
||||
audit("md-audit", ",".join(accounts or []))
|
||||
res = agent_md.audit_agents(accounts=accounts)
|
||||
out(True, agents=res)
|
||||
|
||||
|
||||
def act_md_list(account, path=""):
|
||||
"""List container files via Hatch."""
|
||||
import agent_md
|
||||
audit("md-list", f"{account}:{path}")
|
||||
entries = agent_md.list_files(account, path=path)
|
||||
out(True, account=account, path=path, entries=entries)
|
||||
|
||||
|
||||
def act_md_read(account, filename):
|
||||
"""Read a markdown file from the agent container via Hatch."""
|
||||
import agent_md
|
||||
audit("md-read", f"{account}:{filename}")
|
||||
res = agent_md.read_md(account, filename)
|
||||
out(res.pop("ok", True), **res)
|
||||
|
||||
|
||||
def act_md_write(account, filename, content, overwrite=True, append=False):
|
||||
"""Write content to an agent's container file via Hatch."""
|
||||
import agent_md
|
||||
audit("md-write", f"{account}:{filename}")
|
||||
res = agent_md.write_md(account, filename, content, overwrite=overwrite, append=append)
|
||||
out(res.pop("ok", True), **res)
|
||||
|
||||
|
||||
def act_md_diff(account, filename):
|
||||
"""Diff remote container file against local shared/operators template."""
|
||||
import agent_md
|
||||
res = agent_md.diff_md(account, filename)
|
||||
out(res.pop("ok", True), **res)
|
||||
|
||||
|
||||
def act_md_inject_drive(account, force=False):
|
||||
"""Inject high-drive operational templates into an agent container via Hatch."""
|
||||
import agent_md
|
||||
audit("md-inject-drive", account)
|
||||
res = agent_md.inject_drive(account, force=force)
|
||||
out(res.pop("ok", True), **res)
|
||||
|
||||
|
||||
def act_md_sync_all(force=False):
|
||||
"""Inject high-drive operational templates across all active agents."""
|
||||
import agent_md
|
||||
audit("md-sync-all")
|
||||
results = {}
|
||||
for acct in agent_md.VALID_ACCOUNTS:
|
||||
try:
|
||||
results[acct] = agent_md.inject_drive(acct, force=force)
|
||||
except Exception as e:
|
||||
results[acct] = {"ok": False, "error": str(e)}
|
||||
out(True, results=results)
|
||||
|
||||
|
||||
def act_loop_resolve(dm_id, note=None):
|
||||
f_path = NETVM_ROOT / "followups.json"
|
||||
resolved = False
|
||||
@@ -2951,7 +3026,7 @@ def main(argv):
|
||||
fail("BAD_NAME", "usage: loop-resolve <dm_id> [note]")
|
||||
note = rest[1] if len(rest) > 1 else None
|
||||
act_loop_resolve(rest[0], note=note)
|
||||
elif action in ("ssh", "ssh-mint", "ssh-list", "ssh-show"):
|
||||
elif action in ("ssh", "ssh-mint", "ssh-list", "ssh-show", "ssh-ports", "ssh-info"):
|
||||
if action == "ssh-mint":
|
||||
if not rest:
|
||||
fail("BAD_ARGS", "usage: ssh-mint <name> [--force]")
|
||||
@@ -2962,9 +3037,13 @@ def main(argv):
|
||||
if not rest:
|
||||
fail("BAD_ARGS", "usage: ssh-show <name>")
|
||||
act_ssh_show(rest[0])
|
||||
elif action == "ssh-ports":
|
||||
act_ssh_ports()
|
||||
elif action == "ssh-info":
|
||||
act_ssh_info(rest[0] if rest else None)
|
||||
elif action == "ssh":
|
||||
if not rest or rest[0] not in ("mint", "list", "show"):
|
||||
fail("BAD_NAME", "usage: ssh mint|list|show [...]")
|
||||
if not rest or rest[0] not in ("mint", "list", "show", "ports", "info"):
|
||||
fail("BAD_NAME", "usage: ssh mint|list|show|ports|info [...]")
|
||||
sub = rest[0]
|
||||
args = rest[1:]
|
||||
if sub == "mint":
|
||||
@@ -2977,6 +3056,79 @@ def main(argv):
|
||||
if not args:
|
||||
fail("BAD_ARGS", "usage: ssh show <name>")
|
||||
act_ssh_show(args[0])
|
||||
elif sub == "ports":
|
||||
act_ssh_ports()
|
||||
elif sub == "info":
|
||||
act_ssh_info(args[0] if args else None)
|
||||
elif action in ("md", "md-audit", "md-list", "md-read", "md-write", "md-diff", "md-inject-drive", "md-sync-all"):
|
||||
if action == "md-audit":
|
||||
act_md_audit(accounts=rest or None)
|
||||
elif action == "md-list":
|
||||
if not rest:
|
||||
fail("BAD_ARGS", "usage: md-list <account> [path]")
|
||||
act_md_list(rest[0], path=rest[1] if len(rest) > 1 else "")
|
||||
elif action == "md-read":
|
||||
if len(rest) < 2:
|
||||
fail("BAD_ARGS", "usage: md-read <account> <filename>")
|
||||
act_md_read(rest[0], rest[1])
|
||||
elif action == "md-write":
|
||||
if len(rest) < 3:
|
||||
fail("BAD_ARGS", "usage: md-write <account> <filename> <content>")
|
||||
act_md_write(rest[0], rest[1], rest[2])
|
||||
elif action == "md-diff":
|
||||
if len(rest) < 2:
|
||||
fail("BAD_ARGS", "usage: md-diff <account> <filename>")
|
||||
act_md_diff(rest[0], rest[1])
|
||||
elif action == "md-inject-drive":
|
||||
if not rest:
|
||||
fail("BAD_ARGS", "usage: md-inject-drive <account> [--force]")
|
||||
act_md_inject_drive(rest[0], force=("--force" in rest[1:]))
|
||||
elif action == "md-sync-all":
|
||||
act_md_sync_all(force=("--force" in rest))
|
||||
elif action == "md":
|
||||
if not rest:
|
||||
fail("BAD_NAME", "usage: md audit|list|read|write|diff|inject-drive|sync-all [...]")
|
||||
sub = rest[0]
|
||||
args = rest[1:]
|
||||
if sub == "audit":
|
||||
act_md_audit(accounts=args or None)
|
||||
elif sub == "list":
|
||||
if not args:
|
||||
fail("BAD_ARGS", "usage: md list <account> [path]")
|
||||
act_md_list(args[0], path=args[1] if len(args) > 1 else "")
|
||||
elif sub == "read":
|
||||
if len(args) < 2:
|
||||
fail("BAD_ARGS", "usage: md read <account> <filename>")
|
||||
act_md_read(args[0], args[1])
|
||||
elif sub == "write":
|
||||
if len(args) < 2:
|
||||
fail("BAD_ARGS", "usage: md write <account> <filename> [--content text | --file path]")
|
||||
content = None
|
||||
if "--file" in args:
|
||||
fidx = args.index("--file")
|
||||
if fidx + 1 < len(args):
|
||||
content = Path(args[fidx + 1]).read_text(encoding="utf-8")
|
||||
elif "--content" in args:
|
||||
cidx = args.index("--content")
|
||||
if cidx + 1 < len(args):
|
||||
content = args[cidx + 1]
|
||||
elif len(args) >= 3:
|
||||
content = args[2]
|
||||
if content is None:
|
||||
fail("BAD_ARGS", "usage: md write <account> <filename> <content>")
|
||||
act_md_write(args[0], args[1], content)
|
||||
elif sub == "diff":
|
||||
if len(args) < 2:
|
||||
fail("BAD_ARGS", "usage: md diff <account> <filename>")
|
||||
act_md_diff(args[0], args[1])
|
||||
elif sub == "inject-drive":
|
||||
if not args:
|
||||
fail("BAD_ARGS", "usage: md inject-drive <account> [--force]")
|
||||
act_md_inject_drive(args[0], force=("--force" in args[1:]))
|
||||
elif sub == "sync-all":
|
||||
act_md_sync_all(force=("--force" in args))
|
||||
else:
|
||||
fail("BAD_NAME", f"unknown md subcommand: {sub}")
|
||||
elif action == "loop-remediate":
|
||||
dry = "--dry-run" in rest
|
||||
act_loop_remediate(dry_run=dry)
|
||||
|
||||
@@ -3110,6 +3110,246 @@ def cmd_ssh_show(args):
|
||||
sys.stdout.write(res.stdout)
|
||||
|
||||
|
||||
def cmd_ssh_ports(args):
|
||||
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "ssh-ports"]
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if args.json:
|
||||
sys.stdout.write(res.stdout)
|
||||
return
|
||||
try:
|
||||
d = json.loads(res.stdout)
|
||||
if d.get("ok"):
|
||||
tunnels = d.get("tunnels", {})
|
||||
jump = d.get("jump_host", "34.139.37.135")
|
||||
print("\n" + c_bold(f"=== CONTAINER SSH & REVERSE TUNNEL REGISTRY (JUMP: {jump}) ===") + "\n")
|
||||
headers = ["AGENT", "SSH PORT", "TERM PORT", "USER", "DIAL-IN COMMAND"]
|
||||
rows = []
|
||||
for name, info in tunnels.items():
|
||||
port = info.get("port")
|
||||
tport = info.get("terminal")
|
||||
u = info.get("user", "hatch")
|
||||
dial = f"ssh -J super@{jump} -p {port} {u}@localhost"
|
||||
rows.append([c_cyan(name), str(port), str(tport), u, c_yellow(dial)])
|
||||
print_table(headers, rows)
|
||||
print("\n" + c_dim(" To execute remote cmd: ssh -J super@<jump> -p <port> hatch@localhost '<cmd>'") + "\n")
|
||||
else:
|
||||
print(c_red(f"✗ Failed: {d.get('error')}"))
|
||||
except Exception:
|
||||
sys.stdout.write(res.stdout)
|
||||
|
||||
|
||||
def cmd_ssh_info(args):
|
||||
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "ssh-info", args.name]
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if args.json:
|
||||
sys.stdout.write(res.stdout)
|
||||
return
|
||||
try:
|
||||
d = json.loads(res.stdout)
|
||||
if d.get("ok"):
|
||||
name = d.get("account", args.name)
|
||||
port = d.get("port")
|
||||
u = d.get("container_user", "hatch")
|
||||
jump = d.get("jump_host", "34.139.37.135")
|
||||
print("\n" + c_bold(f"=== SSH DIAL-IN FOR AGENT: {name} ===") + "\n")
|
||||
print(f" Container User: {c_cyan(u)}")
|
||||
print(f" Reverse SSH Port:{c_yellow(str(port))}")
|
||||
print(f" GCP Jump Host: {jump}")
|
||||
print(f" From VM directly: {c_green(d.get('direct_from_vm', ''))}")
|
||||
print(f" Via Jump Host: {c_green(d.get('jump_command', ''))}")
|
||||
print(f"\n Modify .md via SSH:")
|
||||
print(f" {c_dim(d.get('cat_example', ''))}\n")
|
||||
else:
|
||||
print(c_red(f"✗ Failed: {d.get('error')}"))
|
||||
except Exception:
|
||||
sys.stdout.write(res.stdout)
|
||||
|
||||
|
||||
def cmd_md_audit(args):
|
||||
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "md-audit"] + (args.accounts or [])
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if args.json:
|
||||
sys.stdout.write(res.stdout)
|
||||
return
|
||||
try:
|
||||
d = json.loads(res.stdout)
|
||||
if d.get("ok"):
|
||||
agents = d.get("agents", {})
|
||||
print("\n" + c_bold(f"=== MUSE AGENT .MD & OPERATIONAL DRIVE AUDIT ({len(agents)} AGENTS) ===") + "\n")
|
||||
for acct, info in agents.items():
|
||||
if not info.get("connected"):
|
||||
print(c_red(f"[{acct.upper()}] ✗ Connection failed: {info.get('error')}"))
|
||||
continue
|
||||
score = info.get("drive_score", 0)
|
||||
score_str = c_green(f"{score}/100 [HIGH DRIVE]") if score >= 75 else (c_yellow(f"{score}/100 [PARTIAL]") if score >= 50 else c_red(f"{score}/100 [LOW/STALE]"))
|
||||
vm = info.get("vm_id", "unknown")
|
||||
print(f"[{c_bold(acct.upper())}] Drive: {score_str} VM: {c_dim(vm)}")
|
||||
headers = ["FILE", "STATUS", "SIZE", "MODIFIED"]
|
||||
rows = []
|
||||
for fname, finfo in info.get("files", {}).items():
|
||||
exists = finfo.get("exists")
|
||||
st = c_green("✓ present") if exists else c_red("✗ missing")
|
||||
sz = f"{finfo.get('size', 0)} B"
|
||||
mod = (finfo.get("modified") or "-")[:19]
|
||||
rows.append([fname, st, sz, mod])
|
||||
print_table(headers, rows)
|
||||
issues = info.get("issues", [])
|
||||
if issues:
|
||||
print(c_dim(" Issues / Gaps:"))
|
||||
for iss in issues:
|
||||
print(c_yellow(f" • {iss}"))
|
||||
print()
|
||||
print(c_dim(" Fix agent drive: box md inject-drive <agent> | box md sync-all") + "\n")
|
||||
else:
|
||||
print(c_red(f"✗ Failed: {d.get('error')}"))
|
||||
except Exception:
|
||||
sys.stdout.write(res.stdout)
|
||||
|
||||
|
||||
def cmd_md_list(args):
|
||||
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "md-list", args.account, args.path]
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if args.json:
|
||||
sys.stdout.write(res.stdout)
|
||||
return
|
||||
try:
|
||||
d = json.loads(res.stdout)
|
||||
if d.get("ok"):
|
||||
entries = d.get("entries", [])
|
||||
print("\n" + c_bold(f"=== FILES IN {args.account}:{args.path or '/'} ({len(entries)}) ===") + "\n")
|
||||
headers = ["NAME", "TYPE", "SIZE", "MODIFIED"]
|
||||
rows = []
|
||||
for e in entries:
|
||||
t = e.get("type", "file")
|
||||
name_colored = c_cyan(e.get("name")) if t == "directory" else e.get("name")
|
||||
rows.append([name_colored, t, str(e.get("size", "-")), (e.get("modifiedAt") or "-")[:19]])
|
||||
print_table(headers, rows)
|
||||
print()
|
||||
else:
|
||||
print(c_red(f"✗ Failed: {d.get('error')}"))
|
||||
except Exception:
|
||||
sys.stdout.write(res.stdout)
|
||||
|
||||
|
||||
def cmd_md_read(args):
|
||||
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "md-read", args.account, args.filename]
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if args.json:
|
||||
sys.stdout.write(res.stdout)
|
||||
return
|
||||
try:
|
||||
d = json.loads(res.stdout)
|
||||
if d.get("ok"):
|
||||
text = d.get("text", "")
|
||||
print(f"\n{c_bold('--- ' + args.account + ':' + args.filename + ' (' + str(len(text)) + ' chars) ---')}\n")
|
||||
print(text)
|
||||
print()
|
||||
else:
|
||||
print(c_red(f"✗ Failed: {d.get('error')}"))
|
||||
except Exception:
|
||||
sys.stdout.write(res.stdout)
|
||||
|
||||
|
||||
def cmd_md_write(args):
|
||||
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "md-write", args.account, args.filename]
|
||||
if args.file:
|
||||
cmd.extend(["--file", args.file])
|
||||
elif args.content:
|
||||
cmd.extend(["--content", args.content])
|
||||
else:
|
||||
print(c_red("Error: specify --content or --file"))
|
||||
return
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if args.json:
|
||||
sys.stdout.write(res.stdout)
|
||||
return
|
||||
try:
|
||||
d = json.loads(res.stdout)
|
||||
if d.get("ok"):
|
||||
print(c_green(f"✓ Wrote {d.get('bytes_written')} bytes to {args.account}:{args.filename} via Hatch"))
|
||||
else:
|
||||
print(c_red(f"✗ Failed: {d.get('error')}"))
|
||||
except Exception:
|
||||
sys.stdout.write(res.stdout)
|
||||
|
||||
|
||||
def cmd_md_diff(args):
|
||||
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "md-diff", args.account, args.filename]
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if args.json:
|
||||
sys.stdout.write(res.stdout)
|
||||
return
|
||||
try:
|
||||
d = json.loads(res.stdout)
|
||||
if d.get("ok"):
|
||||
if d.get("identical"):
|
||||
print(c_green(f"✓ {args.account}:{args.filename} matches local shared/operators/{args.filename} exactly."))
|
||||
else:
|
||||
print(f"\n{c_bold('=== DIFF: ' + args.account + ':' + args.filename + ' vs shared/operators/' + args.filename + ' ===')}\n")
|
||||
diff = d.get("diff", "")
|
||||
for line in diff.splitlines():
|
||||
if line.startswith("+"):
|
||||
print(c_green(line))
|
||||
elif line.startswith("-"):
|
||||
print(c_red(line))
|
||||
elif line.startswith("@@"):
|
||||
print(c_cyan(line))
|
||||
else:
|
||||
print(line)
|
||||
print()
|
||||
else:
|
||||
print(c_red(f"✗ Failed: {d.get('error')}"))
|
||||
except Exception:
|
||||
sys.stdout.write(res.stdout)
|
||||
|
||||
|
||||
def cmd_md_inject_drive(args):
|
||||
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "md-inject-drive", args.account]
|
||||
if args.force:
|
||||
cmd.append("--force")
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if args.json:
|
||||
sys.stdout.write(res.stdout)
|
||||
return
|
||||
try:
|
||||
d = json.loads(res.stdout)
|
||||
if d.get("ok"):
|
||||
print("\n" + c_bold(f"=== INJECTED OPERATIONAL DRIVE INTO AGENT: {args.account.upper()} ===") + "\n")
|
||||
for u in d.get("updates", []):
|
||||
print(c_green(f" ✓ Injected {u.get('file'):26} ({u.get('bytes')} bytes)"))
|
||||
print(f"\n{c_green('✓ Successfully activated SOUL.md, PROACTIVE_PREFERENCES.md, and HEARTBEAT.md!')}\n")
|
||||
else:
|
||||
print(c_red(f"✗ Failed: {d.get('error')}"))
|
||||
except Exception:
|
||||
sys.stdout.write(res.stdout)
|
||||
|
||||
|
||||
def cmd_md_sync_all(args):
|
||||
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "md-sync-all"]
|
||||
if args.force:
|
||||
cmd.append("--force")
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if args.json:
|
||||
sys.stdout.write(res.stdout)
|
||||
return
|
||||
try:
|
||||
d = json.loads(res.stdout)
|
||||
if d.get("ok"):
|
||||
print("\n" + c_bold("=== FLEET-WIDE OPERATIONAL DRIVE INJECTION ===") + "\n")
|
||||
results = d.get("results", {})
|
||||
for acct, r in results.items():
|
||||
if r.get("ok"):
|
||||
files_str = ", ".join(u.get("file") for u in r.get("updates", []))
|
||||
print(c_green(f" ✓ {acct.upper():6} drive injected ({files_str})"))
|
||||
else:
|
||||
print(c_red(f" ✗ {acct.upper():6} failed: {r.get('error')}"))
|
||||
print(f"\n{c_green('✓ Fleet synchronization complete.')}\n")
|
||||
else:
|
||||
print(c_red(f"✗ Failed: {d.get('error')}"))
|
||||
except Exception:
|
||||
sys.stdout.write(res.stdout)
|
||||
|
||||
|
||||
def cmd_swarm_list(args):
|
||||
cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "swarm-list"]
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
@@ -3433,6 +3673,42 @@ def build_parser():
|
||||
p_s_show = ssh_sub.add_parser("show", parents=[common], help="Show public key content and fingerprint")
|
||||
p_s_show.add_argument("name", help="Identity/agent name")
|
||||
|
||||
p_s_ports = ssh_sub.add_parser("ports", parents=[common], help="List container reverse tunnel port mappings")
|
||||
p_s_info = ssh_sub.add_parser("info", parents=[common], help="Show SSH dial-in commands and reverse tunnel coordinates")
|
||||
p_s_info.add_argument("name", help="Agent identity (e.g. 646, muse, pip)")
|
||||
|
||||
# Domain: MD (Hatch Agent Markdown & Operational Drive)
|
||||
p_md = subparsers.add_parser("md", parents=[common], help="Manage agent .md system files & inject operational DRIVE via Hatch")
|
||||
md_sub = p_md.add_subparsers(dest="action")
|
||||
|
||||
p_md_audit = md_sub.add_parser("audit", parents=[common], help="Audit .md files and DRIVE scores across fleet agents")
|
||||
p_md_audit.add_argument("accounts", nargs="*", help="Optional account filter")
|
||||
|
||||
p_md_list = md_sub.add_parser("list", parents=[common], help="List files in agent container via Hatch")
|
||||
p_md_list.add_argument("account", help="Agent account (e.g. muse, pip, 646, opm, def, dev)")
|
||||
p_md_list.add_argument("path", nargs="?", default="", help="Subdirectory path")
|
||||
|
||||
p_md_read = md_sub.add_parser("read", parents=[common], help="Read an agent .md file via Hatch")
|
||||
p_md_read.add_argument("account", help="Agent account")
|
||||
p_md_read.add_argument("filename", help="Filename (e.g. SOUL.md, HEARTBEAT.md)")
|
||||
|
||||
p_md_write = md_sub.add_parser("write", parents=[common], help="Write an agent .md file via Hatch")
|
||||
p_md_write.add_argument("account", help="Agent account")
|
||||
p_md_write.add_argument("filename", help="Filename (e.g. SOUL.md)")
|
||||
p_md_write.add_argument("--content", help="Text content to write")
|
||||
p_md_write.add_argument("--file", help="Local file to copy from")
|
||||
|
||||
p_md_diff = md_sub.add_parser("diff", parents=[common], help="Diff container .md file against shared operator template")
|
||||
p_md_diff.add_argument("account", help="Agent account")
|
||||
p_md_diff.add_argument("filename", help="Filename (e.g. SOUL.md)")
|
||||
|
||||
p_md_drive = md_sub.add_parser("inject-drive", parents=[common], help="Inject high-drive operator files into an agent container")
|
||||
p_md_drive.add_argument("account", help="Agent account")
|
||||
p_md_drive.add_argument("--force", action="store_true", help="Force overwrite")
|
||||
|
||||
p_md_sync = md_sub.add_parser("sync-all", parents=[common], help="Inject high-drive operator files across all active fleet agents")
|
||||
p_md_sync.add_argument("--force", action="store_true", help="Force overwrite")
|
||||
|
||||
# Domain: HARVEST
|
||||
p_harvest = subparsers.add_parser("harvest", parents=[common], help="Readback & response harvesting engine")
|
||||
harvest_sub = p_harvest.add_subparsers(dest="action")
|
||||
@@ -3771,8 +4047,30 @@ def main():
|
||||
cmd_ssh_mint(args)
|
||||
elif act == "show":
|
||||
cmd_ssh_show(args)
|
||||
elif act == "ports":
|
||||
cmd_ssh_ports(args)
|
||||
elif act == "info":
|
||||
cmd_ssh_info(args)
|
||||
else:
|
||||
p_ssh.print_help()
|
||||
elif args.domain == "md":
|
||||
act = getattr(args, "action", None)
|
||||
if not act or act == "audit":
|
||||
cmd_md_audit(args)
|
||||
elif act == "list":
|
||||
cmd_md_list(args)
|
||||
elif act == "read":
|
||||
cmd_md_read(args)
|
||||
elif act == "write":
|
||||
cmd_md_write(args)
|
||||
elif act == "diff":
|
||||
cmd_md_diff(args)
|
||||
elif act == "inject-drive":
|
||||
cmd_md_inject_drive(args)
|
||||
elif act == "sync-all":
|
||||
cmd_md_sync_all(args)
|
||||
else:
|
||||
p_md.print_help()
|
||||
elif args.domain == "harvest":
|
||||
act = getattr(args, "action", None)
|
||||
if not act or act == "status":
|
||||
|
||||
Reference in New Issue
Block a user