spikuit-cli 0.5.4__tar.gz

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.
@@ -0,0 +1,45 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .eggs/
9
+
10
+ # Virtual environments
11
+ .venv/
12
+ venv/
13
+
14
+ # uv
15
+ uv.lock
16
+
17
+ # IDE
18
+ .vscode/
19
+ .idea/
20
+ *.swp
21
+ *.swo
22
+
23
+ # OS
24
+ .DS_Store
25
+ Thumbs.db
26
+
27
+ # Environment
28
+ .env
29
+ .env.local
30
+
31
+ # Database
32
+ *.db
33
+ *.sqlite3
34
+
35
+ # Brain data (project-local)
36
+ .spikuit/
37
+
38
+ # Coverage
39
+ htmlcov/
40
+ .coverage
41
+ .coverage.*
42
+ spkt-circuit.html
43
+
44
+ # MkDocs
45
+ site/
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: spikuit-cli
3
+ Version: 0.5.4
4
+ Summary: CLI interface for Spikuit (spkt command)
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: pyvis>=0.3.2
7
+ Requires-Dist: spikuit-core[engine]
8
+ Requires-Dist: typer>=0.24.1
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "spikuit-cli"
3
+ version = "0.5.4"
4
+ description = "CLI interface for Spikuit (spkt command)"
5
+ requires-python = ">=3.11"
6
+ dependencies = [
7
+ "pyvis>=0.3.2",
8
+ "spikuit-core[engine]",
9
+ "typer>=0.24.1",
10
+ ]
11
+
12
+ [project.scripts]
13
+ spkt = "spikuit_cli.main:main"
14
+
15
+ [build-system]
16
+ requires = ["hatchling"]
17
+ build-backend = "hatchling.build"
18
+
19
+ [tool.hatch.build.targets.wheel]
20
+ packages = ["src/spikuit_cli"]
@@ -0,0 +1,3 @@
1
+ """Spikuit CLI — spkt command."""
2
+
3
+ __version__ = "0.3.0"
@@ -0,0 +1,17 @@
1
+ """Command modules for the spkt CLI."""
2
+
3
+ from .community import community_app
4
+ from .domain import domain_app
5
+ from .neuron import neuron_app
6
+ from .skills import skills_app
7
+ from .source import source_app
8
+ from .synapse import synapse_app
9
+
10
+ __all__ = [
11
+ "community_app",
12
+ "domain_app",
13
+ "neuron_app",
14
+ "skills_app",
15
+ "source_app",
16
+ "synapse_app",
17
+ ]
@@ -0,0 +1,99 @@
1
+ """Community management commands: spkt community {detect,list}."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import typer
9
+
10
+ from ..helpers import _extract_title, _get_circuit, _out, _run
11
+
12
+ community_app = typer.Typer(help="Manage graph communities.")
13
+
14
+
15
+ @community_app.command(name="detect")
16
+ def community_detect(
17
+ resolution: float = typer.Option(1.0, "--resolution", "-r", help="Louvain resolution parameter"),
18
+ summarize: bool = typer.Option(False, "--summarize", "-s", help="Generate summary neurons for each community"),
19
+ as_json: bool = typer.Option(False, "--json", help="Output as JSON"),
20
+ brain: Optional[Path] = typer.Option(None, "--brain", "-b", help="Brain root directory"),
21
+ ) -> None:
22
+ """Run community detection on the knowledge graph."""
23
+
24
+ async def _detect():
25
+ circuit = _get_circuit(brain)
26
+ await circuit.connect()
27
+ try:
28
+ result = await circuit.detect_communities(resolution=resolution)
29
+ summaries = []
30
+ if summarize and result:
31
+ summaries = await circuit.generate_community_summaries()
32
+ if as_json:
33
+ out = {
34
+ "detected": True,
35
+ "count": len(result),
36
+ "communities": {str(k): v for k, v in result.items()},
37
+ }
38
+ if summaries:
39
+ out["summaries"] = summaries
40
+ _out(out, use_json=True)
41
+ else:
42
+ if not result:
43
+ typer.echo("No communities detected (empty graph).")
44
+ return
45
+ typer.echo(f"Detected {len(result)} community(ies):")
46
+ for cid, members in sorted(result.items()):
47
+ labels = []
48
+ for nid in members[:5]:
49
+ n = await circuit.get_neuron(nid)
50
+ labels.append(_extract_title(n.content) if n else nid)
51
+ suffix = f" (+{len(members) - 5} more)" if len(members) > 5 else ""
52
+ typer.echo(f" [{cid}] {len(members)} neurons: {', '.join(labels)}{suffix}")
53
+ if summaries:
54
+ typer.echo(f"\nGenerated {len(summaries)} community summary neuron(s).")
55
+ finally:
56
+ await circuit.close()
57
+
58
+ _run(_detect())
59
+
60
+
61
+ @community_app.command(name="list")
62
+ def community_list(
63
+ as_json: bool = typer.Option(False, "--json", help="Output as JSON"),
64
+ brain: Optional[Path] = typer.Option(None, "--brain", "-b", help="Brain root directory"),
65
+ ) -> None:
66
+ """Show current community assignments."""
67
+
68
+ async def _list():
69
+ circuit = _get_circuit(brain)
70
+ await circuit.connect()
71
+ try:
72
+ cmap = circuit.community_map()
73
+ if as_json:
74
+ groups: dict[int, list[str]] = {}
75
+ for nid, cid in cmap.items():
76
+ groups.setdefault(cid, []).append(nid)
77
+ _out({
78
+ "count": len(groups),
79
+ "communities": {str(k): v for k, v in groups.items()},
80
+ }, use_json=True)
81
+ else:
82
+ if not cmap:
83
+ typer.echo("No communities assigned yet. Run: spkt community detect")
84
+ return
85
+ groups = {}
86
+ for nid, cid in cmap.items():
87
+ groups.setdefault(cid, []).append(nid)
88
+ typer.echo(f"{len(groups)} community(ies):")
89
+ for cid, members in sorted(groups.items()):
90
+ labels = []
91
+ for nid in members[:5]:
92
+ n = await circuit.get_neuron(nid)
93
+ labels.append(_extract_title(n.content) if n else nid)
94
+ suffix = f" (+{len(members) - 5} more)" if len(members) > 5 else ""
95
+ typer.echo(f" [{cid}] {len(members)} neurons: {', '.join(labels)}{suffix}")
96
+ finally:
97
+ await circuit.close()
98
+
99
+ _run(_list())
@@ -0,0 +1,151 @@
1
+ """Domain management commands: spkt domain {list,rename,merge}."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import typer
9
+
10
+ from ..helpers import _get_circuit, _out, _run
11
+
12
+ domain_app = typer.Typer(help="Manage domains.")
13
+
14
+
15
+ @domain_app.command(name="list")
16
+ def domain_list(
17
+ as_json: bool = typer.Option(False, "--json", help="Output as JSON"),
18
+ brain: Optional[Path] = typer.Option(None, "--brain", "-b", help="Brain root directory"),
19
+ ) -> None:
20
+ """List domains with neuron counts."""
21
+
22
+ async def _list():
23
+ circuit = _get_circuit(brain)
24
+ await circuit.connect()
25
+ try:
26
+ counts = await circuit.get_domain_counts()
27
+ if as_json:
28
+ _out(counts, use_json=True)
29
+ else:
30
+ if not counts:
31
+ typer.echo("No domains found.")
32
+ return
33
+ typer.echo("Domains:")
34
+ for c in counts:
35
+ typer.echo(f" {c['domain']:20s} {c['count']} neurons")
36
+ finally:
37
+ await circuit.close()
38
+
39
+ _run(_list())
40
+
41
+
42
+ @domain_app.command(name="rename")
43
+ def domain_rename(
44
+ old: str = typer.Argument(..., help="Current domain name"),
45
+ new: str = typer.Argument(..., help="New domain name"),
46
+ as_json: bool = typer.Option(False, "--json", help="Output as JSON"),
47
+ brain: Optional[Path] = typer.Option(None, "--brain", "-b", help="Brain root directory"),
48
+ ) -> None:
49
+ """Rename a domain (batch update all neurons)."""
50
+
51
+ async def _rename():
52
+ circuit = _get_circuit(brain)
53
+ await circuit.connect()
54
+ try:
55
+ count = await circuit.rename_domain(old, new)
56
+ if as_json:
57
+ _out({"old": old, "new": new, "updated": count}, use_json=True)
58
+ else:
59
+ typer.echo(f"Renamed '{old}' \u2192 '{new}' ({count} neurons updated)")
60
+ finally:
61
+ await circuit.close()
62
+
63
+ _run(_rename())
64
+
65
+
66
+ @domain_app.command(name="merge")
67
+ def domain_merge(
68
+ domains: list[str] = typer.Argument(..., help="Domains to merge"),
69
+ into: str = typer.Option(..., "--into", help="Target domain name"),
70
+ as_json: bool = typer.Option(False, "--json", help="Output as JSON"),
71
+ brain: Optional[Path] = typer.Option(None, "--brain", "-b", help="Brain root directory"),
72
+ ) -> None:
73
+ """Merge multiple domains into one target domain."""
74
+
75
+ async def _merge():
76
+ circuit = _get_circuit(brain)
77
+ await circuit.connect()
78
+ try:
79
+ count = await circuit.merge_domains(domains, into)
80
+ if as_json:
81
+ _out({"merged": domains, "into": into, "updated": count}, use_json=True)
82
+ else:
83
+ typer.echo(f"Merged {domains} \u2192 '{into}' ({count} neurons updated)")
84
+ finally:
85
+ await circuit.close()
86
+
87
+ _run(_merge())
88
+
89
+
90
+ @domain_app.command(name="audit")
91
+ def domain_audit(
92
+ as_json: bool = typer.Option(False, "--json", help="Output as JSON"),
93
+ brain: Optional[Path] = typer.Option(None, "--brain", "-b", help="Brain root directory"),
94
+ ) -> None:
95
+ """Analyze domain ↔ community alignment and suggest actions."""
96
+
97
+ async def _audit():
98
+ circuit = _get_circuit(brain)
99
+ await circuit.connect()
100
+ try:
101
+ result = await circuit.domain_audit()
102
+ if as_json:
103
+ _out(result, use_json=True)
104
+ else:
105
+ domains = result["domains"]
106
+ suggestions = result["suggestions"]
107
+ keywords = result["community_keywords"]
108
+
109
+ if not domains:
110
+ typer.echo("No domains found. Run 'spkt community detect' first.")
111
+ return
112
+
113
+ typer.echo("Domain ↔ Community Alignment\n")
114
+ for d in domains:
115
+ comms = ", ".join(
116
+ f"c{c['community_id']} ({c['count']})"
117
+ for c in d["communities"]
118
+ )
119
+ typer.echo(f" {d['domain']:20s} {d['neuron_count']} neurons [{comms}]")
120
+
121
+ if keywords:
122
+ typer.echo("\nCommunity Keywords:")
123
+ for cid, kws in sorted(keywords.items(), key=lambda x: x[0]):
124
+ if kws:
125
+ typer.echo(f" c{cid}: {', '.join(kws)}")
126
+
127
+ if suggestions:
128
+ typer.echo(f"\nSuggestions ({len(suggestions)}):")
129
+ for s in suggestions:
130
+ if s["action"] == "split":
131
+ comms_str = ", ".join(
132
+ f"c{c['community_id']} ({c['count']} neurons, keywords: {', '.join(c.get('keywords', []))})"
133
+ for c in s["communities"]
134
+ )
135
+ typer.echo(f" SPLIT '{s['domain']}': spans {len(s['communities'])} communities")
136
+ typer.echo(f" {comms_str}")
137
+ elif s["action"] == "merge":
138
+ doms = ", ".join(
139
+ f"{d['domain']} ({d['count']})"
140
+ for d in s["domains"]
141
+ )
142
+ typer.echo(f" MERGE in c{s['community_id']}: {doms}")
143
+ kws = s.get("keywords", [])
144
+ if kws:
145
+ typer.echo(f" suggested name hint: {', '.join(kws)}")
146
+ else:
147
+ typer.echo("\nNo alignment issues detected.")
148
+ finally:
149
+ await circuit.close()
150
+
151
+ _run(_audit())