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.
Files changed (98) hide show
  1. devrel_origin/__init__.py +15 -0
  2. devrel_origin/cli/__init__.py +92 -0
  3. devrel_origin/cli/_common.py +243 -0
  4. devrel_origin/cli/analytics.py +28 -0
  5. devrel_origin/cli/argus.py +497 -0
  6. devrel_origin/cli/auth.py +227 -0
  7. devrel_origin/cli/config.py +108 -0
  8. devrel_origin/cli/content.py +259 -0
  9. devrel_origin/cli/cost.py +108 -0
  10. devrel_origin/cli/cro.py +298 -0
  11. devrel_origin/cli/deliverables.py +65 -0
  12. devrel_origin/cli/docs.py +91 -0
  13. devrel_origin/cli/doctor.py +178 -0
  14. devrel_origin/cli/experiment.py +29 -0
  15. devrel_origin/cli/growth.py +97 -0
  16. devrel_origin/cli/init.py +472 -0
  17. devrel_origin/cli/intel.py +27 -0
  18. devrel_origin/cli/kb.py +96 -0
  19. devrel_origin/cli/listen.py +31 -0
  20. devrel_origin/cli/marketing.py +66 -0
  21. devrel_origin/cli/migrate.py +45 -0
  22. devrel_origin/cli/run.py +46 -0
  23. devrel_origin/cli/sales.py +57 -0
  24. devrel_origin/cli/schedule.py +62 -0
  25. devrel_origin/cli/synthesize.py +28 -0
  26. devrel_origin/cli/triage.py +29 -0
  27. devrel_origin/cli/video.py +35 -0
  28. devrel_origin/core/__init__.py +58 -0
  29. devrel_origin/core/agent_config.py +75 -0
  30. devrel_origin/core/argus.py +964 -0
  31. devrel_origin/core/atlas.py +1450 -0
  32. devrel_origin/core/base.py +372 -0
  33. devrel_origin/core/cyra.py +563 -0
  34. devrel_origin/core/dex.py +708 -0
  35. devrel_origin/core/echo.py +614 -0
  36. devrel_origin/core/growth/__init__.py +27 -0
  37. devrel_origin/core/growth/recommendations.py +219 -0
  38. devrel_origin/core/growth/target_kinds.py +51 -0
  39. devrel_origin/core/iris.py +513 -0
  40. devrel_origin/core/kai.py +1367 -0
  41. devrel_origin/core/llm.py +542 -0
  42. devrel_origin/core/llm_backends.py +274 -0
  43. devrel_origin/core/mox.py +514 -0
  44. devrel_origin/core/nova.py +349 -0
  45. devrel_origin/core/pax.py +1205 -0
  46. devrel_origin/core/rex.py +532 -0
  47. devrel_origin/core/sage.py +486 -0
  48. devrel_origin/core/sentinel.py +385 -0
  49. devrel_origin/core/types.py +98 -0
  50. devrel_origin/core/video/__init__.py +22 -0
  51. devrel_origin/core/video/assembler.py +131 -0
  52. devrel_origin/core/video/browser_recorder.py +118 -0
  53. devrel_origin/core/video/desktop_recorder.py +254 -0
  54. devrel_origin/core/video/overlay_renderer.py +143 -0
  55. devrel_origin/core/video/script_parser.py +147 -0
  56. devrel_origin/core/video/tts_engine.py +82 -0
  57. devrel_origin/core/vox.py +268 -0
  58. devrel_origin/core/watchdog.py +321 -0
  59. devrel_origin/project/__init__.py +1 -0
  60. devrel_origin/project/config.py +75 -0
  61. devrel_origin/project/cost_sink.py +61 -0
  62. devrel_origin/project/init.py +104 -0
  63. devrel_origin/project/paths.py +75 -0
  64. devrel_origin/project/state.py +241 -0
  65. devrel_origin/project/templates/__init__.py +4 -0
  66. devrel_origin/project/templates/config.toml +24 -0
  67. devrel_origin/project/templates/devrel.gitignore +10 -0
  68. devrel_origin/project/templates/slop-blocklist.md +45 -0
  69. devrel_origin/project/templates/style.md +24 -0
  70. devrel_origin/project/templates/voice.md +29 -0
  71. devrel_origin/quality/__init__.py +66 -0
  72. devrel_origin/quality/editorial.py +357 -0
  73. devrel_origin/quality/persona.py +84 -0
  74. devrel_origin/quality/readability.py +148 -0
  75. devrel_origin/quality/slop.py +167 -0
  76. devrel_origin/quality/style.py +110 -0
  77. devrel_origin/quality/voice.py +15 -0
  78. devrel_origin/tools/__init__.py +9 -0
  79. devrel_origin/tools/analytics.py +304 -0
  80. devrel_origin/tools/api_client.py +393 -0
  81. devrel_origin/tools/apollo_client.py +305 -0
  82. devrel_origin/tools/code_validator.py +428 -0
  83. devrel_origin/tools/github_tools.py +297 -0
  84. devrel_origin/tools/instantly_client.py +412 -0
  85. devrel_origin/tools/kb_harvester.py +340 -0
  86. devrel_origin/tools/mcp_server.py +578 -0
  87. devrel_origin/tools/notifications.py +245 -0
  88. devrel_origin/tools/run_report.py +193 -0
  89. devrel_origin/tools/scheduler.py +231 -0
  90. devrel_origin/tools/search_tools.py +321 -0
  91. devrel_origin/tools/self_improve.py +168 -0
  92. devrel_origin/tools/sheets.py +236 -0
  93. devrel_origin-0.2.14.dist-info/METADATA +354 -0
  94. devrel_origin-0.2.14.dist-info/RECORD +98 -0
  95. devrel_origin-0.2.14.dist-info/WHEEL +5 -0
  96. devrel_origin-0.2.14.dist-info/entry_points.txt +2 -0
  97. devrel_origin-0.2.14.dist-info/licenses/LICENSE +21 -0
  98. devrel_origin-0.2.14.dist-info/top_level.txt +1 -0
@@ -0,0 +1,15 @@
1
+ """devrel-origin — DevRel + Sales + Marketing agent system."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("devrel-origin")
7
+ except PackageNotFoundError:
8
+ # Backwards-compat: pre-0.2.14 PyPI distributions were named
9
+ # "devrel-swarm". If a user is on the old wheel but the new code
10
+ # path (e.g. mid-upgrade or editable install pointing at this tree),
11
+ # fall back so __version__ still resolves.
12
+ try:
13
+ __version__ = version("devrel-swarm")
14
+ except PackageNotFoundError:
15
+ __version__ = "0.0.0+unknown"
@@ -0,0 +1,92 @@
1
+ """Typer CLI app for devrel-origin.
2
+
3
+ Phase 2 registers `init` and `doctor`. Later phases register additional
4
+ verb groups (run, content, sales, marketing, etc.).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import typer
10
+
11
+ from devrel_origin import __version__
12
+ from devrel_origin.cli.analytics import analytics_app
13
+ from devrel_origin.cli.argus import argus_app
14
+ from devrel_origin.cli.auth import auth_command
15
+ from devrel_origin.cli.config import config_app
16
+ from devrel_origin.cli.content import content_app
17
+ from devrel_origin.cli.cost import cost_command
18
+ from devrel_origin.cli.cro import cro_app
19
+ from devrel_origin.cli.deliverables import deliverables_app
20
+ from devrel_origin.cli.docs import docs_app
21
+ from devrel_origin.cli.doctor import doctor_command
22
+ from devrel_origin.cli.experiment import experiment_command
23
+ from devrel_origin.cli.growth import growth_app
24
+ from devrel_origin.cli.init import init_command
25
+ from devrel_origin.cli.intel import intel_command
26
+ from devrel_origin.cli.kb import kb_app
27
+ from devrel_origin.cli.listen import listen_command
28
+ from devrel_origin.cli.marketing import marketing_app
29
+ from devrel_origin.cli.migrate import migrate_command
30
+ from devrel_origin.cli.run import run_command
31
+ from devrel_origin.cli.sales import sales_app
32
+ from devrel_origin.cli.schedule import schedule_app
33
+ from devrel_origin.cli.synthesize import synthesize_command
34
+ from devrel_origin.cli.triage import triage_command
35
+ from devrel_origin.cli.video import video_app
36
+
37
+ app = typer.Typer(
38
+ name="devrel",
39
+ help="DevRel + Sales + Marketing agent system. Run from inside a project repo.",
40
+ no_args_is_help=True,
41
+ add_completion=False,
42
+ )
43
+
44
+
45
+ def _version_callback(value: bool) -> None:
46
+ if value:
47
+ typer.echo(f"devrel-origin {__version__}")
48
+ raise typer.Exit()
49
+
50
+
51
+ @app.callback()
52
+ def main(
53
+ version: bool = typer.Option(
54
+ False,
55
+ "--version",
56
+ callback=_version_callback,
57
+ is_eager=True,
58
+ help="Show version and exit.",
59
+ ),
60
+ ) -> None:
61
+ """Root callback. Subcommands are registered below."""
62
+ return None
63
+
64
+
65
+ app.command(name="init")(init_command)
66
+ app.command(name="auth")(auth_command)
67
+ app.command(name="doctor")(doctor_command)
68
+ app.command(name="migrate")(migrate_command)
69
+ app.add_typer(content_app, name="content")
70
+ app.add_typer(cro_app, name="cro")
71
+ app.command(name="run")(run_command)
72
+ app.command(name="triage")(triage_command)
73
+ app.command(name="listen")(listen_command)
74
+ app.command(name="synthesize")(synthesize_command)
75
+ app.command(name="experiment")(experiment_command)
76
+ app.command(name="intel")(intel_command)
77
+ app.add_typer(sales_app, name="sales")
78
+ app.add_typer(marketing_app, name="marketing")
79
+ app.add_typer(kb_app, name="kb")
80
+ app.add_typer(schedule_app, name="schedule")
81
+ app.command(name="cost")(cost_command)
82
+ app.add_typer(deliverables_app, name="deliverables")
83
+ app.add_typer(config_app, name="config")
84
+ app.add_typer(docs_app, name="docs")
85
+ app.add_typer(video_app, name="video")
86
+ app.add_typer(argus_app, name="argus")
87
+ app.add_typer(growth_app, name="growth")
88
+ app.add_typer(analytics_app, name="analytics")
89
+
90
+
91
+ if __name__ == "__main__":
92
+ app()
@@ -0,0 +1,243 @@
1
+ """Shared CLI helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import tomllib
8
+ from typing import Any
9
+
10
+ import typer
11
+ from dotenv import load_dotenv
12
+ from rich.console import Console
13
+
14
+ from devrel_origin.core.agent_config import AgentConfig
15
+ from devrel_origin.core.atlas import Atlas, DelegationResult
16
+ from devrel_origin.core.llm import LLMClient
17
+ from devrel_origin.project.paths import ProjectNotFoundError, ProjectPaths, find_devrel_root
18
+ from devrel_origin.tools.api_client import PostHogClient
19
+ from devrel_origin.tools.apollo_client import ApolloClient
20
+ from devrel_origin.tools.github_tools import GitHubTools
21
+ from devrel_origin.tools.instantly_client import InstantlyClient
22
+ from devrel_origin.tools.search_tools import SearchTools
23
+
24
+
25
+ def find_paths_or_exit(console: Console) -> ProjectPaths:
26
+ try:
27
+ return ProjectPaths.from_root(find_devrel_root())
28
+ except ProjectNotFoundError as e:
29
+ console.print(f"[red]{e}[/red]")
30
+ raise typer.Exit(code=1) from None
31
+
32
+
33
+ def _load_project_env(paths: ProjectPaths) -> None:
34
+ """Pull keys from .devrel/.env (preferred) and project-root .env (fallback)
35
+ into the process env, without overriding values the user has already
36
+ exported in their shell. Called at the top of build paths so `devrel run`
37
+ works even when the user hasn't `export`ed anything.
38
+
39
+ `override=False` is intentional: shell-exported values take precedence so
40
+ one-off overrides during debugging don't get clobbered by a stale file.
41
+ Both paths are best-effort; missing files are silent.
42
+ """
43
+ if paths.env_file.is_file():
44
+ load_dotenv(paths.env_file, override=False)
45
+ root_env = paths.root / ".env"
46
+ if root_env.is_file() and root_env != paths.env_file:
47
+ load_dotenv(root_env, override=False)
48
+
49
+
50
+ def _read_project_toml(paths: ProjectPaths) -> dict[str, Any]:
51
+ """Parse .devrel/config.toml or return {} on missing/malformed."""
52
+ if not paths.config_file.is_file():
53
+ return {}
54
+ try:
55
+ with paths.config_file.open("rb") as f:
56
+ return tomllib.load(f)
57
+ except (OSError, tomllib.TOMLDecodeError):
58
+ return {}
59
+
60
+
61
+ def _load_agent_config(paths: ProjectPaths) -> AgentConfig:
62
+ """Bridge .devrel/config.toml into Atlas's AgentConfig.
63
+
64
+ Reads [project] for product_name/product_url and [orchestration] for
65
+ agent_timeouts/cro_in_run/analytics_in_run. Other AgentConfig fields stay
66
+ at defaults (the legacy YAML loader at config/agent_config.yaml is not
67
+ wired into the per-project CLI; this is the bridge that makes
68
+ .devrel/config.toml settings actually take effect on `devrel run`).
69
+ """
70
+ raw = _read_project_toml(paths)
71
+ proj = raw.get("project") or {}
72
+ orch = raw.get("orchestration") or {}
73
+
74
+ kwargs: dict[str, Any] = {}
75
+ if proj.get("name"):
76
+ kwargs["product_name"] = str(proj["name"])
77
+ if proj.get("url"):
78
+ kwargs["product_url"] = str(proj["url"])
79
+ if "analytics_in_run" in orch:
80
+ kwargs["analytics_in_run"] = bool(orch["analytics_in_run"])
81
+ if "cro_in_run" in orch:
82
+ kwargs["cro_in_run"] = bool(orch["cro_in_run"])
83
+ timeouts = orch.get("agent_timeouts") or {}
84
+ if timeouts:
85
+ kwargs["agent_timeouts"] = {k: float(v) for k, v in timeouts.items()}
86
+ return AgentConfig(**kwargs)
87
+
88
+
89
+ def _resolve_github_repo(paths: ProjectPaths) -> str:
90
+ """Pick the GitHub repo Sage/etc should target.
91
+
92
+ Order: GITHUB_REPO env > [project].github_repo in .devrel/config.toml >
93
+ empty string (GitHubTools falls back to its DEFAULT_REPO).
94
+ """
95
+ env = os.environ.get("GITHUB_REPO", "").strip()
96
+ if env:
97
+ return env
98
+ raw = _read_project_toml(paths)
99
+ proj = raw.get("project") or {}
100
+ repo = proj.get("github_repo")
101
+ return str(repo).strip() if repo else ""
102
+
103
+
104
+ _MISSING_KEY_HELP = (
105
+ "[red]No LLM API key found.[/red]\n"
106
+ "Fix:\n"
107
+ " - Run [bold]devrel auth[/bold] to configure interactively (writes "
108
+ ".devrel/.env with chmod 600).\n"
109
+ " - Or set [bold]ANTHROPIC_API_KEY[/bold] in your shell.\n"
110
+ " - Or set [bold]OPENROUTER_API_KEY[/bold] for multi-provider routing "
111
+ "(free credits at https://openrouter.ai/)."
112
+ )
113
+
114
+
115
+ def _build_llm_client(paths: ProjectPaths, console: Console) -> LLMClient:
116
+ """Construct an LLMClient honoring provider + per-agent overrides from
117
+ .devrel/config.toml's [llm] section, with env-var fallback.
118
+
119
+ Provider resolution:
120
+ 1. [llm].provider explicitly set ('anthropic' | 'openrouter')
121
+ 2. OPENROUTER_API_KEY set without ANTHROPIC_API_KEY -> openrouter
122
+ 3. Default -> anthropic
123
+
124
+ Either ANTHROPIC_API_KEY or OPENROUTER_API_KEY (matching the chosen
125
+ provider) must be present; otherwise we exit with a clear message
126
+ pointing at `devrel auth`. Pulls keys from .devrel/.env or root .env
127
+ before reading the env so users don't have to `export` anything.
128
+ """
129
+ _load_project_env(paths)
130
+ raw = _read_project_toml(paths)
131
+ llm_cfg = raw.get("llm") or {}
132
+ provider = (llm_cfg.get("provider") or "").strip().lower() or None
133
+ agent_models_raw = llm_cfg.get("agent_models") or {}
134
+ agent_models = {str(k): str(v) for k, v in agent_models_raw.items()}
135
+
136
+ anthropic_key = os.environ.get("ANTHROPIC_API_KEY", "").strip()
137
+ openrouter_key = os.environ.get("OPENROUTER_API_KEY", "").strip()
138
+
139
+ # Decide which provider we'll actually use, matching make_backend's logic
140
+ # so we can validate the key presence before we even construct a client.
141
+ if provider == "openrouter" or (provider is None and openrouter_key and not anthropic_key):
142
+ if not openrouter_key:
143
+ console.print(
144
+ '[red]OPENROUTER_API_KEY is required when [llm].provider = "openrouter".[/red]'
145
+ )
146
+ console.print(_MISSING_KEY_HELP)
147
+ raise typer.Exit(code=1)
148
+ return LLMClient(
149
+ provider="openrouter",
150
+ openrouter_api_key=openrouter_key,
151
+ agent_models=agent_models,
152
+ )
153
+
154
+ if not anthropic_key:
155
+ console.print(_MISSING_KEY_HELP)
156
+ raise typer.Exit(code=1)
157
+ return LLMClient(
158
+ provider="anthropic",
159
+ api_key=anthropic_key,
160
+ agent_models=agent_models,
161
+ )
162
+
163
+
164
+ def build_atlas_or_exit(paths: ProjectPaths, console: Console) -> Atlas:
165
+ llm = _build_llm_client(paths, console)
166
+ posthog = PostHogClient(
167
+ api_key=os.environ.get("POSTHOG_API_KEY", ""),
168
+ project_id=os.environ.get("POSTHOG_PROJECT_ID", ""),
169
+ )
170
+
171
+ # Optional integrations: only construct when the relevant key is present so
172
+ # specialists fall back to their degraded-mode paths instead of crashing on
173
+ # init. Wiring is the regression that left agents (Sage / Echo / Rex / Vox /
174
+ # Pax / Mox) in their no-tool branches even when keys were configured.
175
+ github_token = os.environ.get("GITHUB_TOKEN", "").strip()
176
+ github_repo = _resolve_github_repo(paths)
177
+ if github_repo:
178
+ # Public GitHub repositories can be read unauthenticated. Constructing
179
+ # GitHubTools with an empty token still lets Sage/Rex/Argus use the
180
+ # configured repo instead of silently falling back to no-tool mode.
181
+ github_tools = GitHubTools(token=github_token, repo=github_repo)
182
+ elif github_token:
183
+ github_tools = GitHubTools(token=github_token)
184
+ else:
185
+ github_tools = None
186
+
187
+ if (
188
+ os.environ.get("FIRECRAWL_API_KEY", "").strip()
189
+ or os.environ.get("BRAVE_API_KEY", "").strip()
190
+ ):
191
+ search_tools = SearchTools(
192
+ firecrawl_api_key=os.environ.get("FIRECRAWL_API_KEY", ""),
193
+ brave_api_key=os.environ.get("BRAVE_API_KEY", ""),
194
+ )
195
+ else:
196
+ search_tools = None
197
+
198
+ instantly_key = os.environ.get("INSTANTLY_API_KEY", "").strip()
199
+ instantly_client = InstantlyClient(api_key=instantly_key) if instantly_key else None
200
+
201
+ apollo_key = os.environ.get("APOLLO_API_KEY", "").strip()
202
+ apollo_client = ApolloClient(api_key=apollo_key) if apollo_key else None
203
+
204
+ return Atlas(
205
+ api_client=posthog,
206
+ knowledge_base_path=paths.kb_dir,
207
+ archive_dir=paths.context_dir,
208
+ llm_client=llm,
209
+ github_tools=github_tools,
210
+ search_tools=search_tools,
211
+ config=_load_agent_config(paths),
212
+ instantly_client=instantly_client,
213
+ apollo_client=apollo_client,
214
+ project_paths=paths,
215
+ )
216
+
217
+
218
+ def render_result(result: DelegationResult, console: Console, *, json_output: bool = False) -> None:
219
+ if json_output:
220
+ # DelegationResult is a dataclass; convert via dict()/asdict.
221
+ from dataclasses import asdict
222
+
223
+ try:
224
+ payload = asdict(result)
225
+ except TypeError:
226
+ payload = {
227
+ "agent": getattr(result, "agent", "?"),
228
+ "task": getattr(result, "task", "?"),
229
+ "success": getattr(result, "success", False),
230
+ "output": getattr(result, "output", None),
231
+ "error": getattr(result, "error", None),
232
+ }
233
+ typer.echo(json.dumps(payload, default=str, indent=2))
234
+ return
235
+ if not result.success:
236
+ console.print(f"[red]✗[/red] {result.agent} failed: {result.error}")
237
+ raise typer.Exit(code=1)
238
+ console.print(f"[green]✓[/green] {result.agent} completed")
239
+ if isinstance(result.output, dict):
240
+ for k, v in list(result.output.items())[:8]:
241
+ console.print(f" [dim]{k}:[/dim] {str(v)[:120]}")
242
+ elif result.output:
243
+ console.print(f" {str(result.output)[:300]}")
@@ -0,0 +1,28 @@
1
+ """Deprecated alias. Use `devrel argus ...`. Retained until v1.0 for backward compat."""
2
+
3
+ import warnings
4
+
5
+ import typer
6
+
7
+ from devrel_origin.cli.argus import argus_app
8
+
9
+ analytics_app = typer.Typer(
10
+ name="analytics",
11
+ help="DEPRECATED: use `devrel argus` instead. Forwarding to argus...",
12
+ invoke_without_command=False,
13
+ )
14
+
15
+
16
+ @analytics_app.callback()
17
+ def _deprecation_notice() -> None:
18
+ warnings.warn(
19
+ "`devrel analytics ...` is deprecated; use `devrel argus ...` instead. "
20
+ "The alias will be removed in v1.0.",
21
+ DeprecationWarning,
22
+ stacklevel=2,
23
+ )
24
+
25
+
26
+ # Forward all subcommands from argus_app
27
+ for cmd in argus_app.registered_commands:
28
+ analytics_app.registered_commands.append(cmd)