Vibecode Flick
track this build4 phases, 12 steps, beginner friendly0%The visible social content + analytics loop is buildable, but a credible replacement needs more than the first screen. Flick earns its keep through platform APIs, distribution, so expect a weekend or multi-day build and a narrower personal scope.
You are building a lean indie version of Flick. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Flick · indie build A personal social content planner whose honest core is a hand-posting queue that needs no API at all, with official publishing and hashtag research layered on top inside the limits each platform publishes, rather than a tool that pretends the platforms' rules do not apply. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with PostgreSQL and a database-backed job table | scheduled publishing needs a durable queue, and a second queue system is another thing to run | | Platforms | Official APIs only, with per-platform limits stored as data with a checked date | limits and formats change, and a hard-coded one is wrong within a year | | Media | Local storage, with an expiring public link only where an API fetches from a URL | check whether your platform accepts an upload or fetches from an address | | Queue | A hand-posting queue as the default path | it works for every network and every account type, including ones with no API | | Research | The official hashtag endpoint within its quota, plus your own post history | the quota is small and the alternative breaks the rules | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the server and the publishing worker. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **PostgreSQL 16 or newer** · free locally - Why: The queue lease and the attempt log need transactions so two workers never publish the same item. - Get it: Docker or your package manager. - Verify: psql connects and creates a table - [ ] **A professional account on the platform you will publish to, with an approved app** · free, and the review takes time - Why: The publishing and research APIs require a professional account type and app permissions the platform reviews, and a personal account cannot use them at all. - Get it: Convert or create the account type the developer documentation names, create an app, and request the permissions it lists; the hashtag endpoint needs a separate feature approval. - Verify: A test publish through the API succeeds on the account - [ ] **The platform's published limits, read and dated** · free - Why: Caption lengths, hashtag counts, image formats, carousel size, the publishing rate and the hashtag quota are all published and all change, and they live here as data. - Get it: Read the content publishing and hashtag search documentation and copy the current numbers into the limits file with today's date. - Verify: The limits file has a checked date and a source for every value - [ ] **Somewhere to serve media from an expiring link** (optional) · pennies per month - Why: If your platform's publishing API fetches media from an address rather than accepting an upload, local files need a temporary public one. - Get it: Object storage with signed expiring links, or a small host with tokens; skip it if the API accepts uploads. - Verify: A link works for the stated period and then stops - [ ] **Your last fifty published posts with their links** · free - Why: The only honest hashtag performance data is your own, and the metrics phase needs real posts to fetch against. - Get it: Import them from the platform's data export or paste their links into the queue as already posted. - Verify: Fifty posted items exist with links ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Scraping, hidden browsers or unofficial endpoints. - Reach estimates, predictions and best time to post. - Team approvals and asset libraries. - Every network at once. - reliable direct publishing across networks - analytics history and attribution - team approvals and asset workflows - protection from API and policy changes - calendar-provider edge cases and timezone correctness If one of those is essential to you, that is the reason to keep paying for Flick, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Flick The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a content planner that drafts, previews per platform, and publishes only through the official APIs to replace Flick. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Every network publishes rules about how software may post, read and research on its behalf, and the tools that ignore them are the ones whose users wake up to a suspended account. So this build talks to a platform only through its official API, with the permissions and the account type that API requires, and stays within its published limits. Where no API exists for what you want, the tool does not scrape a page or drive a hidden browser; it prepares the post and hands it to you to publish by hand, which is slower and keeps the account. And the analytics report only what the platform returned about your own posts, dated when it was fetched. There is no reach estimate, no engagement prediction and no best time to post derived from a dozen data points, because a number with no basis is worse than no number. ### Stack (fixed, do not substitute) - Runtime: TypeScript with PostgreSQL and a database-backed job table · scheduled publishing needs a durable queue, and a second queue system is another thing to run - Platforms: Official APIs only, with per-platform limits stored as data with a checked date · limits and formats change, and a hard-coded one is wrong within a year - Media: Local storage, with an expiring public link only where an API fetches from a URL · check whether your platform accepts an upload or fetches from an address - Queue: A hand-posting queue as the default path · it works for every network and every account type, including ones with no API - Research: The official hashtag endpoint within its quota, plus your own post history · the quota is small and the alternative breaks the rules ### Data model (create this before Phase 1) - drafts(id, idea, body, media_ids_json, created_at, updated_at) - variants(id, draft_id, platform, caption, hashtags_json, media_order_json, limits_version) - platform_limits(platform, version, caption_max, hashtag_max, image_formats_json, carousel_max, checked_on, source_url) · data, not code - queue_items(id, variant_id, publish_at, mode, state, published_id, attempts, last_error) · mode is by_hand or api - publish_attempts(id, queue_item_id, at, container_id, result, error_json) · so a retry can check before posting again - hashtag_lookups(id, hashtag, platform, looked_up_at, hashtag_id, result_json) · counted against the weekly quota - metrics_snapshots(id, published_id, platform, fetched_at, values_json) · every figure carries the time it was fetched - A queue item is by_hand or api, and the by_hand path is complete on its own, so the tool is useful before any platform has approved anything. ### Phase 1 · Drafts and platform variants as data Build: Drafts with media, a variant per platform, and a limits table holding caption length, hashtag count, accepted image formats and carousel size per platform with a checked date and the source page. The preview flags every violation and shows the limit's date. Nothing is hard-coded. Trap: Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. Done when: A variant exceeding a stored limit is flagged in the editor with the limit and its checked date. Changing a limit in data changes the flag with no code change. A draft with several images produces a variant per platform with its own caption and order. The preview renders each variant approximately as the platform would, including truncation. Do not build yet: No queue, publishing, research or analytics. ### Phase 2 · A queue that needs no API Build: A calendar queue with reminders at publish time. The by-hand flow copies the caption, presents the media in order for download or share, opens the platform, and marks the item posted with a link pasted back. Export the queue as a calendar file. Reschedule by drag. Trap: Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. Done when: A by-hand item can be posted in under a minute from reminder to marked done, timed. The reminder fires at publish time in the user's timezone. Exporting the queue produces a calendar file that opens in a calendar application. Marking an item posted stores the pasted link for later metrics. Do not build yet: No API publishing, research or analytics. ### Phase 3 · Publishing through the official API, once Build: OAuth for one platform with the account type its publishing API requires. A worker leases due items, creates the media container, records its id, then publishes. Before any retry, check the recorded container's status. Read the platform's publishing usage endpoint and refuse to schedule past its limit. Trap: Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. Done when: An item publishes on schedule and its platform id is stored. A simulated timeout after publish does not create a second post, tested by injecting the failure. Scheduling beyond the platform's published limit is refused with the usage figure shown. Where the API fetches media from a URL, the link expires after the stated period and stops working, tested. Do not build yet: No hashtag research or analytics. ### Phase 4 · Hashtag research inside the quota Build: The official hashtag search for the platform that has one, with every lookup counted against its quota, which at the time of writing is thirty unique hashtags per seven days per user and requires an approved public content access feature. Cache results, show the remaining budget, and refuse rather than fail when it is spent. Build the rest from your own published posts and their metrics. Trap: Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. Done when: Every lookup is recorded and the remaining weekly budget is shown before a search. A repeated hashtag within the window is served from cache and not counted. A search past the budget is refused with the date it resets. Hashtag performance from your own posts is computed from stored metrics rather than estimated. Do not build yet: No analytics history or export yet. ### Phase 5 · Metrics with a date on them, and leaving Build: Fetch metrics for your own published posts on a schedule and store each fetch as a snapshot. Show every figure with its fetch time and never as a live number. Handle token expiry by pausing API items and telling the person, not by failing silently. Export drafts, queue, snapshots and lookups. Disconnect by revoking and deleting tokens. Trap: Displaying the last fetched engagement as though it were current, or comparing snapshots taken days apart as though they were a trend, turns the metrics page into fiction. Date every figure, compare only like with like, and say what was not fetched. Done when: Every metric shows its fetch time and a post with no fetch shows none rather than zero. An expired token pauses API items, leaves by-hand items running, and notifies the person. Export produces complete files for drafts, queue, snapshots and lookups. Disconnecting removes tokens and leaves stored content intact. Do not build yet: Nothing after this. ### Out of scope (and why) - Any scraping, hidden browser or unofficial endpoint, which breaks the platforms' terms and risks the account this tool exists to serve. - Reach estimates, engagement predictions and best time to post, which are numbers with no basis at a personal scale. - Team approvals and asset libraries, which are workflow products the paid tool sells to agencies. - Every network at once, since each official API has its own review, account requirements and limits, and one done properly beats six done badly. ### README must contain - That the tool talks to platforms only through official APIs and never scrapes, drives a hidden browser or exceeds a published limit - That the by-hand queue is complete on its own and works with no platform access at all - That a publish retry checks the recorded container before posting, so a timeout never posts twice - That hashtag lookups are counted against the platform's quota, cached, and refused when spent - That every metric carries the time it was fetched and nothing is presented as live or predicted ===== AGENTS.md ===== # Agent instructions · Flick indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with PostgreSQL and a database-backed job table, Official APIs only, with per-platform limits stored as data with a checked date, Local storage, with an expiring public link only where an API fetches from a URL, A hand-posting queue as the default path, The official hashtag endpoint within its quota, plus your own post history. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. - Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. - Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. - Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. ===== BUILD_PLAN.md ===== # Build plan · Flick A personal social content planner whose honest core is a hand-posting queue that needs no API at all, with official publishing and hashtag research layered on top inside the limits each platform publishes, rather than a tool that pretends the platforms' rules do not apply. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Drafts and platform variants as data One idea becomes per-platform versions checked against limits that are stored, dated and changeable. ### Steps 1. Store limits as dated data Caption, hashtags, formats, carousel size, checked date, source. Files: `config/platform-limits.json`, `src/limits.ts` 2. Flag violations in the editor With the limit and when it was checked. Files: `src/editor/validate.ts` 3. Preview with truncation What the platform will show is what matters. Files: `src/editor/preview.ts` ### Done when - [ ] Violations are flagged with the limit and its date - [ ] Limits change with a data edit - [ ] Each platform gets its own variant - [ ] Previews show truncation ### Watch out - Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. ## Phase 2 · A queue that needs no API Posting by hand is fast enough that the tool is useful with zero platform access. ### Steps 1. Make by-hand posting one flow Copy, media, open, paste link, done. Files: `src/queue/byhand.ts` 2. Remind in the user's zone A reminder an hour off is a post that misses its slot. Files: `src/queue/reminders.ts` 3. Export the queue as a calendar So the plan is visible where the person already looks. Files: `src/queue/ics.ts` ### Done when - [ ] By-hand posting takes under a minute - [ ] Reminders fire in the user's timezone - [ ] The queue exports as a calendar file - [ ] Posted links are stored ### Watch out - Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. ## Phase 3 · Publishing through the official API, once An eligible account publishes on schedule and a retry never posts twice. ### Steps 1. Lease items and record the container before publishing So a retry can check instead of guess. Files: `src/publish/worker.ts`, `src/publish/attempts.ts` 2. Check status before any retry Injected timeout after publish, one post. Files: `tests/publish-retry.test.ts` 3. Read the usage endpoint before scheduling The limit is published, and the endpoint reports where you are against it. Files: `src/publish/limits.ts` ### Done when - [ ] Scheduled items publish with ids stored - [ ] A timeout after publish never posts twice - [ ] Scheduling past the limit is refused - [ ] Expiring media links expire ### Watch out - Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. ## Phase 4 · Hashtag research inside the quota Research uses the official endpoint, spends its small weekly budget deliberately, and learns from your own posts. ### Steps 1. Count and cache every lookup Thirty unique in seven days is the budget, and the cache stretches it. Files: `src/research/quota.ts`, `src/research/quota.test.ts` 2. Refuse when spent, with the reset date A refusal here beats an error at the platform. Files: `src/research/search.ts` 3. Learn from your own posts Real metrics on your own hashtags, not a guess about anyone else's. Files: `src/research/own.ts` ### Done when - [ ] Lookups are counted and the budget is shown - [ ] Repeats within the window come from cache - [ ] A spent budget refuses with a reset date - [ ] Own-post hashtag performance uses stored metrics ### Watch out - Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. ## Not in this build - Scraping, hidden browsers or unofficial endpoints. - Reach estimates, predictions and best time to post. - Team approvals and asset libraries. - Every network at once. ## After v1, if you want it - A second platform done to the same standard, once the first has run for a month without a duplicate - A content calendar grouped by theme, which is the planning view people actually want - First-comment hashtags as a variant option, if the platform's API supports it when you check ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Drafts, queue, attempts, lookups and snapshots. DATABASE_URL=postgres://localhost:5432/planner # Optional. Only for API publishing. The by-hand queue works without it. PLATFORM_APP_ID=an app id from the developer console # Optional · secret. Only for API publishing. Never logged. PLATFORM_APP_SECRET=app-secret-...redacted # Required. Dated limits per platform. A data edit, not a deploy. LIMITS_PATH=./config/platform-limits.json # Required. Copied from the published limit with its date. Recheck before changing. HASHTAG_QUOTA_PER_WINDOW=30 # Required. The window the quota applies to. Lookups are counted against it. HASHTAG_QUOTA_WINDOW_DAYS=7 # Optional. Only where the API fetches from a URL. The link stops working after this. MEDIA_LINK_TTL_MINUTES=60 # Required. Present as a constant. There is no other value that keeps the account. SCRAPE_OR_DRIVE_BROWSER=never
You are building a lean indie version of Flick. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Flick · indie build A personal social content planner whose honest core is a hand-posting queue that needs no API at all, with official publishing and hashtag research layered on top inside the limits each platform publishes, rather than a tool that pretends the platforms' rules do not apply. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with PostgreSQL and a database-backed job table | scheduled publishing needs a durable queue, and a second queue system is another thing to run | | Platforms | Official APIs only, with per-platform limits stored as data with a checked date | limits and formats change, and a hard-coded one is wrong within a year | | Media | Local storage, with an expiring public link only where an API fetches from a URL | check whether your platform accepts an upload or fetches from an address | | Queue | A hand-posting queue as the default path | it works for every network and every account type, including ones with no API | | Research | The official hashtag endpoint within its quota, plus your own post history | the quota is small and the alternative breaks the rules | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the server and the publishing worker. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **PostgreSQL 16 or newer** · free locally - Why: The queue lease and the attempt log need transactions so two workers never publish the same item. - Get it: Docker or your package manager. - Verify: psql connects and creates a table - [ ] **A professional account on the platform you will publish to, with an approved app** · free, and the review takes time - Why: The publishing and research APIs require a professional account type and app permissions the platform reviews, and a personal account cannot use them at all. - Get it: Convert or create the account type the developer documentation names, create an app, and request the permissions it lists; the hashtag endpoint needs a separate feature approval. - Verify: A test publish through the API succeeds on the account - [ ] **The platform's published limits, read and dated** · free - Why: Caption lengths, hashtag counts, image formats, carousel size, the publishing rate and the hashtag quota are all published and all change, and they live here as data. - Get it: Read the content publishing and hashtag search documentation and copy the current numbers into the limits file with today's date. - Verify: The limits file has a checked date and a source for every value - [ ] **Somewhere to serve media from an expiring link** (optional) · pennies per month - Why: If your platform's publishing API fetches media from an address rather than accepting an upload, local files need a temporary public one. - Get it: Object storage with signed expiring links, or a small host with tokens; skip it if the API accepts uploads. - Verify: A link works for the stated period and then stops - [ ] **Your last fifty published posts with their links** · free - Why: The only honest hashtag performance data is your own, and the metrics phase needs real posts to fetch against. - Get it: Import them from the platform's data export or paste their links into the queue as already posted. - Verify: Fifty posted items exist with links ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Scraping, hidden browsers or unofficial endpoints. - Reach estimates, predictions and best time to post. - Team approvals and asset libraries. - Every network at once. - reliable direct publishing across networks - analytics history and attribution - team approvals and asset workflows - protection from API and policy changes - calendar-provider edge cases and timezone correctness If one of those is essential to you, that is the reason to keep paying for Flick, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Flick The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a content planner that drafts, previews per platform, and publishes only through the official APIs to replace Flick. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Every network publishes rules about how software may post, read and research on its behalf, and the tools that ignore them are the ones whose users wake up to a suspended account. So this build talks to a platform only through its official API, with the permissions and the account type that API requires, and stays within its published limits. Where no API exists for what you want, the tool does not scrape a page or drive a hidden browser; it prepares the post and hands it to you to publish by hand, which is slower and keeps the account. And the analytics report only what the platform returned about your own posts, dated when it was fetched. There is no reach estimate, no engagement prediction and no best time to post derived from a dozen data points, because a number with no basis is worse than no number. ### Stack (fixed, do not substitute) - Runtime: TypeScript with PostgreSQL and a database-backed job table · scheduled publishing needs a durable queue, and a second queue system is another thing to run - Platforms: Official APIs only, with per-platform limits stored as data with a checked date · limits and formats change, and a hard-coded one is wrong within a year - Media: Local storage, with an expiring public link only where an API fetches from a URL · check whether your platform accepts an upload or fetches from an address - Queue: A hand-posting queue as the default path · it works for every network and every account type, including ones with no API - Research: The official hashtag endpoint within its quota, plus your own post history · the quota is small and the alternative breaks the rules ### Data model (create this before Phase 1) - drafts(id, idea, body, media_ids_json, created_at, updated_at) - variants(id, draft_id, platform, caption, hashtags_json, media_order_json, limits_version) - platform_limits(platform, version, caption_max, hashtag_max, image_formats_json, carousel_max, checked_on, source_url) · data, not code - queue_items(id, variant_id, publish_at, mode, state, published_id, attempts, last_error) · mode is by_hand or api - publish_attempts(id, queue_item_id, at, container_id, result, error_json) · so a retry can check before posting again - hashtag_lookups(id, hashtag, platform, looked_up_at, hashtag_id, result_json) · counted against the weekly quota - metrics_snapshots(id, published_id, platform, fetched_at, values_json) · every figure carries the time it was fetched - A queue item is by_hand or api, and the by_hand path is complete on its own, so the tool is useful before any platform has approved anything. ### Phase 1 · Drafts and platform variants as data Build: Drafts with media, a variant per platform, and a limits table holding caption length, hashtag count, accepted image formats and carousel size per platform with a checked date and the source page. The preview flags every violation and shows the limit's date. Nothing is hard-coded. Trap: Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. Done when: A variant exceeding a stored limit is flagged in the editor with the limit and its checked date. Changing a limit in data changes the flag with no code change. A draft with several images produces a variant per platform with its own caption and order. The preview renders each variant approximately as the platform would, including truncation. Do not build yet: No queue, publishing, research or analytics. ### Phase 2 · A queue that needs no API Build: A calendar queue with reminders at publish time. The by-hand flow copies the caption, presents the media in order for download or share, opens the platform, and marks the item posted with a link pasted back. Export the queue as a calendar file. Reschedule by drag. Trap: Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. Done when: A by-hand item can be posted in under a minute from reminder to marked done, timed. The reminder fires at publish time in the user's timezone. Exporting the queue produces a calendar file that opens in a calendar application. Marking an item posted stores the pasted link for later metrics. Do not build yet: No API publishing, research or analytics. ### Phase 3 · Publishing through the official API, once Build: OAuth for one platform with the account type its publishing API requires. A worker leases due items, creates the media container, records its id, then publishes. Before any retry, check the recorded container's status. Read the platform's publishing usage endpoint and refuse to schedule past its limit. Trap: Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. Done when: An item publishes on schedule and its platform id is stored. A simulated timeout after publish does not create a second post, tested by injecting the failure. Scheduling beyond the platform's published limit is refused with the usage figure shown. Where the API fetches media from a URL, the link expires after the stated period and stops working, tested. Do not build yet: No hashtag research or analytics. ### Phase 4 · Hashtag research inside the quota Build: The official hashtag search for the platform that has one, with every lookup counted against its quota, which at the time of writing is thirty unique hashtags per seven days per user and requires an approved public content access feature. Cache results, show the remaining budget, and refuse rather than fail when it is spent. Build the rest from your own published posts and their metrics. Trap: Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. Done when: Every lookup is recorded and the remaining weekly budget is shown before a search. A repeated hashtag within the window is served from cache and not counted. A search past the budget is refused with the date it resets. Hashtag performance from your own posts is computed from stored metrics rather than estimated. Do not build yet: No analytics history or export yet. ### Phase 5 · Metrics with a date on them, and leaving Build: Fetch metrics for your own published posts on a schedule and store each fetch as a snapshot. Show every figure with its fetch time and never as a live number. Handle token expiry by pausing API items and telling the person, not by failing silently. Export drafts, queue, snapshots and lookups. Disconnect by revoking and deleting tokens. Trap: Displaying the last fetched engagement as though it were current, or comparing snapshots taken days apart as though they were a trend, turns the metrics page into fiction. Date every figure, compare only like with like, and say what was not fetched. Done when: Every metric shows its fetch time and a post with no fetch shows none rather than zero. An expired token pauses API items, leaves by-hand items running, and notifies the person. Export produces complete files for drafts, queue, snapshots and lookups. Disconnecting removes tokens and leaves stored content intact. Do not build yet: Nothing after this. ### Out of scope (and why) - Any scraping, hidden browser or unofficial endpoint, which breaks the platforms' terms and risks the account this tool exists to serve. - Reach estimates, engagement predictions and best time to post, which are numbers with no basis at a personal scale. - Team approvals and asset libraries, which are workflow products the paid tool sells to agencies. - Every network at once, since each official API has its own review, account requirements and limits, and one done properly beats six done badly. ### README must contain - That the tool talks to platforms only through official APIs and never scrapes, drives a hidden browser or exceeds a published limit - That the by-hand queue is complete on its own and works with no platform access at all - That a publish retry checks the recorded container before posting, so a timeout never posts twice - That hashtag lookups are counted against the platform's quota, cached, and refused when spent - That every metric carries the time it was fetched and nothing is presented as live or predicted ===== AGENTS.md ===== # Agent instructions · Flick indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with PostgreSQL and a database-backed job table, Official APIs only, with per-platform limits stored as data with a checked date, Local storage, with an expiring public link only where an API fetches from a URL, A hand-posting queue as the default path, The official hashtag endpoint within its quota, plus your own post history. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. - Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. - Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. - Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. ===== BUILD_PLAN.md ===== # Build plan · Flick A personal social content planner whose honest core is a hand-posting queue that needs no API at all, with official publishing and hashtag research layered on top inside the limits each platform publishes, rather than a tool that pretends the platforms' rules do not apply. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Drafts and platform variants as data One idea becomes per-platform versions checked against limits that are stored, dated and changeable. ### Steps 1. Store limits as dated data Caption, hashtags, formats, carousel size, checked date, source. Files: `config/platform-limits.json`, `src/limits.ts` 2. Flag violations in the editor With the limit and when it was checked. Files: `src/editor/validate.ts` 3. Preview with truncation What the platform will show is what matters. Files: `src/editor/preview.ts` ### Done when - [ ] Violations are flagged with the limit and its date - [ ] Limits change with a data edit - [ ] Each platform gets its own variant - [ ] Previews show truncation ### Watch out - Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. ## Phase 2 · A queue that needs no API Posting by hand is fast enough that the tool is useful with zero platform access. ### Steps 1. Make by-hand posting one flow Copy, media, open, paste link, done. Files: `src/queue/byhand.ts` 2. Remind in the user's zone A reminder an hour off is a post that misses its slot. Files: `src/queue/reminders.ts` 3. Export the queue as a calendar So the plan is visible where the person already looks. Files: `src/queue/ics.ts` ### Done when - [ ] By-hand posting takes under a minute - [ ] Reminders fire in the user's timezone - [ ] The queue exports as a calendar file - [ ] Posted links are stored ### Watch out - Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. ## Phase 3 · Publishing through the official API, once An eligible account publishes on schedule and a retry never posts twice. ### Steps 1. Lease items and record the container before publishing So a retry can check instead of guess. Files: `src/publish/worker.ts`, `src/publish/attempts.ts` 2. Check status before any retry Injected timeout after publish, one post. Files: `tests/publish-retry.test.ts` 3. Read the usage endpoint before scheduling The limit is published, and the endpoint reports where you are against it. Files: `src/publish/limits.ts` ### Done when - [ ] Scheduled items publish with ids stored - [ ] A timeout after publish never posts twice - [ ] Scheduling past the limit is refused - [ ] Expiring media links expire ### Watch out - Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. ## Phase 4 · Hashtag research inside the quota Research uses the official endpoint, spends its small weekly budget deliberately, and learns from your own posts. ### Steps 1. Count and cache every lookup Thirty unique in seven days is the budget, and the cache stretches it. Files: `src/research/quota.ts`, `src/research/quota.test.ts` 2. Refuse when spent, with the reset date A refusal here beats an error at the platform. Files: `src/research/search.ts` 3. Learn from your own posts Real metrics on your own hashtags, not a guess about anyone else's. Files: `src/research/own.ts` ### Done when - [ ] Lookups are counted and the budget is shown - [ ] Repeats within the window come from cache - [ ] A spent budget refuses with a reset date - [ ] Own-post hashtag performance uses stored metrics ### Watch out - Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. ## Not in this build - Scraping, hidden browsers or unofficial endpoints. - Reach estimates, predictions and best time to post. - Team approvals and asset libraries. - Every network at once. ## After v1, if you want it - A second platform done to the same standard, once the first has run for a month without a duplicate - A content calendar grouped by theme, which is the planning view people actually want - First-comment hashtags as a variant option, if the platform's API supports it when you check ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Drafts, queue, attempts, lookups and snapshots. DATABASE_URL=postgres://localhost:5432/planner # Optional. Only for API publishing. The by-hand queue works without it. PLATFORM_APP_ID=an app id from the developer console # Optional · secret. Only for API publishing. Never logged. PLATFORM_APP_SECRET=app-secret-...redacted # Required. Dated limits per platform. A data edit, not a deploy. LIMITS_PATH=./config/platform-limits.json # Required. Copied from the published limit with its date. Recheck before changing. HASHTAG_QUOTA_PER_WINDOW=30 # Required. The window the quota applies to. Lookups are counted against it. HASHTAG_QUOTA_WINDOW_DAYS=7 # Optional. Only where the API fetches from a URL. The link stops working after this. MEDIA_LINK_TTL_MINUTES=60 # Required. Present as a constant. There is no other value that keeps the account. SCRAPE_OR_DRIVE_BROWSER=never
You are building a production product version of Flick. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Flick · product brief ## Problem The visible social content + analytics loop is buildable, but a credible replacement needs more than the first screen. Flick earns its keep through platform APIs, distribution, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A planner that gets a post out by hand in under a minute, publishes through the official API exactly once where the account qualifies, spends its research quota deliberately, and never shows a number it cannot date. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node.js 22 - hosted database and worker - official platform OAuth credentials where available ## Explicit non-goals for v1 - Scraping, hidden browsers or unofficial endpoints. - Reach estimates, predictions and best time to post. - Team approvals and asset libraries. - Every network at once. - reliable direct publishing across networks - analytics history and attribution - team approvals and asset workflows - protection from API and policy changes - calendar-provider edge cases and timezone correctness ## Success criteria - Limit violations are confirmed flagged from dated data - By-hand posting is confirmed complete with no platform access - A timeout after publish is confirmed never to post twice - Hashtag lookups are confirmed counted, cached and refused when spent - Every metric is confirmed to carry its fetch time ===== BRIEF.md ===== # Build brief · Flick The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a content planner that drafts, previews per platform, and publishes only through the official APIs to replace Flick. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Every network publishes rules about how software may post, read and research on its behalf, and the tools that ignore them are the ones whose users wake up to a suspended account. So this build talks to a platform only through its official API, with the permissions and the account type that API requires, and stays within its published limits. Where no API exists for what you want, the tool does not scrape a page or drive a hidden browser; it prepares the post and hands it to you to publish by hand, which is slower and keeps the account. And the analytics report only what the platform returned about your own posts, dated when it was fetched. There is no reach estimate, no engagement prediction and no best time to post derived from a dozen data points, because a number with no basis is worse than no number. ### Stack (fixed, do not substitute) - Runtime: TypeScript with PostgreSQL and a database-backed job table · scheduled publishing needs a durable queue, and a second queue system is another thing to run - Platforms: Official APIs only, with per-platform limits stored as data with a checked date · limits and formats change, and a hard-coded one is wrong within a year - Media: Local storage, with an expiring public link only where an API fetches from a URL · check whether your platform accepts an upload or fetches from an address - Queue: A hand-posting queue as the default path · it works for every network and every account type, including ones with no API - Research: The official hashtag endpoint within its quota, plus your own post history · the quota is small and the alternative breaks the rules ### Data model (create this before Phase 1) - drafts(id, idea, body, media_ids_json, created_at, updated_at) - variants(id, draft_id, platform, caption, hashtags_json, media_order_json, limits_version) - platform_limits(platform, version, caption_max, hashtag_max, image_formats_json, carousel_max, checked_on, source_url) · data, not code - queue_items(id, variant_id, publish_at, mode, state, published_id, attempts, last_error) · mode is by_hand or api - publish_attempts(id, queue_item_id, at, container_id, result, error_json) · so a retry can check before posting again - hashtag_lookups(id, hashtag, platform, looked_up_at, hashtag_id, result_json) · counted against the weekly quota - metrics_snapshots(id, published_id, platform, fetched_at, values_json) · every figure carries the time it was fetched - A queue item is by_hand or api, and the by_hand path is complete on its own, so the tool is useful before any platform has approved anything. ### Phase 1 · Drafts and platform variants as data Build: Drafts with media, a variant per platform, and a limits table holding caption length, hashtag count, accepted image formats and carousel size per platform with a checked date and the source page. The preview flags every violation and shows the limit's date. Nothing is hard-coded. Trap: Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. Done when: A variant exceeding a stored limit is flagged in the editor with the limit and its checked date. Changing a limit in data changes the flag with no code change. A draft with several images produces a variant per platform with its own caption and order. The preview renders each variant approximately as the platform would, including truncation. Do not build yet: No queue, publishing, research or analytics. ### Phase 2 · A queue that needs no API Build: A calendar queue with reminders at publish time. The by-hand flow copies the caption, presents the media in order for download or share, opens the platform, and marks the item posted with a link pasted back. Export the queue as a calendar file. Reschedule by drag. Trap: Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. Done when: A by-hand item can be posted in under a minute from reminder to marked done, timed. The reminder fires at publish time in the user's timezone. Exporting the queue produces a calendar file that opens in a calendar application. Marking an item posted stores the pasted link for later metrics. Do not build yet: No API publishing, research or analytics. ### Phase 3 · Publishing through the official API, once Build: OAuth for one platform with the account type its publishing API requires. A worker leases due items, creates the media container, records its id, then publishes. Before any retry, check the recorded container's status. Read the platform's publishing usage endpoint and refuse to schedule past its limit. Trap: Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. Done when: An item publishes on schedule and its platform id is stored. A simulated timeout after publish does not create a second post, tested by injecting the failure. Scheduling beyond the platform's published limit is refused with the usage figure shown. Where the API fetches media from a URL, the link expires after the stated period and stops working, tested. Do not build yet: No hashtag research or analytics. ### Phase 4 · Hashtag research inside the quota Build: The official hashtag search for the platform that has one, with every lookup counted against its quota, which at the time of writing is thirty unique hashtags per seven days per user and requires an approved public content access feature. Cache results, show the remaining budget, and refuse rather than fail when it is spent. Build the rest from your own published posts and their metrics. Trap: Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. Done when: Every lookup is recorded and the remaining weekly budget is shown before a search. A repeated hashtag within the window is served from cache and not counted. A search past the budget is refused with the date it resets. Hashtag performance from your own posts is computed from stored metrics rather than estimated. Do not build yet: No analytics history or export yet. ### Phase 5 · Metrics with a date on them, and leaving Build: Fetch metrics for your own published posts on a schedule and store each fetch as a snapshot. Show every figure with its fetch time and never as a live number. Handle token expiry by pausing API items and telling the person, not by failing silently. Export drafts, queue, snapshots and lookups. Disconnect by revoking and deleting tokens. Trap: Displaying the last fetched engagement as though it were current, or comparing snapshots taken days apart as though they were a trend, turns the metrics page into fiction. Date every figure, compare only like with like, and say what was not fetched. Done when: Every metric shows its fetch time and a post with no fetch shows none rather than zero. An expired token pauses API items, leaves by-hand items running, and notifies the person. Export produces complete files for drafts, queue, snapshots and lookups. Disconnecting removes tokens and leaves stored content intact. Do not build yet: Nothing after this. ### Out of scope (and why) - Any scraping, hidden browser or unofficial endpoint, which breaks the platforms' terms and risks the account this tool exists to serve. - Reach estimates, engagement predictions and best time to post, which are numbers with no basis at a personal scale. - Team approvals and asset libraries, which are workflow products the paid tool sells to agencies. - Every network at once, since each official API has its own review, account requirements and limits, and one done properly beats six done badly. ### README must contain - That the tool talks to platforms only through official APIs and never scrapes, drives a hidden browser or exceeds a published limit - That the by-hand queue is complete on its own and works with no platform access at all - That a publish retry checks the recorded container before posting, so a timeout never posts twice - That hashtag lookups are counted against the platform's quota, cached, and refused when spent - That every metric carries the time it was fetched and nothing is presented as live or predicted ===== ARCHITECTURE.md ===== # Architecture · Flick ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with PostgreSQL and a database-backed job table | scheduled publishing needs a durable queue, and a second queue system is another thing to run | | Platforms | Official APIs only, with per-platform limits stored as data with a checked date | limits and formats change, and a hard-coded one is wrong within a year | | Media | Local storage, with an expiring public link only where an API fetches from a URL | check whether your platform accepts an upload or fetches from an address | | Queue | A hand-posting queue as the default path | it works for every network and every account type, including ones with no API | | Research | The official hashtag endpoint within its quota, plus your own post history | the quota is small and the alternative breaks the rules | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Variants | Per-platform versions checked against dated limits. | Any editor, provided limits are data with a checked date. | | Queue | The by-hand path and scheduling. | Any interface, provided by-hand posting is complete without an API. | | Publishing | Leased items, recorded containers, status before retry. | Any platform, provided a retry checks before posting. | | Research | Counted, cached lookups within quota. | Not swappable for scraping, which risks the account. | | Metrics | Dated snapshots of your own posts. | Any store, provided every figure carries its fetch time. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_URL` · required, secret · Drafts, queue, attempts, lookups and snapshots. - `PLATFORM_APP_ID` · optional · Only for API publishing. The by-hand queue works without it. - `PLATFORM_APP_SECRET` · optional, secret · Only for API publishing. Never logged. - `LIMITS_PATH` · required · Dated limits per platform. A data edit, not a deploy. - `HASHTAG_QUOTA_PER_WINDOW` · required · Copied from the published limit with its date. Recheck before changing. - `HASHTAG_QUOTA_WINDOW_DAYS` · required · The window the quota applies to. Lookups are counted against it. - `MEDIA_LINK_TTL_MINUTES` · optional · Only where the API fetches from a URL. The link stops working after this. - `SCRAPE_OR_DRIVE_BROWSER` · required · Present as a constant. There is no other value that keeps the account. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Flick product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with PostgreSQL and a database-backed job table, Official APIs only, with per-platform limits stored as data with a checked date, Local storage, with an expiring public link only where an API fetches from a URL, A hand-posting queue as the default path, The official hashtag endpoint within its quota, plus your own post history. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. - Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. - Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. - Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. - Displaying the last fetched engagement as though it were current, or comparing snapshots taken days apart as though they were a trend, turns the metrics page into fiction. Date every figure, compare only like with like, and say what was not fetched. ===== MILESTONES.md ===== # Delivery milestones · Flick Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Drafts and platform variants as data One idea becomes per-platform versions checked against limits that are stored, dated and changeable. ### Steps 1. Store limits as dated data Caption, hashtags, formats, carousel size, checked date, source. Files: `config/platform-limits.json`, `src/limits.ts` 2. Flag violations in the editor With the limit and when it was checked. Files: `src/editor/validate.ts` 3. Preview with truncation What the platform will show is what matters. Files: `src/editor/preview.ts` ### Done when - [ ] Violations are flagged with the limit and its date - [ ] Limits change with a data edit - [ ] Each platform gets its own variant - [ ] Previews show truncation ### Watch out - Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. ## M2 · A queue that needs no API Posting by hand is fast enough that the tool is useful with zero platform access. ### Steps 1. Make by-hand posting one flow Copy, media, open, paste link, done. Files: `src/queue/byhand.ts` 2. Remind in the user's zone A reminder an hour off is a post that misses its slot. Files: `src/queue/reminders.ts` 3. Export the queue as a calendar So the plan is visible where the person already looks. Files: `src/queue/ics.ts` ### Done when - [ ] By-hand posting takes under a minute - [ ] Reminders fire in the user's timezone - [ ] The queue exports as a calendar file - [ ] Posted links are stored ### Watch out - Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. ## M3 · Publishing through the official API, once An eligible account publishes on schedule and a retry never posts twice. ### Steps 1. Lease items and record the container before publishing So a retry can check instead of guess. Files: `src/publish/worker.ts`, `src/publish/attempts.ts` 2. Check status before any retry Injected timeout after publish, one post. Files: `tests/publish-retry.test.ts` 3. Read the usage endpoint before scheduling The limit is published, and the endpoint reports where you are against it. Files: `src/publish/limits.ts` ### Done when - [ ] Scheduled items publish with ids stored - [ ] A timeout after publish never posts twice - [ ] Scheduling past the limit is refused - [ ] Expiring media links expire ### Watch out - Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. ## M4 · Hashtag research inside the quota Research uses the official endpoint, spends its small weekly budget deliberately, and learns from your own posts. ### Steps 1. Count and cache every lookup Thirty unique in seven days is the budget, and the cache stretches it. Files: `src/research/quota.ts`, `src/research/quota.test.ts` 2. Refuse when spent, with the reset date A refusal here beats an error at the platform. Files: `src/research/search.ts` 3. Learn from your own posts Real metrics on your own hashtags, not a guess about anyone else's. Files: `src/research/own.ts` ### Done when - [ ] Lookups are counted and the budget is shown - [ ] Repeats within the window come from cache - [ ] A spent budget refuses with a reset date - [ ] Own-post hashtag performance uses stored metrics ### Watch out - Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. ## M5 · Metrics with a date on them, and leaving (production only) Every figure says when it was fetched, tokens expire gracefully, and everything exports. ### Steps 1. Snapshot metrics with fetch times A figure without a date is a claim without a basis. Files: `src/metrics/snapshots.ts` 2. Pause on token expiry, keep by-hand running The queue is still useful without the API. Files: `src/publish/tokens.ts` 3. Export everything and disconnect cleanly Content stays, tokens go. Files: `src/export.ts`, `src/disconnect.ts` ### Done when - [ ] Every metric shows its fetch time - [ ] Token expiry pauses API items only - [ ] Export is complete - [ ] Disconnect keeps content and removes tokens ### Watch out - Displaying the last fetched engagement as though it were current, or comparing snapshots taken days apart as though they were a trend, turns the metrics page into fiction. Date every figure, compare only like with like, and say what was not fetched. ===== OPERATIONS.md ===== # Operations · Flick ## Backup Drafts and media are the work; attempts and lookups are the audit trail that keeps you inside the rules. Back up the database and media together, nightly, since a restored queue without its attempt log may republish. ## Restore Restore and confirm the attempt log survived alongside the queue, because a queue item in a publishing state with no recorded container will be retried and may post twice. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Publishing usage against the platform's limit, the remaining hashtag budget, and token expiry dates. All three fail quietly and all three are visible in the database before they fail. ## Incident checklist A duplicate post or a suspended account is the incident, and both trace to the same two causes: a retry that did not check the container, or a call outside the official API. The attempt log answers the first and the constant answers the second. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Limit violations are confirmed flagged from dated data - [ ] By-hand posting is confirmed complete with no platform access - [ ] A timeout after publish is confirmed never to post twice - [ ] Hashtag lookups are confirmed counted, cached and refused when spent - [ ] Every metric is confirmed to carry its fetch time ## Launch constraint Do not market omitted Flick capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Drafts, queue, attempts, lookups and snapshots. DATABASE_URL=postgres://localhost:5432/planner # Optional. Only for API publishing. The by-hand queue works without it. PLATFORM_APP_ID=an app id from the developer console # Optional · secret. Only for API publishing. Never logged. PLATFORM_APP_SECRET=app-secret-...redacted # Required. Dated limits per platform. A data edit, not a deploy. LIMITS_PATH=./config/platform-limits.json # Required. Copied from the published limit with its date. Recheck before changing. HASHTAG_QUOTA_PER_WINDOW=30 # Required. The window the quota applies to. Lookups are counted against it. HASHTAG_QUOTA_WINDOW_DAYS=7 # Optional. Only where the API fetches from a URL. The link stops working after this. MEDIA_LINK_TTL_MINUTES=60 # Required. Present as a constant. There is no other value that keeps the account. SCRAPE_OR_DRIVE_BROWSER=never
# Flick · indie build A personal social content planner whose honest core is a hand-posting queue that needs no API at all, with official publishing and hashtag research layered on top inside the limits each platform publishes, rather than a tool that pretends the platforms' rules do not apply. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with PostgreSQL and a database-backed job table | scheduled publishing needs a durable queue, and a second queue system is another thing to run | | Platforms | Official APIs only, with per-platform limits stored as data with a checked date | limits and formats change, and a hard-coded one is wrong within a year | | Media | Local storage, with an expiring public link only where an API fetches from a URL | check whether your platform accepts an upload or fetches from an address | | Queue | A hand-posting queue as the default path | it works for every network and every account type, including ones with no API | | Research | The official hashtag endpoint within its quota, plus your own post history | the quota is small and the alternative breaks the rules | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the server and the publishing worker. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **PostgreSQL 16 or newer** · free locally - Why: The queue lease and the attempt log need transactions so two workers never publish the same item. - Get it: Docker or your package manager. - Verify: psql connects and creates a table - [ ] **A professional account on the platform you will publish to, with an approved app** · free, and the review takes time - Why: The publishing and research APIs require a professional account type and app permissions the platform reviews, and a personal account cannot use them at all. - Get it: Convert or create the account type the developer documentation names, create an app, and request the permissions it lists; the hashtag endpoint needs a separate feature approval. - Verify: A test publish through the API succeeds on the account - [ ] **The platform's published limits, read and dated** · free - Why: Caption lengths, hashtag counts, image formats, carousel size, the publishing rate and the hashtag quota are all published and all change, and they live here as data. - Get it: Read the content publishing and hashtag search documentation and copy the current numbers into the limits file with today's date. - Verify: The limits file has a checked date and a source for every value - [ ] **Somewhere to serve media from an expiring link** (optional) · pennies per month - Why: If your platform's publishing API fetches media from an address rather than accepting an upload, local files need a temporary public one. - Get it: Object storage with signed expiring links, or a small host with tokens; skip it if the API accepts uploads. - Verify: A link works for the stated period and then stops - [ ] **Your last fifty published posts with their links** · free - Why: The only honest hashtag performance data is your own, and the metrics phase needs real posts to fetch against. - Get it: Import them from the platform's data export or paste their links into the queue as already posted. - Verify: Fifty posted items exist with links ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Scraping, hidden browsers or unofficial endpoints. - Reach estimates, predictions and best time to post. - Team approvals and asset libraries. - Every network at once. - reliable direct publishing across networks - analytics history and attribution - team approvals and asset workflows - protection from API and policy changes - calendar-provider edge cases and timezone correctness If one of those is essential to you, that is the reason to keep paying for Flick, and the README should say so rather than pretend.
# Build brief · Flick The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a content planner that drafts, previews per platform, and publishes only through the official APIs to replace Flick. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Every network publishes rules about how software may post, read and research on its behalf, and the tools that ignore them are the ones whose users wake up to a suspended account. So this build talks to a platform only through its official API, with the permissions and the account type that API requires, and stays within its published limits. Where no API exists for what you want, the tool does not scrape a page or drive a hidden browser; it prepares the post and hands it to you to publish by hand, which is slower and keeps the account. And the analytics report only what the platform returned about your own posts, dated when it was fetched. There is no reach estimate, no engagement prediction and no best time to post derived from a dozen data points, because a number with no basis is worse than no number. ### Stack (fixed, do not substitute) - Runtime: TypeScript with PostgreSQL and a database-backed job table · scheduled publishing needs a durable queue, and a second queue system is another thing to run - Platforms: Official APIs only, with per-platform limits stored as data with a checked date · limits and formats change, and a hard-coded one is wrong within a year - Media: Local storage, with an expiring public link only where an API fetches from a URL · check whether your platform accepts an upload or fetches from an address - Queue: A hand-posting queue as the default path · it works for every network and every account type, including ones with no API - Research: The official hashtag endpoint within its quota, plus your own post history · the quota is small and the alternative breaks the rules ### Data model (create this before Phase 1) - drafts(id, idea, body, media_ids_json, created_at, updated_at) - variants(id, draft_id, platform, caption, hashtags_json, media_order_json, limits_version) - platform_limits(platform, version, caption_max, hashtag_max, image_formats_json, carousel_max, checked_on, source_url) · data, not code - queue_items(id, variant_id, publish_at, mode, state, published_id, attempts, last_error) · mode is by_hand or api - publish_attempts(id, queue_item_id, at, container_id, result, error_json) · so a retry can check before posting again - hashtag_lookups(id, hashtag, platform, looked_up_at, hashtag_id, result_json) · counted against the weekly quota - metrics_snapshots(id, published_id, platform, fetched_at, values_json) · every figure carries the time it was fetched - A queue item is by_hand or api, and the by_hand path is complete on its own, so the tool is useful before any platform has approved anything. ### Phase 1 · Drafts and platform variants as data Build: Drafts with media, a variant per platform, and a limits table holding caption length, hashtag count, accepted image formats and carousel size per platform with a checked date and the source page. The preview flags every violation and shows the limit's date. Nothing is hard-coded. Trap: Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. Done when: A variant exceeding a stored limit is flagged in the editor with the limit and its checked date. Changing a limit in data changes the flag with no code change. A draft with several images produces a variant per platform with its own caption and order. The preview renders each variant approximately as the platform would, including truncation. Do not build yet: No queue, publishing, research or analytics. ### Phase 2 · A queue that needs no API Build: A calendar queue with reminders at publish time. The by-hand flow copies the caption, presents the media in order for download or share, opens the platform, and marks the item posted with a link pasted back. Export the queue as a calendar file. Reschedule by drag. Trap: Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. Done when: A by-hand item can be posted in under a minute from reminder to marked done, timed. The reminder fires at publish time in the user's timezone. Exporting the queue produces a calendar file that opens in a calendar application. Marking an item posted stores the pasted link for later metrics. Do not build yet: No API publishing, research or analytics. ### Phase 3 · Publishing through the official API, once Build: OAuth for one platform with the account type its publishing API requires. A worker leases due items, creates the media container, records its id, then publishes. Before any retry, check the recorded container's status. Read the platform's publishing usage endpoint and refuse to schedule past its limit. Trap: Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. Done when: An item publishes on schedule and its platform id is stored. A simulated timeout after publish does not create a second post, tested by injecting the failure. Scheduling beyond the platform's published limit is refused with the usage figure shown. Where the API fetches media from a URL, the link expires after the stated period and stops working, tested. Do not build yet: No hashtag research or analytics. ### Phase 4 · Hashtag research inside the quota Build: The official hashtag search for the platform that has one, with every lookup counted against its quota, which at the time of writing is thirty unique hashtags per seven days per user and requires an approved public content access feature. Cache results, show the remaining budget, and refuse rather than fail when it is spent. Build the rest from your own published posts and their metrics. Trap: Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. Done when: Every lookup is recorded and the remaining weekly budget is shown before a search. A repeated hashtag within the window is served from cache and not counted. A search past the budget is refused with the date it resets. Hashtag performance from your own posts is computed from stored metrics rather than estimated. Do not build yet: No analytics history or export yet. ### Phase 5 · Metrics with a date on them, and leaving Build: Fetch metrics for your own published posts on a schedule and store each fetch as a snapshot. Show every figure with its fetch time and never as a live number. Handle token expiry by pausing API items and telling the person, not by failing silently. Export drafts, queue, snapshots and lookups. Disconnect by revoking and deleting tokens. Trap: Displaying the last fetched engagement as though it were current, or comparing snapshots taken days apart as though they were a trend, turns the metrics page into fiction. Date every figure, compare only like with like, and say what was not fetched. Done when: Every metric shows its fetch time and a post with no fetch shows none rather than zero. An expired token pauses API items, leaves by-hand items running, and notifies the person. Export produces complete files for drafts, queue, snapshots and lookups. Disconnecting removes tokens and leaves stored content intact. Do not build yet: Nothing after this. ### Out of scope (and why) - Any scraping, hidden browser or unofficial endpoint, which breaks the platforms' terms and risks the account this tool exists to serve. - Reach estimates, engagement predictions and best time to post, which are numbers with no basis at a personal scale. - Team approvals and asset libraries, which are workflow products the paid tool sells to agencies. - Every network at once, since each official API has its own review, account requirements and limits, and one done properly beats six done badly. ### README must contain - That the tool talks to platforms only through official APIs and never scrapes, drives a hidden browser or exceeds a published limit - That the by-hand queue is complete on its own and works with no platform access at all - That a publish retry checks the recorded container before posting, so a timeout never posts twice - That hashtag lookups are counted against the platform's quota, cached, and refused when spent - That every metric carries the time it was fetched and nothing is presented as live or predicted
# Agent instructions · Flick indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with PostgreSQL and a database-backed job table, Official APIs only, with per-platform limits stored as data with a checked date, Local storage, with an expiring public link only where an API fetches from a URL, A hand-posting queue as the default path, The official hashtag endpoint within its quota, plus your own post history. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. - Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. - Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. - Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget.
# Build plan · Flick A personal social content planner whose honest core is a hand-posting queue that needs no API at all, with official publishing and hashtag research layered on top inside the limits each platform publishes, rather than a tool that pretends the platforms' rules do not apply. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Drafts and platform variants as data One idea becomes per-platform versions checked against limits that are stored, dated and changeable. ### Steps 1. Store limits as dated data Caption, hashtags, formats, carousel size, checked date, source. Files: `config/platform-limits.json`, `src/limits.ts` 2. Flag violations in the editor With the limit and when it was checked. Files: `src/editor/validate.ts` 3. Preview with truncation What the platform will show is what matters. Files: `src/editor/preview.ts` ### Done when - [ ] Violations are flagged with the limit and its date - [ ] Limits change with a data edit - [ ] Each platform gets its own variant - [ ] Previews show truncation ### Watch out - Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. ## Phase 2 · A queue that needs no API Posting by hand is fast enough that the tool is useful with zero platform access. ### Steps 1. Make by-hand posting one flow Copy, media, open, paste link, done. Files: `src/queue/byhand.ts` 2. Remind in the user's zone A reminder an hour off is a post that misses its slot. Files: `src/queue/reminders.ts` 3. Export the queue as a calendar So the plan is visible where the person already looks. Files: `src/queue/ics.ts` ### Done when - [ ] By-hand posting takes under a minute - [ ] Reminders fire in the user's timezone - [ ] The queue exports as a calendar file - [ ] Posted links are stored ### Watch out - Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. ## Phase 3 · Publishing through the official API, once An eligible account publishes on schedule and a retry never posts twice. ### Steps 1. Lease items and record the container before publishing So a retry can check instead of guess. Files: `src/publish/worker.ts`, `src/publish/attempts.ts` 2. Check status before any retry Injected timeout after publish, one post. Files: `tests/publish-retry.test.ts` 3. Read the usage endpoint before scheduling The limit is published, and the endpoint reports where you are against it. Files: `src/publish/limits.ts` ### Done when - [ ] Scheduled items publish with ids stored - [ ] A timeout after publish never posts twice - [ ] Scheduling past the limit is refused - [ ] Expiring media links expire ### Watch out - Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. ## Phase 4 · Hashtag research inside the quota Research uses the official endpoint, spends its small weekly budget deliberately, and learns from your own posts. ### Steps 1. Count and cache every lookup Thirty unique in seven days is the budget, and the cache stretches it. Files: `src/research/quota.ts`, `src/research/quota.test.ts` 2. Refuse when spent, with the reset date A refusal here beats an error at the platform. Files: `src/research/search.ts` 3. Learn from your own posts Real metrics on your own hashtags, not a guess about anyone else's. Files: `src/research/own.ts` ### Done when - [ ] Lookups are counted and the budget is shown - [ ] Repeats within the window come from cache - [ ] A spent budget refuses with a reset date - [ ] Own-post hashtag performance uses stored metrics ### Watch out - Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. ## Not in this build - Scraping, hidden browsers or unofficial endpoints. - Reach estimates, predictions and best time to post. - Team approvals and asset libraries. - Every network at once. ## After v1, if you want it - A second platform done to the same standard, once the first has run for a month without a duplicate - A content calendar grouped by theme, which is the planning view people actually want - First-comment hashtags as a variant option, if the platform's API supports it when you check
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Drafts, queue, attempts, lookups and snapshots. DATABASE_URL=postgres://localhost:5432/planner # Optional. Only for API publishing. The by-hand queue works without it. PLATFORM_APP_ID=an app id from the developer console # Optional · secret. Only for API publishing. Never logged. PLATFORM_APP_SECRET=app-secret-...redacted # Required. Dated limits per platform. A data edit, not a deploy. LIMITS_PATH=./config/platform-limits.json # Required. Copied from the published limit with its date. Recheck before changing. HASHTAG_QUOTA_PER_WINDOW=30 # Required. The window the quota applies to. Lookups are counted against it. HASHTAG_QUOTA_WINDOW_DAYS=7 # Optional. Only where the API fetches from a URL. The link stops working after this. MEDIA_LINK_TTL_MINUTES=60 # Required. Present as a constant. There is no other value that keeps the account. SCRAPE_OR_DRIVE_BROWSER=never
# Flick · product brief ## Problem The visible social content + analytics loop is buildable, but a credible replacement needs more than the first screen. Flick earns its keep through platform APIs, distribution, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A planner that gets a post out by hand in under a minute, publishes through the official API exactly once where the account qualifies, spends its research quota deliberately, and never shows a number it cannot date. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node.js 22 - hosted database and worker - official platform OAuth credentials where available ## Explicit non-goals for v1 - Scraping, hidden browsers or unofficial endpoints. - Reach estimates, predictions and best time to post. - Team approvals and asset libraries. - Every network at once. - reliable direct publishing across networks - analytics history and attribution - team approvals and asset workflows - protection from API and policy changes - calendar-provider edge cases and timezone correctness ## Success criteria - Limit violations are confirmed flagged from dated data - By-hand posting is confirmed complete with no platform access - A timeout after publish is confirmed never to post twice - Hashtag lookups are confirmed counted, cached and refused when spent - Every metric is confirmed to carry its fetch time
# Build brief · Flick The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a content planner that drafts, previews per platform, and publishes only through the official APIs to replace Flick. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Every network publishes rules about how software may post, read and research on its behalf, and the tools that ignore them are the ones whose users wake up to a suspended account. So this build talks to a platform only through its official API, with the permissions and the account type that API requires, and stays within its published limits. Where no API exists for what you want, the tool does not scrape a page or drive a hidden browser; it prepares the post and hands it to you to publish by hand, which is slower and keeps the account. And the analytics report only what the platform returned about your own posts, dated when it was fetched. There is no reach estimate, no engagement prediction and no best time to post derived from a dozen data points, because a number with no basis is worse than no number. ### Stack (fixed, do not substitute) - Runtime: TypeScript with PostgreSQL and a database-backed job table · scheduled publishing needs a durable queue, and a second queue system is another thing to run - Platforms: Official APIs only, with per-platform limits stored as data with a checked date · limits and formats change, and a hard-coded one is wrong within a year - Media: Local storage, with an expiring public link only where an API fetches from a URL · check whether your platform accepts an upload or fetches from an address - Queue: A hand-posting queue as the default path · it works for every network and every account type, including ones with no API - Research: The official hashtag endpoint within its quota, plus your own post history · the quota is small and the alternative breaks the rules ### Data model (create this before Phase 1) - drafts(id, idea, body, media_ids_json, created_at, updated_at) - variants(id, draft_id, platform, caption, hashtags_json, media_order_json, limits_version) - platform_limits(platform, version, caption_max, hashtag_max, image_formats_json, carousel_max, checked_on, source_url) · data, not code - queue_items(id, variant_id, publish_at, mode, state, published_id, attempts, last_error) · mode is by_hand or api - publish_attempts(id, queue_item_id, at, container_id, result, error_json) · so a retry can check before posting again - hashtag_lookups(id, hashtag, platform, looked_up_at, hashtag_id, result_json) · counted against the weekly quota - metrics_snapshots(id, published_id, platform, fetched_at, values_json) · every figure carries the time it was fetched - A queue item is by_hand or api, and the by_hand path is complete on its own, so the tool is useful before any platform has approved anything. ### Phase 1 · Drafts and platform variants as data Build: Drafts with media, a variant per platform, and a limits table holding caption length, hashtag count, accepted image formats and carousel size per platform with a checked date and the source page. The preview flags every violation and shows the limit's date. Nothing is hard-coded. Trap: Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. Done when: A variant exceeding a stored limit is flagged in the editor with the limit and its checked date. Changing a limit in data changes the flag with no code change. A draft with several images produces a variant per platform with its own caption and order. The preview renders each variant approximately as the platform would, including truncation. Do not build yet: No queue, publishing, research or analytics. ### Phase 2 · A queue that needs no API Build: A calendar queue with reminders at publish time. The by-hand flow copies the caption, presents the media in order for download or share, opens the platform, and marks the item posted with a link pasted back. Export the queue as a calendar file. Reschedule by drag. Trap: Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. Done when: A by-hand item can be posted in under a minute from reminder to marked done, timed. The reminder fires at publish time in the user's timezone. Exporting the queue produces a calendar file that opens in a calendar application. Marking an item posted stores the pasted link for later metrics. Do not build yet: No API publishing, research or analytics. ### Phase 3 · Publishing through the official API, once Build: OAuth for one platform with the account type its publishing API requires. A worker leases due items, creates the media container, records its id, then publishes. Before any retry, check the recorded container's status. Read the platform's publishing usage endpoint and refuse to schedule past its limit. Trap: Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. Done when: An item publishes on schedule and its platform id is stored. A simulated timeout after publish does not create a second post, tested by injecting the failure. Scheduling beyond the platform's published limit is refused with the usage figure shown. Where the API fetches media from a URL, the link expires after the stated period and stops working, tested. Do not build yet: No hashtag research or analytics. ### Phase 4 · Hashtag research inside the quota Build: The official hashtag search for the platform that has one, with every lookup counted against its quota, which at the time of writing is thirty unique hashtags per seven days per user and requires an approved public content access feature. Cache results, show the remaining budget, and refuse rather than fail when it is spent. Build the rest from your own published posts and their metrics. Trap: Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. Done when: Every lookup is recorded and the remaining weekly budget is shown before a search. A repeated hashtag within the window is served from cache and not counted. A search past the budget is refused with the date it resets. Hashtag performance from your own posts is computed from stored metrics rather than estimated. Do not build yet: No analytics history or export yet. ### Phase 5 · Metrics with a date on them, and leaving Build: Fetch metrics for your own published posts on a schedule and store each fetch as a snapshot. Show every figure with its fetch time and never as a live number. Handle token expiry by pausing API items and telling the person, not by failing silently. Export drafts, queue, snapshots and lookups. Disconnect by revoking and deleting tokens. Trap: Displaying the last fetched engagement as though it were current, or comparing snapshots taken days apart as though they were a trend, turns the metrics page into fiction. Date every figure, compare only like with like, and say what was not fetched. Done when: Every metric shows its fetch time and a post with no fetch shows none rather than zero. An expired token pauses API items, leaves by-hand items running, and notifies the person. Export produces complete files for drafts, queue, snapshots and lookups. Disconnecting removes tokens and leaves stored content intact. Do not build yet: Nothing after this. ### Out of scope (and why) - Any scraping, hidden browser or unofficial endpoint, which breaks the platforms' terms and risks the account this tool exists to serve. - Reach estimates, engagement predictions and best time to post, which are numbers with no basis at a personal scale. - Team approvals and asset libraries, which are workflow products the paid tool sells to agencies. - Every network at once, since each official API has its own review, account requirements and limits, and one done properly beats six done badly. ### README must contain - That the tool talks to platforms only through official APIs and never scrapes, drives a hidden browser or exceeds a published limit - That the by-hand queue is complete on its own and works with no platform access at all - That a publish retry checks the recorded container before posting, so a timeout never posts twice - That hashtag lookups are counted against the platform's quota, cached, and refused when spent - That every metric carries the time it was fetched and nothing is presented as live or predicted
# Architecture · Flick ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with PostgreSQL and a database-backed job table | scheduled publishing needs a durable queue, and a second queue system is another thing to run | | Platforms | Official APIs only, with per-platform limits stored as data with a checked date | limits and formats change, and a hard-coded one is wrong within a year | | Media | Local storage, with an expiring public link only where an API fetches from a URL | check whether your platform accepts an upload or fetches from an address | | Queue | A hand-posting queue as the default path | it works for every network and every account type, including ones with no API | | Research | The official hashtag endpoint within its quota, plus your own post history | the quota is small and the alternative breaks the rules | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Variants | Per-platform versions checked against dated limits. | Any editor, provided limits are data with a checked date. | | Queue | The by-hand path and scheduling. | Any interface, provided by-hand posting is complete without an API. | | Publishing | Leased items, recorded containers, status before retry. | Any platform, provided a retry checks before posting. | | Research | Counted, cached lookups within quota. | Not swappable for scraping, which risks the account. | | Metrics | Dated snapshots of your own posts. | Any store, provided every figure carries its fetch time. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_URL` · required, secret · Drafts, queue, attempts, lookups and snapshots. - `PLATFORM_APP_ID` · optional · Only for API publishing. The by-hand queue works without it. - `PLATFORM_APP_SECRET` · optional, secret · Only for API publishing. Never logged. - `LIMITS_PATH` · required · Dated limits per platform. A data edit, not a deploy. - `HASHTAG_QUOTA_PER_WINDOW` · required · Copied from the published limit with its date. Recheck before changing. - `HASHTAG_QUOTA_WINDOW_DAYS` · required · The window the quota applies to. Lookups are counted against it. - `MEDIA_LINK_TTL_MINUTES` · optional · Only where the API fetches from a URL. The link stops working after this. - `SCRAPE_OR_DRIVE_BROWSER` · required · Present as a constant. There is no other value that keeps the account. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Flick product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with PostgreSQL and a database-backed job table, Official APIs only, with per-platform limits stored as data with a checked date, Local storage, with an expiring public link only where an API fetches from a URL, A hand-posting queue as the default path, The official hashtag endpoint within its quota, plus your own post history. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. - Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. - Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. - Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. - Displaying the last fetched engagement as though it were current, or comparing snapshots taken days apart as though they were a trend, turns the metrics page into fiction. Date every figure, compare only like with like, and say what was not fetched.
# Delivery milestones · Flick Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Drafts and platform variants as data One idea becomes per-platform versions checked against limits that are stored, dated and changeable. ### Steps 1. Store limits as dated data Caption, hashtags, formats, carousel size, checked date, source. Files: `config/platform-limits.json`, `src/limits.ts` 2. Flag violations in the editor With the limit and when it was checked. Files: `src/editor/validate.ts` 3. Preview with truncation What the platform will show is what matters. Files: `src/editor/preview.ts` ### Done when - [ ] Violations are flagged with the limit and its date - [ ] Limits change with a data edit - [ ] Each platform gets its own variant - [ ] Previews show truncation ### Watch out - Hard-coding a caption length or a carousel size means the tool is silently wrong the day the platform changes it, and the failure appears as a rejected post at publish time rather than in the editor. Store limits as data with the date they were checked, show that date, and make updating one a data change. ## M2 · A queue that needs no API Posting by hand is fast enough that the tool is useful with zero platform access. ### Steps 1. Make by-hand posting one flow Copy, media, open, paste link, done. Files: `src/queue/byhand.ts` 2. Remind in the user's zone A reminder an hour off is a post that misses its slot. Files: `src/queue/reminders.ts` 3. Export the queue as a calendar So the plan is visible where the person already looks. Files: `src/queue/ics.ts` ### Done when - [ ] By-hand posting takes under a minute - [ ] Reminders fire in the user's timezone - [ ] The queue exports as a calendar file - [ ] Posted links are stored ### Watch out - Building the API integration first means the tool does nothing until an app review completes, which can take weeks and may not succeed for a personal account. Build the by-hand path first and make it complete, since it works for every network. ## M3 · Publishing through the official API, once An eligible account publishes on schedule and a retry never posts twice. ### Steps 1. Lease items and record the container before publishing So a retry can check instead of guess. Files: `src/publish/worker.ts`, `src/publish/attempts.ts` 2. Check status before any retry Injected timeout after publish, one post. Files: `tests/publish-retry.test.ts` 3. Read the usage endpoint before scheduling The limit is published, and the endpoint reports where you are against it. Files: `src/publish/limits.ts` ### Done when - [ ] Scheduled items publish with ids stored - [ ] A timeout after publish never posts twice - [ ] Scheduling past the limit is refused - [ ] Expiring media links expire ### Watch out - Retrying a publish call that timed out after the platform had already posted creates a duplicate the account owner sees before you do. Record the container id before publishing, and on retry query its status rather than publishing again. For the platform built here the published limit is a hundred API-published posts in a moving twenty-four hours, and the usage endpoint exists to be read before scheduling. ## M4 · Hashtag research inside the quota Research uses the official endpoint, spends its small weekly budget deliberately, and learns from your own posts. ### Steps 1. Count and cache every lookup Thirty unique in seven days is the budget, and the cache stretches it. Files: `src/research/quota.ts`, `src/research/quota.test.ts` 2. Refuse when spent, with the reset date A refusal here beats an error at the platform. Files: `src/research/search.ts` 3. Learn from your own posts Real metrics on your own hashtags, not a guess about anyone else's. Files: `src/research/own.ts` ### Done when - [ ] Lookups are counted and the budget is shown - [ ] Repeats within the window come from cache - [ ] A spent budget refuses with a reset date - [ ] Own-post hashtag performance uses stored metrics ### Watch out - Building research on scraped hashtag pages or an unofficial endpoint breaks the platform's terms and puts the account at risk, and the alternative of burning the weekly quota on the first day leaves nothing for the rest of the week. Count every lookup, cache, and show the budget. ## M5 · Metrics with a date on them, and leaving (production only) Every figure says when it was fetched, tokens expire gracefully, and everything exports. ### Steps 1. Snapshot metrics with fetch times A figure without a date is a claim without a basis. Files: `src/metrics/snapshots.ts` 2. Pause on token expiry, keep by-hand running The queue is still useful without the API. Files: `src/publish/tokens.ts` 3. Export everything and disconnect cleanly Content stays, tokens go. Files: `src/export.ts`, `src/disconnect.ts` ### Done when - [ ] Every metric shows its fetch time - [ ] Token expiry pauses API items only - [ ] Export is complete - [ ] Disconnect keeps content and removes tokens ### Watch out - Displaying the last fetched engagement as though it were current, or comparing snapshots taken days apart as though they were a trend, turns the metrics page into fiction. Date every figure, compare only like with like, and say what was not fetched.
# Operations · Flick ## Backup Drafts and media are the work; attempts and lookups are the audit trail that keeps you inside the rules. Back up the database and media together, nightly, since a restored queue without its attempt log may republish. ## Restore Restore and confirm the attempt log survived alongside the queue, because a queue item in a publishing state with no recorded container will be retried and may post twice. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Publishing usage against the platform's limit, the remaining hashtag budget, and token expiry dates. All three fail quietly and all three are visible in the database before they fail. ## Incident checklist A duplicate post or a suspended account is the incident, and both trace to the same two causes: a retry that did not check the container, or a call outside the official API. The attempt log answers the first and the constant answers the second. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Limit violations are confirmed flagged from dated data - [ ] By-hand posting is confirmed complete with no platform access - [ ] A timeout after publish is confirmed never to post twice - [ ] Hashtag lookups are confirmed counted, cached and refused when spent - [ ] Every metric is confirmed to carry its fetch time ## Launch constraint Do not market omitted Flick capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Drafts, queue, attempts, lookups and snapshots. DATABASE_URL=postgres://localhost:5432/planner # Optional. Only for API publishing. The by-hand queue works without it. PLATFORM_APP_ID=an app id from the developer console # Optional · secret. Only for API publishing. Never logged. PLATFORM_APP_SECRET=app-secret-...redacted # Required. Dated limits per platform. A data edit, not a deploy. LIMITS_PATH=./config/platform-limits.json # Required. Copied from the published limit with its date. Recheck before changing. HASHTAG_QUOTA_PER_WINDOW=30 # Required. The window the quota applies to. Lookups are counted against it. HASHTAG_QUOTA_WINDOW_DAYS=7 # Optional. Only where the API fetches from a URL. The link stops working after this. MEDIA_LINK_TTL_MINUTES=60 # Required. Present as a constant. There is no other value that keeps the account. SCRAPE_OR_DRIVE_BROWSER=never
$ choose a build depth, inspect the files, then open the complete pack in your agent
Flick: Social tools survive because networks change APIs, permissions, formats, and anti-spam rules constantly.
xreliable direct publishing across networks
xanalytics history and attribution
xteam approvals and asset workflows
xprotection from API and policy changes
xcalendar-provider edge cases and timezone correctness
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
Flick pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| solo | $18.89/workspace | $14.84/workspace | 4 social profiles; 1 user; 30 scheduled posts/social profile/month; 30 tracked Instagram posts/month. |
| pro | $40.47/workspace | $32.38/workspace | 8 social profiles; 2 users; unlimited scheduled posts and tracked Instagram posts. |
| agency | $91.74/workspace | $74.20/workspace | 20 social profiles; 5 users; unlimited scheduled posts and tracked Instagram posts. |
free tierno free tier (7-day trial only)
billingmonthly + annual (about 20% lower on annual billing); trial auto-activates a paid plan unless cancelled
hidden costsExtra social profiles cost about $5.4/month; extra users on eligible plans cost about $10.79/month; tax may be added.
verified 2026-08-14 · source ↗
Vibecode Flick
Kinda. The core of Flick is buildable in a weekend with the prompt on this page, but there are real gaps: reliable direct publishing across networks, analytics history and attribution. Read the honest list above before committing.
How much does Flick cost?
Flick's pricing is usage-based or varies by plan · The official pricing URL is recorded, but a stable current amount was not safely recoverable in this pass. Do not publish a number until rechecked..
What do I lose by replacing Flick?
Honestly: reliable direct publishing across networks; analytics history and attribution; team approvals and asset workflows; protection from API and policy changes; calendar-provider edge cases and timezone correctness. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Flick?
Yes: Plann Free (One Instagram account gets thirty posts, five videos, AI captions, hashtag help and thin analytics each month.) The prompt is for when you want it exactly your way.