Use this skill for client-facing, agent-driven episode operations in this repository.
What This Skill Runs
Run the main CLI at scripts/ai_podcasting_client.py for episode operations:
doctor: Verify the credential, client identity, TCR show grant, and required operation scopes.list-episodes: ListTCRepisodes with rich per-episode summaries and filters forpublished,unpublished, orall.submit-episode: Create a retry-safe new episode via/client/v1/episodes.update-intro-copy: Patch intro/title/thumbnail/outro assets for an existing unpublished episode via/client/v1/episodes/{sourceId}/intro.
This skill calls the versioned WIN client API at https://api.aipodcast.ing/client/v1/...
directly. Do not send agent operations through the frontend or automate the browser UI.
WIN enforces the authenticated principal's operation scopes and allowed shows server-side.
The scripts read the token only from ~/.secrets/aipodcasting/env by default, or from the
credential file path selected by AIPODCASTING_CLIENT_API_KEY_FILE. They do not accept secret
flags or secret-value environment fallbacks.
Auth Setup
Never paste the actual API key into this skill, a payload JSON file, a command argument, or chat. Adi shares the key out-of-band. Store it in the user's local environment before running the client.
Recommended local setup:
mkdir -p ~/.secrets/aipodcasting
printf 'AIPODCASTING_CLIENT_API_KEY=<key-from-Adi>\n' > ~/.secrets/aipodcasting/env
chmod 600 ~/.secrets/aipodcasting/env
If the secret file lives somewhere else, set AIPODCASTING_CLIENT_API_KEY_FILE to that absolute
path. This variable selects a file; it never contains the credential itself.
For direct raw API usage, send the same key as Authorization: Bearer <key-from-Adi>.
For a customer install or upgrade, follow references/client-setup.md and require a successful
doctor result before running mutations.
Use scripts/aip_local_upload_helper.py only when the user gives a local file path for a file-like
field and no source URL is available. For TCR main episode submissions, prefer a Descript web URL
copied from Descript for the main source when one exists; do not export or upload an MP4 just to
create a source link. The helper requests a purpose-specific temporary upload intent and returns
an R2 cache/ URL for the main CLI to use. Callers cannot choose a raw lifecycle prefix.
Keep this implicit in chat unless the user asks.
Media Source Rule For TCR
When a Descript project/composition/source URL exists, use that URL as the source. Do not export, upload, or submit an MP4 just to create a source file.
The expected Descript web URL shape is:
https://web.descript.com/<project-id>/<composition-id>
Copy the actual URL from Descript. Do not fabricate IDs, and do not require an exact path shape if
Descript provides a slightly different web.descript.com source URL.
Use these shapes:
{
"mainSourceUrl": "https://web.descript.com/01234567-89ab-4cde-8f01-23456789abcd"
}
For intro updates, use:
{
"introSourceUrl": "https://web.descript.com/01234567-89ab-4cde-8f01-23456789abcd"
}
The client normalizes these source fields to the backend payload internally. Do not send raw,
recordingLink, or introFile in agent payloads.
MP4 URLs and local MP4 paths are fallback inputs only. If both a Descript URL and an MP4 are known, submit the Descript URL and omit the MP4.
All script and reference paths in this skill are relative to the skill directory itself, not the
repository root. Do not run scripts/... from the repo root unless you first cd into this skill
directory. When in doubt, use the absolute skill path shown by the harness.
Quick Start
- Check the installed credential and grants:
python3 scripts/ai_podcasting_client.py \
--json doctor
- List episodes to find the target ID:
python3 scripts/ai_podcasting_client.py \
--json list-episodes
To include the sanitized upstream episode payload under each item:
python3 scripts/ai_podcasting_client.py \
--json list-episodes \
--publication-state published \
--include-raw
To narrow to unpublished episodes in a date range:
python3 scripts/ai_podcasting_client.py \
--json list-episodes \
--publication-state unpublished \
--start-date 2026-01-01 \
--end-date 2026-01-31
- Submit a new episode (creates a new episode; no
source_idinput needed):
python3 scripts/ai_podcasting_client.py \
--json submit-episode \
--payload-file references/submit-episode.example.json
- Update intro copy for an existing episode (
source_idrequired):
python3 scripts/ai_podcasting_client.py \
--json update-intro-copy \
--source-id <EPISODE_SOURCE_ID> \
--payload-file references/update-intro-copy-tcr.example.json
- Upload a local supporting file and get a temporary public URL:
python3 scripts/aip_local_upload_helper.py \
--json --purpose thumbnail /absolute/path/to/file.png
Interface Notes
- Fixed endpoint:
https://api.aipodcast.ing/client/v1 - Fixed show:
TCR - Auth: bearer token from
~/.secrets/aipodcasting/envor the file selected byAIPODCASTING_CLIENT_API_KEY_FILE - The CLI does not accept base-url overrides or env-based base URL changes.
- The CLI does not accept show selection; all submit/list operations are locked to
TCR. submit-episodesendsIdempotency-Keyusing the command request ID. The JSON envelope returns that request ID, anddata.idempotency_keyrepeats it on success. Retry an uncertain submission with--request-id <same-id>; use a new request ID only for an intentionally separate episode.- Local uploads are purpose-scoped
cache/transport objects. Useepisode_main,episode_asset,episode_intro,episode_outro, orthumbnail; never treat their URLs as permanent inventory. - JSON is the default output contract. Use
--plainor--humanonly for operator inspection. list-episodesreturns a rich summary by default in JSON mode. Each item now includes fields such asthumbnailText, publishing metadata, preview text for long fields, normalized file links, and other lightweight episode context.list-episodesdefaults to--publication-state all.list-episodessupports--publication-state all|published|unpublished, plus optional--start-dateand--end-datefilters.- Results are sorted newest-first before
--limitis applied, so--limit 5returns the latest five matching episodes. - Use
--include-rawwhen the agent needs the sanitized upstream episode object in addition to the default summary. - JSON mode returns a stable envelope with:
schema_versioncommandstatusdataerrormeta
Required Vs Optional Inputs
doctor: Required: a credential file containingAIPODCASTING_CLIENT_API_KEY. Run after setup or rotation and before debugging a rejected operation.list-episodes: Required: none. Optional:--publication-state,--start-date,--end-date,--limit,--include-raw.--publication-statechoices:all(default)publishedunpublishedDefault JSON output includes a rich per-episode summary with fields such as:source_id,title,show,status,thumbnailTextcreated_at,updated_at, publishing metadata, and guest-review flags- preview text plus lengths for long copy fields like show notes or editor notes
- normalized file links, artwork links, deliverable links, processed-asset links, ads, and
other lightweight metadata when present
Items are sorted newest-first before
limitis applied. Thedatapayload also echoes the appliedfiltersobject andmatched_count. With--include-raw, each item also includesraw_episodecontaining the sanitized upstream payload.
submit-episode: Required:--payload-filewithmainSourceUrl. Show handling: always forced toTCRby the CLI.mainSourceUrlmay be either:- a public HTTP/HTTPS URL
- a local file path, which the helper uploads first
Prefer a Descript web URL in
mainSourceUrlwhen a Descript project/composition/source is available. The app accepts MP4 URLs and local MP4 paths, but the CLI will warn because those are fallback inputs for TCR, not the preferred source. If both a Descript web URL and an exported MP4 are known, put the Descript web URL inmainSourceUrland omit the MP4. Do not require a specific Descript path shape; use the Descript web URL the user or browser provides. TCR main episode submissions reject.mp3main-source inputs. Use the original recording, session, or video source link instead, such as Riverside, YouTube, or a direct non-MP3 media URL. This is not a Riverside-only allowlist. The client normalizesmainSourceUrlto the backend submit shape automatically.assetUrlsmay also be public URLs or local file paths; local paths are uploaded first. Optional: any additional backend-supported episode fields. UsecustomNewsletterDraftUrlfor a client-provided Ghost newsletter draft, preview, editor, or slug URL. The client preserves richer payloads such asdeliverables.thumbnails.options,deliverables.thumbnails.video.variants, andfiles.episode_outro.
update-intro-copy: Required (command):--source-id,--payload-file. Intended target: an existing unpublished episode. The client supports the current app intro payload directly. For conversation-driven usage, prefer these user-facing fields: There are no required patch fields beyondsource_id. Common patch fields:introSourceUrl,title,videoThumbnails,thumbnailText,transcript,instructionsToEditor,customNewsletterDraftUrl,audioThumbnailLink,outroMusicLink. For TCR intro source updates, prefer a Descript web URL inintroSourceUrlwhen one exists. Do not export, upload, or submit an MP4 for the intro source when a Descript URL is available.customNewsletterDraftUrlstores a client-provided Ghost newsletter draft link under episode submission metadata. It does not publish or replace the generated newsletter by itself.videoThumbnailsmay be either:- one public HTTP/HTTPS URL
- a list of public HTTP/HTTPS URLs
The client normalizes
videoThumbnailsinto the app's thumbnail shape: deliverables.thumbnails.video.url= first thumbnail URLdeliverables.thumbnails.video.variants= ordered list of all provided thumbnail URLs The client also accepts the full current app payload for non-source fields if the agent already has it, but source updates must still useintroSourceUrl. Local paths are allowed for file-like fields. The helper uploads them and the client uses the returned public URLs.
Conversation Policy
When values are missing in chat context, follow this flow:
-
Before asking follow-up questions, scan the current chat thread and reuse any values the user already provided. Do not ask again for values that are already clear in context.
-
First disambiguate the operation when the user's wording does not make it clear whether they mean a new main episode submission or an intro update for an existing episode. Do not assume that "submit", "this episode", or similar phrasing means
submit-episode. If the intent is ambiguous, ask exactly: "Do you want to:- submit a new main episode source
- update intro/title/thumbnail assets for an existing episode
Reply with 1 or 2." Only continue into submit or intro-specific prompts after the user picks one.
-
For submit flow, ask for the missing required submit value, but in that same first reply also surface the common optional fields the user may want to set up front. Required submit value:
- main episode source link as either a public HTTP/HTTPS URL or a local file path. Prefer a Descript web URL for TCR when available. MP4 URLs and local MP4 paths are accepted but should be described as fallback inputs, not the preferred source. Do not export, upload, or submit an MP4 when a Descript web URL is available. Common optional submit values to mention in the same first prompt:
- title
- showNotes
- assetUrls
- editorNotes
- thumbnailText
- priority
- scheduledDate
- needsGuestReview
- guests
- customNewsletterDraftUrl
-
Use this default submit prompt shape when the source is missing: "Send the main episode source as a Descript web URL if you have one. If not, send another public HTTP/HTTPS source URL or a local absolute file path.
You can also include any of these optional fields now if you want them set on creation:
- title
- showNotes
- assetUrls
- editorNotes
- thumbnailText
- priority
- scheduledDate
- needsGuestReview
- guests
- customNewsletterDraftUrl
If you send them together, I can submit the episode in one pass."
-
Intro updates are only for unpublished episodes.
-
For intro updates without
source_id, immediately runlist-episodeswith--publication-state unpublished. If the user already providedstartDateandendDate, include them. Do not ask the user for publication scope first. -
Ask the user which episode to target using an enumerated list, not raw ids only. Render exactly:
1. <short title> — <source_id>2. <short title> — <source_id>...Then ask:Reply with the episode number or source_id.If the user replies with a number (for example4), map that number to the correspondingsource_idand continue without asking them to repeat the full id. -
Ask only for the fields the user wants to change.
-
Enforce a strict two-step prompt sequence for intro updates when
source_idis missing:- Step 1 message: episode list +
Reply with the episode number or source_id. - Step 2 message (only after episode is selected): required/optional field collection.
- Step 1 message: episode list +
-
For intro updates, use one prompt shape by default: "Episode selected: <source_id>. Provide any fields you want to update.
Common fields:
- introSourceUrl (prefer a Descript web URL for TCR intro source updates)
- title
- videoThumbnails (give one URL or multiple URLs)
- thumbnailText
- transcript
- instructionsToEditor
- customNewsletterDraftUrl
- audioThumbnailLink
- outroMusicLink
You only need to send the fields you want to change, and I will patch just those."
Never ask the user to pick an episode id again after step 1 is completed.
11. If optional values are unclear, omit them instead of guessing.
12. Use --dry-run only if the user explicitly wants a preview before the write call.
It is an internal preview/debug tool, not a normal client-facing step.
13. For customNewsletterDraftUrl, provide a public HTTP/HTTPS Ghost draft, preview, editor, or
slug URL. It is not a local file upload field.
14. For file-type fields (mainSourceUrl, introSourceUrl, videoThumbnails, audioThumbnailLink, outroMusicLink, and submit assetUrls entries):
- The client accepts either public HTTP/HTTPS URLs or local file paths.
- If the user provides a local file path, run
scripts/aip_local_upload_helper.pyfirst and use its returned public URL. - Do not pass unresolved local filesystem paths to the episode API payload.
Resources
scripts/ai_podcasting_client.py: Single client interface with subcommands.scripts/aip_local_upload_helper.py: Purpose-scoped local upload helper; returns temporarycache/URLs in the same stable JSON envelope.references/submit-episode.example.json: Example payload for submit flow.references/update-intro-copy.example.json: Example payload for intro/copy patch flow.references/update-intro-copy-tcr.example.json: Example payload for TCR-style final title/thumbnail updates.references/client-setup.md: Customer install, credential migration, doctor, and retry contract.