12
13
AUTH_HEADER = "x-podcaster-api-key"
14
DEFAULT_TIMEOUT_SECONDS = 30
15
+DEFAULT_PODCAST_CONFIG_PATH = Path(__file__).resolve().parent.parent / "config" / "podcast.json"
16
17
18
class PodcasterHandoffError(RuntimeError):
28
parser.add_argument("--publish-mode", default="normal", help="Publish mode; only normal is eligible for Podcaster handoff.")
29
parser.add_argument("--manifest", type=Path, help="Optional publish manifest used for article hash/source artifact metadata.")
30
parser.add_argument("--podcaster-dry-run", action="store_true", help="Ask Podcaster to validate without generating an episode; intended only for the manual smoke workflow.")
31
+ parser.add_argument("--podcast-config", type=Path, default=None, help="Path to podcast config JSON (default: config/podcast.json relative to repo root).")
32
parser.add_argument("--endpoint", default=os.environ.get("PODCASTER_ENDPOINT", ""))
33
parser.add_argument("--timeout", type=int, default=DEFAULT_TIMEOUT_SECONDS)
34
return parser.parse_args(argv)
87
return payload
88
89
90
+def _load_podcast_config(path: Path | None) -> dict[str, Any]:
91
+ """Load the podcast config file containing podcast_config and script_directions."""
92
+ config_path = path if path is not None else DEFAULT_PODCAST_CONFIG_PATH
93
+ if not config_path.exists():
94
+ return {}
95
+ try:
96
+ payload = json.loads(config_path.read_text(encoding="utf-8"))
97
+ except (OSError, json.JSONDecodeError) as exc:
98
+ raise PodcasterHandoffError(f"Podcast config could not be read: {config_path}") from exc
99
+ if not isinstance(payload, dict):
100
+ raise PodcasterHandoffError(f"Podcast config must be a JSON object: {config_path}")
101
+ return payload
102
+
103
+
104
def _source_artifact_refs(manifest: dict[str, Any]) -> list[dict[str, str]]:
105
refs: list[dict[str, str]] = []
106
for artifact in manifest.get("source_artifacts", []):
148
publish_run_id: str,
149
publish_mode: str = "normal",
150
manifest_path: Path | None = None,
151
+ podcast_config_path: Path | None = None,
152
podcaster_dry_run: bool = False,
153
) -> dict[str, Any]:
154
manifest = _load_manifest(manifest_path)
171
source_refs = _source_artifact_refs(manifest)
172
if source_refs:
173
payload["source_artifacts"] = source_refs
174
+
175
+ podcast_cfg = _load_podcast_config(podcast_config_path)
176
+ if "podcast_config" in podcast_cfg:
177
+ val = podcast_cfg["podcast_config"]
178
+ if not isinstance(val, dict):
179
+ raise PodcasterHandoffError("podcast_config must be a JSON object")
180
+ payload["podcast_config"] = val
181
+ if "script_directions" in podcast_cfg:
182
+ val = podcast_cfg["script_directions"]
183
+ if not isinstance(val, dict):
184
+ raise PodcasterHandoffError("script_directions must be a JSON object")
185
+ payload["script_directions"] = val
186
+
187
if podcaster_dry_run:
188
payload["dry_run"] = True
189
return payload
262
publish_run_id=args.publish_run_id,
263
publish_mode=args.publish_mode,
264
manifest_path=args.manifest,
265
+ podcast_config_path=args.podcast_config,
266
podcaster_dry_run=args.podcaster_dry_run,
267
)
268
post_handoff(endpoint, api_key, payload, timeout=args.timeout)