| Server IP : 3.147.158.171 / Your IP : 216.73.216.88 Web Server : Apache/2.4.67 (Amazon Linux) OpenSSL/3.5.5 System : Linux ip-172-31-2-178.us-east-2.compute.internal 6.1.172-216.329.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Wed May 20 06:31:34 UTC 2026 x86_64 User : ec2-user ( 1000) PHP Version : 8.4.21 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /tsai/repo/.claude/skills/schedule-characters/ |
Upload File : |
---
name: schedule-characters
description: Set calendar-based article posting schedules for characters on a site
argument-hint: <env> <site_slug> "<description>"
---
# Schedule Characters
Configure calendar-based `articleSchedule` values for characters on a production site, based on a loose natural-language description of when they should post.
**Arguments:** $ARGUMENTS
Parse as:
- `env` — first token: `tsai` or `scike`
- `site_slug` — second token (e.g. `jpt`, `guitar`, `news`)
- `description` — remaining text (may be quoted), describing the desired posting cadence
If any of these are missing, ask the user before proceeding.
**Scope:** this command sets a character's calendar `articleSchedule` (days/hours). Calendar schedules are the only posting mechanism — a character posts only when it has a schedule whose day constraints match.
---
## Environment constants
| env | SSH host | Repo path | TSAI_ENV |
|-----|----------|-----------|----------|
| `tsai` | `ssh -i ~/.ssh/tsai.pem ec2-user@3.147.158.171` | `/tsai/repo` | `production` |
| `scike` | `ssh -i ~/.ssh/tsai.pem ec2-user@scike.ai` | `/data/tsai` | `scike_ai` |
---
## Step 1 — Get cb_host and list characters with current article schedules
Get the `cb_host` for the site:
```bash
ssh -i ~/.ssh/tsai.pem ec2-user@{host} \
"cd {repo}/api && source venv/bin/activate && \
TSAI_ENV={tsai_env} python3 -c \"
from app.services.site_config_service import SiteConfigService
svc = SiteConfigService()
site = svc.get_site_by_slug('{site_slug}')
if not site:
slugs = sorted(s.slug for s in svc.get_all_sites())
print('NOT_FOUND: ' + ', '.join(slugs))
else:
print('CB_HOST:' + site.cb_host)
\""
```
If the output starts with `NOT_FOUND`, stop and tell the user which slugs are available.
Then list characters using the `cb_host` as the Origin header:
```bash
ssh -i ~/.ssh/tsai.pem ec2-user@{host} \
"curl -s -H 'Origin: https://{cb_host}' \
'http://localhost:8000/api/v1/cms/characters/' | \
python3 -c \"
import sys, json
data = json.load(sys.stdin)['data']
for c in data:
print(json.dumps({
'name': c['name'],
'slug': c['slug'],
'enabled': c.get('enabled'),
'systemWide': c.get('systemWide'),
'articleSchedule': c.get('articleSchedule'),
}))
\""
```
Present a compact table of name, slug, enabled, systemWide, and current `articleSchedule` (daysOfWeek / daysOfMonth / preferredHours).
Only characters that are `enabled` and not `systemWide` are candidates for scheduling unless the user explicitly names a systemWide character.
---
## Step 2 — Propose a concrete plan
Translate the description into per-character calendar schedules using these rules:
- **Named characters take precedence.** If the description names specific characters (e.g. "schedule Suzie for Tuesday mornings"), schedule only those and leave others untouched.
- **Distribute for coverage.** If the description asks for aggregate coverage ("at least one character posts each day"), assign `daysOfWeek` across enabled, non-system-wide characters so the union covers 0–6 with no required duplicates.
- **Time-of-day mapping** for `preferredHours`:
- morning → `8,9,10`
- midday/noon → `11,12`
- afternoon → `13,14,15`
- evening → `18,19,20`
- leave empty if unspecified
- **Cadence mapping:**
- weekly / "once a week" / day-named phrases → `daysOfWeek`
- monthly / "on the Nth" → `daysOfMonth`
- do not set both unless the user explicitly asked for both
- **Day numbering:** 0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri, 6=Sat.
Output one line per affected character:
```
<name> (<slug>) — daysOfWeek=[…] daysOfMonth=[…] preferredHours=[…]
```
Also list any characters whose current `articleSchedule` should be cleared (only if the description implies it).
---
## Step 3 — Confirm
Ask the user to approve the plan or suggest edits. **Do not proceed to Step 4 until the user confirms.**
---
## Step 4 — Apply
For each affected character, SSH to the server and run `schedule_character.py`. Use `--clear` for removals; otherwise pass the relevant day/hour CSV flags.
**Always invoke with `python3 -u`** (unbuffered stdout). Each invocation reloads site configs from Drupal and is slow, so a multi-character batch takes a while; without `-u`, Python withholds stdout when it isn't a terminal and progress appears frozen even though the remote work is proceeding.
```bash
ssh -i ~/.ssh/tsai.pem ec2-user@{host} \
"cd {repo}/api && source venv/bin/activate && \
python3 -u schedule_character.py '{slug}' '{site_slug}' \
--env {tsai_env} \
--days-of-week '{dow_csv}' \
[--days-of-month '{dom_csv}'] \
[--preferred-hours '{hours_csv}']"
```
Clear variant:
```bash
ssh -i ~/.ssh/tsai.pem ec2-user@{host} \
"cd {repo}/api && source venv/bin/activate && \
python3 -u schedule_character.py '{slug}' '{site_slug}' \
--env {tsai_env} --clear"
```
Omit empty flags entirely rather than passing empty strings. If any call errors, report it and stop before running the rest.
For a multi-character batch, loop in a single SSH session. Even with `-u`, treat the streamed stdout as progress only — do **not** judge completion from it. Confirm the result with the Step 5 re-query, which reads the live state directly.
---
## Step 5 — Report result
Re-query the characters list (the same call as Step 1) and use it as the authoritative "after" state. This is the source of truth — the per-call stdout from Step 4 can lag behind the actual server state due to output buffering, so never report completion from stdout alone.
For each updated character, show:
- **Name / slug**
- **Before:** old schedule (from Step 1)
- **After:** new schedule (from the Step 5 re-query)
Remind the user:
- A character posts only when it has a calendar schedule whose day constraints match; characters with no schedule never post.
- Changes are picked up on the next run of the systemd article timer on the server.