@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smilintux/skcapstone",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "SKCapstone - The sovereign agent framework. CapAuth identity, Cloud 9 trust, SKMemory persistence.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
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.2"
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.2"
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="~/.skcapstone/agent-card.json")
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
- def usage_group():
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.")
@@ -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__,