devrel-origin 0.2.14__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.
- devrel_origin/__init__.py +15 -0
- devrel_origin/cli/__init__.py +92 -0
- devrel_origin/cli/_common.py +243 -0
- devrel_origin/cli/analytics.py +28 -0
- devrel_origin/cli/argus.py +497 -0
- devrel_origin/cli/auth.py +227 -0
- devrel_origin/cli/config.py +108 -0
- devrel_origin/cli/content.py +259 -0
- devrel_origin/cli/cost.py +108 -0
- devrel_origin/cli/cro.py +298 -0
- devrel_origin/cli/deliverables.py +65 -0
- devrel_origin/cli/docs.py +91 -0
- devrel_origin/cli/doctor.py +178 -0
- devrel_origin/cli/experiment.py +29 -0
- devrel_origin/cli/growth.py +97 -0
- devrel_origin/cli/init.py +472 -0
- devrel_origin/cli/intel.py +27 -0
- devrel_origin/cli/kb.py +96 -0
- devrel_origin/cli/listen.py +31 -0
- devrel_origin/cli/marketing.py +66 -0
- devrel_origin/cli/migrate.py +45 -0
- devrel_origin/cli/run.py +46 -0
- devrel_origin/cli/sales.py +57 -0
- devrel_origin/cli/schedule.py +62 -0
- devrel_origin/cli/synthesize.py +28 -0
- devrel_origin/cli/triage.py +29 -0
- devrel_origin/cli/video.py +35 -0
- devrel_origin/core/__init__.py +58 -0
- devrel_origin/core/agent_config.py +75 -0
- devrel_origin/core/argus.py +964 -0
- devrel_origin/core/atlas.py +1450 -0
- devrel_origin/core/base.py +372 -0
- devrel_origin/core/cyra.py +563 -0
- devrel_origin/core/dex.py +708 -0
- devrel_origin/core/echo.py +614 -0
- devrel_origin/core/growth/__init__.py +27 -0
- devrel_origin/core/growth/recommendations.py +219 -0
- devrel_origin/core/growth/target_kinds.py +51 -0
- devrel_origin/core/iris.py +513 -0
- devrel_origin/core/kai.py +1367 -0
- devrel_origin/core/llm.py +542 -0
- devrel_origin/core/llm_backends.py +274 -0
- devrel_origin/core/mox.py +514 -0
- devrel_origin/core/nova.py +349 -0
- devrel_origin/core/pax.py +1205 -0
- devrel_origin/core/rex.py +532 -0
- devrel_origin/core/sage.py +486 -0
- devrel_origin/core/sentinel.py +385 -0
- devrel_origin/core/types.py +98 -0
- devrel_origin/core/video/__init__.py +22 -0
- devrel_origin/core/video/assembler.py +131 -0
- devrel_origin/core/video/browser_recorder.py +118 -0
- devrel_origin/core/video/desktop_recorder.py +254 -0
- devrel_origin/core/video/overlay_renderer.py +143 -0
- devrel_origin/core/video/script_parser.py +147 -0
- devrel_origin/core/video/tts_engine.py +82 -0
- devrel_origin/core/vox.py +268 -0
- devrel_origin/core/watchdog.py +321 -0
- devrel_origin/project/__init__.py +1 -0
- devrel_origin/project/config.py +75 -0
- devrel_origin/project/cost_sink.py +61 -0
- devrel_origin/project/init.py +104 -0
- devrel_origin/project/paths.py +75 -0
- devrel_origin/project/state.py +241 -0
- devrel_origin/project/templates/__init__.py +4 -0
- devrel_origin/project/templates/config.toml +24 -0
- devrel_origin/project/templates/devrel.gitignore +10 -0
- devrel_origin/project/templates/slop-blocklist.md +45 -0
- devrel_origin/project/templates/style.md +24 -0
- devrel_origin/project/templates/voice.md +29 -0
- devrel_origin/quality/__init__.py +66 -0
- devrel_origin/quality/editorial.py +357 -0
- devrel_origin/quality/persona.py +84 -0
- devrel_origin/quality/readability.py +148 -0
- devrel_origin/quality/slop.py +167 -0
- devrel_origin/quality/style.py +110 -0
- devrel_origin/quality/voice.py +15 -0
- devrel_origin/tools/__init__.py +9 -0
- devrel_origin/tools/analytics.py +304 -0
- devrel_origin/tools/api_client.py +393 -0
- devrel_origin/tools/apollo_client.py +305 -0
- devrel_origin/tools/code_validator.py +428 -0
- devrel_origin/tools/github_tools.py +297 -0
- devrel_origin/tools/instantly_client.py +412 -0
- devrel_origin/tools/kb_harvester.py +340 -0
- devrel_origin/tools/mcp_server.py +578 -0
- devrel_origin/tools/notifications.py +245 -0
- devrel_origin/tools/run_report.py +193 -0
- devrel_origin/tools/scheduler.py +231 -0
- devrel_origin/tools/search_tools.py +321 -0
- devrel_origin/tools/self_improve.py +168 -0
- devrel_origin/tools/sheets.py +236 -0
- devrel_origin-0.2.14.dist-info/METADATA +354 -0
- devrel_origin-0.2.14.dist-info/RECORD +98 -0
- devrel_origin-0.2.14.dist-info/WHEEL +5 -0
- devrel_origin-0.2.14.dist-info/entry_points.txt +2 -0
- devrel_origin-0.2.14.dist-info/licenses/LICENSE +21 -0
- devrel_origin-0.2.14.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""`devrel listen` — social-media listening via Echo."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from devrel_origin.cli._common import build_atlas_or_exit, find_paths_or_exit, render_result
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def listen_command(
|
|
16
|
+
platforms: str = typer.Option(
|
|
17
|
+
"reddit,hn,twitter",
|
|
18
|
+
"--platforms",
|
|
19
|
+
help="Comma-separated platforms to scan.",
|
|
20
|
+
),
|
|
21
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Scan social media for product mentions and sentiment."""
|
|
24
|
+
paths = find_paths_or_exit(console)
|
|
25
|
+
atlas = build_atlas_or_exit(paths, console)
|
|
26
|
+
|
|
27
|
+
async def _do() -> None:
|
|
28
|
+
result = await atlas.run_single_task("echo", f"Scan {platforms} for product mentions")
|
|
29
|
+
render_result(result, console, json_output=json_output)
|
|
30
|
+
|
|
31
|
+
asyncio.run(_do())
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""`devrel marketing {blog, landing, social, campaign}` — Mox-powered surfaces."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from devrel_origin.cli._common import build_atlas_or_exit, find_paths_or_exit, render_result
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
marketing_app = typer.Typer(
|
|
15
|
+
name="marketing",
|
|
16
|
+
help="Marketing campaigns: blog posts, landing pages, social, full campaigns.",
|
|
17
|
+
no_args_is_help=True,
|
|
18
|
+
add_completion=False,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _run(task: str, json_output: bool) -> None:
|
|
23
|
+
paths = find_paths_or_exit(console)
|
|
24
|
+
atlas = build_atlas_or_exit(paths, console)
|
|
25
|
+
|
|
26
|
+
async def _do() -> None:
|
|
27
|
+
result = await atlas.run_single_task("mox", task)
|
|
28
|
+
render_result(result, console, json_output=json_output)
|
|
29
|
+
|
|
30
|
+
asyncio.run(_do())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@marketing_app.command("blog")
|
|
34
|
+
def blog(
|
|
35
|
+
topic: str = typer.Argument(..., help="Blog topic."),
|
|
36
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Write a blog post on a topic."""
|
|
39
|
+
_run(f"Write blog post: {topic}", json_output)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@marketing_app.command("landing")
|
|
43
|
+
def landing(
|
|
44
|
+
topic: str = typer.Argument(..., help="Landing page topic."),
|
|
45
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Write landing page copy."""
|
|
48
|
+
_run(f"Write landing page copy: {topic}", json_output)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@marketing_app.command("social")
|
|
52
|
+
def social(
|
|
53
|
+
topic: str = typer.Argument(..., help="Social topic."),
|
|
54
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Write a social media batch."""
|
|
57
|
+
_run(f"Write social batch: {topic}", json_output)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@marketing_app.command("campaign")
|
|
61
|
+
def campaign(
|
|
62
|
+
brief: str = typer.Argument(..., help="Campaign brief."),
|
|
63
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Build a full marketing campaign."""
|
|
66
|
+
_run(f"Build campaign: {brief}", json_output)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""`devrel migrate` - upgrade the project state.db schema in place.
|
|
2
|
+
|
|
3
|
+
State DBs created by older devrel-origin releases (e.g. 0.2.4 / schema v4)
|
|
4
|
+
must be upgraded to the current SCHEMA_VERSION before agents can write to
|
|
5
|
+
the new tables. `init_db()` is idempotent and runs the v5 migration on any
|
|
6
|
+
existing DB; this verb just exposes it as a discoverable CLI command so
|
|
7
|
+
users don't have to import internals.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
|
|
15
|
+
from devrel_origin.cli._common import find_paths_or_exit
|
|
16
|
+
from devrel_origin.project.state import SCHEMA_VERSION, get_schema_version, init_db
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def migrate_command() -> None:
|
|
22
|
+
"""Upgrade the project state.db schema to the current version."""
|
|
23
|
+
paths = find_paths_or_exit(console)
|
|
24
|
+
db = paths.state_db
|
|
25
|
+
|
|
26
|
+
before = get_schema_version(db) if db.is_file() else None
|
|
27
|
+
if before == SCHEMA_VERSION:
|
|
28
|
+
console.print(
|
|
29
|
+
f"[green]✓[/green] state.db already at schema v{SCHEMA_VERSION}; nothing to migrate."
|
|
30
|
+
)
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
init_db(db)
|
|
34
|
+
after = get_schema_version(db)
|
|
35
|
+
|
|
36
|
+
if after != SCHEMA_VERSION:
|
|
37
|
+
console.print(
|
|
38
|
+
f"[red]✗[/red] migration ran but schema version is v{after}, expected v{SCHEMA_VERSION}."
|
|
39
|
+
)
|
|
40
|
+
raise typer.Exit(code=1)
|
|
41
|
+
|
|
42
|
+
if before is None:
|
|
43
|
+
console.print(f"[green]✓[/green] state.db created at schema v{after}.")
|
|
44
|
+
else:
|
|
45
|
+
console.print(f"[green]✓[/green] state.db migrated v{before} -> v{after}.")
|
devrel_origin/cli/run.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""`devrel run` — full weekly pipeline, health check, or single agent."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from devrel_origin.cli._common import build_atlas_or_exit, find_paths_or_exit
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run_command(
|
|
16
|
+
health: bool = typer.Option(False, "--health", help="Only run the Watchdog health check."),
|
|
17
|
+
agent: str = typer.Option("", "--agent", help="Run a single agent by name (e.g., 'kai')."),
|
|
18
|
+
task: str = typer.Option("", "--task", help="Task description for --agent."),
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Run the full weekly pipeline (default), or a subset via flags."""
|
|
21
|
+
paths = find_paths_or_exit(console)
|
|
22
|
+
atlas = build_atlas_or_exit(paths, console)
|
|
23
|
+
|
|
24
|
+
async def _do() -> None:
|
|
25
|
+
if health:
|
|
26
|
+
result = await atlas.run_single_task("watchdog", "Check system health")
|
|
27
|
+
console.print(
|
|
28
|
+
f"[green]✓[/green] watchdog: {str(result.output)[:300]}"
|
|
29
|
+
if result.success
|
|
30
|
+
else f"[red]✗[/red] {result.error}"
|
|
31
|
+
)
|
|
32
|
+
return
|
|
33
|
+
if agent:
|
|
34
|
+
t = task or f"Run {agent} with default settings"
|
|
35
|
+
result = await atlas.run_single_task(agent, t)
|
|
36
|
+
if result.success:
|
|
37
|
+
console.print(f"[green]✓[/green] {agent}: {str(result.output)[:300]}")
|
|
38
|
+
else:
|
|
39
|
+
console.print(f"[red]✗[/red] {agent} failed: {result.error}")
|
|
40
|
+
raise typer.Exit(code=1)
|
|
41
|
+
return
|
|
42
|
+
# Full weekly pipeline.
|
|
43
|
+
ctx = await atlas.run_weekly_cycle()
|
|
44
|
+
console.print(f"[bold green]Weekly cycle complete.[/bold green] week_of={ctx.week_of}")
|
|
45
|
+
|
|
46
|
+
asyncio.run(_do())
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""`devrel sales {outreach, battlecard, sequence}` — Pax-powered sales surfaces."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from devrel_origin.cli._common import build_atlas_or_exit, find_paths_or_exit, render_result
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
sales_app = typer.Typer(
|
|
15
|
+
name="sales",
|
|
16
|
+
help="Sales enablement: outreach, battle cards, nurture sequences.",
|
|
17
|
+
no_args_is_help=True,
|
|
18
|
+
add_completion=False,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _run(task: str, json_output: bool) -> None:
|
|
23
|
+
paths = find_paths_or_exit(console)
|
|
24
|
+
atlas = build_atlas_or_exit(paths, console)
|
|
25
|
+
|
|
26
|
+
async def _do() -> None:
|
|
27
|
+
result = await atlas.run_single_task("pax", task)
|
|
28
|
+
render_result(result, console, json_output=json_output)
|
|
29
|
+
|
|
30
|
+
asyncio.run(_do())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@sales_app.command("outreach")
|
|
34
|
+
def outreach(
|
|
35
|
+
company: str = typer.Argument(..., help="Target company."),
|
|
36
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Draft a cold outreach email for a target company."""
|
|
39
|
+
_run(f"Draft outreach email for {company}", json_output)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@sales_app.command("battlecard")
|
|
43
|
+
def battlecard(
|
|
44
|
+
competitor: str = typer.Argument(..., help="Competitor."),
|
|
45
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Build a sales battle card against a competitor."""
|
|
48
|
+
_run(f"Build battle card vs. {competitor}", json_output)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@sales_app.command("sequence")
|
|
52
|
+
def sequence(
|
|
53
|
+
campaign: str = typer.Argument(..., help="Campaign description."),
|
|
54
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Design a multi-touch nurture sequence."""
|
|
57
|
+
_run(f"Design nurture sequence: {campaign}", json_output)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""`devrel schedule {install, list, remove}` — wraps tools.scheduler.Scheduler.
|
|
2
|
+
|
|
3
|
+
The Scheduler ctor accepts `project_dir` (string path); we pass the
|
|
4
|
+
project root resolved from `.devrel/`. The CLI is a thin wrapper around
|
|
5
|
+
`install_cron`, `remove_cron`, and `list_entries`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
|
|
13
|
+
from devrel_origin.cli._common import find_paths_or_exit
|
|
14
|
+
from devrel_origin.tools.scheduler import Scheduler
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
schedule_app = typer.Typer(
|
|
19
|
+
name="schedule",
|
|
20
|
+
help="Manage cron-based agent scheduling.",
|
|
21
|
+
no_args_is_help=True,
|
|
22
|
+
add_completion=False,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _build_scheduler() -> Scheduler:
|
|
27
|
+
paths = find_paths_or_exit(console)
|
|
28
|
+
return Scheduler(project_dir=str(paths.root))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@schedule_app.command("install")
|
|
32
|
+
def install() -> None:
|
|
33
|
+
"""Install agent schedule into the user crontab."""
|
|
34
|
+
sched = _build_scheduler()
|
|
35
|
+
lines = sched.install_cron()
|
|
36
|
+
console.print(f"[green]✓[/green] Installed {len(lines)} cron entry(ies)")
|
|
37
|
+
for line in lines:
|
|
38
|
+
console.print(f" [dim]{line}[/dim]")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@schedule_app.command("list")
|
|
42
|
+
def list_entries() -> None:
|
|
43
|
+
"""List the configured schedule entries."""
|
|
44
|
+
sched = _build_scheduler()
|
|
45
|
+
entries = sched.list_entries()
|
|
46
|
+
if not entries:
|
|
47
|
+
console.print("[yellow]No schedule entries configured.[/yellow]")
|
|
48
|
+
return
|
|
49
|
+
for e in entries:
|
|
50
|
+
flag = "[green]on[/green]" if e.get("enabled") else "[dim]off[/dim]"
|
|
51
|
+
console.print(
|
|
52
|
+
f" {flag} [bold]{e.get('name', '?')}[/bold] "
|
|
53
|
+
f"[dim]{e.get('cron', '?')}[/dim] {e.get('description', '')}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@schedule_app.command("remove")
|
|
58
|
+
def remove() -> None:
|
|
59
|
+
"""Remove all devrel-origin entries from the user crontab."""
|
|
60
|
+
sched = _build_scheduler()
|
|
61
|
+
sched.remove_cron()
|
|
62
|
+
console.print("[green]✓[/green] Removed devrel-origin cron entries")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""`devrel synthesize` — theme extraction via Iris."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from devrel_origin.cli._common import build_atlas_or_exit, find_paths_or_exit, render_result
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def synthesize_command(
|
|
16
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Extract themes from latest social + triage signals via Iris."""
|
|
19
|
+
paths = find_paths_or_exit(console)
|
|
20
|
+
atlas = build_atlas_or_exit(paths, console)
|
|
21
|
+
|
|
22
|
+
async def _do() -> None:
|
|
23
|
+
result = await atlas.run_single_task(
|
|
24
|
+
"iris", "Extract themes from latest social + triage signals"
|
|
25
|
+
)
|
|
26
|
+
render_result(result, console, json_output=json_output)
|
|
27
|
+
|
|
28
|
+
asyncio.run(_do())
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""`devrel triage` — GitHub issue triage via Sage."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from devrel_origin.cli._common import build_atlas_or_exit, find_paths_or_exit, render_result
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def triage_command(
|
|
16
|
+
days: int = typer.Option(7, "--days", help="Look back this many days."),
|
|
17
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Triage GitHub issues from the last N days."""
|
|
20
|
+
paths = find_paths_or_exit(console)
|
|
21
|
+
atlas = build_atlas_or_exit(paths, console)
|
|
22
|
+
|
|
23
|
+
async def _do() -> None:
|
|
24
|
+
result = await atlas.run_single_task(
|
|
25
|
+
"sage", f"Triage GitHub issues from the last {days} days"
|
|
26
|
+
)
|
|
27
|
+
render_result(result, console, json_output=json_output)
|
|
28
|
+
|
|
29
|
+
asyncio.run(_do())
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""`devrel video record` — screen-recorded tutorials via Vox."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from devrel_origin.cli._common import build_atlas_or_exit, find_paths_or_exit, render_result
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
video_app = typer.Typer(
|
|
15
|
+
name="video",
|
|
16
|
+
help="Video tutorial production.",
|
|
17
|
+
no_args_is_help=True,
|
|
18
|
+
add_completion=False,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@video_app.command("record")
|
|
23
|
+
def record(
|
|
24
|
+
script: str = typer.Argument(..., help="Path to script markdown OR raw task description."),
|
|
25
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Record a screen-recorded video tutorial via Vox."""
|
|
28
|
+
paths = find_paths_or_exit(console)
|
|
29
|
+
atlas = build_atlas_or_exit(paths, console)
|
|
30
|
+
|
|
31
|
+
async def _do() -> None:
|
|
32
|
+
result = await atlas.run_single_task("vox", f"Record video tutorial: {script}")
|
|
33
|
+
render_result(result, console, json_output=json_output)
|
|
34
|
+
|
|
35
|
+
asyncio.run(_do())
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DevTools Advocate Agent System
|
|
3
|
+
|
|
4
|
+
A multi-agent system for autonomous developer advocacy,
|
|
5
|
+
built on Claude Agent SDK and Model Context Protocol (MCP).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from devrel_origin.core.argus import (
|
|
9
|
+
Argus,
|
|
10
|
+
PerformanceMetric,
|
|
11
|
+
PerformanceReport,
|
|
12
|
+
Recommendation,
|
|
13
|
+
)
|
|
14
|
+
from devrel_origin.core.atlas import Atlas
|
|
15
|
+
from devrel_origin.core.cyra import (
|
|
16
|
+
CroReport,
|
|
17
|
+
Cyra,
|
|
18
|
+
DropOff,
|
|
19
|
+
FunnelStep,
|
|
20
|
+
Hypothesis,
|
|
21
|
+
)
|
|
22
|
+
from devrel_origin.core.dex import Dex
|
|
23
|
+
from devrel_origin.core.echo import Echo
|
|
24
|
+
from devrel_origin.core.iris import Iris
|
|
25
|
+
from devrel_origin.core.kai import Kai
|
|
26
|
+
from devrel_origin.core.mox import Mox
|
|
27
|
+
from devrel_origin.core.nova import Nova
|
|
28
|
+
from devrel_origin.core.pax import Pax
|
|
29
|
+
from devrel_origin.core.rex import Rex
|
|
30
|
+
from devrel_origin.core.sage import Sage
|
|
31
|
+
from devrel_origin.core.sentinel import Sentinel
|
|
32
|
+
from devrel_origin.core.vox import Vox
|
|
33
|
+
from devrel_origin.core.watchdog import Watchdog
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"Argus",
|
|
37
|
+
"Atlas",
|
|
38
|
+
"Cyra",
|
|
39
|
+
"Dex",
|
|
40
|
+
"Echo",
|
|
41
|
+
"Kai",
|
|
42
|
+
"Mox",
|
|
43
|
+
"Sage",
|
|
44
|
+
"Iris",
|
|
45
|
+
"Nova",
|
|
46
|
+
"Pax",
|
|
47
|
+
"Rex",
|
|
48
|
+
"Sentinel",
|
|
49
|
+
"Vox",
|
|
50
|
+
"Watchdog",
|
|
51
|
+
"CroReport",
|
|
52
|
+
"DropOff",
|
|
53
|
+
"FunnelStep",
|
|
54
|
+
"Hypothesis",
|
|
55
|
+
"PerformanceMetric",
|
|
56
|
+
"PerformanceReport",
|
|
57
|
+
"Recommendation",
|
|
58
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Agent configuration loader from YAML."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
DEFAULT_WORKFLOW_ORDER = ["sage", "iris", "nova", "kai"]
|
|
14
|
+
DEFAULT_AGENT_CONFIG: dict[str, Any] = {
|
|
15
|
+
"temperature": 0.7,
|
|
16
|
+
"model": "claude-sonnet-4-5-20250929",
|
|
17
|
+
"max_tokens": 4096,
|
|
18
|
+
}
|
|
19
|
+
DEFAULT_RETRY = {
|
|
20
|
+
"max_retries": 3,
|
|
21
|
+
"initial_delay_seconds": 5,
|
|
22
|
+
"backoff_multiplier": 2.0,
|
|
23
|
+
"max_delay_seconds": 60,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class AgentConfig:
|
|
29
|
+
"""Parsed agent configuration."""
|
|
30
|
+
|
|
31
|
+
product_name: str = os.getenv("PRODUCT_NAME", "OpenClaw")
|
|
32
|
+
product_url: str = os.getenv("PRODUCT_URL", "https://openclaw.ai")
|
|
33
|
+
budget_limit_usd: float = 10.0 # Weekly budget; 0 = unlimited
|
|
34
|
+
agents: dict[str, Any] = field(default_factory=dict)
|
|
35
|
+
workflow_order: list[str] = field(default_factory=lambda: list(DEFAULT_WORKFLOW_ORDER))
|
|
36
|
+
retry_settings: dict[str, Any] = field(default_factory=lambda: dict(DEFAULT_RETRY))
|
|
37
|
+
api_clients: dict[str, Any] = field(default_factory=dict)
|
|
38
|
+
logging_config: dict[str, Any] = field(default_factory=dict)
|
|
39
|
+
analytics_in_run: bool = True
|
|
40
|
+
cro_in_run: bool = False # Stage 5c: Cyra CRO audit (opt-in; Vega+Selene added in Waves 2/3)
|
|
41
|
+
agent_timeouts: dict[str, float] = field(
|
|
42
|
+
default_factory=dict
|
|
43
|
+
) # per-agent timeout override (seconds)
|
|
44
|
+
|
|
45
|
+
def get_agent_config(self, agent_name: str) -> dict[str, Any]:
|
|
46
|
+
"""Get config for a specific agent, merging with defaults."""
|
|
47
|
+
defaults = dict(DEFAULT_AGENT_CONFIG)
|
|
48
|
+
overrides = self.agents.get(agent_name, {})
|
|
49
|
+
return {**defaults, **overrides}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def load_config(path: Path) -> AgentConfig:
|
|
53
|
+
"""Load config from YAML file, falling back to defaults."""
|
|
54
|
+
if not path.exists():
|
|
55
|
+
logger.warning(f"Config not found at {path}, using defaults")
|
|
56
|
+
return AgentConfig()
|
|
57
|
+
|
|
58
|
+
with open(path) as f:
|
|
59
|
+
raw = yaml.safe_load(f) or {}
|
|
60
|
+
|
|
61
|
+
return AgentConfig(
|
|
62
|
+
product_name=raw.get("product_name", os.getenv("PRODUCT_NAME", "OpenClaw")),
|
|
63
|
+
product_url=raw.get("product_url", os.getenv("PRODUCT_URL", "https://openclaw.ai")),
|
|
64
|
+
budget_limit_usd=raw.get("budget_limit_usd", 10.0),
|
|
65
|
+
agents=raw.get("agents", {}),
|
|
66
|
+
workflow_order=raw.get("orchestration", {}).get("workflow_order", DEFAULT_WORKFLOW_ORDER),
|
|
67
|
+
retry_settings={**DEFAULT_RETRY, **raw.get("retry_settings", {})},
|
|
68
|
+
api_clients=raw.get("api_clients", {}),
|
|
69
|
+
logging_config=raw.get("logging", {}),
|
|
70
|
+
analytics_in_run=raw.get("orchestration", {}).get("analytics_in_run", True),
|
|
71
|
+
cro_in_run=raw.get("orchestration", {}).get("cro_in_run", False),
|
|
72
|
+
agent_timeouts={
|
|
73
|
+
k: float(v) for k, v in raw.get("orchestration", {}).get("agent_timeouts", {}).items()
|
|
74
|
+
},
|
|
75
|
+
)
|