@smilintux/skcapstone 0.4.5 → 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 (44) 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/blueprint_registry.py +78 -0
  20. package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
  21. package/src/skcapstone/cli/__init__.py +2 -0
  22. package/src/skcapstone/cli/daemon.py +116 -41
  23. package/src/skcapstone/cli/itil.py +434 -0
  24. package/src/skcapstone/cli/skills_cmd.py +90 -26
  25. package/src/skcapstone/cli/soul.py +47 -24
  26. package/src/skcapstone/consciousness_config.py +27 -0
  27. package/src/skcapstone/coordination.py +1 -0
  28. package/src/skcapstone/daemon.py +47 -20
  29. package/src/skcapstone/dreaming.py +761 -0
  30. package/src/skcapstone/fuse_mount.py +21 -13
  31. package/src/skcapstone/heartbeat.py +33 -29
  32. package/src/skcapstone/itil.py +1104 -0
  33. package/src/skcapstone/launchd.py +426 -0
  34. package/src/skcapstone/mcp_server.py +258 -0
  35. package/src/skcapstone/mcp_tools/__init__.py +2 -0
  36. package/src/skcapstone/mcp_tools/gtd_tools.py +1 -1
  37. package/src/skcapstone/mcp_tools/itil_tools.py +657 -0
  38. package/src/skcapstone/mcp_tools/notification_tools.py +12 -11
  39. package/src/skcapstone/notifications.py +40 -27
  40. package/src/skcapstone/onboard.py +130 -10
  41. package/src/skcapstone/scheduled_tasks.py +107 -0
  42. package/src/skcapstone/service_health.py +81 -2
  43. package/src/skcapstone/soul.py +19 -0
  44. package/src/skcapstone/systemd.py +17 -0
@@ -517,22 +517,36 @@ def register_soul_commands(main: click.Group) -> None:
517
517
  @soul_registry.command("list")
518
518
  @click.option("--url", default=None, help="Registry API base URL.")
519
519
  def registry_list(url):
520
- """List all blueprints in the remote registry."""
521
- from ..blueprint_registry import BlueprintRegistryClient, BlueprintRegistryError
520
+ """List all blueprints in the remote registry.
522
521
 
523
- kwargs = {}
522
+ Pulls from the soul-blueprints GitHub repo. Falls back to
523
+ the souls.skworld.io API if --url is set.
524
+ """
525
+ from ..blueprint_registry import (
526
+ BlueprintRegistryClient,
527
+ BlueprintRegistryError,
528
+ _fetch_github_blueprints,
529
+ )
530
+
531
+ blueprints = None
532
+ source = "github"
533
+
534
+ # If custom URL, try the API server first
524
535
  if url:
525
- kwargs["base_url"] = url
526
- client = BlueprintRegistryClient(**kwargs)
536
+ try:
537
+ client = BlueprintRegistryClient(base_url=url)
538
+ blueprints = client.list_blueprints()
539
+ source = "registry"
540
+ except BlueprintRegistryError:
541
+ pass
527
542
 
528
- try:
529
- blueprints = client.list_blueprints()
530
- except BlueprintRegistryError as e:
531
- console.print(f"\n [red]Registry error:[/] {e}\n")
532
- sys.exit(1)
543
+ # Default: pull from GitHub repo
544
+ if blueprints is None:
545
+ blueprints = _fetch_github_blueprints()
546
+ source = "github"
533
547
 
534
548
  if not blueprints:
535
- console.print("\n [dim]No blueprints found in the registry.[/]\n")
549
+ console.print("\n [dim]No blueprints found.[/]\n")
536
550
  return
537
551
 
538
552
  table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
@@ -547,27 +561,38 @@ def register_soul_commands(main: click.Group) -> None:
547
561
  bp.get("category", ""),
548
562
  )
549
563
 
564
+ source_label = "" if source == "github" else " [dim](registry)[/]"
550
565
  console.print()
551
566
  console.print(table)
552
- console.print(f"\n [dim]{len(blueprints)} blueprint(s) in registry[/]\n")
567
+ console.print(f"\n [dim]{len(blueprints)} blueprint(s){source_label}[/]\n")
553
568
 
554
569
  @soul_registry.command("search")
555
570
  @click.argument("query")
556
571
  @click.option("--url", default=None, help="Registry API base URL.")
557
572
  def registry_search(query, url):
558
- """Search the remote registry for blueprints."""
559
- from ..blueprint_registry import BlueprintRegistryClient, BlueprintRegistryError
573
+ """Search the remote registry for blueprints.
560
574
 
561
- kwargs = {}
575
+ Searches the soul-blueprints GitHub repo by name and category.
576
+ """
577
+ from ..blueprint_registry import (
578
+ BlueprintRegistryClient,
579
+ BlueprintRegistryError,
580
+ _fetch_github_blueprints,
581
+ )
582
+
583
+ results = None
584
+
585
+ # If custom URL, try the API server first
562
586
  if url:
563
- kwargs["base_url"] = url
564
- client = BlueprintRegistryClient(**kwargs)
587
+ try:
588
+ client = BlueprintRegistryClient(base_url=url)
589
+ results = client.search_blueprints(query)
590
+ except BlueprintRegistryError:
591
+ pass
565
592
 
566
- try:
567
- results = client.search_blueprints(query)
568
- except BlueprintRegistryError as e:
569
- console.print(f"\n [red]Registry error:[/] {e}\n")
570
- sys.exit(1)
593
+ # Default: search GitHub repo
594
+ if results is None:
595
+ results = _fetch_github_blueprints(query)
571
596
 
572
597
  if not results:
573
598
  console.print(f"\n [dim]No blueprints matching '{query}'.[/]\n")
@@ -577,14 +602,12 @@ def register_soul_commands(main: click.Group) -> None:
577
602
  table.add_column("Name", style="cyan", no_wrap=True)
578
603
  table.add_column("Display Name", style="bold")
579
604
  table.add_column("Category", style="yellow")
580
- table.add_column("Vibe", style="dim")
581
605
 
582
606
  for bp in results:
583
607
  table.add_row(
584
608
  bp.get("name", "?"),
585
609
  bp.get("display_name", ""),
586
610
  bp.get("category", ""),
587
- (bp.get("vibe", "") or "")[:60],
588
611
  )
589
612
 
590
613
  console.print()
@@ -117,3 +117,30 @@ def write_default_config(home: Path) -> Path:
117
117
  config_path.write_text(header + content, encoding="utf-8")
118
118
  logger.info("Wrote default consciousness config to %s", config_path)
119
119
  return config_path
120
+
121
+
122
+ def load_dreaming_config(
123
+ home: Path,
124
+ config_path: Optional[Path] = None,
125
+ ):
126
+ """Load dreaming config from the consciousness.yaml ``dreaming:`` section.
127
+
128
+ Args:
129
+ home: Agent home directory.
130
+ config_path: Explicit path to config file (overrides default).
131
+
132
+ Returns:
133
+ DreamingConfig (defaults if section is missing or unparseable).
134
+ """
135
+ from .dreaming import DreamingConfig
136
+
137
+ yaml_path = config_path or (home / "config" / CONFIG_FILENAME)
138
+ if not yaml_path.exists():
139
+ return DreamingConfig()
140
+ try:
141
+ raw = yaml.safe_load(yaml_path.read_text(encoding="utf-8"))
142
+ if raw and isinstance(raw, dict) and "dreaming" in raw:
143
+ return DreamingConfig.model_validate(raw["dreaming"])
144
+ except Exception as exc:
145
+ logger.warning("Failed to parse dreaming config: %s", exc)
146
+ return DreamingConfig()
@@ -123,6 +123,7 @@ class AgentFile(BaseModel):
123
123
  claimed_tasks: list[str] = Field(default_factory=list)
124
124
  completed_tasks: list[str] = Field(default_factory=list)
125
125
  capabilities: list[str] = Field(default_factory=list)
126
+ itil_claims: list[str] = Field(default_factory=list)
126
127
  notes: str = ""
127
128
 
128
129
 
@@ -1145,16 +1145,35 @@ class DaemonService:
1145
1145
  logger.debug("Auto-journal write failed: %s", exc)
1146
1146
 
1147
1147
  try:
1148
- from .memory_engine import store as mem_store
1149
- mem_store(
1150
- self.config.home,
1151
- f"Received message from {sender}: {preview}",
1152
- tags=["skcomm-received"],
1153
- source="daemon",
1154
- )
1155
- logger.debug("Memory stored for incoming message from %s", sender)
1148
+ self._store_skcomm_receipt(sender, preview)
1149
+ logger.debug("SKComm receipt stored for incoming message from %s", sender)
1156
1150
  except Exception as exc:
1157
- logger.debug("Auto-journal memory store failed: %s", exc)
1151
+ logger.debug("SKComm receipt store failed: %s", exc)
1152
+
1153
+ def _store_skcomm_receipt(self, sender: str, preview: str) -> None:
1154
+ """Write a skcomm receipt to the skcomm/received/ directory.
1155
+
1156
+ These are transport bookkeeping, NOT persistent memories, so they
1157
+ go to ``~/.skcapstone/agents/{agent}/skcomm/received/`` instead of
1158
+ polluting the memory/ tree that skmemory indexes.
1159
+ """
1160
+ import json
1161
+ import uuid
1162
+ from datetime import datetime, timezone
1163
+
1164
+ agent_name = os.environ.get("SKCAPSTONE_AGENT", "lumina")
1165
+ recv_dir = self.config.home / "agents" / agent_name / "skcomm" / "received"
1166
+ recv_dir.mkdir(parents=True, exist_ok=True)
1167
+
1168
+ receipt_id = uuid.uuid4().hex[:12]
1169
+ receipt = {
1170
+ "id": receipt_id,
1171
+ "sender": sender,
1172
+ "preview": preview,
1173
+ "received_at": datetime.now(timezone.utc).isoformat(),
1174
+ }
1175
+ path = recv_dir / f"{receipt_id}.json"
1176
+ path.write_text(json.dumps(receipt, indent=2), encoding="utf-8")
1158
1177
 
1159
1178
  def _healing_loop(self) -> None:
1160
1179
  """Periodically run self-healing diagnostics (every 5 min)."""
@@ -1259,17 +1278,25 @@ class DaemonService:
1259
1278
  except Exception:
1260
1279
  stats.update(disk_total_gb=0, disk_used_gb=0, disk_free_gb=0)
1261
1280
  try:
1262
- meminfo: dict = {}
1263
- with open("/proc/meminfo") as fh:
1264
- for line in fh:
1265
- parts = line.split()
1266
- if len(parts) >= 2:
1267
- meminfo[parts[0].rstrip(":")] = int(parts[1])
1268
- total_kb = meminfo.get("MemTotal", 0)
1269
- avail_kb = meminfo.get("MemAvailable", 0)
1270
- stats["memory_total_mb"] = round(total_kb / 1024)
1271
- stats["memory_used_mb"] = round((total_kb - avail_kb) / 1024)
1272
- stats["memory_free_mb"] = round(avail_kb / 1024)
1281
+ import platform as _platform
1282
+ if _platform.system() == "Linux":
1283
+ meminfo: dict = {}
1284
+ with open("/proc/meminfo") as fh:
1285
+ for line in fh:
1286
+ parts = line.split()
1287
+ if len(parts) >= 2:
1288
+ meminfo[parts[0].rstrip(":")] = int(parts[1])
1289
+ total_kb = meminfo.get("MemTotal", 0)
1290
+ avail_kb = meminfo.get("MemAvailable", 0)
1291
+ stats["memory_total_mb"] = round(total_kb / 1024)
1292
+ stats["memory_used_mb"] = round((total_kb - avail_kb) / 1024)
1293
+ stats["memory_free_mb"] = round(avail_kb / 1024)
1294
+ else:
1295
+ import psutil
1296
+ mem = psutil.virtual_memory()
1297
+ stats["memory_total_mb"] = round(mem.total / (1024 * 1024))
1298
+ stats["memory_used_mb"] = round((mem.total - mem.available) / (1024 * 1024))
1299
+ stats["memory_free_mb"] = round(mem.available / (1024 * 1024))
1273
1300
  except Exception:
1274
1301
  stats.update(memory_total_mb=0, memory_used_mb=0, memory_free_mb=0)
1275
1302
  return stats