cognitive-modules 0.3.0__py3-none-any.whl → 0.4.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.
- cognitive/cli.py +203 -1
- cognitive/registry.py +325 -11
- {cognitive_modules-0.3.0.dist-info → cognitive_modules-0.4.0.dist-info}/METADATA +43 -33
- {cognitive_modules-0.3.0.dist-info → cognitive_modules-0.4.0.dist-info}/RECORD +8 -8
- cognitive_modules-0.4.0.dist-info/entry_points.txt +2 -0
- cognitive_modules-0.3.0.dist-info/entry_points.txt +0 -2
- {cognitive_modules-0.3.0.dist-info → cognitive_modules-0.4.0.dist-info}/WHEEL +0 -0
- {cognitive_modules-0.3.0.dist-info → cognitive_modules-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {cognitive_modules-0.3.0.dist-info → cognitive_modules-0.4.0.dist-info}/top_level.txt +0 -0
cognitive/cli.py
CHANGED
|
@@ -5,8 +5,12 @@ Commands:
|
|
|
5
5
|
cog list List installed modules
|
|
6
6
|
cog run <module> <input> Run a module
|
|
7
7
|
cog validate <module> Validate module structure
|
|
8
|
+
cog add <url> --module <name> Add module from GitHub (recommended)
|
|
9
|
+
cog update <module> Update module to latest version
|
|
10
|
+
cog versions <url> List available versions for a repo
|
|
8
11
|
cog install <source> Install module from git/local/registry
|
|
9
|
-
cog
|
|
12
|
+
cog remove <module> Remove an installed module
|
|
13
|
+
cog uninstall <module> Remove an installed module (alias)
|
|
10
14
|
cog init <name> Create a new module from template
|
|
11
15
|
cog search <query> Search the public registry
|
|
12
16
|
cog doctor Check environment setup
|
|
@@ -31,6 +35,11 @@ from .registry import (
|
|
|
31
35
|
uninstall_module,
|
|
32
36
|
search_registry,
|
|
33
37
|
fetch_registry,
|
|
38
|
+
install_from_github_url,
|
|
39
|
+
update_module,
|
|
40
|
+
get_installed_module_info,
|
|
41
|
+
get_module_version,
|
|
42
|
+
list_github_tags,
|
|
34
43
|
USER_MODULES_DIR,
|
|
35
44
|
)
|
|
36
45
|
from .loader import load_module, detect_format
|
|
@@ -203,6 +212,185 @@ def install_cmd(
|
|
|
203
212
|
raise typer.Exit(1)
|
|
204
213
|
|
|
205
214
|
|
|
215
|
+
@app.command("add")
|
|
216
|
+
def add_cmd(
|
|
217
|
+
url: str = typer.Argument(..., help="GitHub URL or org/repo shorthand"),
|
|
218
|
+
module: str = typer.Option(None, "--module", "-m", help="Module path within repo"),
|
|
219
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="Override module name"),
|
|
220
|
+
branch: str = typer.Option("main", "--branch", "-b", help="Git branch"),
|
|
221
|
+
tag: Optional[str] = typer.Option(None, "--tag", "-t", help="Git tag/version (e.g., v1.0.0)"),
|
|
222
|
+
):
|
|
223
|
+
"""
|
|
224
|
+
Add a cognitive module from GitHub.
|
|
225
|
+
|
|
226
|
+
Examples:
|
|
227
|
+
|
|
228
|
+
cog add https://github.com/ziel-io/cognitive-modules --module code-simplifier
|
|
229
|
+
|
|
230
|
+
cog add ziel-io/cognitive-modules -m code-reviewer
|
|
231
|
+
|
|
232
|
+
cog add org/repo -m my-module --tag v1.0.0
|
|
233
|
+
|
|
234
|
+
cog add org/repo -m path/to/module --name my-module
|
|
235
|
+
"""
|
|
236
|
+
rprint(f"[cyan]→[/cyan] Adding module from: {url}")
|
|
237
|
+
if module:
|
|
238
|
+
rprint(f" Module path: {module}")
|
|
239
|
+
if tag:
|
|
240
|
+
rprint(f" Version: {tag}")
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
target = install_from_github_url(
|
|
244
|
+
url=url,
|
|
245
|
+
module_path=module,
|
|
246
|
+
name=name,
|
|
247
|
+
branch=branch,
|
|
248
|
+
tag=tag,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Validate installed module
|
|
252
|
+
is_valid, errors, warnings = validate_module(str(target))
|
|
253
|
+
|
|
254
|
+
if not is_valid:
|
|
255
|
+
rprint(f"[red]✗ Installed module failed validation:[/red]")
|
|
256
|
+
for e in errors:
|
|
257
|
+
rprint(f" - {e}")
|
|
258
|
+
uninstall_module(target.name)
|
|
259
|
+
raise typer.Exit(1)
|
|
260
|
+
|
|
261
|
+
# Get version info
|
|
262
|
+
version = get_module_version(target)
|
|
263
|
+
version_str = f" v{version}" if version else ""
|
|
264
|
+
|
|
265
|
+
rprint(f"[green]✓ Added: {target.name}{version_str}[/green]")
|
|
266
|
+
rprint(f" Location: {target}")
|
|
267
|
+
|
|
268
|
+
if warnings:
|
|
269
|
+
rprint(f"[yellow] Warnings: {len(warnings)}[/yellow]")
|
|
270
|
+
|
|
271
|
+
rprint(f"\nRun with:")
|
|
272
|
+
rprint(f" [cyan]cog run {target.name} --args \"your input\"[/cyan]")
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
rprint(f"[red]✗ Failed to add module: {e}[/red]")
|
|
276
|
+
raise typer.Exit(1)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@app.command("remove")
|
|
280
|
+
def remove_cmd(
|
|
281
|
+
module: str = typer.Argument(..., help="Module name to remove"),
|
|
282
|
+
):
|
|
283
|
+
"""Remove an installed cognitive module (alias for uninstall)."""
|
|
284
|
+
uninstall_cmd(module)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@app.command("update")
|
|
288
|
+
def update_cmd(
|
|
289
|
+
module: str = typer.Argument(..., help="Module name to update"),
|
|
290
|
+
tag: Optional[str] = typer.Option(None, "--tag", "-t", help="Update to specific tag/version"),
|
|
291
|
+
):
|
|
292
|
+
"""
|
|
293
|
+
Update an installed module to the latest version.
|
|
294
|
+
|
|
295
|
+
Examples:
|
|
296
|
+
|
|
297
|
+
cog update code-simplifier
|
|
298
|
+
|
|
299
|
+
cog update code-simplifier --tag v2.0.0
|
|
300
|
+
"""
|
|
301
|
+
rprint(f"[cyan]→[/cyan] Updating module: {module}")
|
|
302
|
+
|
|
303
|
+
# Check if module is installed
|
|
304
|
+
info = get_installed_module_info(module)
|
|
305
|
+
if not info:
|
|
306
|
+
rprint(f"[red]✗ Module not found or not installed from GitHub: {module}[/red]")
|
|
307
|
+
rprint(f" Only modules installed with 'cog add' can be updated.")
|
|
308
|
+
raise typer.Exit(1)
|
|
309
|
+
|
|
310
|
+
github_url = info.get("github_url")
|
|
311
|
+
if not github_url:
|
|
312
|
+
rprint(f"[red]✗ Module was not installed from GitHub: {module}[/red]")
|
|
313
|
+
raise typer.Exit(1)
|
|
314
|
+
|
|
315
|
+
# Get current version
|
|
316
|
+
current_path = USER_MODULES_DIR / module
|
|
317
|
+
old_version = get_module_version(current_path) if current_path.exists() else None
|
|
318
|
+
|
|
319
|
+
if old_version:
|
|
320
|
+
rprint(f" Current version: {old_version}")
|
|
321
|
+
|
|
322
|
+
try:
|
|
323
|
+
# Re-install from source
|
|
324
|
+
module_path = info.get("module_path")
|
|
325
|
+
branch = info.get("branch", "main")
|
|
326
|
+
|
|
327
|
+
# Use specified tag or keep using the same ref type
|
|
328
|
+
use_tag = tag or info.get("tag")
|
|
329
|
+
|
|
330
|
+
target = install_from_github_url(
|
|
331
|
+
url=github_url,
|
|
332
|
+
module_path=module_path,
|
|
333
|
+
name=module,
|
|
334
|
+
branch=branch if not use_tag else "main",
|
|
335
|
+
tag=use_tag,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# Get new version
|
|
339
|
+
new_version = get_module_version(target)
|
|
340
|
+
|
|
341
|
+
if old_version and new_version:
|
|
342
|
+
if old_version == new_version:
|
|
343
|
+
rprint(f"[green]✓ Already up to date: {module} v{new_version}[/green]")
|
|
344
|
+
else:
|
|
345
|
+
rprint(f"[green]✓ Updated: {module} v{old_version} → v{new_version}[/green]")
|
|
346
|
+
elif new_version:
|
|
347
|
+
rprint(f"[green]✓ Updated: {module} to v{new_version}[/green]")
|
|
348
|
+
else:
|
|
349
|
+
rprint(f"[green]✓ Updated: {module}[/green]")
|
|
350
|
+
|
|
351
|
+
except Exception as e:
|
|
352
|
+
rprint(f"[red]✗ Failed to update module: {e}[/red]")
|
|
353
|
+
raise typer.Exit(1)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
@app.command("versions")
|
|
357
|
+
def versions_cmd(
|
|
358
|
+
url: str = typer.Argument(..., help="GitHub URL or org/repo shorthand"),
|
|
359
|
+
limit: int = typer.Option(10, "--limit", "-l", help="Max versions to show"),
|
|
360
|
+
):
|
|
361
|
+
"""
|
|
362
|
+
List available versions (tags) for a GitHub repository.
|
|
363
|
+
|
|
364
|
+
Examples:
|
|
365
|
+
|
|
366
|
+
cog versions ziel-io/cognitive-modules
|
|
367
|
+
|
|
368
|
+
cog versions https://github.com/org/repo
|
|
369
|
+
"""
|
|
370
|
+
rprint(f"[cyan]→[/cyan] Fetching versions from: {url}\n")
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
tags = list_github_tags(url, limit=limit)
|
|
374
|
+
|
|
375
|
+
if not tags:
|
|
376
|
+
rprint("[yellow]No tags/versions found.[/yellow]")
|
|
377
|
+
return
|
|
378
|
+
|
|
379
|
+
table = Table(title=f"Available Versions ({len(tags)})")
|
|
380
|
+
table.add_column("Version", style="cyan")
|
|
381
|
+
table.add_column("Install Command")
|
|
382
|
+
|
|
383
|
+
for tag in tags:
|
|
384
|
+
cmd = f"cog add {url} --tag {tag}"
|
|
385
|
+
table.add_row(tag, f"[dim]{cmd}[/dim]")
|
|
386
|
+
|
|
387
|
+
console.print(table)
|
|
388
|
+
|
|
389
|
+
except Exception as e:
|
|
390
|
+
rprint(f"[red]✗ Failed to fetch versions: {e}[/red]")
|
|
391
|
+
raise typer.Exit(1)
|
|
392
|
+
|
|
393
|
+
|
|
206
394
|
@app.command("uninstall")
|
|
207
395
|
def uninstall_cmd(
|
|
208
396
|
module: str = typer.Argument(..., help="Module name to uninstall"),
|
|
@@ -403,6 +591,20 @@ def info_cmd(
|
|
|
403
591
|
|
|
404
592
|
rprint(f"\n[bold]Path:[/bold] {m['path']}")
|
|
405
593
|
rprint(f"[bold]Prompt size:[/bold] {len(m['prompt'])} chars")
|
|
594
|
+
|
|
595
|
+
# Show installation info if available
|
|
596
|
+
install_info = get_installed_module_info(meta.get('name', module))
|
|
597
|
+
if install_info:
|
|
598
|
+
rprint(f"\n[bold]Installation:[/bold]")
|
|
599
|
+
if install_info.get("github_url"):
|
|
600
|
+
rprint(f" Source: {install_info['github_url']}")
|
|
601
|
+
if install_info.get("tag"):
|
|
602
|
+
rprint(f" Tag: {install_info['tag']}")
|
|
603
|
+
elif install_info.get("branch"):
|
|
604
|
+
rprint(f" Branch: {install_info['branch']}")
|
|
605
|
+
if install_info.get("installed_time"):
|
|
606
|
+
rprint(f" Installed: {install_info['installed_time'][:10]}")
|
|
607
|
+
rprint(f"\n Update with: [cyan]cog update {meta.get('name', module)}[/cyan]")
|
|
406
608
|
|
|
407
609
|
|
|
408
610
|
@app.callback(invoke_without_command=True)
|
cognitive/registry.py
CHANGED
|
@@ -17,8 +17,11 @@ import subprocess
|
|
|
17
17
|
import tempfile
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from typing import Optional
|
|
20
|
-
from urllib.request import urlopen
|
|
20
|
+
from urllib.request import urlopen, Request
|
|
21
21
|
from urllib.error import URLError
|
|
22
|
+
import zipfile
|
|
23
|
+
import io
|
|
24
|
+
import re
|
|
22
25
|
|
|
23
26
|
# Standard module search paths
|
|
24
27
|
SEARCH_PATHS = [
|
|
@@ -54,9 +57,11 @@ def find_module(name: str) -> Optional[Path]:
|
|
|
54
57
|
"""Find a module by name, searching all paths in order."""
|
|
55
58
|
for base_path in get_search_paths():
|
|
56
59
|
module_path = base_path / name
|
|
57
|
-
# Support
|
|
60
|
+
# Support v2, v1, and v0 formats
|
|
58
61
|
if module_path.exists():
|
|
59
|
-
if (module_path / "
|
|
62
|
+
if (module_path / "module.yaml").exists() or \
|
|
63
|
+
(module_path / "MODULE.md").exists() or \
|
|
64
|
+
(module_path / "module.md").exists():
|
|
60
65
|
return module_path
|
|
61
66
|
return None
|
|
62
67
|
|
|
@@ -75,12 +80,16 @@ def list_modules() -> list[dict]:
|
|
|
75
80
|
continue
|
|
76
81
|
if module_dir.name in seen:
|
|
77
82
|
continue
|
|
78
|
-
# Support both formats
|
|
79
|
-
if not ((module_dir / "MODULE.md").exists() or (module_dir / "module.md").exists()):
|
|
80
|
-
continue
|
|
81
83
|
|
|
82
|
-
# Detect format
|
|
83
|
-
|
|
84
|
+
# Detect format: v2, v1, or v0
|
|
85
|
+
if (module_dir / "module.yaml").exists():
|
|
86
|
+
fmt = "v2"
|
|
87
|
+
elif (module_dir / "MODULE.md").exists():
|
|
88
|
+
fmt = "v1"
|
|
89
|
+
elif (module_dir / "module.md").exists():
|
|
90
|
+
fmt = "v0"
|
|
91
|
+
else:
|
|
92
|
+
continue
|
|
84
93
|
|
|
85
94
|
seen.add(module_dir.name)
|
|
86
95
|
modules.append({
|
|
@@ -105,9 +114,9 @@ def install_from_local(source: Path, name: Optional[str] = None) -> Path:
|
|
|
105
114
|
if not source.exists():
|
|
106
115
|
raise FileNotFoundError(f"Source not found: {source}")
|
|
107
116
|
|
|
108
|
-
# Check for valid module (
|
|
109
|
-
if not (
|
|
110
|
-
raise ValueError(f"Not a valid module (missing MODULE.md or module.md): {source}")
|
|
117
|
+
# Check for valid module (v2, v1, or v0 format)
|
|
118
|
+
if not _is_valid_module(source):
|
|
119
|
+
raise ValueError(f"Not a valid module (missing module.yaml, MODULE.md, or module.md): {source}")
|
|
111
120
|
|
|
112
121
|
module_name = name or source.name
|
|
113
122
|
target = ensure_user_modules_dir() / module_name
|
|
@@ -116,9 +125,277 @@ def install_from_local(source: Path, name: Optional[str] = None) -> Path:
|
|
|
116
125
|
shutil.rmtree(target)
|
|
117
126
|
|
|
118
127
|
shutil.copytree(source, target)
|
|
128
|
+
|
|
129
|
+
# Record source info for update tracking
|
|
130
|
+
_record_module_source(module_name, source)
|
|
131
|
+
|
|
119
132
|
return target
|
|
120
133
|
|
|
121
134
|
|
|
135
|
+
def _record_module_source(
|
|
136
|
+
name: str,
|
|
137
|
+
source: Path,
|
|
138
|
+
github_url: str = None,
|
|
139
|
+
module_path: str = None,
|
|
140
|
+
tag: str = None,
|
|
141
|
+
branch: str = None,
|
|
142
|
+
version: str = None,
|
|
143
|
+
):
|
|
144
|
+
"""Record module source info for future updates."""
|
|
145
|
+
manifest_path = Path.home() / ".cognitive" / "installed.json"
|
|
146
|
+
manifest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
147
|
+
|
|
148
|
+
# Load existing manifest
|
|
149
|
+
manifest = {}
|
|
150
|
+
if manifest_path.exists():
|
|
151
|
+
try:
|
|
152
|
+
with open(manifest_path, 'r') as f:
|
|
153
|
+
manifest = json.load(f)
|
|
154
|
+
except:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
# Get current timestamp
|
|
158
|
+
from datetime import datetime
|
|
159
|
+
now = datetime.now().isoformat()
|
|
160
|
+
|
|
161
|
+
# Update entry
|
|
162
|
+
manifest[name] = {
|
|
163
|
+
"source": str(source),
|
|
164
|
+
"github_url": github_url,
|
|
165
|
+
"module_path": module_path,
|
|
166
|
+
"tag": tag,
|
|
167
|
+
"branch": branch,
|
|
168
|
+
"version": version,
|
|
169
|
+
"installed_at": str(Path.home() / ".cognitive" / "modules" / name),
|
|
170
|
+
"installed_time": now,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
with open(manifest_path, 'w') as f:
|
|
174
|
+
json.dump(manifest, f, indent=2)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def get_installed_module_info(name: str) -> Optional[dict]:
|
|
178
|
+
"""Get installation info for a module."""
|
|
179
|
+
manifest_path = Path.home() / ".cognitive" / "installed.json"
|
|
180
|
+
if not manifest_path.exists():
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
with open(manifest_path, 'r') as f:
|
|
185
|
+
manifest = json.load(f)
|
|
186
|
+
return manifest.get(name)
|
|
187
|
+
except:
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def get_module_version(module_path: Path) -> Optional[str]:
|
|
192
|
+
"""Extract version from a module's metadata."""
|
|
193
|
+
import yaml
|
|
194
|
+
|
|
195
|
+
# Try v2 format (module.yaml)
|
|
196
|
+
yaml_path = module_path / "module.yaml"
|
|
197
|
+
if yaml_path.exists():
|
|
198
|
+
try:
|
|
199
|
+
with open(yaml_path, 'r') as f:
|
|
200
|
+
data = yaml.safe_load(f)
|
|
201
|
+
return data.get("version")
|
|
202
|
+
except:
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
# Try v1 format (MODULE.md with frontmatter)
|
|
206
|
+
md_path = module_path / "MODULE.md"
|
|
207
|
+
if not md_path.exists():
|
|
208
|
+
md_path = module_path / "module.md"
|
|
209
|
+
|
|
210
|
+
if md_path.exists():
|
|
211
|
+
try:
|
|
212
|
+
with open(md_path, 'r') as f:
|
|
213
|
+
content = f.read()
|
|
214
|
+
if content.startswith("---"):
|
|
215
|
+
parts = content.split("---", 2)
|
|
216
|
+
if len(parts) >= 3:
|
|
217
|
+
meta = yaml.safe_load(parts[1])
|
|
218
|
+
return meta.get("version")
|
|
219
|
+
except:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def update_module(name: str) -> tuple[Path, str, str]:
|
|
226
|
+
"""
|
|
227
|
+
Update an installed module to the latest version.
|
|
228
|
+
|
|
229
|
+
Returns: (path, old_version, new_version)
|
|
230
|
+
"""
|
|
231
|
+
info = get_installed_module_info(name)
|
|
232
|
+
if not info:
|
|
233
|
+
raise ValueError(f"Module not installed or no source info: {name}")
|
|
234
|
+
|
|
235
|
+
github_url = info.get("github_url")
|
|
236
|
+
if not github_url:
|
|
237
|
+
raise ValueError(f"Module was not installed from GitHub, cannot update: {name}")
|
|
238
|
+
|
|
239
|
+
# Get current version
|
|
240
|
+
current_path = USER_MODULES_DIR / name
|
|
241
|
+
old_version = get_module_version(current_path) if current_path.exists() else None
|
|
242
|
+
|
|
243
|
+
# Re-install from source
|
|
244
|
+
module_path = info.get("module_path")
|
|
245
|
+
tag = info.get("tag")
|
|
246
|
+
branch = info.get("branch", "main")
|
|
247
|
+
|
|
248
|
+
# If installed with a specific tag, use that tag; otherwise use branch
|
|
249
|
+
ref = tag if tag else branch
|
|
250
|
+
|
|
251
|
+
new_path = install_from_github_url(
|
|
252
|
+
url=github_url,
|
|
253
|
+
module_path=module_path,
|
|
254
|
+
name=name,
|
|
255
|
+
tag=tag,
|
|
256
|
+
branch=branch if not tag else "main",
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# Get new version
|
|
260
|
+
new_version = get_module_version(new_path)
|
|
261
|
+
|
|
262
|
+
return new_path, old_version, new_version
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def install_from_github_url(
|
|
266
|
+
url: str,
|
|
267
|
+
module_path: Optional[str] = None,
|
|
268
|
+
name: Optional[str] = None,
|
|
269
|
+
branch: str = "main",
|
|
270
|
+
tag: Optional[str] = None,
|
|
271
|
+
) -> Path:
|
|
272
|
+
"""
|
|
273
|
+
Install a module from a GitHub URL without requiring git.
|
|
274
|
+
|
|
275
|
+
Uses GitHub's ZIP download feature for lightweight installation.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
url: GitHub URL or org/repo shorthand
|
|
279
|
+
module_path: Path to module within repository
|
|
280
|
+
name: Override module name
|
|
281
|
+
branch: Git branch (default: main)
|
|
282
|
+
tag: Git tag/release version (takes priority over branch)
|
|
283
|
+
|
|
284
|
+
Examples:
|
|
285
|
+
install_from_github_url("https://github.com/ziel-io/cognitive-modules",
|
|
286
|
+
module_path="cognitive/modules/code-simplifier")
|
|
287
|
+
install_from_github_url("ziel-io/cognitive-modules",
|
|
288
|
+
module_path="code-simplifier",
|
|
289
|
+
tag="v1.0.0")
|
|
290
|
+
"""
|
|
291
|
+
# Parse shorthand (org/repo)
|
|
292
|
+
original_url = url
|
|
293
|
+
if not url.startswith("http"):
|
|
294
|
+
url = f"https://github.com/{url}"
|
|
295
|
+
|
|
296
|
+
# Extract org/repo from URL
|
|
297
|
+
match = re.match(r"https://github\.com/([^/]+)/([^/]+)/?", url)
|
|
298
|
+
if not match:
|
|
299
|
+
raise ValueError(f"Invalid GitHub URL: {url}")
|
|
300
|
+
|
|
301
|
+
org, repo = match.groups()
|
|
302
|
+
repo = repo.rstrip(".git")
|
|
303
|
+
github_url = f"https://github.com/{org}/{repo}"
|
|
304
|
+
|
|
305
|
+
# Build ZIP download URL (tag takes priority over branch)
|
|
306
|
+
if tag:
|
|
307
|
+
# Try tag first, then as a release tag
|
|
308
|
+
zip_url = f"https://github.com/{org}/{repo}/archive/refs/tags/{tag}.zip"
|
|
309
|
+
else:
|
|
310
|
+
zip_url = f"https://github.com/{org}/{repo}/archive/refs/heads/{branch}.zip"
|
|
311
|
+
|
|
312
|
+
try:
|
|
313
|
+
# Download ZIP
|
|
314
|
+
req = Request(zip_url, headers={"User-Agent": "cognitive-modules/1.0"})
|
|
315
|
+
with urlopen(req, timeout=30) as response:
|
|
316
|
+
zip_data = response.read()
|
|
317
|
+
except URLError as e:
|
|
318
|
+
if tag:
|
|
319
|
+
raise RuntimeError(f"Failed to download tag '{tag}' from GitHub: {e}")
|
|
320
|
+
raise RuntimeError(f"Failed to download from GitHub: {e}")
|
|
321
|
+
|
|
322
|
+
# Extract to temp directory
|
|
323
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
324
|
+
tmppath = Path(tmpdir)
|
|
325
|
+
|
|
326
|
+
# Extract ZIP
|
|
327
|
+
with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
|
|
328
|
+
zf.extractall(tmppath)
|
|
329
|
+
|
|
330
|
+
# Find extracted directory (usually repo-branch or repo-tag)
|
|
331
|
+
extracted_dirs = list(tmppath.iterdir())
|
|
332
|
+
if not extracted_dirs:
|
|
333
|
+
raise RuntimeError("ZIP file was empty")
|
|
334
|
+
repo_root = extracted_dirs[0]
|
|
335
|
+
|
|
336
|
+
# Determine source path
|
|
337
|
+
if module_path:
|
|
338
|
+
# Try different module path patterns
|
|
339
|
+
source = None
|
|
340
|
+
possible_paths = [
|
|
341
|
+
repo_root / module_path,
|
|
342
|
+
repo_root / "cognitive" / "modules" / module_path,
|
|
343
|
+
repo_root / "modules" / module_path,
|
|
344
|
+
]
|
|
345
|
+
for p in possible_paths:
|
|
346
|
+
if p.exists() and _is_valid_module(p):
|
|
347
|
+
source = p
|
|
348
|
+
break
|
|
349
|
+
|
|
350
|
+
if not source:
|
|
351
|
+
raise FileNotFoundError(
|
|
352
|
+
f"Module not found at: {module_path}\n"
|
|
353
|
+
f"Searched in: {[str(p.relative_to(repo_root)) for p in possible_paths]}"
|
|
354
|
+
)
|
|
355
|
+
else:
|
|
356
|
+
# Use repo root as module
|
|
357
|
+
source = repo_root
|
|
358
|
+
if not _is_valid_module(source):
|
|
359
|
+
raise ValueError(
|
|
360
|
+
f"Repository root is not a valid module. "
|
|
361
|
+
f"Use --module to specify the module path."
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Determine module name and version
|
|
365
|
+
module_name = name or source.name
|
|
366
|
+
version = get_module_version(source)
|
|
367
|
+
|
|
368
|
+
# Install to user modules dir
|
|
369
|
+
target = ensure_user_modules_dir() / module_name
|
|
370
|
+
|
|
371
|
+
if target.exists():
|
|
372
|
+
shutil.rmtree(target)
|
|
373
|
+
|
|
374
|
+
shutil.copytree(source, target)
|
|
375
|
+
|
|
376
|
+
# Record source info for future updates
|
|
377
|
+
_record_module_source(
|
|
378
|
+
name=module_name,
|
|
379
|
+
source=source,
|
|
380
|
+
github_url=github_url,
|
|
381
|
+
module_path=module_path,
|
|
382
|
+
tag=tag,
|
|
383
|
+
branch=branch,
|
|
384
|
+
version=version,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
return target
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _is_valid_module(path: Path) -> bool:
|
|
391
|
+
"""Check if a directory is a valid cognitive module."""
|
|
392
|
+
return (
|
|
393
|
+
(path / "module.yaml").exists() or # v2 format
|
|
394
|
+
(path / "MODULE.md").exists() or # v1 format
|
|
395
|
+
(path / "module.md").exists() # v0 format
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
|
|
122
399
|
def install_from_git(url: str, subdir: Optional[str] = None, name: Optional[str] = None) -> Path:
|
|
123
400
|
"""
|
|
124
401
|
Install a module from a git repository.
|
|
@@ -274,3 +551,40 @@ def search_registry(query: str) -> list[dict]:
|
|
|
274
551
|
})
|
|
275
552
|
|
|
276
553
|
return results
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def list_github_tags(url: str, limit: int = 10) -> list[str]:
|
|
557
|
+
"""
|
|
558
|
+
List available tags/releases from a GitHub repository.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
url: GitHub URL or org/repo shorthand
|
|
562
|
+
limit: Maximum number of tags to return
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
List of tag names (e.g., ["v1.2.0", "v1.1.0", "v1.0.0"])
|
|
566
|
+
"""
|
|
567
|
+
# Parse shorthand
|
|
568
|
+
if not url.startswith("http"):
|
|
569
|
+
url = f"https://github.com/{url}"
|
|
570
|
+
|
|
571
|
+
match = re.match(r"https://github\.com/([^/]+)/([^/]+)/?", url)
|
|
572
|
+
if not match:
|
|
573
|
+
raise ValueError(f"Invalid GitHub URL: {url}")
|
|
574
|
+
|
|
575
|
+
org, repo = match.groups()
|
|
576
|
+
repo = repo.rstrip(".git")
|
|
577
|
+
|
|
578
|
+
# Use GitHub API to get tags
|
|
579
|
+
api_url = f"https://api.github.com/repos/{org}/{repo}/tags?per_page={limit}"
|
|
580
|
+
|
|
581
|
+
try:
|
|
582
|
+
req = Request(api_url, headers={
|
|
583
|
+
"User-Agent": "cognitive-modules/1.0",
|
|
584
|
+
"Accept": "application/vnd.github.v3+json",
|
|
585
|
+
})
|
|
586
|
+
with urlopen(req, timeout=10) as response:
|
|
587
|
+
data = json.loads(response.read().decode())
|
|
588
|
+
return [tag["name"] for tag in data]
|
|
589
|
+
except URLError as e:
|
|
590
|
+
return []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cognitive-modules
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Structured LLM task runner with schema validation, confidence scoring, and subagent orchestration
|
|
5
5
|
Author: ziel-io
|
|
6
6
|
License: MIT
|
|
@@ -68,7 +68,7 @@ Cognitive Modules 是一种 AI 任务定义规范,专为需要**强约束、
|
|
|
68
68
|
- **子代理编排** - `@call:module` 支持模块间调用
|
|
69
69
|
- **参数传递** - `$ARGUMENTS` 运行时替换
|
|
70
70
|
- **多 LLM 支持** - OpenAI / Anthropic / MiniMax / Ollama
|
|
71
|
-
- **公共注册表** - `
|
|
71
|
+
- **公共注册表** - `cogn install registry:module-name`
|
|
72
72
|
|
|
73
73
|
## 安装
|
|
74
74
|
|
|
@@ -94,13 +94,13 @@ export LLM_PROVIDER=minimax
|
|
|
94
94
|
export MINIMAX_API_KEY=sk-xxx
|
|
95
95
|
|
|
96
96
|
# 运行代码审查
|
|
97
|
-
|
|
97
|
+
cogn run code-reviewer --args "def login(u,p): return db.query(f'SELECT * FROM users WHERE name={u}')" --pretty
|
|
98
98
|
|
|
99
99
|
# 运行任务排序
|
|
100
|
-
|
|
100
|
+
cogn run task-prioritizer --args "修复bug(紧急), 写文档, 优化性能" --pretty
|
|
101
101
|
|
|
102
102
|
# 运行 API 设计
|
|
103
|
-
|
|
103
|
+
cogn run api-designer --args "用户系统 CRUD API" --pretty
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
## 与 Skills 对比
|
|
@@ -114,48 +114,58 @@ cog run api-designer --args "用户系统 CRUD API" --pretty
|
|
|
114
114
|
| 推理过程 | ❌ | ✅ 必须 rationale |
|
|
115
115
|
| 参数传递 | ✅ $ARGUMENTS | ✅ $ARGUMENTS |
|
|
116
116
|
| 子代理 | ✅ context: fork | ✅ @call + context |
|
|
117
|
-
| 验证工具 | ❌ | ✅
|
|
118
|
-
| 注册表 | ❌ | ✅
|
|
117
|
+
| 验证工具 | ❌ | ✅ cogn validate |
|
|
118
|
+
| 注册表 | ❌ | ✅ cogn install |
|
|
119
119
|
|
|
120
120
|
## CLI 命令
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
123
|
# 模块管理
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
cogn list # 列出已安装模块
|
|
125
|
+
cogn info <module> # 查看模块详情
|
|
126
|
+
cogn validate <module> # 验证模块结构
|
|
127
127
|
|
|
128
128
|
# 运行模块
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
cogn run <module> input.json -o output.json --pretty
|
|
130
|
+
cogn run <module> --args "需求描述" --pretty
|
|
131
|
+
cogn run <module> --args "需求" --subagent # 启用子代理
|
|
132
132
|
|
|
133
133
|
# 创建模块
|
|
134
|
-
|
|
134
|
+
cogn init <name> -d "描述"
|
|
135
135
|
|
|
136
|
-
#
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
# 从 GitHub 安装(推荐)
|
|
137
|
+
cogn add ziel-io/cognitive-modules -m code-simplifier
|
|
138
|
+
cogn add org/repo -m module-name --tag v1.0.0 # 安装指定版本
|
|
139
|
+
cogn remove <module> # 删除模块
|
|
140
|
+
|
|
141
|
+
# 版本管理
|
|
142
|
+
cogn update <module> # 更新到最新版本
|
|
143
|
+
cogn update <module> --tag v2.0.0 # 更新到指定版本
|
|
144
|
+
cogn versions <url> # 查看可用版本
|
|
145
|
+
|
|
146
|
+
# 其他安装方式
|
|
147
|
+
cogn install github:user/repo/path
|
|
148
|
+
cogn install registry:module-name
|
|
149
|
+
cogn uninstall <module>
|
|
140
150
|
|
|
141
151
|
# 注册表
|
|
142
|
-
|
|
143
|
-
|
|
152
|
+
cogn registry # 查看公共模块
|
|
153
|
+
cogn search <query> # 搜索模块
|
|
144
154
|
|
|
145
155
|
# 环境检查
|
|
146
|
-
|
|
156
|
+
cogn doctor
|
|
147
157
|
```
|
|
148
158
|
|
|
149
159
|
## 内置模块
|
|
150
160
|
|
|
151
161
|
| 模块 | 功能 | 示例 |
|
|
152
162
|
|------|------|------|
|
|
153
|
-
| `code-reviewer` | 代码审查 | `
|
|
154
|
-
| `code-simplifier` | 代码简化 | `
|
|
155
|
-
| `task-prioritizer` | 任务优先级排序 | `
|
|
156
|
-
| `api-designer` | REST API 设计 | `
|
|
157
|
-
| `ui-spec-generator` | UI 规范生成 | `
|
|
158
|
-
| `product-analyzer` | 产品分析(子代理示例) | `
|
|
163
|
+
| `code-reviewer` | 代码审查 | `cogn run code-reviewer --args "你的代码"` |
|
|
164
|
+
| `code-simplifier` | 代码简化 | `cogn run code-simplifier --args "复杂代码"` |
|
|
165
|
+
| `task-prioritizer` | 任务优先级排序 | `cogn run task-prioritizer --args "任务1,任务2"` |
|
|
166
|
+
| `api-designer` | REST API 设计 | `cogn run api-designer --args "订单系统"` |
|
|
167
|
+
| `ui-spec-generator` | UI 规范生成 | `cogn run ui-spec-generator --args "电商首页"` |
|
|
168
|
+
| `product-analyzer` | 产品分析(子代理示例) | `cogn run product-analyzer --args "健康产品" -s` |
|
|
159
169
|
|
|
160
170
|
## 模块格式
|
|
161
171
|
|
|
@@ -239,7 +249,7 @@ export MINIMAX_API_KEY=sk-xxx
|
|
|
239
249
|
export LLM_PROVIDER=ollama
|
|
240
250
|
|
|
241
251
|
# 检查配置
|
|
242
|
-
|
|
252
|
+
cogn doctor
|
|
243
253
|
```
|
|
244
254
|
|
|
245
255
|
## 创建新模块(完整流程)
|
|
@@ -340,14 +350,14 @@ EOF
|
|
|
340
350
|
### Step 4: 验证模块
|
|
341
351
|
|
|
342
352
|
```bash
|
|
343
|
-
|
|
344
|
-
|
|
353
|
+
cogn validate code-simplifier
|
|
354
|
+
cogn list # 确认模块出现在列表中
|
|
345
355
|
```
|
|
346
356
|
|
|
347
357
|
### Step 5: 测试运行
|
|
348
358
|
|
|
349
359
|
```bash
|
|
350
|
-
|
|
360
|
+
cogn run code-simplifier --args "def calc(x): if x > 0: if x < 10: return x * 2 else: return x else: return 0" --pretty
|
|
351
361
|
```
|
|
352
362
|
|
|
353
363
|
### Step 6: 添加示例(可选)
|
|
@@ -384,8 +394,8 @@ pip install -e ".[dev]"
|
|
|
384
394
|
pytest tests/ -v
|
|
385
395
|
|
|
386
396
|
# 创建新模块(使用模板)
|
|
387
|
-
|
|
388
|
-
|
|
397
|
+
cogn init my-module -d "模块描述"
|
|
398
|
+
cogn validate my-module
|
|
389
399
|
```
|
|
390
400
|
|
|
391
401
|
## 项目结构
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
cognitive/__init__.py,sha256=uSX5NuOWyW0qtq1bnxbzabZ0OQakAtsjF0MWbjQBvwE,401
|
|
2
|
-
cognitive/cli.py,sha256=
|
|
2
|
+
cognitive/cli.py,sha256=A8NUW5nvJpk6H3sJIZWC6HHvuSibR7QKDm0PlVGpwcs,21495
|
|
3
3
|
cognitive/loader.py,sha256=3_ShEfefOMP2EJjpQ1VGTGio4K_GUWLZMQHa0RgvBwg,8715
|
|
4
|
-
cognitive/registry.py,sha256=
|
|
4
|
+
cognitive/registry.py,sha256=qLGyvUxSDP4frs46UlPa5jCcKubqiikWT3Y9JrBqfFc,18549
|
|
5
5
|
cognitive/runner.py,sha256=KFO_pV7YTluYaL98_5bPp_HTNgwsTvlUjgNYlI8mo-w,8467
|
|
6
6
|
cognitive/subagent.py,sha256=fb7LWwNF6YcJtC_T1dK0EvzqWMBnav-kiCIpvVohEBw,8142
|
|
7
7
|
cognitive/templates.py,sha256=lKC197X9aQIA-npUvVCaplSwvhxjsH_KYVCQtrTZrL4,4712
|
|
8
8
|
cognitive/validator.py,sha256=1v1HUHYOlAc2sYCkIq_gnUMMnca0fdtQwr7UMBFpp04,12200
|
|
9
9
|
cognitive/providers/__init__.py,sha256=hqhVA1IEXpVtyCAteXhO5yD8a8ikQpVIPEKJVHLtRFY,7492
|
|
10
|
-
cognitive_modules-0.
|
|
11
|
-
cognitive_modules-0.
|
|
12
|
-
cognitive_modules-0.
|
|
13
|
-
cognitive_modules-0.
|
|
14
|
-
cognitive_modules-0.
|
|
15
|
-
cognitive_modules-0.
|
|
10
|
+
cognitive_modules-0.4.0.dist-info/licenses/LICENSE,sha256=NXFYUy2hPJdh3NHRxMChTnMiQD9k8zFxkmR7gWefexc,1064
|
|
11
|
+
cognitive_modules-0.4.0.dist-info/METADATA,sha256=iF4YKfLj1CzOZuoYDH0JjuH_S2eh7MbV91KQfm-pdoI,11833
|
|
12
|
+
cognitive_modules-0.4.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
13
|
+
cognitive_modules-0.4.0.dist-info/entry_points.txt,sha256=1yqKB-MjHrRoe8FcYm-TgZDqGEZxzBPsd0xC4pcfBNU,43
|
|
14
|
+
cognitive_modules-0.4.0.dist-info/top_level.txt,sha256=kGIfDucCKylo8cRBtxER_v3DHIea-Sol9x9YSJo1u3Y,10
|
|
15
|
+
cognitive_modules-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|