@smilintux/skcapstone 0.4.3 → 0.4.4
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/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/skcapstone/__init__.py +70 -1
- package/src/skcapstone/cli/card.py +19 -2
- package/src/skcapstone/cli/config_cmd.py +53 -1
- package/src/skcapstone/cli/peer.py +3 -1
- package/src/skcapstone/cli/peers_dir.py +3 -1
- package/src/skcapstone/cli/preflight_cmd.py +4 -0
- package/src/skcapstone/cli/usage_cmd.py +7 -2
- package/src/skcapstone/doctor.py +115 -0
- package/src/skcapstone/onboard.py +46 -0
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "skcapstone"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.4"
|
|
8
8
|
description = "Sovereign Agent Framework — conscious AI through identity, trust, memory, and security"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "GPL-3.0-or-later"}
|
|
@@ -11,7 +11,7 @@ import os
|
|
|
11
11
|
import platform
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
-
__version__ = "0.4.
|
|
14
|
+
__version__ = "0.4.4"
|
|
15
15
|
__author__ = "smilinTux"
|
|
16
16
|
|
|
17
17
|
|
|
@@ -76,3 +76,72 @@ def shared_home() -> Path:
|
|
|
76
76
|
Path to the shared skcapstone root.
|
|
77
77
|
"""
|
|
78
78
|
return Path(AGENT_HOME).expanduser()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def ensure_skeleton(agent_name: str | None = None) -> None:
|
|
82
|
+
"""Create all expected directories for the shared root and agent home.
|
|
83
|
+
|
|
84
|
+
Idempotent — safe to call multiple times. Creates any missing
|
|
85
|
+
directories so that all CLI commands and services find the paths
|
|
86
|
+
they expect.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
agent_name: Agent name (defaults to SKCAPSTONE_AGENT).
|
|
90
|
+
"""
|
|
91
|
+
root = shared_home()
|
|
92
|
+
name = agent_name or SKCAPSTONE_AGENT
|
|
93
|
+
agent_dir = root / "agents" / name
|
|
94
|
+
|
|
95
|
+
# Shared root directories
|
|
96
|
+
for d in (
|
|
97
|
+
root / "config",
|
|
98
|
+
root / "identity",
|
|
99
|
+
root / "security",
|
|
100
|
+
root / "skills",
|
|
101
|
+
root / "heartbeats",
|
|
102
|
+
root / "peers",
|
|
103
|
+
root / "coordination" / "tasks",
|
|
104
|
+
root / "coordination" / "agents",
|
|
105
|
+
root / "logs",
|
|
106
|
+
root / "comms" / "inbox",
|
|
107
|
+
root / "comms" / "outbox",
|
|
108
|
+
root / "comms" / "archive",
|
|
109
|
+
root / "archive",
|
|
110
|
+
root / "deployments",
|
|
111
|
+
root / "docs",
|
|
112
|
+
root / "metrics",
|
|
113
|
+
root / "memory",
|
|
114
|
+
root / "sync" / "outbox",
|
|
115
|
+
root / "sync" / "inbox",
|
|
116
|
+
root / "sync" / "archive",
|
|
117
|
+
root / "trust" / "febs",
|
|
118
|
+
):
|
|
119
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
120
|
+
|
|
121
|
+
# Per-agent directories
|
|
122
|
+
for d in (
|
|
123
|
+
agent_dir / "memory" / "short-term",
|
|
124
|
+
agent_dir / "memory" / "mid-term",
|
|
125
|
+
agent_dir / "memory" / "long-term",
|
|
126
|
+
agent_dir / "soul" / "installed",
|
|
127
|
+
agent_dir / "wallet",
|
|
128
|
+
agent_dir / "seeds",
|
|
129
|
+
agent_dir / "identity",
|
|
130
|
+
agent_dir / "config",
|
|
131
|
+
agent_dir / "logs",
|
|
132
|
+
agent_dir / "security",
|
|
133
|
+
agent_dir / "cloud9",
|
|
134
|
+
agent_dir / "trust" / "febs",
|
|
135
|
+
agent_dir / "sync" / "outbox",
|
|
136
|
+
agent_dir / "sync" / "inbox",
|
|
137
|
+
agent_dir / "sync" / "archive",
|
|
138
|
+
agent_dir / "reflections",
|
|
139
|
+
agent_dir / "improvements",
|
|
140
|
+
agent_dir / "scripts",
|
|
141
|
+
agent_dir / "cron",
|
|
142
|
+
agent_dir / "archive",
|
|
143
|
+
agent_dir / "comms" / "inbox",
|
|
144
|
+
agent_dir / "comms" / "outbox",
|
|
145
|
+
agent_dir / "comms" / "archive",
|
|
146
|
+
):
|
|
147
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
@@ -74,15 +74,32 @@ def register_card_commands(main: click.Group) -> None:
|
|
|
74
74
|
console.print(f" [dim]Saved to: {out_path}[/]\n")
|
|
75
75
|
|
|
76
76
|
@card.command("show")
|
|
77
|
-
@click.argument("filepath", default=
|
|
77
|
+
@click.argument("filepath", default=None, required=False)
|
|
78
78
|
def card_show(filepath):
|
|
79
|
-
"""Display an agent card.
|
|
79
|
+
"""Display an agent card.
|
|
80
|
+
|
|
81
|
+
If no filepath is given, looks in the agent home directory first,
|
|
82
|
+
then falls back to ~/.skcapstone/agent-card.json.
|
|
83
|
+
"""
|
|
80
84
|
from ..agent_card import AgentCard
|
|
85
|
+
from .. import agent_home, AGENT_HOME
|
|
86
|
+
|
|
87
|
+
if filepath is None:
|
|
88
|
+
# Try agent-scoped path first, then shared root
|
|
89
|
+
candidates = [
|
|
90
|
+
Path(agent_home()) / "agent-card.json",
|
|
91
|
+
Path(AGENT_HOME).expanduser() / "agent-card.json",
|
|
92
|
+
]
|
|
93
|
+
filepath = next(
|
|
94
|
+
(str(c) for c in candidates if c.exists()),
|
|
95
|
+
str(candidates[0]), # default for error message
|
|
96
|
+
)
|
|
81
97
|
|
|
82
98
|
try:
|
|
83
99
|
agent_card = AgentCard.load(filepath)
|
|
84
100
|
except FileNotFoundError:
|
|
85
101
|
console.print(f"[red]Card not found: {filepath}[/]")
|
|
102
|
+
console.print("[dim]Generate one with: skcapstone card generate[/]")
|
|
86
103
|
raise SystemExit(1)
|
|
87
104
|
|
|
88
105
|
verified = AgentCard.verify_signature(agent_card)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Config commands: validate."""
|
|
1
|
+
"""Config commands: show, validate."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -18,6 +18,58 @@ def register_config_commands(main: click.Group) -> None:
|
|
|
18
18
|
def config():
|
|
19
19
|
"""Config management — validate and inspect agent configuration."""
|
|
20
20
|
|
|
21
|
+
@config.command("show")
|
|
22
|
+
@click.option(
|
|
23
|
+
"--home", default=AGENT_HOME, type=click.Path(),
|
|
24
|
+
help="Agent home directory.",
|
|
25
|
+
)
|
|
26
|
+
@click.option("--json-out", is_flag=True, help="Output as machine-readable JSON.")
|
|
27
|
+
def show(home: str, json_out: bool) -> None:
|
|
28
|
+
"""Show current agent configuration.
|
|
29
|
+
|
|
30
|
+
Displays the contents of config.yaml, consciousness.yaml, and
|
|
31
|
+
model_profiles.yaml from the agent home directory.
|
|
32
|
+
"""
|
|
33
|
+
import yaml
|
|
34
|
+
|
|
35
|
+
home_path = Path(home).expanduser()
|
|
36
|
+
config_dir = home_path / "config"
|
|
37
|
+
|
|
38
|
+
if not config_dir.exists():
|
|
39
|
+
console.print(f"[red]Config directory not found: {config_dir}[/]")
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
|
|
42
|
+
config_files = ["config.yaml", "consciousness.yaml", "model_profiles.yaml"]
|
|
43
|
+
all_data: dict = {}
|
|
44
|
+
|
|
45
|
+
for fname in config_files:
|
|
46
|
+
fpath = config_dir / fname
|
|
47
|
+
if fpath.exists():
|
|
48
|
+
try:
|
|
49
|
+
data = yaml.safe_load(fpath.read_text(encoding="utf-8"))
|
|
50
|
+
all_data[fname] = data
|
|
51
|
+
except Exception as exc:
|
|
52
|
+
all_data[fname] = {"error": str(exc)}
|
|
53
|
+
else:
|
|
54
|
+
all_data[fname] = None
|
|
55
|
+
|
|
56
|
+
if json_out:
|
|
57
|
+
click.echo(json.dumps(all_data, indent=2, default=str))
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
for fname, data in all_data.items():
|
|
61
|
+
if data is None:
|
|
62
|
+
console.print(f" [dim]{fname}[/] [yellow]not found[/]")
|
|
63
|
+
elif "error" in data:
|
|
64
|
+
console.print(f" [dim]{fname}[/] [red]{data['error']}[/]")
|
|
65
|
+
else:
|
|
66
|
+
console.print(f"\n [bold cyan]{fname}[/]")
|
|
67
|
+
console.print(f" [dim]{config_dir / fname}[/]")
|
|
68
|
+
formatted = yaml.dump(data, default_flow_style=False, indent=2)
|
|
69
|
+
for line in formatted.strip().split("\n"):
|
|
70
|
+
console.print(f" {line}")
|
|
71
|
+
console.print()
|
|
72
|
+
|
|
21
73
|
@config.command("validate")
|
|
22
74
|
@click.option(
|
|
23
75
|
"--home", default=AGENT_HOME, type=click.Path(),
|
|
@@ -20,7 +20,9 @@ def register_peer_commands(main: click.Group) -> None:
|
|
|
20
20
|
|
|
21
21
|
@main.group()
|
|
22
22
|
def peer():
|
|
23
|
-
"""Peer management — discover, add, and manage trusted contacts.
|
|
23
|
+
"""Peer management — discover, add, and manage trusted contacts.
|
|
24
|
+
|
|
25
|
+
Identity-layer peers (PGP keys, trust). For transport routing, see 'peers'."""
|
|
24
26
|
|
|
25
27
|
@peer.command("add")
|
|
26
28
|
@click.option("--card", "card_path", type=click.Path(exists=True), help="Import from identity card.")
|
|
@@ -17,7 +17,9 @@ def register_peers_dir_commands(main: click.Group) -> None:
|
|
|
17
17
|
|
|
18
18
|
@main.group("peers")
|
|
19
19
|
def peers_dir():
|
|
20
|
-
"""Peer transport directory — routing addresses for the mesh.
|
|
20
|
+
"""Peer transport directory — routing addresses for the mesh.
|
|
21
|
+
|
|
22
|
+
SKComm transport endpoints. For identity/trust peers, see 'peer'."""
|
|
21
23
|
|
|
22
24
|
@peers_dir.command("list")
|
|
23
25
|
@click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
|
|
@@ -33,8 +33,12 @@ def register_preflight_commands(main: click.Group) -> None:
|
|
|
33
33
|
skcapstone preflight --json-out
|
|
34
34
|
"""
|
|
35
35
|
import json
|
|
36
|
+
from .. import ensure_skeleton
|
|
36
37
|
from ..preflight import PreflightChecker
|
|
37
38
|
|
|
39
|
+
# Ensure all expected directories exist before checking
|
|
40
|
+
ensure_skeleton()
|
|
41
|
+
|
|
38
42
|
checker = PreflightChecker(home=Path(home).expanduser())
|
|
39
43
|
summary = checker.run_all()
|
|
40
44
|
|
|
@@ -14,13 +14,18 @@ from ._common import AGENT_HOME, console
|
|
|
14
14
|
def register_usage_commands(main: click.Group) -> None:
|
|
15
15
|
"""Register the ``skcapstone usage`` command group."""
|
|
16
16
|
|
|
17
|
-
@main.group("usage")
|
|
18
|
-
|
|
17
|
+
@main.group("usage", invoke_without_command=True)
|
|
18
|
+
@click.pass_context
|
|
19
|
+
def usage_group(ctx):
|
|
19
20
|
"""Show LLM token usage and cost estimates.
|
|
20
21
|
|
|
21
22
|
Tracks input/output tokens per model per day.
|
|
22
23
|
Data is stored in ~/.skcapstone/usage/tokens-{date}.json.
|
|
24
|
+
|
|
25
|
+
When called without a subcommand, shows today's usage.
|
|
23
26
|
"""
|
|
27
|
+
if ctx.invoked_subcommand is None:
|
|
28
|
+
ctx.invoke(today_cmd)
|
|
24
29
|
|
|
25
30
|
@usage_group.command("daily")
|
|
26
31
|
@click.option("--home", default=AGENT_HOME, type=click.Path(), help="Agent home directory.")
|
package/src/skcapstone/doctor.py
CHANGED
|
@@ -13,6 +13,7 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import importlib
|
|
15
15
|
import json
|
|
16
|
+
import os
|
|
16
17
|
import shutil
|
|
17
18
|
import subprocess
|
|
18
19
|
from dataclasses import dataclass, field
|
|
@@ -618,6 +619,120 @@ def _check_versions() -> list[Check]:
|
|
|
618
619
|
return checks
|
|
619
620
|
|
|
620
621
|
|
|
622
|
+
@dataclass
|
|
623
|
+
class FixResult:
|
|
624
|
+
"""Result of attempting to auto-fix a failing check.
|
|
625
|
+
|
|
626
|
+
Attributes:
|
|
627
|
+
check_name: Name of the check that was fixed.
|
|
628
|
+
success: Whether the fix succeeded.
|
|
629
|
+
action: Description of what was done.
|
|
630
|
+
error: Error message if the fix failed.
|
|
631
|
+
"""
|
|
632
|
+
|
|
633
|
+
check_name: str
|
|
634
|
+
success: bool
|
|
635
|
+
action: str = ""
|
|
636
|
+
error: str = ""
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def run_fixes(report: DiagnosticReport, home: Path) -> list[FixResult]:
|
|
640
|
+
"""Attempt to auto-fix failing checks by creating missing directories and files.
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
report: Diagnostic report with failing checks.
|
|
644
|
+
home: Agent home directory.
|
|
645
|
+
|
|
646
|
+
Returns:
|
|
647
|
+
List of FixResult for each attempted fix.
|
|
648
|
+
"""
|
|
649
|
+
results: list[FixResult] = []
|
|
650
|
+
|
|
651
|
+
for check in report.checks:
|
|
652
|
+
if check.passed:
|
|
653
|
+
continue
|
|
654
|
+
|
|
655
|
+
# Fix missing directories
|
|
656
|
+
if check.name.startswith("home:") and check.name != "home:exists" and check.name != "home:manifest":
|
|
657
|
+
dirname = check.name.split(":", 1)[1]
|
|
658
|
+
dirpath = home / dirname
|
|
659
|
+
try:
|
|
660
|
+
dirpath.mkdir(parents=True, exist_ok=True)
|
|
661
|
+
results.append(FixResult(
|
|
662
|
+
check_name=check.name,
|
|
663
|
+
success=True,
|
|
664
|
+
action=f"Created directory {dirpath}",
|
|
665
|
+
))
|
|
666
|
+
except OSError as exc:
|
|
667
|
+
results.append(FixResult(
|
|
668
|
+
check_name=check.name,
|
|
669
|
+
success=False,
|
|
670
|
+
error=str(exc),
|
|
671
|
+
))
|
|
672
|
+
|
|
673
|
+
# Fix missing manifest
|
|
674
|
+
elif check.name == "home:manifest":
|
|
675
|
+
manifest_path = home / "manifest.json"
|
|
676
|
+
try:
|
|
677
|
+
data = {
|
|
678
|
+
"name": os.environ.get("SKCAPSTONE_AGENT", "sovereign"),
|
|
679
|
+
"version": "0.0.0",
|
|
680
|
+
"created_at": "",
|
|
681
|
+
"connectors": [],
|
|
682
|
+
}
|
|
683
|
+
manifest_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
684
|
+
results.append(FixResult(
|
|
685
|
+
check_name=check.name,
|
|
686
|
+
success=True,
|
|
687
|
+
action=f"Created default manifest at {manifest_path}",
|
|
688
|
+
))
|
|
689
|
+
except OSError as exc:
|
|
690
|
+
results.append(FixResult(
|
|
691
|
+
check_name=check.name,
|
|
692
|
+
success=False,
|
|
693
|
+
error=str(exc),
|
|
694
|
+
))
|
|
695
|
+
|
|
696
|
+
# Fix missing memory store
|
|
697
|
+
elif check.name == "memory:store":
|
|
698
|
+
agent_name = os.environ.get("SKCAPSTONE_AGENT", "lumina")
|
|
699
|
+
memory_dir = home / "agents" / agent_name / "memory"
|
|
700
|
+
try:
|
|
701
|
+
for layer in ("short-term", "mid-term", "long-term"):
|
|
702
|
+
(memory_dir / layer).mkdir(parents=True, exist_ok=True)
|
|
703
|
+
results.append(FixResult(
|
|
704
|
+
check_name=check.name,
|
|
705
|
+
success=True,
|
|
706
|
+
action=f"Created memory directories at {memory_dir}",
|
|
707
|
+
))
|
|
708
|
+
except OSError as exc:
|
|
709
|
+
results.append(FixResult(
|
|
710
|
+
check_name=check.name,
|
|
711
|
+
success=False,
|
|
712
|
+
error=str(exc),
|
|
713
|
+
))
|
|
714
|
+
|
|
715
|
+
# Fix missing sync directory
|
|
716
|
+
elif check.name == "sync:dir":
|
|
717
|
+
sync_dir = home / "sync"
|
|
718
|
+
try:
|
|
719
|
+
for subdir in ("outbox", "inbox", "archive"):
|
|
720
|
+
(sync_dir / subdir).mkdir(parents=True, exist_ok=True)
|
|
721
|
+
results.append(FixResult(
|
|
722
|
+
check_name=check.name,
|
|
723
|
+
success=True,
|
|
724
|
+
action=f"Created sync directories at {sync_dir}",
|
|
725
|
+
))
|
|
726
|
+
except OSError as exc:
|
|
727
|
+
results.append(FixResult(
|
|
728
|
+
check_name=check.name,
|
|
729
|
+
success=False,
|
|
730
|
+
error=str(exc),
|
|
731
|
+
))
|
|
732
|
+
|
|
733
|
+
return results
|
|
734
|
+
|
|
735
|
+
|
|
621
736
|
def _get_tool_version(tool: str) -> Optional[str]:
|
|
622
737
|
"""Try to get a tool's version string.
|
|
623
738
|
|
|
@@ -109,6 +109,52 @@ def _step_identity(home_path: Path, name: str, email: str | None) -> tuple[str,
|
|
|
109
109
|
|
|
110
110
|
(home_path / "skills").mkdir(parents=True, exist_ok=True)
|
|
111
111
|
|
|
112
|
+
# Create full skeleton so all commands work from day one
|
|
113
|
+
agent_slug = name.lower().replace(" ", "-")
|
|
114
|
+
agent_dir = home_path / "agents" / agent_slug
|
|
115
|
+
|
|
116
|
+
skeleton_dirs = [
|
|
117
|
+
# Shared root directories
|
|
118
|
+
home_path / "heartbeats",
|
|
119
|
+
home_path / "peers",
|
|
120
|
+
home_path / "coordination" / "tasks",
|
|
121
|
+
home_path / "coordination" / "agents",
|
|
122
|
+
home_path / "logs",
|
|
123
|
+
home_path / "comms" / "inbox",
|
|
124
|
+
home_path / "comms" / "outbox",
|
|
125
|
+
home_path / "comms" / "archive",
|
|
126
|
+
home_path / "archive",
|
|
127
|
+
home_path / "deployments",
|
|
128
|
+
home_path / "docs",
|
|
129
|
+
home_path / "metrics",
|
|
130
|
+
# Per-agent directories
|
|
131
|
+
agent_dir / "memory" / "short-term",
|
|
132
|
+
agent_dir / "memory" / "mid-term",
|
|
133
|
+
agent_dir / "memory" / "long-term",
|
|
134
|
+
agent_dir / "soul" / "installed",
|
|
135
|
+
agent_dir / "wallet",
|
|
136
|
+
agent_dir / "seeds",
|
|
137
|
+
agent_dir / "identity",
|
|
138
|
+
agent_dir / "config",
|
|
139
|
+
agent_dir / "logs",
|
|
140
|
+
agent_dir / "security",
|
|
141
|
+
agent_dir / "cloud9",
|
|
142
|
+
agent_dir / "trust" / "febs",
|
|
143
|
+
agent_dir / "sync" / "outbox",
|
|
144
|
+
agent_dir / "sync" / "inbox",
|
|
145
|
+
agent_dir / "sync" / "archive",
|
|
146
|
+
agent_dir / "reflections",
|
|
147
|
+
agent_dir / "improvements",
|
|
148
|
+
agent_dir / "scripts",
|
|
149
|
+
agent_dir / "cron",
|
|
150
|
+
agent_dir / "archive",
|
|
151
|
+
agent_dir / "comms" / "inbox",
|
|
152
|
+
agent_dir / "comms" / "outbox",
|
|
153
|
+
agent_dir / "comms" / "archive",
|
|
154
|
+
]
|
|
155
|
+
for d in skeleton_dirs:
|
|
156
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
|
|
112
158
|
manifest = {
|
|
113
159
|
"name": name,
|
|
114
160
|
"version": __version__,
|