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.
- spikuit_cli-0.5.4/.gitignore +45 -0
- spikuit_cli-0.5.4/PKG-INFO +8 -0
- spikuit_cli-0.5.4/pyproject.toml +20 -0
- spikuit_cli-0.5.4/src/spikuit_cli/__init__.py +3 -0
- spikuit_cli-0.5.4/src/spikuit_cli/commands/__init__.py +17 -0
- spikuit_cli-0.5.4/src/spikuit_cli/commands/community.py +99 -0
- spikuit_cli-0.5.4/src/spikuit_cli/commands/domain.py +151 -0
- spikuit_cli-0.5.4/src/spikuit_cli/commands/neuron.py +348 -0
- spikuit_cli-0.5.4/src/spikuit_cli/commands/skills.py +274 -0
- spikuit_cli-0.5.4/src/spikuit_cli/commands/source.py +569 -0
- spikuit_cli-0.5.4/src/spikuit_cli/commands/synapse.py +156 -0
- spikuit_cli-0.5.4/src/spikuit_cli/helpers.py +93 -0
- spikuit_cli-0.5.4/src/spikuit_cli/main.py +1536 -0
- spikuit_cli-0.5.4/src/spikuit_cli/skills/spkt-curator/SKILL.md +185 -0
- spikuit_cli-0.5.4/src/spikuit_cli/skills/spkt-qabot/SKILL.md +83 -0
- spikuit_cli-0.5.4/src/spikuit_cli/skills/spkt-teach/SKILL.md +123 -0
- spikuit_cli-0.5.4/src/spikuit_cli/skills/spkt-tutor/SKILL.md +128 -0
|
@@ -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,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,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())
|