@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.
- package/.github/workflows/publish.yml +8 -1
- package/docs/CUSTOM_AGENT.md +184 -0
- package/docs/GETTING_STARTED.md +3 -0
- package/launchd/com.skcapstone.daemon.plist +52 -0
- package/launchd/com.skcapstone.memory-compress.plist +45 -0
- package/launchd/com.skcapstone.skcomm-heartbeat.plist +33 -0
- package/launchd/com.skcapstone.skcomm-queue-drain.plist +34 -0
- package/launchd/install-launchd.sh +156 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/archive-sessions.sh +88 -0
- package/scripts/install.sh +39 -8
- package/scripts/notion-api.py +259 -0
- package/scripts/nvidia-proxy.mjs +856 -0
- package/scripts/proxy-monitor.sh +89 -0
- package/scripts/skgateway.mjs +856 -0
- package/scripts/telegram-catchup-all.sh +136 -0
- package/src/skcapstone/__init__.py +1 -1
- package/src/skcapstone/blueprint_registry.py +78 -0
- package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
- package/src/skcapstone/cli/__init__.py +2 -0
- package/src/skcapstone/cli/daemon.py +116 -41
- package/src/skcapstone/cli/itil.py +434 -0
- package/src/skcapstone/cli/skills_cmd.py +90 -26
- package/src/skcapstone/cli/soul.py +47 -24
- package/src/skcapstone/consciousness_config.py +27 -0
- package/src/skcapstone/coordination.py +1 -0
- package/src/skcapstone/daemon.py +47 -20
- package/src/skcapstone/dreaming.py +761 -0
- package/src/skcapstone/fuse_mount.py +21 -13
- package/src/skcapstone/heartbeat.py +33 -29
- package/src/skcapstone/itil.py +1104 -0
- package/src/skcapstone/launchd.py +426 -0
- package/src/skcapstone/mcp_server.py +258 -0
- package/src/skcapstone/mcp_tools/__init__.py +2 -0
- package/src/skcapstone/mcp_tools/gtd_tools.py +1 -1
- package/src/skcapstone/mcp_tools/itil_tools.py +657 -0
- package/src/skcapstone/mcp_tools/notification_tools.py +12 -11
- package/src/skcapstone/notifications.py +40 -27
- package/src/skcapstone/onboard.py +130 -10
- package/src/skcapstone/scheduled_tasks.py +107 -0
- package/src/skcapstone/service_health.py +81 -2
- package/src/skcapstone/soul.py +19 -0
- 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
|
-
|
|
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
|
-
|
|
526
|
-
|
|
536
|
+
try:
|
|
537
|
+
client = BlueprintRegistryClient(base_url=url)
|
|
538
|
+
blueprints = client.list_blueprints()
|
|
539
|
+
source = "registry"
|
|
540
|
+
except BlueprintRegistryError:
|
|
541
|
+
pass
|
|
527
542
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
564
|
-
|
|
587
|
+
try:
|
|
588
|
+
client = BlueprintRegistryClient(base_url=url)
|
|
589
|
+
results = client.search_blueprints(query)
|
|
590
|
+
except BlueprintRegistryError:
|
|
591
|
+
pass
|
|
565
592
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
|
package/src/skcapstone/daemon.py
CHANGED
|
@@ -1145,16 +1145,35 @@ class DaemonService:
|
|
|
1145
1145
|
logger.debug("Auto-journal write failed: %s", exc)
|
|
1146
1146
|
|
|
1147
1147
|
try:
|
|
1148
|
-
|
|
1149
|
-
|
|
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("
|
|
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
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
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
|