@smilintux/skcapstone 0.4.6 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.github/workflows/publish.yml +8 -1
  2. package/docs/CUSTOM_AGENT.md +184 -0
  3. package/docs/GETTING_STARTED.md +3 -0
  4. package/launchd/com.skcapstone.daemon.plist +52 -0
  5. package/launchd/com.skcapstone.memory-compress.plist +45 -0
  6. package/launchd/com.skcapstone.skcomm-heartbeat.plist +33 -0
  7. package/launchd/com.skcapstone.skcomm-queue-drain.plist +34 -0
  8. package/launchd/install-launchd.sh +156 -0
  9. package/package.json +1 -1
  10. package/pyproject.toml +1 -1
  11. package/scripts/archive-sessions.sh +88 -0
  12. package/scripts/install.sh +39 -8
  13. package/scripts/notion-api.py +259 -0
  14. package/scripts/nvidia-proxy.mjs +856 -0
  15. package/scripts/proxy-monitor.sh +89 -0
  16. package/scripts/skgateway.mjs +856 -0
  17. package/scripts/telegram-catchup-all.sh +136 -0
  18. package/src/skcapstone/__init__.py +1 -1
  19. package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
  20. package/src/skcapstone/cli/__init__.py +2 -0
  21. package/src/skcapstone/cli/daemon.py +116 -41
  22. package/src/skcapstone/cli/itil.py +434 -0
  23. package/src/skcapstone/consciousness_config.py +27 -0
  24. package/src/skcapstone/coordination.py +1 -0
  25. package/src/skcapstone/daemon.py +19 -11
  26. package/src/skcapstone/dreaming.py +761 -0
  27. package/src/skcapstone/fuse_mount.py +21 -13
  28. package/src/skcapstone/heartbeat.py +33 -29
  29. package/src/skcapstone/itil.py +1104 -0
  30. package/src/skcapstone/launchd.py +426 -0
  31. package/src/skcapstone/mcp_server.py +258 -0
  32. package/src/skcapstone/mcp_tools/__init__.py +2 -0
  33. package/src/skcapstone/mcp_tools/gtd_tools.py +1 -1
  34. package/src/skcapstone/mcp_tools/itil_tools.py +657 -0
  35. package/src/skcapstone/onboard.py +130 -10
  36. package/src/skcapstone/scheduled_tasks.py +107 -0
  37. package/src/skcapstone/service_health.py +81 -2
  38. package/src/skcapstone/systemd.py +17 -0
@@ -31,6 +31,7 @@ import errno
31
31
  import json
32
32
  import logging
33
33
  import os
34
+ import platform
34
35
  import stat
35
36
  import subprocess
36
37
  import sys
@@ -1001,16 +1002,17 @@ class FUSEDaemon:
1001
1002
  mount_str = str(self._mount_point)
1002
1003
 
1003
1004
  # Linux: parse /proc/mounts
1004
- proc_mounts = Path("/proc/mounts")
1005
- if proc_mounts.exists():
1006
- try:
1007
- for line in proc_mounts.read_text(encoding="utf-8").splitlines():
1008
- parts = line.split()
1009
- if len(parts) >= 2 and parts[1] == mount_str:
1010
- return True
1011
- except OSError as exc:
1012
- logger.warning("Failed to read /proc/mounts: %s", exc)
1013
- return False
1005
+ if platform.system() == "Linux":
1006
+ proc_mounts = Path("/proc/mounts")
1007
+ if proc_mounts.exists():
1008
+ try:
1009
+ for line in proc_mounts.read_text(encoding="utf-8").splitlines():
1010
+ parts = line.split()
1011
+ if len(parts) >= 2 and parts[1] == mount_str:
1012
+ return True
1013
+ except OSError as exc:
1014
+ logger.warning("Failed to read /proc/mounts: %s", exc)
1015
+ return False
1014
1016
 
1015
1017
  # macOS / other: use mount command
1016
1018
  try:
@@ -1113,8 +1115,13 @@ class FUSEDaemon:
1113
1115
 
1114
1116
  mount_str = str(self._mount_point)
1115
1117
 
1116
- # Linux: fusermount
1117
- for cmd in (["fusermount", "-u", mount_str], ["umount", mount_str]):
1118
+ # On Linux try fusermount first, then umount; on macOS skip fusermount
1119
+ if platform.system() == "Linux":
1120
+ unmount_cmds = [["fusermount", "-u", mount_str], ["umount", mount_str]]
1121
+ else:
1122
+ unmount_cmds = [["umount", mount_str]]
1123
+
1124
+ for cmd in unmount_cmds:
1118
1125
  try:
1119
1126
  result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
1120
1127
  if result.returncode == 0:
@@ -1130,7 +1137,8 @@ class FUSEDaemon:
1130
1137
  except (FileNotFoundError, subprocess.TimeoutExpired, OSError) as exc:
1131
1138
  logger.debug("Unmount command %s failed: %s", cmd, exc)
1132
1139
 
1133
- logger.error("Could not unmount %s try: fusermount -u %s", mount_str, mount_str)
1140
+ hint = "fusermount -u" if platform.system() == "Linux" else "umount"
1141
+ logger.error("Could not unmount %s — try: %s %s", mount_str, hint, mount_str)
1134
1142
  return False
1135
1143
 
1136
1144
  def status(self) -> Dict[str, Any]:
@@ -398,27 +398,30 @@ class HeartbeatBeacon:
398
398
  mem = psutil.virtual_memory()
399
399
  mem_used_mb = (mem.total - mem.available) // (1024 * 1024)
400
400
  except ImportError:
401
- # Fallback: /proc on Linux
402
- try:
403
- loadavg = Path("/proc/loadavg")
404
- if loadavg.exists():
405
- parts = loadavg.read_text().split()
406
- cpu_load = round(float(parts[0]), 2)
407
- except Exception as exc:
408
- logger.debug("CPU load fallback failed: %s", exc)
409
- try:
410
- meminfo = Path("/proc/meminfo")
411
- if meminfo.exists():
412
- info: dict[str, int] = {}
413
- for line in meminfo.read_text().splitlines():
414
- parts = line.split()
415
- if len(parts) >= 2:
416
- info[parts[0].rstrip(":")] = int(parts[1])
417
- total_kb = info.get("MemTotal", 0)
418
- avail_kb = info.get("MemAvailable", 0)
419
- mem_used_mb = (total_kb - avail_kb) // 1024
420
- except Exception as exc:
421
- logger.debug("Memory fallback failed: %s", exc)
401
+ # Fallback: /proc on Linux only
402
+ if platform.system() == "Linux":
403
+ try:
404
+ loadavg = Path("/proc/loadavg")
405
+ if loadavg.exists():
406
+ parts = loadavg.read_text().split()
407
+ cpu_load = round(float(parts[0]), 2)
408
+ except Exception as exc:
409
+ logger.debug("CPU load fallback failed: %s", exc)
410
+ try:
411
+ meminfo = Path("/proc/meminfo")
412
+ if meminfo.exists():
413
+ info: dict[str, int] = {}
414
+ for line in meminfo.read_text().splitlines():
415
+ parts = line.split()
416
+ if len(parts) >= 2:
417
+ info[parts[0].rstrip(":")] = int(parts[1])
418
+ total_kb = info.get("MemTotal", 0)
419
+ avail_kb = info.get("MemAvailable", 0)
420
+ mem_used_mb = (total_kb - avail_kb) // 1024
421
+ except Exception as exc:
422
+ logger.debug("Memory fallback failed: %s", exc)
423
+ else:
424
+ logger.debug("psutil not available and not on Linux; skipping CPU/mem detection")
422
425
  except Exception as exc:
423
426
  logger.debug("CPU/mem detection failed: %s", exc)
424
427
 
@@ -439,14 +442,15 @@ class HeartbeatBeacon:
439
442
  mem_total = mem.total // (1024 * 1024)
440
443
  mem_avail = mem.available // (1024 * 1024)
441
444
  except ImportError:
442
- # Fallback: read from /proc/meminfo on Linux
443
- meminfo = Path("/proc/meminfo")
444
- if meminfo.exists():
445
- for line in meminfo.read_text().splitlines():
446
- if line.startswith("MemTotal:"):
447
- mem_total = int(line.split()[1]) // 1024
448
- elif line.startswith("MemAvailable:"):
449
- mem_avail = int(line.split()[1]) // 1024
445
+ # Fallback: read from /proc/meminfo on Linux only
446
+ if platform.system() == "Linux":
447
+ meminfo = Path("/proc/meminfo")
448
+ if meminfo.exists():
449
+ for line in meminfo.read_text().splitlines():
450
+ if line.startswith("MemTotal:"):
451
+ mem_total = int(line.split()[1]) // 1024
452
+ elif line.startswith("MemAvailable:"):
453
+ mem_avail = int(line.split()[1]) // 1024
450
454
 
451
455
  gpu_available = False
452
456
  gpu_name = ""