monoco-toolkit 0.2.8__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
- monoco/cli/project.py +35 -31
- monoco/cli/workspace.py +26 -16
- monoco/core/agent/__init__.py +0 -2
- monoco/core/agent/action.py +44 -20
- monoco/core/agent/adapters.py +20 -16
- monoco/core/agent/protocol.py +5 -4
- monoco/core/agent/state.py +21 -21
- monoco/core/config.py +90 -33
- monoco/core/execution.py +21 -16
- monoco/core/feature.py +8 -5
- monoco/core/git.py +61 -30
- monoco/core/hooks.py +57 -0
- monoco/core/injection.py +47 -44
- monoco/core/integrations.py +50 -35
- monoco/core/lsp.py +12 -1
- monoco/core/output.py +35 -16
- monoco/core/registry.py +3 -2
- monoco/core/setup.py +190 -124
- monoco/core/skills.py +121 -107
- monoco/core/state.py +12 -10
- monoco/core/sync.py +85 -56
- monoco/core/telemetry.py +10 -6
- monoco/core/workspace.py +26 -19
- monoco/daemon/app.py +123 -79
- monoco/daemon/commands.py +14 -13
- monoco/daemon/models.py +11 -3
- monoco/daemon/reproduce_stats.py +8 -8
- monoco/daemon/services.py +32 -33
- monoco/daemon/stats.py +59 -40
- monoco/features/config/commands.py +38 -25
- monoco/features/i18n/adapter.py +4 -5
- monoco/features/i18n/commands.py +83 -49
- monoco/features/i18n/core.py +94 -54
- monoco/features/issue/adapter.py +6 -7
- monoco/features/issue/commands.py +468 -272
- monoco/features/issue/core.py +419 -312
- monoco/features/issue/domain/lifecycle.py +33 -23
- monoco/features/issue/domain/models.py +71 -38
- monoco/features/issue/domain/parser.py +92 -69
- monoco/features/issue/domain/workspace.py +19 -16
- monoco/features/issue/engine/__init__.py +3 -3
- monoco/features/issue/engine/config.py +18 -25
- monoco/features/issue/engine/machine.py +72 -39
- monoco/features/issue/engine/models.py +4 -2
- monoco/features/issue/linter.py +287 -157
- monoco/features/issue/lsp/definition.py +26 -19
- monoco/features/issue/migration.py +45 -34
- monoco/features/issue/models.py +29 -13
- monoco/features/issue/monitor.py +24 -8
- monoco/features/issue/resources/en/SKILL.md +6 -2
- monoco/features/issue/validator.py +383 -208
- monoco/features/skills/__init__.py +0 -1
- monoco/features/skills/core.py +24 -18
- monoco/features/spike/adapter.py +4 -5
- monoco/features/spike/commands.py +51 -38
- monoco/features/spike/core.py +24 -16
- monoco/main.py +34 -21
- {monoco_toolkit-0.2.8.dist-info → monoco_toolkit-0.3.0.dist-info}/METADATA +1 -1
- monoco_toolkit-0.3.0.dist-info/RECORD +84 -0
- monoco_toolkit-0.2.8.dist-info/RECORD +0 -83
- {monoco_toolkit-0.2.8.dist-info → monoco_toolkit-0.3.0.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.2.8.dist-info → monoco_toolkit-0.3.0.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.2.8.dist-info → monoco_toolkit-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .core import init
|
monoco/features/skills/core.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import re
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
from typing import Dict, List, Any
|
|
@@ -6,22 +5,23 @@ from rich.console import Console
|
|
|
6
5
|
|
|
7
6
|
console = Console()
|
|
8
7
|
|
|
8
|
+
|
|
9
9
|
def init(root: Path, resources: List[Dict[str, Any]]):
|
|
10
10
|
"""
|
|
11
11
|
Initialize the Skills module.
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
Args:
|
|
14
14
|
root: Project root directory.
|
|
15
|
-
resources: List of resource dicts from modules.
|
|
16
|
-
Expected format:
|
|
15
|
+
resources: List of resource dicts from modules.
|
|
16
|
+
Expected format:
|
|
17
17
|
{
|
|
18
18
|
"skills": { "name": "content" },
|
|
19
|
-
"prompts": { "name": "content" }
|
|
19
|
+
"prompts": { "name": "content" }
|
|
20
20
|
}
|
|
21
21
|
"""
|
|
22
22
|
skills_root = root / "Toolkit" / "skills"
|
|
23
23
|
skills_root.mkdir(parents=True, exist_ok=True)
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
# 1. Write Skills
|
|
26
26
|
for res in resources:
|
|
27
27
|
if "skills" in res:
|
|
@@ -30,7 +30,7 @@ def init(root: Path, resources: List[Dict[str, Any]]):
|
|
|
30
30
|
target_dir.mkdir(exist_ok=True)
|
|
31
31
|
target_file = target_dir / "SKILL.md"
|
|
32
32
|
# Idempotency: Overwrite if content is different? Or just always overwrite?
|
|
33
|
-
# User asked for "scaffold", implies creation.
|
|
33
|
+
# User asked for "scaffold", implies creation.
|
|
34
34
|
# Let's overwrite to ensure extensive "Repair" capability.
|
|
35
35
|
target_file.write_text(content, encoding="utf-8")
|
|
36
36
|
console.print(f"[dim] - Scaffolding skill:[/dim] {name}")
|
|
@@ -38,20 +38,23 @@ def init(root: Path, resources: List[Dict[str, Any]]):
|
|
|
38
38
|
# 2. Update Agent Docs
|
|
39
39
|
update_agent_docs(root, resources)
|
|
40
40
|
|
|
41
|
+
|
|
41
42
|
def update_agent_docs(root: Path, resources: List[Dict[str, Any]]):
|
|
42
43
|
"""
|
|
43
44
|
Inject prompts into AGENTS.md, GEMINI.md, CLAUDE.md.
|
|
44
45
|
"""
|
|
45
46
|
target_files = ["AGENTS.md", "GEMINI.md", "CLAUDE.md"]
|
|
46
|
-
|
|
47
|
+
|
|
47
48
|
# Aggregate Prompts
|
|
48
|
-
aggregated_prompt = "\n\n".join(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
aggregated_prompt = "\n\n".join(
|
|
50
|
+
[
|
|
51
|
+
res["prompts"][name]
|
|
52
|
+
for res in resources
|
|
53
|
+
if "prompts" in res
|
|
54
|
+
for name in res["prompts"]
|
|
55
|
+
]
|
|
56
|
+
)
|
|
57
|
+
|
|
55
58
|
injection_content = f"""
|
|
56
59
|
## Monoco Toolkit
|
|
57
60
|
|
|
@@ -63,6 +66,7 @@ The following tools and skills are available in this environment.
|
|
|
63
66
|
for filename in target_files:
|
|
64
67
|
_inject_section(root / filename, injection_content)
|
|
65
68
|
|
|
69
|
+
|
|
66
70
|
def _inject_section(file_path: Path, content: str):
|
|
67
71
|
if not file_path.exists():
|
|
68
72
|
# Create if not exists? User said "Edit AGENTS.md...", implies existence.
|
|
@@ -73,15 +77,17 @@ def _inject_section(file_path: Path, content: str):
|
|
|
73
77
|
return
|
|
74
78
|
|
|
75
79
|
original_content = file_path.read_text(encoding="utf-8")
|
|
76
|
-
|
|
80
|
+
|
|
77
81
|
# Regex to find existing section
|
|
78
82
|
# Matches ## Monoco Toolkit ... until next ## or End of String
|
|
79
83
|
pattern = r"(## Monoco Toolkit.*?)(\n## |\Z)"
|
|
80
|
-
|
|
84
|
+
|
|
81
85
|
# Check if section exists
|
|
82
86
|
if re.search(pattern, original_content, re.DOTALL):
|
|
83
87
|
# Replace
|
|
84
|
-
new_content = re.sub(
|
|
88
|
+
new_content = re.sub(
|
|
89
|
+
pattern, f"{content.strip()}\n\n\\2", original_content, flags=re.DOTALL
|
|
90
|
+
)
|
|
85
91
|
if new_content != original_content:
|
|
86
92
|
file_path.write_text(new_content, encoding="utf-8")
|
|
87
93
|
console.print(f"[green]✔[/green] Updated {file_path.name}")
|
monoco/features/spike/adapter.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import Dict
|
|
|
3
3
|
from monoco.core.feature import MonocoFeature, IntegrationData
|
|
4
4
|
from monoco.features.spike import core
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
class SpikeFeature(MonocoFeature):
|
|
7
8
|
@property
|
|
8
9
|
def name(self) -> str:
|
|
@@ -16,15 +17,13 @@ class SpikeFeature(MonocoFeature):
|
|
|
16
17
|
# Determine language from config, default to 'en'
|
|
17
18
|
lang = config.get("i18n", {}).get("source_lang", "en")
|
|
18
19
|
base_dir = Path(__file__).parent / "resources"
|
|
19
|
-
|
|
20
|
+
|
|
20
21
|
prompt_file = base_dir / lang / "AGENTS.md"
|
|
21
22
|
if not prompt_file.exists():
|
|
22
23
|
prompt_file = base_dir / "en" / "AGENTS.md"
|
|
23
|
-
|
|
24
|
+
|
|
24
25
|
content = ""
|
|
25
26
|
if prompt_file.exists():
|
|
26
27
|
content = prompt_file.read_text(encoding="utf-8").strip()
|
|
27
28
|
|
|
28
|
-
return IntegrationData(
|
|
29
|
-
system_prompts={"Spike (Research)": content}
|
|
30
|
-
)
|
|
29
|
+
return IntegrationData(system_prompts={"Spike (Research)": content})
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import typer
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from rich.console import Console
|
|
4
|
-
from typing import Annotated
|
|
5
3
|
|
|
6
4
|
from monoco.core.config import get_config
|
|
7
5
|
from monoco.core.output import AgentOutput, OutputManager
|
|
@@ -9,6 +7,7 @@ from . import core
|
|
|
9
7
|
|
|
10
8
|
app = typer.Typer(help="Spike & Repo Management.")
|
|
11
9
|
|
|
10
|
+
|
|
12
11
|
@app.command("init")
|
|
13
12
|
def init(
|
|
14
13
|
json: AgentOutput = False,
|
|
@@ -17,17 +16,20 @@ def init(
|
|
|
17
16
|
config = get_config()
|
|
18
17
|
root_dir = Path(config.paths.root)
|
|
19
18
|
spikes_dir_name = config.paths.spikes
|
|
20
|
-
|
|
19
|
+
|
|
21
20
|
core.ensure_gitignore(root_dir, spikes_dir_name)
|
|
22
|
-
|
|
21
|
+
|
|
23
22
|
# Create the directory
|
|
24
23
|
(root_dir / spikes_dir_name).mkdir(exist_ok=True)
|
|
25
|
-
|
|
26
|
-
OutputManager.print(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
|
|
25
|
+
OutputManager.print(
|
|
26
|
+
{
|
|
27
|
+
"status": "initialized",
|
|
28
|
+
"directory": spikes_dir_name,
|
|
29
|
+
"gitignore_updated": True,
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
|
|
31
33
|
|
|
32
34
|
@app.command("add")
|
|
33
35
|
def add_repo(
|
|
@@ -37,54 +39,60 @@ def add_repo(
|
|
|
37
39
|
"""Add a new research repository."""
|
|
38
40
|
config = get_config()
|
|
39
41
|
root_dir = Path(config.paths.root)
|
|
40
|
-
|
|
42
|
+
|
|
41
43
|
# Infer name from URL
|
|
42
44
|
# e.g., https://github.com/foo/bar.git -> bar
|
|
43
45
|
# e.g., git@github.com:foo/bar.git -> bar
|
|
44
46
|
name = url.split("/")[-1]
|
|
45
47
|
if name.endswith(".git"):
|
|
46
48
|
name = name[:-4]
|
|
47
|
-
|
|
49
|
+
|
|
48
50
|
core.update_config_repos(root_dir, name, url)
|
|
49
|
-
OutputManager.print(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
OutputManager.print(
|
|
52
|
+
{
|
|
53
|
+
"status": "added",
|
|
54
|
+
"name": name,
|
|
55
|
+
"url": url,
|
|
56
|
+
"message": "Run 'monoco spike sync' to download content.",
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
55
60
|
|
|
56
61
|
@app.command("remove")
|
|
57
62
|
def remove_repo(
|
|
58
63
|
name: str = typer.Argument(..., help="Repository Name"),
|
|
59
|
-
force: bool = typer.Option(
|
|
64
|
+
force: bool = typer.Option(
|
|
65
|
+
False, "--force", "-f", help="Force delete physical directory without asking"
|
|
66
|
+
),
|
|
60
67
|
json: AgentOutput = False,
|
|
61
68
|
):
|
|
62
69
|
"""Remove a repository from configuration."""
|
|
63
70
|
config = get_config()
|
|
64
71
|
root_dir = Path(config.paths.root)
|
|
65
72
|
spikes_dir = root_dir / config.paths.spikes
|
|
66
|
-
|
|
73
|
+
|
|
67
74
|
if name not in config.project.spike_repos:
|
|
68
75
|
OutputManager.error(f"Repo {name} not found in configuration.")
|
|
69
76
|
return
|
|
70
77
|
|
|
71
78
|
# Remove from config
|
|
72
79
|
core.update_config_repos(root_dir, name, "", remove=True)
|
|
73
|
-
|
|
80
|
+
|
|
74
81
|
target_path = spikes_dir / name
|
|
75
82
|
deleted = False
|
|
76
83
|
if target_path.exists():
|
|
77
|
-
if force or typer.confirm(
|
|
84
|
+
if force or typer.confirm(
|
|
85
|
+
f"Do you want to delete the directory {target_path}?", default=False
|
|
86
|
+
):
|
|
78
87
|
core.remove_repo_dir(spikes_dir, name)
|
|
79
88
|
deleted = True
|
|
80
89
|
else:
|
|
81
90
|
deleted = False
|
|
82
|
-
|
|
83
|
-
OutputManager.print(
|
|
84
|
-
"status": "removed",
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
})
|
|
91
|
+
|
|
92
|
+
OutputManager.print(
|
|
93
|
+
{"status": "removed", "name": name, "directory_deleted": deleted}
|
|
94
|
+
)
|
|
95
|
+
|
|
88
96
|
|
|
89
97
|
@app.command("sync")
|
|
90
98
|
def sync_repos(
|
|
@@ -93,28 +101,33 @@ def sync_repos(
|
|
|
93
101
|
"""Sync (Clone/Pull) all configured repositories."""
|
|
94
102
|
# Force reload config to get latest updates
|
|
95
103
|
config = get_config()
|
|
96
|
-
|
|
104
|
+
|
|
97
105
|
root_dir = Path(config.paths.root)
|
|
98
106
|
spikes_dir = root_dir / config.paths.spikes
|
|
99
107
|
spikes_dir.mkdir(exist_ok=True)
|
|
100
|
-
|
|
108
|
+
|
|
101
109
|
repos = config.project.spike_repos
|
|
102
|
-
|
|
110
|
+
|
|
103
111
|
if not repos:
|
|
104
|
-
OutputManager.print(
|
|
112
|
+
OutputManager.print(
|
|
113
|
+
{"status": "empty", "message": "No repositories configured."}, title="Sync"
|
|
114
|
+
)
|
|
105
115
|
return
|
|
106
|
-
|
|
116
|
+
|
|
107
117
|
results = []
|
|
108
|
-
|
|
118
|
+
|
|
109
119
|
for name, url in repos.items():
|
|
110
120
|
try:
|
|
111
121
|
core.sync_repo(root_dir, spikes_dir, name, url)
|
|
112
122
|
results.append({"name": name, "status": "synced", "url": url})
|
|
113
123
|
except Exception as e:
|
|
114
|
-
|
|
115
|
-
|
|
124
|
+
results.append(
|
|
125
|
+
{"name": name, "status": "failed", "error": str(e), "url": url}
|
|
126
|
+
)
|
|
127
|
+
|
|
116
128
|
OutputManager.print(results, title="Sync Results")
|
|
117
129
|
|
|
130
|
+
|
|
118
131
|
@app.command("list")
|
|
119
132
|
def list_repos(
|
|
120
133
|
json: AgentOutput = False,
|
|
@@ -122,10 +135,10 @@ def list_repos(
|
|
|
122
135
|
"""List configured repositories."""
|
|
123
136
|
config = get_config()
|
|
124
137
|
repos = config.project.spike_repos
|
|
125
|
-
|
|
138
|
+
|
|
126
139
|
if not repos:
|
|
127
140
|
OutputManager.print([], title="Repositories")
|
|
128
141
|
return
|
|
129
|
-
|
|
142
|
+
|
|
130
143
|
data = [{"name": name, "url": url} for name, url in repos.items()]
|
|
131
144
|
OutputManager.print(data, title="Repositories")
|
monoco/features/spike/core.py
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import shutil
|
|
3
2
|
import subprocess
|
|
4
3
|
|
|
5
4
|
from pathlib import Path
|
|
6
|
-
from typing import
|
|
5
|
+
from typing import List
|
|
7
6
|
from rich.console import Console
|
|
8
7
|
|
|
9
|
-
from monoco.core.config import
|
|
8
|
+
from monoco.core.config import load_raw_config, save_raw_config, ConfigScope
|
|
10
9
|
|
|
11
10
|
console = Console()
|
|
12
11
|
|
|
12
|
+
|
|
13
13
|
def run_git_command(cmd: List[str], cwd: Path) -> bool:
|
|
14
14
|
"""Run a git command in the specified directory."""
|
|
15
15
|
try:
|
|
@@ -19,7 +19,7 @@ def run_git_command(cmd: List[str], cwd: Path) -> bool:
|
|
|
19
19
|
check=True,
|
|
20
20
|
stdout=subprocess.PIPE,
|
|
21
21
|
stderr=subprocess.PIPE,
|
|
22
|
-
text=True
|
|
22
|
+
text=True,
|
|
23
23
|
)
|
|
24
24
|
return True
|
|
25
25
|
except subprocess.CalledProcessError as e:
|
|
@@ -29,25 +29,29 @@ def run_git_command(cmd: List[str], cwd: Path) -> bool:
|
|
|
29
29
|
console.print("[red]Error:[/red] git command not found.")
|
|
30
30
|
return False
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
def update_config_repos(
|
|
34
|
+
root: Path, repo_name: str, repo_url: str, remove: bool = False
|
|
35
|
+
):
|
|
33
36
|
"""Update the repos list in the config file."""
|
|
34
37
|
# Use core config utils
|
|
35
38
|
data = load_raw_config(ConfigScope.PROJECT, project_root=str(root))
|
|
36
|
-
|
|
39
|
+
|
|
37
40
|
# Ensure structure exists
|
|
38
41
|
if "project" not in data:
|
|
39
42
|
data["project"] = {}
|
|
40
43
|
if "spike_repos" not in data["project"]:
|
|
41
44
|
data["project"]["spike_repos"] = {}
|
|
42
|
-
|
|
45
|
+
|
|
43
46
|
if remove:
|
|
44
47
|
if repo_name in data["project"]["spike_repos"]:
|
|
45
48
|
del data["project"]["spike_repos"][repo_name]
|
|
46
49
|
else:
|
|
47
50
|
data["project"]["spike_repos"][repo_name] = repo_url
|
|
48
|
-
|
|
51
|
+
|
|
49
52
|
save_raw_config(ConfigScope.PROJECT, data, project_root=str(root))
|
|
50
53
|
|
|
54
|
+
|
|
51
55
|
def ensure_gitignore(root: Path, target_dir_name: str):
|
|
52
56
|
"""Ensure the target directory is in .gitignore."""
|
|
53
57
|
gitignore = root / ".gitignore"
|
|
@@ -62,24 +66,28 @@ def ensure_gitignore(root: Path, target_dir_name: str):
|
|
|
62
66
|
with open(gitignore, "a") as f:
|
|
63
67
|
f.write(f"{prefix}{target_dir_name}/\n")
|
|
64
68
|
|
|
69
|
+
|
|
65
70
|
def sync_repo(root: Path, spikes_dir: Path, name: str, url: str):
|
|
66
71
|
"""Clone or Pull a repo."""
|
|
67
72
|
target_path = spikes_dir / name
|
|
68
|
-
|
|
73
|
+
|
|
69
74
|
if target_path.exists() and (target_path / ".git").exists():
|
|
70
75
|
console.print(f"Updating [bold]{name}[/bold]...")
|
|
71
76
|
run_git_command(["git", "pull"], cwd=target_path)
|
|
72
77
|
else:
|
|
73
|
-
# If dir exists but not a git repo, warn or error?
|
|
78
|
+
# If dir exists but not a git repo, warn or error?
|
|
74
79
|
# For safety, if non-empty and not git, skip or error.
|
|
75
80
|
if target_path.exists() and any(target_path.iterdir()):
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
console.print(
|
|
82
|
+
f"[yellow]Skipping {name}:[/yellow] Directory exists and is not empty, but not a git repo."
|
|
83
|
+
)
|
|
84
|
+
return
|
|
78
85
|
|
|
79
86
|
console.print(f"Cloning [bold]{name}[/bold]...")
|
|
80
87
|
target_path.mkdir(parents=True, exist_ok=True)
|
|
81
88
|
run_git_command(["git", "clone", url, "."], cwd=target_path)
|
|
82
89
|
|
|
90
|
+
|
|
83
91
|
def remove_repo_dir(spikes_dir: Path, name: str):
|
|
84
92
|
"""Physically remove the repo directory."""
|
|
85
93
|
target_path = spikes_dir / name
|
|
@@ -87,6 +95,7 @@ def remove_repo_dir(spikes_dir: Path, name: str):
|
|
|
87
95
|
if target_path.exists():
|
|
88
96
|
shutil.rmtree(target_path)
|
|
89
97
|
|
|
98
|
+
|
|
90
99
|
SKILL_CONTENT = """---
|
|
91
100
|
name: git-repo-spike
|
|
92
101
|
description: Manage external Git repositories as References in `.reference/`.
|
|
@@ -107,14 +116,13 @@ This skill normalizes how we introduce external code repositories.
|
|
|
107
116
|
3. **Remove**: `monoco spike remove <name>`
|
|
108
117
|
"""
|
|
109
118
|
|
|
119
|
+
|
|
110
120
|
def init(root: Path, spikes_dir_name: str):
|
|
111
121
|
"""Initialize Spike environment."""
|
|
112
122
|
ensure_gitignore(root, spikes_dir_name)
|
|
113
123
|
(root / spikes_dir_name).mkdir(exist_ok=True)
|
|
114
124
|
|
|
115
125
|
return {
|
|
116
|
-
"skills": {
|
|
117
|
-
|
|
118
|
-
},
|
|
119
|
-
"prompts": {} # Handled by adapter via resource files
|
|
126
|
+
"skills": {"git-repo-spike": SKILL_CONTENT},
|
|
127
|
+
"prompts": {}, # Handled by adapter via resource files
|
|
120
128
|
}
|
monoco/main.py
CHANGED
|
@@ -7,7 +7,7 @@ app = typer.Typer(
|
|
|
7
7
|
name="monoco",
|
|
8
8
|
help="Monoco Agent Native Toolkit",
|
|
9
9
|
add_completion=False,
|
|
10
|
-
no_args_is_help=True
|
|
10
|
+
no_args_is_help=True,
|
|
11
11
|
)
|
|
12
12
|
|
|
13
13
|
|
|
@@ -15,9 +15,9 @@ def version_callback(value: bool):
|
|
|
15
15
|
if value:
|
|
16
16
|
# Try to read from pyproject.toml first (for dev mode)
|
|
17
17
|
from pathlib import Path
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
version = "unknown"
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
try:
|
|
22
22
|
# Look for pyproject.toml relative to this file
|
|
23
23
|
# monoco/main.py -> ../pyproject.toml
|
|
@@ -28,13 +28,14 @@ def version_callback(value: bool):
|
|
|
28
28
|
if line.strip().startswith('version = "'):
|
|
29
29
|
version = line.split('"')[1]
|
|
30
30
|
break
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
if version == "unknown":
|
|
33
33
|
import importlib.metadata
|
|
34
|
+
|
|
34
35
|
version = importlib.metadata.version("monoco-toolkit")
|
|
35
36
|
except Exception:
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
# Fallback
|
|
38
|
+
pass
|
|
38
39
|
|
|
39
40
|
print(f"Monoco Toolkit v{version}")
|
|
40
41
|
raise typer.Exit()
|
|
@@ -44,27 +45,32 @@ def version_callback(value: bool):
|
|
|
44
45
|
def main(
|
|
45
46
|
ctx: typer.Context,
|
|
46
47
|
version: Optional[bool] = typer.Option(
|
|
47
|
-
None,
|
|
48
|
+
None,
|
|
49
|
+
"--version",
|
|
50
|
+
"-v",
|
|
51
|
+
help="Show version and exit",
|
|
52
|
+
callback=version_callback,
|
|
53
|
+
is_eager=True,
|
|
48
54
|
),
|
|
49
55
|
root: Optional[str] = typer.Option(
|
|
50
56
|
None, "--root", help="Explicitly specify the Monoco Workspace root directory."
|
|
51
|
-
)
|
|
57
|
+
),
|
|
52
58
|
):
|
|
53
59
|
"""
|
|
54
60
|
Monoco Toolkit - The sensory and motor system for Monoco Agents.
|
|
55
61
|
"""
|
|
56
62
|
# Capture command execution
|
|
57
63
|
from monoco.core.telemetry import capture_event
|
|
64
|
+
|
|
58
65
|
if ctx.invoked_subcommand:
|
|
59
66
|
capture_event("cli_command_executed", {"command": ctx.invoked_subcommand})
|
|
60
67
|
|
|
61
68
|
# Strict Workspace Resolution
|
|
62
69
|
# Commands allowed to run without a workspace
|
|
63
|
-
NO_WORKSPACE_COMMANDS = ["init", "clone"]
|
|
64
|
-
|
|
70
|
+
NO_WORKSPACE_COMMANDS = ["init", "clone"]
|
|
71
|
+
|
|
65
72
|
# Initialize Config
|
|
66
73
|
from monoco.core.config import get_config, find_monoco_root
|
|
67
|
-
from pathlib import Path
|
|
68
74
|
|
|
69
75
|
# If subcommand is not in whitelist, we enforce workspace
|
|
70
76
|
require_workspace = False
|
|
@@ -74,7 +80,7 @@ def main(
|
|
|
74
80
|
try:
|
|
75
81
|
# We pass root if provided. If require_workspace is True, get_config will throw if not found.
|
|
76
82
|
# Note: If root is None, it defaults to CWD in get_config.
|
|
77
|
-
|
|
83
|
+
|
|
78
84
|
# Auto-discover root if not provided
|
|
79
85
|
config_root = root
|
|
80
86
|
if config_root is None:
|
|
@@ -82,25 +88,30 @@ def main(
|
|
|
82
88
|
# Only use discovered root if it actually has .monoco
|
|
83
89
|
if (discovered / ".monoco").exists():
|
|
84
90
|
config_root = str(discovered)
|
|
85
|
-
|
|
91
|
+
|
|
86
92
|
get_config(project_root=config_root, require_project=require_workspace)
|
|
87
93
|
except FileNotFoundError as e:
|
|
88
94
|
# Graceful exit for workspace errors
|
|
89
95
|
from rich.console import Console
|
|
96
|
+
|
|
90
97
|
console = Console()
|
|
91
98
|
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
92
|
-
console.print(
|
|
99
|
+
console.print(
|
|
100
|
+
"[yellow]Tip:[/yellow] Run this command in a Monoco Workspace root (containing .monoco), or use [bold]--root <path>[/bold]."
|
|
101
|
+
)
|
|
93
102
|
raise typer.Exit(code=1)
|
|
94
103
|
|
|
104
|
+
|
|
95
105
|
from monoco.core.setup import init_cli
|
|
106
|
+
|
|
96
107
|
app.command(name="init")(init_cli)
|
|
97
108
|
|
|
98
109
|
from monoco.core.sync import sync_command, uninstall_command
|
|
110
|
+
|
|
99
111
|
app.command(name="sync")(sync_command)
|
|
100
112
|
app.command(name="uninstall")(uninstall_command)
|
|
101
113
|
|
|
102
114
|
|
|
103
|
-
|
|
104
115
|
@app.command()
|
|
105
116
|
def info():
|
|
106
117
|
"""
|
|
@@ -108,7 +119,7 @@ def info():
|
|
|
108
119
|
"""
|
|
109
120
|
from pydantic import BaseModel
|
|
110
121
|
from monoco.core.config import get_config
|
|
111
|
-
|
|
122
|
+
|
|
112
123
|
settings = get_config()
|
|
113
124
|
|
|
114
125
|
class Status(BaseModel):
|
|
@@ -118,8 +129,9 @@ def info():
|
|
|
118
129
|
project: str
|
|
119
130
|
|
|
120
131
|
mode = "Agent (JSON)" if os.getenv("AGENT_FLAG") == "true" else "Human (Rich)"
|
|
121
|
-
|
|
132
|
+
|
|
122
133
|
import importlib.metadata
|
|
134
|
+
|
|
123
135
|
try:
|
|
124
136
|
version = importlib.metadata.version("monoco-toolkit")
|
|
125
137
|
except importlib.metadata.PackageNotFoundError:
|
|
@@ -129,14 +141,15 @@ def info():
|
|
|
129
141
|
version=version,
|
|
130
142
|
mode=mode,
|
|
131
143
|
root=os.getcwd(),
|
|
132
|
-
project=f"{settings.project.name} ({settings.project.key})"
|
|
144
|
+
project=f"{settings.project.name} ({settings.project.key})",
|
|
133
145
|
)
|
|
134
|
-
|
|
146
|
+
|
|
135
147
|
print_output(status, title="Monoco Toolkit Status")
|
|
136
|
-
|
|
148
|
+
|
|
137
149
|
if mode == "Human (Rich)":
|
|
138
150
|
print_output(settings, title="Current Configuration")
|
|
139
151
|
|
|
152
|
+
|
|
140
153
|
# Register Feature Modules
|
|
141
154
|
# Register Feature Modules
|
|
142
155
|
from monoco.features.issue import commands as issue_cmd
|
|
@@ -154,8 +167,8 @@ app.add_typer(project_cmd.app, name="project", help="Manage projects")
|
|
|
154
167
|
app.add_typer(workspace_cmd.app, name="workspace", help="Manage workspace")
|
|
155
168
|
|
|
156
169
|
|
|
157
|
-
|
|
158
170
|
from monoco.daemon.commands import serve
|
|
171
|
+
|
|
159
172
|
app.command(name="serve")(serve)
|
|
160
173
|
|
|
161
174
|
if __name__ == "__main__":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: monoco-toolkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Agent Native Toolkit for Monoco - Task Management & Kanban for AI Agents
|
|
5
5
|
Project-URL: Homepage, https://monoco.io
|
|
6
6
|
Project-URL: Repository, https://github.com/IndenScale/Monoco
|