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,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)
|