-
Notifications
You must be signed in to change notification settings - Fork 448
chore(conductor): add dev run script, preview URL, and safe defaults #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f87d17f
0f9d429
a3c92ed
20a6929
0417cae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| "$schema" = "https://conductor.build/schemas/settings.repo.schema.json" | ||
|
|
||
| [scripts] | ||
| setup = "pip install -r requirements.txt && python manage.py migrate" | ||
| auto_run_after_setup = false | ||
|
|
||
| [scripts.run.dev] | ||
| command = "python manage.py runserver $CONDUCTOR_PORT" | ||
| default = true | ||
|
|
||
| [[preview_urls]] | ||
| name = "BrightBean Studio" | ||
| url = "http://localhost:$CONDUCTOR_PORT" | ||
|
|
||
| [prompts] | ||
| general = "Read README.md and Makefile before starting. Check the agency-os token budget at ~/Projects/agency-os/state/agent_token_budgets.json before spawning subagents or running heavy LLM work. Prefer free/local LLM for quick triage and mechanical fixes. Reserve premier design models for actual design work and only when token budget allows. Nothing gets sent, posted, deployed-to-client, or invoiced without Eric's explicit sign-off. Never push directly to main or master - use a feature branch and PR." |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| # BrightBean Social Readiness — Plants and Chairs | ||
|
|
||
| Re-derived 2026-07-18 (kimi) after the original file was lost. Sources: ingestion script | ||
| `scripts/ingest_assets.py` (branch `fix-brightbean-llms-setup`), live filesystem audit, and a | ||
| full Airtable DAM scan (base `appjU8WwLSSaSNJxC`, table `🎞 Digital assets`). | ||
|
|
||
| ## 1. Verified source directories (live audit 2026-07-18) | ||
|
|
||
| | Directory | Media files | | ||
| |---|---| | ||
| | `agency-os/output/social-ready-2026-06-25/feed` | 26 | | ||
| | `agency-os/output/content-pool-2026-06-23` | 255 (incl. `reels/` with the 2 mp4s) | | ||
| | `~/Downloads/pac_store-ites_3d/item_1` | 142 | | ||
| | `agency-os/reels` | 0 (empty; reels live in content-pool) | | ||
|
|
||
| All 30 files mapped in `ASSET_METADATA` are present on disk (30/30). The two mp4s were | ||
| found at `content-pool-2026-06-23/reels/`, not `agency-os/reels/`. | ||
|
|
||
| ## 2. DAM audit (Airtable, 2026-07-18) | ||
|
|
||
| - 2,120 total records. 936 have valid local paths, 114 point to missing files, 1,070 have | ||
| no Local Path at all. | ||
| - Valid by project: UNIVERZE 358, PNC-MERCH 198, PC-1985 60, PC-WFF 56, PC-007B 31, | ||
| PC-FLEA 30. | ||
| - Missing by project: PNC 53, PC-1985 15, SJB-EXPLORER 12, PC-SEAVITA 9. | ||
| - Full dump: `/tmp/dam_audit.json` (regenerate via the Airtable API when needed). | ||
|
|
||
| ## 3. Ingestion candidate set (30 files, all verified present) | ||
|
|
||
| - Hero photos: `IMG_1761.png` (Plants Over Chairs hero), `IMG_1634.png` (Plants and Jeeps | ||
| hero), both in `pac_store-ites_3d/item_1`. | ||
| - Reels: `new-01-screenprint-process.mp4` (screen-print process), | ||
| `new-03-beach-lifestyle.mp4` (studio/place), both in `content-pool-2026-06-23/reels/`. | ||
| - Social-ready feed set (26 PNGs in `social-ready-2026-06-25/feed`): s01-s10 heroes and | ||
| grids, v2 series (stone, payasotee, vanlife, dotcap, barlogo), v3 cards (petstore, | ||
| productos, mialma, scatter pink), post-2..5 (flowers, vanlife royal, payaso tote, | ||
| beanie), cap-dir3 streetwear, cap-dir4 AR. | ||
|
|
||
| Runbook: `python scripts/ingest_assets.py --dry-run` first, then real run. The script | ||
| dedupes by SHA-256, uploads to R2 via Django storage, and creates MediaAsset rows. | ||
| **Blocker before any run: rotate the exposed R2 credentials, then set the new pair as Fly | ||
| secrets (`S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`).** | ||
|
|
||
| ## 4. Eight-post draft calendar (drafts only; nothing posts without Eric) | ||
|
|
||
| Cadence Tue/Fri, starting after accounts connect. All copy is placeholder direction for | ||
| the brand check, not final copy. | ||
|
|
||
| | # | Date | Asset | Format | Direction | | ||
| |---|------|-------|--------|-----------| | ||
| | 1 | Tue 2026-07-21 | `s01-iconic-hero.png` | IG feed | Iconic cap hero. Re-introduce the drop. | | ||
| | 2 | Fri 2026-07-24 | `new-01-screenprint-process.mp4` | Reel | Process video: screen-print behind the scenes. | | ||
| | 3 | Tue 2026-07-28 | `v2-01-stone-hero.png` | IG feed | Stone colorway hero. | | ||
| | 4 | Fri 2026-07-31 | `s06-iconic-grid2.png` | IG feed | Grid/detail post. | | ||
| | 5 | Tue 2026-08-04 | `v2-02-payasotee.png` | IG feed | Payaso tee graphic feature. | | ||
| | 6 | Fri 2026-08-07 | `new-03-beach-lifestyle.mp4` | Reel | Lifestyle/place reel. | | ||
| | 7 | Tue 2026-08-11 | `cap-dir3-streetwear-post.png` | IG feed | Streetwear styling post. | | ||
| | 8 | Fri 2026-08-14 | `s08-scatter-sky.png` | IG feed | Scatter print, soft closer for the run. | | ||
|
|
||
| Crosspost: once Bluesky/Mastodon are connected, mirror 1, 3, 5, 7 as stills. Captions go | ||
| through the QA/brand gate and Eric approval before scheduling. | ||
|
|
||
| ## 5. Account connection status (2026-07-18) | ||
|
|
||
| - Instagram: blocked at Meta SMS 2FA in the token-generation step (PAC-337, needs Eric). | ||
| App credentials (`PLATFORM_INSTAGRAM_APP_ID/SECRET`) are set as Fly secrets. | ||
| - Bluesky: no account yet. Needs studio handle + App Password (agent connects it). | ||
| - Mastodon: no instance/account yet. | ||
| - LinkedIn, Pinterest, TikTok, Google/YouTube: app credentials exist in | ||
| `brightbean-studio/.env`; each still needs its OAuth grant flow. | ||
| - R2/S3: Fly secrets present but the pair is exposed; rotate before uploads. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| app = 'brightbean-pnc' | ||
| primary_region = 'lax' | ||
|
|
||
| [build] | ||
|
|
||
| [env] | ||
| PORT = '8000' | ||
|
|
||
| [http_service] | ||
| internal_port = 8000 | ||
| force_https = true | ||
| auto_stop_machines = true | ||
| auto_start_machines = true | ||
| min_machines_running = 0 | ||
| processes = ['app'] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For deployments using this new Useful? React with 👍 / 👎. |
||
|
|
||
| [[vm]] | ||
| memory = '1gb' | ||
| cpu_kind = 'shared' | ||
| cpus = 1 | ||
| processes = ['app'] | ||
|
|
||
| [deploy] | ||
| release_command = 'python manage.py migrate --noinput' | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| #!/usr/bin/env python3 | ||
| """Connect social accounts to BrightBean from stored credentials. | ||
|
|
||
| Wires Bluesky (handle + app password) and Mastodon (instance + user token) | ||
| into the BrightBean production workspace, replicating the web connect flows | ||
| (apps/social_accounts/views.py) for headless use. | ||
|
|
||
| Reads credentials from environment (set them as Fly secrets or pass inline): | ||
| BLUESKY_HANDLE, BLUESKY_APP_PASSWORD | ||
| MASTODON_INSTANCE_URL, MASTODON_ACCESS_TOKEN | ||
|
|
||
| Usage (production): | ||
| fly ssh console --app brightbean-pnc -C "python scripts/connect_social_accounts.py" | ||
|
|
||
| Does NOT post anything. Connecting only; publishing stays behind the Eric gate. | ||
| """ | ||
|
|
||
| import os | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) | ||
| os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production") | ||
|
|
||
| import django | ||
|
|
||
| django.setup() | ||
|
|
||
| from django.utils import timezone # noqa: E402 | ||
| from apps.social_accounts.models import SocialAccount # noqa: E402 | ||
| from apps.workspaces.models import Workspace # noqa: E402 | ||
| from apps.calendar.services import create_default_queue_and_slots # noqa: E402 | ||
| from apps.credentials.models import PlatformCredential # noqa: E402 | ||
| from providers.bluesky import BlueskyProvider # noqa: E402 | ||
|
|
||
|
|
||
| def _get_workspace(): | ||
| ws = Workspace.objects.order_by("created_at").first() | ||
| if ws is None: | ||
| raise RuntimeError("No workspace found in this BrightBean instance") | ||
| return ws | ||
|
|
||
|
|
||
| def _upsert(workspace, platform, *, platform_id, name, handle, avatar, followers, | ||
| access_token, refresh_token="", instance_url=""): | ||
| account, created = SocialAccount.objects.update_or_create( | ||
| workspace_id=workspace.id, | ||
| platform=platform, | ||
| account_platform_id=platform_id, | ||
| defaults={ | ||
| "account_name": name, | ||
| "account_handle": handle or "", | ||
| "avatar_url": avatar or "", | ||
| "follower_count": followers or 0, | ||
| "oauth_access_token": access_token, | ||
| "oauth_refresh_token": refresh_token or "", | ||
| "instance_url": instance_url or "", | ||
| "connection_status": SocialAccount.ConnectionStatus.CONNECTED, | ||
| "last_error": "", | ||
| "analytics_needs_reconnect": False, | ||
| }, | ||
| ) | ||
| if created: | ||
| create_default_queue_and_slots(account) | ||
| return account, created | ||
|
|
||
|
|
||
| def connect_bluesky(workspace): | ||
| handle = os.environ.get("BLUESKY_HANDLE", "").lstrip("@") | ||
| app_password = os.environ.get("BLUESKY_APP_PASSWORD", "") | ||
| if not handle or not app_password: | ||
| print("Bluesky: SKIP (BLUESKY_HANDLE / BLUESKY_APP_PASSWORD not set)") | ||
| return | ||
| provider = BlueskyProvider() | ||
| tokens = provider.create_session(handle, app_password) | ||
| profile = provider.get_profile(tokens.access_token) | ||
| account, created = _upsert( | ||
| workspace, PlatformCredential.Platform.BLUESKY, | ||
| platform_id=profile.platform_id, name=profile.name, handle=profile.handle, | ||
| avatar=profile.avatar_url, followers=profile.follower_count, | ||
| access_token=tokens.access_token, refresh_token=tokens.refresh_token, | ||
| instance_url=provider.pds_url, | ||
| ) | ||
| print(f"Bluesky: {'created' if created else 'updated'} -> {profile.handle}") | ||
|
|
||
|
|
||
| def connect_mastodon(workspace): | ||
| import requests | ||
|
|
||
| instance = os.environ.get("MASTODON_INSTANCE_URL", "").rstrip("/") | ||
| token = os.environ.get("MASTODON_ACCESS_TOKEN", "") | ||
| if not instance or not token: | ||
| print("Mastodon: SKIP (MASTODON_INSTANCE_URL / MASTODON_ACCESS_TOKEN not set)") | ||
| return | ||
| resp = requests.get( | ||
| f"{instance}/api/v1/accounts/verify_credentials", | ||
| headers={"Authorization": f"Bearer {token}"}, timeout=20, | ||
| ) | ||
| if resp.status_code != 200: | ||
| print(f"Mastodon: token check failed ({resp.status_code}): {resp.text[:200]}") | ||
| print("Mastodon: if 403 'confirmed e-mail', click the confirmation link in the inbox first") | ||
| return | ||
| me = resp.json() | ||
| account, created = _upsert( | ||
| workspace, PlatformCredential.Platform.MASTODON, | ||
| platform_id=str(me["id"]), name=me.get("display_name") or me.get("acct", ""), | ||
| handle=me.get("acct", ""), avatar=me.get("avatar", ""), | ||
| followers=me.get("followers_count", 0), | ||
| access_token=token, instance_url=instance, | ||
| ) | ||
| print(f"Mastodon: {'created' if created else 'updated'} -> {me.get('acct')}") | ||
|
|
||
|
|
||
| def main(): | ||
| workspace = _get_workspace() | ||
| print(f"Workspace: {workspace.name} ({workspace.id})") | ||
| connect_bluesky(workspace) | ||
| connect_mastodon(workspace) | ||
| total = SocialAccount.objects.filter(workspace=workspace).count() | ||
| print(f"Connected social accounts in workspace: {total}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a deployment follows the documented Instagram (Direct) setup and sets only
PLATFORM_INSTAGRAM_APP_ID/PLATFORM_INSTAGRAM_APP_SECRET, this fallback also makes the shared_META_CREDENTIALScomplete forfacebook,instagram, andthreadsbecausePLATFORM_CREDENTIALS_FROM_ENVmaps all three to this dict. Those connectors then appear configured and their Facebook/Threads/Graph OAuth flows use the Instagram Login App ID/secret, which the README documents as different from the Facebook App ID/secret, so direct-only deployments get broken connect options instead of leaving those platforms unconfigured.Useful? React with 👍 / 👎.