Vibecode ClosersCopy
track this build4 phases, 12 steps, beginner friendly0%The visible AI copywriting loop is buildable, but a credible replacement needs more than the first screen. ClosersCopy earns its keep through model, data, workflow, so expect a weekend or multi-day build and a narrower personal scope.
You are building a lean indie version of ClosersCopy. 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 ===== # ClosersCopy · indie build A copywriting workspace whose hard problems are refusing to manufacture the social proof that the genre invites, forcing every factual claim to be resolved before a draft can ship, and keeping a personal voice from a corpus rather than from a slider. 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 | Next.js 15 with TypeScript, single user, running on your own machine | drafts and client details are not something to put through a shared workspace for a solo build | | Storage | SQLite with append-only draft versions | regeneration is the normal operation here and overwriting loses the version you preferred | | Generation | One model call per section with the framework version recorded | a framework is the product and a prompt buried in code is not a framework | | Claims | A deterministic extractor plus a blocked ready state | a badge is ignored on the second day and a blocked state is not | | Voice | Constraints derived from your own writing samples, checked after generation | a tone slider does nothing measurable and a vocabulary check does | ## 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 workspace. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v22 or higher - [ ] **An API key for a text model** · pay per call; a draft is fractions of a penny and the ceiling is what keeps it there - Why: Generation is one call per section, and it is the only part of this build that leaves your machine or costs money. - Get it: Create a key with the provider you prefer and set a spend limit in their console as well as the ceiling here. - Verify: One section generates and the model identifier is stored on the draft version - [ ] **The current text of the consumer reviews and testimonials rule** · free - Why: This build refuses a whole category of output because of it, and the refusal list should be written from the rule rather than from a summary of it. - Get it: Read the rule text and the Commission's business guidance, note the date you read it, and record the current civil penalty figure. - Verify: The refusal patterns in the code map to named sections of the rule - [ ] **Three to five pieces of your own writing per project** · free - Why: The voice constraints are derived from these, and without them the output is generic marketing language with your product name in it. - Get it: Collect writing you were happy with: emails, posts, an about page. - Verify: Constraints are derived and differ between two different sample sets - [ ] **The claim patterns the extractor recognises** · free - Why: The extractor is deterministic on purpose, which means its coverage is exactly the list you wrote, so the list is a design decision rather than an implementation detail. - Get it: Write down the patterns for numbers, superlatives, guarantees, comparisons, deadlines and quoted speech. - Verify: The list is in a readable file and the fixture document produces the expected claim count ## 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: - Testimonials, reviews, ratings and case studies in any form. - A stock template library. - Automated publishing to advertising platforms. - A quality score for generated copy. - team governance and integrations - vendor-managed prompt and quality tuning - proprietary models or classifiers - brand-trained workflows If one of those is essential to you, that is the reason to keep paying for ClosersCopy, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · ClosersCopy 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 copywriting workspace with reusable frameworks, versioned drafts and a claims check that runs before anything is marked ready to replace ClosersCopy. 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 Sales copy is the genre where a language model will invent a customer, a statistic and a deadline in the same paragraph, and the first two of those are regulated in the United States. The Federal Trade Commission's rule on the use of consumer reviews and testimonials, 16 CFR Part 465, took effect on 21 October 2024. It prohibits creating, buying, selling or disseminating reviews or testimonials that misrepresent that they were written by someone who does not exist, which the Commission's own guidance applies to AI-generated fake reviews, or by someone who never had the experience described. Section 465.5 requires a clear and conspicuous disclosure when a testimonial comes from a company insider. Knowing violations carry civil penalties, and the maximum amount is adjusted, so look it up rather than repeating a figure from a blog post. So this generator never writes a testimonial, a customer quote, a review, a star rating, a named case study or a 'what our users say' block. Those are inputs you supply with an attribution, or they do not appear at all. If the model returns testimonial-shaped text anyway, the draft is refused, not edited down. The second rule is about the rest of it: every number, superlative, guarantee, comparison and deadline in a draft is flagged and has to be resolved against a source you name before the draft can be marked ready. An unresolved claim is not a warning badge, it is a blocked state. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 with TypeScript, single user, running on your own machine · drafts and client details are not something to put through a shared workspace for a solo build - Storage: SQLite with append-only draft versions · regeneration is the normal operation here and overwriting loses the version you preferred - Generation: One model call per section with the framework version recorded · a framework is the product and a prompt buried in code is not a framework - Claims: A deterministic extractor plus a blocked ready state · a badge is ignored on the second day and a blocked state is not - Voice: Constraints derived from your own writing samples, checked after generation · a tone slider does nothing measurable and a vocabulary check does ### Data model (create this before Phase 1) - frameworks(id, name, description, created_at) - framework_versions(id, framework_id, version, sections_json, published_at) · immutable, and every draft records which one it used - projects(id, name, audience, offer, created_at) · the brief, kept separate from the copy - drafts(id, project_id, framework_version_id, title, created_at, state) · state is writing, blocked or ready - draft_versions(id, draft_id, ordinal, body_md, model_id, prompt_version, created_at, note) · append-only, never updated - claims(id, draft_version_id, kind, sentence, offset, state, source_url, source_note, resolved_at) · kind is number, superlative, guarantee, comparison, deadline or quoted_speech - voice_samples(id, project_id, body, words, banned_terms_json) · your own writing, not a personality setting - A draft moves to ready only when every claim row on its latest version is resolved. That is a query, not a checkbox, and it is the only path to the ready state. ### Phase 1 · A framework is data, not a prompt in a file Build: Model a framework as an ordered list of named sections, each with a purpose, an instruction, a length target and an optional example. Publishing a framework writes an immutable version row. Editing a published framework starts a new version. A draft stores the version it was made from. Trap: Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. Done when: Publishing a framework twice produces two version rows and the first is unchanged, verified by hash. A draft records its framework version and regenerating it uses that version unless the user explicitly upgrades, tested. A framework section with an empty instruction is refused at publish with the section named. The framework editor and the generator read the same schema, so a section kind cannot exist in one and not the other. Do not build yet: No generation, versioning of drafts, claims checking or voice work yet. ### Phase 2 · Drafts that accumulate instead of overwrite Build: Store each generation and each manual save as a new draft version row with the model identifier and prompt version. Show a word-level difference between any two versions. Let a version be restored by creating a new version from it rather than by rewinding. Trap: Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. Done when: No code path updates a draft version row, enforced by a database trigger and tested. Regenerating produces a new version and the previous body is byte-identical to what it was, asserted. A word-level difference between any two versions renders correctly on a fixture pair. Restoring an older version creates a new version whose body matches the restored one exactly. Do not build yet: No claims checking, voice constraints, export or cost ceiling yet. ### Phase 3 · The claims check, and the shapes that are refused outright Build: After each generation, scan the body for sentences containing numbers and percentages, superlatives, guarantee language, comparisons to named competitors, time-pressure phrases and quoted speech. Write a claim row per sentence. Separately detect testimonial and review shapes and refuse the whole generation with the offending sentence shown. Trap: Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. Done when: A generation containing a quoted customer sentence, a star rating or a named case study is refused and no draft version is written, tested with fixtures for each shape. Every sentence containing a digit, a superlative or guarantee language produces a claim row, tested against a fixture document with a known expected count. A draft cannot enter the ready state while any claim on its latest version is unresolved, enforced by the state transition and tested. Resolving a claim requires either a source URL or a written note, and an empty resolution is refused. Do not build yet: No voice constraints, export, cost ceiling or deletion yet. ### Phase 4 · Voice from your own writing Build: Take three to five pieces of your own writing per project. Derive from them a banned-term list, a preferred-term list, a sentence-length distribution and a reading-level band. Pass the constraints into the prompt, then check the output against them and report every violation with its sentence. Trap: A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. Done when: Generating without any voice samples is allowed but reports that no constraints were applied, so the difference is visible. A banned term appearing in generated output is reported with the sentence and the draft cannot be marked ready until it is removed or the term is unbanned deliberately. The reading level and mean sentence length of the output are computed and shown against the sample band. Changing the sample set changes the derived constraints, tested by comparing two sample sets. Do not build yet: No export, cost ceiling or deletion yet. ### Phase 5 · Getting copy out, and keeping the bill small Build: Export a draft as Markdown with a front matter block naming the framework version, model identifier, prompt version and the resolved claims with their sources. Export a whole project as a folder. Track spend per generation against a monthly ceiling. Deleting a project removes its drafts, versions, claims and voice samples in one transaction. Trap: Exporting only the final text separates the copy from the sources that were used to resolve its claims, so six months later nobody can tell whether the forty percent figure in the headline was checked or invented, which is precisely the record the rule zero exists to create. Put the resolved claims and their sources in the exported file. Done when: An exported Markdown file contains the framework version, model identifier, prompt version and every resolved claim with its source, checked against a real draft. Exporting a project produces a folder that reads correctly without this application. Reaching the monthly ceiling blocks generation with the figure shown and leaves editing, claim resolution and export working. Deleting a project removes every related row in one transaction, verified by querying each table afterwards. Do not build yet: Nothing after this. ### Out of scope (and why) - Generating testimonials, reviews, ratings or named case studies in any form, because the rule that covers them is a trade regulation with civil penalties attached and no build shortcut is worth that. - A stock library of proven templates, which is a licensing and maintenance commitment rather than a weekend of code and is most of what the lifetime deal is selling. - Automated publishing to advertising platforms, each of which has its own policy review that your copy has to pass on its own terms. - A quality score for generated copy, which would be a number with no basis until you have conversion data, and by then you have the conversion data. ### README must contain - That the generator never produces testimonials, reviews or case studies, with the rule and its effective date named - That a draft cannot be marked ready while any extracted claim is unresolved, and what counts as a resolution - That draft versions are append-only and how to compare and restore them - That voice constraints come from your own writing samples and are checked after generation, not requested in a prompt - That exports carry the framework version, model identifier and resolved claims with their sources ===== AGENTS.md ===== # Agent instructions · ClosersCopy indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js 15 with TypeScript, single user, running on your own machine, SQLite with append-only draft versions, One model call per section with the framework version recorded, A deterministic extractor plus a blocked ready state, Constraints derived from your own writing samples, checked after generation. 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 - Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. - Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. - Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. - A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. ===== BUILD_PLAN.md ===== # Build plan · ClosersCopy A copywriting workspace whose hard problems are refusing to manufacture the social proof that the genre invites, forcing every factual claim to be resolved before a draft can ship, and keeping a personal voice from a corpus rather than from a slider. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A framework is data, not a prompt in a file Frameworks are versioned ordered sections with their own instructions, and every draft records the version it used. ### Steps 1. Model sections with purpose, instruction and length A framework you can read is a framework you can fix. Files: `src/frameworks/schema.ts` 2. Freeze a framework on publish So a draft from March is still explicable in December. Files: `src/frameworks/publish.ts`, `tests/framework-version.test.ts` 3. Stamp the version on every draft And require an explicit action to move a draft to a newer one. Files: `src/drafts/create.ts` ### Done when - [ ] Two publishes produce two immutable versions - [ ] A draft records and reuses its framework version - [ ] An empty section instruction is refused at publish - [ ] Editor and generator share one schema ### Watch out - Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. ## Phase 2 · Drafts that accumulate instead of overwrite Every generation and every edit adds a version, and any two versions can be compared. ### Steps 1. Append versions, never update Enforced in the schema so it cannot be forgotten in a route. Files: `src/db/schema.sql`, `tests/append-only.test.ts` 2. Record model identifier and prompt version per version The only way to explain a quality change later. Files: `src/drafts/generate.ts` 3. Diff at word level and restore by appending A linear history is one you can reason about. Files: `src/drafts/diff.ts`, `src/drafts/restore.ts` ### Done when - [ ] Draft version updates are refused by the database - [ ] Regeneration leaves the previous body untouched - [ ] Word-level diff renders on a fixture pair - [ ] Restore appends rather than rewinds ### Watch out - Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. ## Phase 3 · The claims check, and the shapes that are refused outright Factual claims are extracted deterministically and block the ready state until resolved, and testimonial-shaped output is rejected. ### Steps 1. Extract claims deterministically from a readable pattern list So the same draft always produces the same rows. Files: `src/claims/extract.ts`, `fixtures/claims.md` 2. Refuse testimonial and review shapes at generation Refuse the generation, do not edit the sentence out. Files: `src/claims/refuse.ts`, `tests/refuse.test.ts` 3. Gate the ready state on a query, not a checkbox The state transition runs the query itself. Files: `src/drafts/state.ts`, `tests/ready-gate.test.ts` ### Done when - [ ] Testimonial-shaped output is refused with the sentence shown - [ ] Claim rows match the expected count on a fixture document - [ ] Ready is impossible while a claim is unresolved - [ ] An empty resolution is refused ### Watch out - Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. ## Phase 4 · Voice from your own writing Tone is a set of measurable constraints derived from samples you supply, checked after generation. ### Steps 1. Derive constraints from samples, not from a slider Banned terms, preferred terms, sentence length, reading level. Files: `src/voice/derive.ts`, `tests/voice.test.ts` 2. Check output against the constraints after generation The prompt is a request; the check is the enforcement. Files: `src/voice/check.ts` 3. Report violations by sentence and block ready A list of offending sentences, not a score. Files: `src/voice/report.ts` ### Done when - [ ] Generation with no samples says so explicitly - [ ] A banned term blocks the ready state with its sentence shown - [ ] Reading level and sentence length are shown against the band - [ ] Different sample sets produce different constraints ### Watch out - A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. ## Not in this build - Testimonials, reviews, ratings and case studies in any form. - A stock template library. - Automated publishing to advertising platforms. - A quality score for generated copy. ## After v1, if you want it - A per-project claim library, so a figure checked once is reusable with its source attached - A regression set of briefs run against a new model before you switch, compared by the claim and voice checks - An import path for copy written elsewhere, so it goes through the same claims check before it ships ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Frameworks, projects, drafts, versions, claims and voice samples. DATABASE_PATH=./data/copy.db # Required · secret. The text model. Give it a provider-side spend limit too. MODEL_API_KEY=sk-...redacted # Required. Stored on every draft version, because output quality changes with it. MODEL_ID=a text model identifier # Required. Generation stops here. Editing, claim resolution and export continue. MONTHLY_COST_CEILING=5.00 # Required. The readable pattern list. Extending it is the normal maintenance of this app. CLAIM_PATTERNS_PATH=./config/claim-patterns.json # Required. Markdown with front matter carrying framework, model and resolved claims. EXPORT_DIR=./exports
You are building a lean indie version of ClosersCopy. 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 ===== # ClosersCopy · indie build A copywriting workspace whose hard problems are refusing to manufacture the social proof that the genre invites, forcing every factual claim to be resolved before a draft can ship, and keeping a personal voice from a corpus rather than from a slider. 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 | Next.js 15 with TypeScript, single user, running on your own machine | drafts and client details are not something to put through a shared workspace for a solo build | | Storage | SQLite with append-only draft versions | regeneration is the normal operation here and overwriting loses the version you preferred | | Generation | One model call per section with the framework version recorded | a framework is the product and a prompt buried in code is not a framework | | Claims | A deterministic extractor plus a blocked ready state | a badge is ignored on the second day and a blocked state is not | | Voice | Constraints derived from your own writing samples, checked after generation | a tone slider does nothing measurable and a vocabulary check does | ## 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 workspace. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v22 or higher - [ ] **An API key for a text model** · pay per call; a draft is fractions of a penny and the ceiling is what keeps it there - Why: Generation is one call per section, and it is the only part of this build that leaves your machine or costs money. - Get it: Create a key with the provider you prefer and set a spend limit in their console as well as the ceiling here. - Verify: One section generates and the model identifier is stored on the draft version - [ ] **The current text of the consumer reviews and testimonials rule** · free - Why: This build refuses a whole category of output because of it, and the refusal list should be written from the rule rather than from a summary of it. - Get it: Read the rule text and the Commission's business guidance, note the date you read it, and record the current civil penalty figure. - Verify: The refusal patterns in the code map to named sections of the rule - [ ] **Three to five pieces of your own writing per project** · free - Why: The voice constraints are derived from these, and without them the output is generic marketing language with your product name in it. - Get it: Collect writing you were happy with: emails, posts, an about page. - Verify: Constraints are derived and differ between two different sample sets - [ ] **The claim patterns the extractor recognises** · free - Why: The extractor is deterministic on purpose, which means its coverage is exactly the list you wrote, so the list is a design decision rather than an implementation detail. - Get it: Write down the patterns for numbers, superlatives, guarantees, comparisons, deadlines and quoted speech. - Verify: The list is in a readable file and the fixture document produces the expected claim count ## 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: - Testimonials, reviews, ratings and case studies in any form. - A stock template library. - Automated publishing to advertising platforms. - A quality score for generated copy. - team governance and integrations - vendor-managed prompt and quality tuning - proprietary models or classifiers - brand-trained workflows If one of those is essential to you, that is the reason to keep paying for ClosersCopy, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · ClosersCopy 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 copywriting workspace with reusable frameworks, versioned drafts and a claims check that runs before anything is marked ready to replace ClosersCopy. 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 Sales copy is the genre where a language model will invent a customer, a statistic and a deadline in the same paragraph, and the first two of those are regulated in the United States. The Federal Trade Commission's rule on the use of consumer reviews and testimonials, 16 CFR Part 465, took effect on 21 October 2024. It prohibits creating, buying, selling or disseminating reviews or testimonials that misrepresent that they were written by someone who does not exist, which the Commission's own guidance applies to AI-generated fake reviews, or by someone who never had the experience described. Section 465.5 requires a clear and conspicuous disclosure when a testimonial comes from a company insider. Knowing violations carry civil penalties, and the maximum amount is adjusted, so look it up rather than repeating a figure from a blog post. So this generator never writes a testimonial, a customer quote, a review, a star rating, a named case study or a 'what our users say' block. Those are inputs you supply with an attribution, or they do not appear at all. If the model returns testimonial-shaped text anyway, the draft is refused, not edited down. The second rule is about the rest of it: every number, superlative, guarantee, comparison and deadline in a draft is flagged and has to be resolved against a source you name before the draft can be marked ready. An unresolved claim is not a warning badge, it is a blocked state. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 with TypeScript, single user, running on your own machine · drafts and client details are not something to put through a shared workspace for a solo build - Storage: SQLite with append-only draft versions · regeneration is the normal operation here and overwriting loses the version you preferred - Generation: One model call per section with the framework version recorded · a framework is the product and a prompt buried in code is not a framework - Claims: A deterministic extractor plus a blocked ready state · a badge is ignored on the second day and a blocked state is not - Voice: Constraints derived from your own writing samples, checked after generation · a tone slider does nothing measurable and a vocabulary check does ### Data model (create this before Phase 1) - frameworks(id, name, description, created_at) - framework_versions(id, framework_id, version, sections_json, published_at) · immutable, and every draft records which one it used - projects(id, name, audience, offer, created_at) · the brief, kept separate from the copy - drafts(id, project_id, framework_version_id, title, created_at, state) · state is writing, blocked or ready - draft_versions(id, draft_id, ordinal, body_md, model_id, prompt_version, created_at, note) · append-only, never updated - claims(id, draft_version_id, kind, sentence, offset, state, source_url, source_note, resolved_at) · kind is number, superlative, guarantee, comparison, deadline or quoted_speech - voice_samples(id, project_id, body, words, banned_terms_json) · your own writing, not a personality setting - A draft moves to ready only when every claim row on its latest version is resolved. That is a query, not a checkbox, and it is the only path to the ready state. ### Phase 1 · A framework is data, not a prompt in a file Build: Model a framework as an ordered list of named sections, each with a purpose, an instruction, a length target and an optional example. Publishing a framework writes an immutable version row. Editing a published framework starts a new version. A draft stores the version it was made from. Trap: Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. Done when: Publishing a framework twice produces two version rows and the first is unchanged, verified by hash. A draft records its framework version and regenerating it uses that version unless the user explicitly upgrades, tested. A framework section with an empty instruction is refused at publish with the section named. The framework editor and the generator read the same schema, so a section kind cannot exist in one and not the other. Do not build yet: No generation, versioning of drafts, claims checking or voice work yet. ### Phase 2 · Drafts that accumulate instead of overwrite Build: Store each generation and each manual save as a new draft version row with the model identifier and prompt version. Show a word-level difference between any two versions. Let a version be restored by creating a new version from it rather than by rewinding. Trap: Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. Done when: No code path updates a draft version row, enforced by a database trigger and tested. Regenerating produces a new version and the previous body is byte-identical to what it was, asserted. A word-level difference between any two versions renders correctly on a fixture pair. Restoring an older version creates a new version whose body matches the restored one exactly. Do not build yet: No claims checking, voice constraints, export or cost ceiling yet. ### Phase 3 · The claims check, and the shapes that are refused outright Build: After each generation, scan the body for sentences containing numbers and percentages, superlatives, guarantee language, comparisons to named competitors, time-pressure phrases and quoted speech. Write a claim row per sentence. Separately detect testimonial and review shapes and refuse the whole generation with the offending sentence shown. Trap: Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. Done when: A generation containing a quoted customer sentence, a star rating or a named case study is refused and no draft version is written, tested with fixtures for each shape. Every sentence containing a digit, a superlative or guarantee language produces a claim row, tested against a fixture document with a known expected count. A draft cannot enter the ready state while any claim on its latest version is unresolved, enforced by the state transition and tested. Resolving a claim requires either a source URL or a written note, and an empty resolution is refused. Do not build yet: No voice constraints, export, cost ceiling or deletion yet. ### Phase 4 · Voice from your own writing Build: Take three to five pieces of your own writing per project. Derive from them a banned-term list, a preferred-term list, a sentence-length distribution and a reading-level band. Pass the constraints into the prompt, then check the output against them and report every violation with its sentence. Trap: A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. Done when: Generating without any voice samples is allowed but reports that no constraints were applied, so the difference is visible. A banned term appearing in generated output is reported with the sentence and the draft cannot be marked ready until it is removed or the term is unbanned deliberately. The reading level and mean sentence length of the output are computed and shown against the sample band. Changing the sample set changes the derived constraints, tested by comparing two sample sets. Do not build yet: No export, cost ceiling or deletion yet. ### Phase 5 · Getting copy out, and keeping the bill small Build: Export a draft as Markdown with a front matter block naming the framework version, model identifier, prompt version and the resolved claims with their sources. Export a whole project as a folder. Track spend per generation against a monthly ceiling. Deleting a project removes its drafts, versions, claims and voice samples in one transaction. Trap: Exporting only the final text separates the copy from the sources that were used to resolve its claims, so six months later nobody can tell whether the forty percent figure in the headline was checked or invented, which is precisely the record the rule zero exists to create. Put the resolved claims and their sources in the exported file. Done when: An exported Markdown file contains the framework version, model identifier, prompt version and every resolved claim with its source, checked against a real draft. Exporting a project produces a folder that reads correctly without this application. Reaching the monthly ceiling blocks generation with the figure shown and leaves editing, claim resolution and export working. Deleting a project removes every related row in one transaction, verified by querying each table afterwards. Do not build yet: Nothing after this. ### Out of scope (and why) - Generating testimonials, reviews, ratings or named case studies in any form, because the rule that covers them is a trade regulation with civil penalties attached and no build shortcut is worth that. - A stock library of proven templates, which is a licensing and maintenance commitment rather than a weekend of code and is most of what the lifetime deal is selling. - Automated publishing to advertising platforms, each of which has its own policy review that your copy has to pass on its own terms. - A quality score for generated copy, which would be a number with no basis until you have conversion data, and by then you have the conversion data. ### README must contain - That the generator never produces testimonials, reviews or case studies, with the rule and its effective date named - That a draft cannot be marked ready while any extracted claim is unresolved, and what counts as a resolution - That draft versions are append-only and how to compare and restore them - That voice constraints come from your own writing samples and are checked after generation, not requested in a prompt - That exports carry the framework version, model identifier and resolved claims with their sources ===== AGENTS.md ===== # Agent instructions · ClosersCopy indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js 15 with TypeScript, single user, running on your own machine, SQLite with append-only draft versions, One model call per section with the framework version recorded, A deterministic extractor plus a blocked ready state, Constraints derived from your own writing samples, checked after generation. 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 - Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. - Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. - Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. - A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. ===== BUILD_PLAN.md ===== # Build plan · ClosersCopy A copywriting workspace whose hard problems are refusing to manufacture the social proof that the genre invites, forcing every factual claim to be resolved before a draft can ship, and keeping a personal voice from a corpus rather than from a slider. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A framework is data, not a prompt in a file Frameworks are versioned ordered sections with their own instructions, and every draft records the version it used. ### Steps 1. Model sections with purpose, instruction and length A framework you can read is a framework you can fix. Files: `src/frameworks/schema.ts` 2. Freeze a framework on publish So a draft from March is still explicable in December. Files: `src/frameworks/publish.ts`, `tests/framework-version.test.ts` 3. Stamp the version on every draft And require an explicit action to move a draft to a newer one. Files: `src/drafts/create.ts` ### Done when - [ ] Two publishes produce two immutable versions - [ ] A draft records and reuses its framework version - [ ] An empty section instruction is refused at publish - [ ] Editor and generator share one schema ### Watch out - Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. ## Phase 2 · Drafts that accumulate instead of overwrite Every generation and every edit adds a version, and any two versions can be compared. ### Steps 1. Append versions, never update Enforced in the schema so it cannot be forgotten in a route. Files: `src/db/schema.sql`, `tests/append-only.test.ts` 2. Record model identifier and prompt version per version The only way to explain a quality change later. Files: `src/drafts/generate.ts` 3. Diff at word level and restore by appending A linear history is one you can reason about. Files: `src/drafts/diff.ts`, `src/drafts/restore.ts` ### Done when - [ ] Draft version updates are refused by the database - [ ] Regeneration leaves the previous body untouched - [ ] Word-level diff renders on a fixture pair - [ ] Restore appends rather than rewinds ### Watch out - Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. ## Phase 3 · The claims check, and the shapes that are refused outright Factual claims are extracted deterministically and block the ready state until resolved, and testimonial-shaped output is rejected. ### Steps 1. Extract claims deterministically from a readable pattern list So the same draft always produces the same rows. Files: `src/claims/extract.ts`, `fixtures/claims.md` 2. Refuse testimonial and review shapes at generation Refuse the generation, do not edit the sentence out. Files: `src/claims/refuse.ts`, `tests/refuse.test.ts` 3. Gate the ready state on a query, not a checkbox The state transition runs the query itself. Files: `src/drafts/state.ts`, `tests/ready-gate.test.ts` ### Done when - [ ] Testimonial-shaped output is refused with the sentence shown - [ ] Claim rows match the expected count on a fixture document - [ ] Ready is impossible while a claim is unresolved - [ ] An empty resolution is refused ### Watch out - Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. ## Phase 4 · Voice from your own writing Tone is a set of measurable constraints derived from samples you supply, checked after generation. ### Steps 1. Derive constraints from samples, not from a slider Banned terms, preferred terms, sentence length, reading level. Files: `src/voice/derive.ts`, `tests/voice.test.ts` 2. Check output against the constraints after generation The prompt is a request; the check is the enforcement. Files: `src/voice/check.ts` 3. Report violations by sentence and block ready A list of offending sentences, not a score. Files: `src/voice/report.ts` ### Done when - [ ] Generation with no samples says so explicitly - [ ] A banned term blocks the ready state with its sentence shown - [ ] Reading level and sentence length are shown against the band - [ ] Different sample sets produce different constraints ### Watch out - A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. ## Not in this build - Testimonials, reviews, ratings and case studies in any form. - A stock template library. - Automated publishing to advertising platforms. - A quality score for generated copy. ## After v1, if you want it - A per-project claim library, so a figure checked once is reusable with its source attached - A regression set of briefs run against a new model before you switch, compared by the claim and voice checks - An import path for copy written elsewhere, so it goes through the same claims check before it ships ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Frameworks, projects, drafts, versions, claims and voice samples. DATABASE_PATH=./data/copy.db # Required · secret. The text model. Give it a provider-side spend limit too. MODEL_API_KEY=sk-...redacted # Required. Stored on every draft version, because output quality changes with it. MODEL_ID=a text model identifier # Required. Generation stops here. Editing, claim resolution and export continue. MONTHLY_COST_CEILING=5.00 # Required. The readable pattern list. Extending it is the normal maintenance of this app. CLAIM_PATTERNS_PATH=./config/claim-patterns.json # Required. Markdown with front matter carrying framework, model and resolved claims. EXPORT_DIR=./exports
You are building a production product version of ClosersCopy. 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 ===== # ClosersCopy · product brief ## Problem The visible AI copywriting loop is buildable, but a credible replacement needs more than the first screen. ClosersCopy earns its keep through model, data, workflow, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A copy workspace that will not invent a customer, will not let a draft reach ready with an unchecked number in it, keeps every version you generated, and exports the copy with the record of how its claims were resolved. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - OpenAI API key in .env - Node.js 22 - SQLite database ## Explicit non-goals for v1 - Testimonials, reviews, ratings and case studies in any form. - A stock template library. - Automated publishing to advertising platforms. - A quality score for generated copy. - team governance and integrations - vendor-managed prompt and quality tuning - proprietary models or classifiers - brand-trained workflows ## Success criteria - Testimonial-shaped generations are confirmed refused with no version written - The ready state is confirmed impossible with an unresolved claim - Draft version updates are confirmed refused by the database - Banned terms from voice samples are confirmed to block ready - Exports are confirmed to carry framework, model and resolved claims ===== BRIEF.md ===== # Build brief · ClosersCopy 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 copywriting workspace with reusable frameworks, versioned drafts and a claims check that runs before anything is marked ready to replace ClosersCopy. 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 Sales copy is the genre where a language model will invent a customer, a statistic and a deadline in the same paragraph, and the first two of those are regulated in the United States. The Federal Trade Commission's rule on the use of consumer reviews and testimonials, 16 CFR Part 465, took effect on 21 October 2024. It prohibits creating, buying, selling or disseminating reviews or testimonials that misrepresent that they were written by someone who does not exist, which the Commission's own guidance applies to AI-generated fake reviews, or by someone who never had the experience described. Section 465.5 requires a clear and conspicuous disclosure when a testimonial comes from a company insider. Knowing violations carry civil penalties, and the maximum amount is adjusted, so look it up rather than repeating a figure from a blog post. So this generator never writes a testimonial, a customer quote, a review, a star rating, a named case study or a 'what our users say' block. Those are inputs you supply with an attribution, or they do not appear at all. If the model returns testimonial-shaped text anyway, the draft is refused, not edited down. The second rule is about the rest of it: every number, superlative, guarantee, comparison and deadline in a draft is flagged and has to be resolved against a source you name before the draft can be marked ready. An unresolved claim is not a warning badge, it is a blocked state. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 with TypeScript, single user, running on your own machine · drafts and client details are not something to put through a shared workspace for a solo build - Storage: SQLite with append-only draft versions · regeneration is the normal operation here and overwriting loses the version you preferred - Generation: One model call per section with the framework version recorded · a framework is the product and a prompt buried in code is not a framework - Claims: A deterministic extractor plus a blocked ready state · a badge is ignored on the second day and a blocked state is not - Voice: Constraints derived from your own writing samples, checked after generation · a tone slider does nothing measurable and a vocabulary check does ### Data model (create this before Phase 1) - frameworks(id, name, description, created_at) - framework_versions(id, framework_id, version, sections_json, published_at) · immutable, and every draft records which one it used - projects(id, name, audience, offer, created_at) · the brief, kept separate from the copy - drafts(id, project_id, framework_version_id, title, created_at, state) · state is writing, blocked or ready - draft_versions(id, draft_id, ordinal, body_md, model_id, prompt_version, created_at, note) · append-only, never updated - claims(id, draft_version_id, kind, sentence, offset, state, source_url, source_note, resolved_at) · kind is number, superlative, guarantee, comparison, deadline or quoted_speech - voice_samples(id, project_id, body, words, banned_terms_json) · your own writing, not a personality setting - A draft moves to ready only when every claim row on its latest version is resolved. That is a query, not a checkbox, and it is the only path to the ready state. ### Phase 1 · A framework is data, not a prompt in a file Build: Model a framework as an ordered list of named sections, each with a purpose, an instruction, a length target and an optional example. Publishing a framework writes an immutable version row. Editing a published framework starts a new version. A draft stores the version it was made from. Trap: Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. Done when: Publishing a framework twice produces two version rows and the first is unchanged, verified by hash. A draft records its framework version and regenerating it uses that version unless the user explicitly upgrades, tested. A framework section with an empty instruction is refused at publish with the section named. The framework editor and the generator read the same schema, so a section kind cannot exist in one and not the other. Do not build yet: No generation, versioning of drafts, claims checking or voice work yet. ### Phase 2 · Drafts that accumulate instead of overwrite Build: Store each generation and each manual save as a new draft version row with the model identifier and prompt version. Show a word-level difference between any two versions. Let a version be restored by creating a new version from it rather than by rewinding. Trap: Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. Done when: No code path updates a draft version row, enforced by a database trigger and tested. Regenerating produces a new version and the previous body is byte-identical to what it was, asserted. A word-level difference between any two versions renders correctly on a fixture pair. Restoring an older version creates a new version whose body matches the restored one exactly. Do not build yet: No claims checking, voice constraints, export or cost ceiling yet. ### Phase 3 · The claims check, and the shapes that are refused outright Build: After each generation, scan the body for sentences containing numbers and percentages, superlatives, guarantee language, comparisons to named competitors, time-pressure phrases and quoted speech. Write a claim row per sentence. Separately detect testimonial and review shapes and refuse the whole generation with the offending sentence shown. Trap: Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. Done when: A generation containing a quoted customer sentence, a star rating or a named case study is refused and no draft version is written, tested with fixtures for each shape. Every sentence containing a digit, a superlative or guarantee language produces a claim row, tested against a fixture document with a known expected count. A draft cannot enter the ready state while any claim on its latest version is unresolved, enforced by the state transition and tested. Resolving a claim requires either a source URL or a written note, and an empty resolution is refused. Do not build yet: No voice constraints, export, cost ceiling or deletion yet. ### Phase 4 · Voice from your own writing Build: Take three to five pieces of your own writing per project. Derive from them a banned-term list, a preferred-term list, a sentence-length distribution and a reading-level band. Pass the constraints into the prompt, then check the output against them and report every violation with its sentence. Trap: A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. Done when: Generating without any voice samples is allowed but reports that no constraints were applied, so the difference is visible. A banned term appearing in generated output is reported with the sentence and the draft cannot be marked ready until it is removed or the term is unbanned deliberately. The reading level and mean sentence length of the output are computed and shown against the sample band. Changing the sample set changes the derived constraints, tested by comparing two sample sets. Do not build yet: No export, cost ceiling or deletion yet. ### Phase 5 · Getting copy out, and keeping the bill small Build: Export a draft as Markdown with a front matter block naming the framework version, model identifier, prompt version and the resolved claims with their sources. Export a whole project as a folder. Track spend per generation against a monthly ceiling. Deleting a project removes its drafts, versions, claims and voice samples in one transaction. Trap: Exporting only the final text separates the copy from the sources that were used to resolve its claims, so six months later nobody can tell whether the forty percent figure in the headline was checked or invented, which is precisely the record the rule zero exists to create. Put the resolved claims and their sources in the exported file. Done when: An exported Markdown file contains the framework version, model identifier, prompt version and every resolved claim with its source, checked against a real draft. Exporting a project produces a folder that reads correctly without this application. Reaching the monthly ceiling blocks generation with the figure shown and leaves editing, claim resolution and export working. Deleting a project removes every related row in one transaction, verified by querying each table afterwards. Do not build yet: Nothing after this. ### Out of scope (and why) - Generating testimonials, reviews, ratings or named case studies in any form, because the rule that covers them is a trade regulation with civil penalties attached and no build shortcut is worth that. - A stock library of proven templates, which is a licensing and maintenance commitment rather than a weekend of code and is most of what the lifetime deal is selling. - Automated publishing to advertising platforms, each of which has its own policy review that your copy has to pass on its own terms. - A quality score for generated copy, which would be a number with no basis until you have conversion data, and by then you have the conversion data. ### README must contain - That the generator never produces testimonials, reviews or case studies, with the rule and its effective date named - That a draft cannot be marked ready while any extracted claim is unresolved, and what counts as a resolution - That draft versions are append-only and how to compare and restore them - That voice constraints come from your own writing samples and are checked after generation, not requested in a prompt - That exports carry the framework version, model identifier and resolved claims with their sources ===== ARCHITECTURE.md ===== # Architecture · ClosersCopy ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js 15 with TypeScript, single user, running on your own machine | drafts and client details are not something to put through a shared workspace for a solo build | | Storage | SQLite with append-only draft versions | regeneration is the normal operation here and overwriting loses the version you preferred | | Generation | One model call per section with the framework version recorded | a framework is the product and a prompt buried in code is not a framework | | Claims | A deterministic extractor plus a blocked ready state | a badge is ignored on the second day and a blocked state is not | | Voice | Constraints derived from your own writing samples, checked after generation | a tone slider does nothing measurable and a vocabulary check does | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Frameworks | Versioned section definitions. | Any structure, provided drafts stamp the version they used. | | Drafts | Append-only versions, diff and restore. | Any store that refuses updates to a version row. | | Claims | Deterministic extraction, refusal shapes and the ready gate. | Not swappable for a model-based checker, which approves confidently and records nothing. | | Voice | Constraints derived from samples and post-generation checking. | Any measures, provided the check runs after generation. | | Export | Markdown with provenance and project deletion. | Any format that carries the claims and their sources. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_PATH` · required · Frameworks, projects, drafts, versions, claims and voice samples. - `MODEL_API_KEY` · required, secret · The text model. Give it a provider-side spend limit too. - `MODEL_ID` · required · Stored on every draft version, because output quality changes with it. - `MONTHLY_COST_CEILING` · required · Generation stops here. Editing, claim resolution and export continue. - `CLAIM_PATTERNS_PATH` · required · The readable pattern list. Extending it is the normal maintenance of this app. - `EXPORT_DIR` · required · Markdown with front matter carrying framework, model and resolved claims. ## 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 · ClosersCopy product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Next.js 15 with TypeScript, single user, running on your own machine, SQLite with append-only draft versions, One model call per section with the framework version recorded, A deterministic extractor plus a blocked ready state, Constraints derived from your own writing samples, checked after generation. - 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 - Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. - Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. - Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. - A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. - Exporting only the final text separates the copy from the sources that were used to resolve its claims, so six months later nobody can tell whether the forty percent figure in the headline was checked or invented, which is precisely the record the rule zero exists to create. Put the resolved claims and their sources in the exported file. ===== MILESTONES.md ===== # Delivery milestones · ClosersCopy Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A framework is data, not a prompt in a file Frameworks are versioned ordered sections with their own instructions, and every draft records the version it used. ### Steps 1. Model sections with purpose, instruction and length A framework you can read is a framework you can fix. Files: `src/frameworks/schema.ts` 2. Freeze a framework on publish So a draft from March is still explicable in December. Files: `src/frameworks/publish.ts`, `tests/framework-version.test.ts` 3. Stamp the version on every draft And require an explicit action to move a draft to a newer one. Files: `src/drafts/create.ts` ### Done when - [ ] Two publishes produce two immutable versions - [ ] A draft records and reuses its framework version - [ ] An empty section instruction is refused at publish - [ ] Editor and generator share one schema ### Watch out - Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. ## M2 · Drafts that accumulate instead of overwrite Every generation and every edit adds a version, and any two versions can be compared. ### Steps 1. Append versions, never update Enforced in the schema so it cannot be forgotten in a route. Files: `src/db/schema.sql`, `tests/append-only.test.ts` 2. Record model identifier and prompt version per version The only way to explain a quality change later. Files: `src/drafts/generate.ts` 3. Diff at word level and restore by appending A linear history is one you can reason about. Files: `src/drafts/diff.ts`, `src/drafts/restore.ts` ### Done when - [ ] Draft version updates are refused by the database - [ ] Regeneration leaves the previous body untouched - [ ] Word-level diff renders on a fixture pair - [ ] Restore appends rather than rewinds ### Watch out - Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. ## M3 · The claims check, and the shapes that are refused outright Factual claims are extracted deterministically and block the ready state until resolved, and testimonial-shaped output is rejected. ### Steps 1. Extract claims deterministically from a readable pattern list So the same draft always produces the same rows. Files: `src/claims/extract.ts`, `fixtures/claims.md` 2. Refuse testimonial and review shapes at generation Refuse the generation, do not edit the sentence out. Files: `src/claims/refuse.ts`, `tests/refuse.test.ts` 3. Gate the ready state on a query, not a checkbox The state transition runs the query itself. Files: `src/drafts/state.ts`, `tests/ready-gate.test.ts` ### Done when - [ ] Testimonial-shaped output is refused with the sentence shown - [ ] Claim rows match the expected count on a fixture document - [ ] Ready is impossible while a claim is unresolved - [ ] An empty resolution is refused ### Watch out - Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. ## M4 · Voice from your own writing Tone is a set of measurable constraints derived from samples you supply, checked after generation. ### Steps 1. Derive constraints from samples, not from a slider Banned terms, preferred terms, sentence length, reading level. Files: `src/voice/derive.ts`, `tests/voice.test.ts` 2. Check output against the constraints after generation The prompt is a request; the check is the enforcement. Files: `src/voice/check.ts` 3. Report violations by sentence and block ready A list of offending sentences, not a score. Files: `src/voice/report.ts` ### Done when - [ ] Generation with no samples says so explicitly - [ ] A banned term blocks the ready state with its sentence shown - [ ] Reading level and sentence length are shown against the band - [ ] Different sample sets produce different constraints ### Watch out - A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. ## M5 · Getting copy out, and keeping the bill small (production only) Drafts export as plain files with their provenance, spend has a ceiling, and a project can be deleted properly. ### Steps 1. Export copy together with its provenance The claims and sources travel with the text or the record is lost. Files: `src/export/draft.ts`, `tests/export.test.ts` 2. Cap monthly spend and degrade to editing Everything except generation keeps working at the ceiling. Files: `src/cost/ceiling.ts` 3. Delete a project completely in one transaction Including voice samples, which are your own writing. Files: `src/projects/delete.ts`, `tests/delete.test.ts` ### Done when - [ ] Exports carry framework, model, prompt and resolved claims - [ ] A project folder reads without the application - [ ] The ceiling blocks generation only - [ ] Project deletion leaves no rows behind ### Watch out - Exporting only the final text separates the copy from the sources that were used to resolve its claims, so six months later nobody can tell whether the forty percent figure in the headline was checked or invented, which is precisely the record the rule zero exists to create. Put the resolved claims and their sources in the exported file. ===== OPERATIONS.md ===== # Operations · ClosersCopy ## Backup The database, which holds every version and every claim resolution. The resolutions are the part you cannot recreate, because they represent a person having checked something. ## Restore Restore and confirm that drafts in the ready state still have zero unresolved claims. A restore that loses claim rows can silently promote a blocked draft, so check that query first. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Spend against the ceiling, the refusal rate for testimonial shapes, and the number of claims resolved with a note rather than a source. A rising note-only rate means the check is being waved through. ## Incident checklist The incident is a published claim that was not true. The draft versions, the claim rows and the exported front matter together show what was asserted, when, and what it was checked against. 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 - [ ] Testimonial-shaped generations are confirmed refused with no version written - [ ] The ready state is confirmed impossible with an unresolved claim - [ ] Draft version updates are confirmed refused by the database - [ ] Banned terms from voice samples are confirmed to block ready - [ ] Exports are confirmed to carry framework, model and resolved claims ## Launch constraint Do not market omitted ClosersCopy 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. Frameworks, projects, drafts, versions, claims and voice samples. DATABASE_PATH=./data/copy.db # Required · secret. The text model. Give it a provider-side spend limit too. MODEL_API_KEY=sk-...redacted # Required. Stored on every draft version, because output quality changes with it. MODEL_ID=a text model identifier # Required. Generation stops here. Editing, claim resolution and export continue. MONTHLY_COST_CEILING=5.00 # Required. The readable pattern list. Extending it is the normal maintenance of this app. CLAIM_PATTERNS_PATH=./config/claim-patterns.json # Required. Markdown with front matter carrying framework, model and resolved claims. EXPORT_DIR=./exports
# ClosersCopy · indie build A copywriting workspace whose hard problems are refusing to manufacture the social proof that the genre invites, forcing every factual claim to be resolved before a draft can ship, and keeping a personal voice from a corpus rather than from a slider. 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 | Next.js 15 with TypeScript, single user, running on your own machine | drafts and client details are not something to put through a shared workspace for a solo build | | Storage | SQLite with append-only draft versions | regeneration is the normal operation here and overwriting loses the version you preferred | | Generation | One model call per section with the framework version recorded | a framework is the product and a prompt buried in code is not a framework | | Claims | A deterministic extractor plus a blocked ready state | a badge is ignored on the second day and a blocked state is not | | Voice | Constraints derived from your own writing samples, checked after generation | a tone slider does nothing measurable and a vocabulary check does | ## 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 workspace. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v22 or higher - [ ] **An API key for a text model** · pay per call; a draft is fractions of a penny and the ceiling is what keeps it there - Why: Generation is one call per section, and it is the only part of this build that leaves your machine or costs money. - Get it: Create a key with the provider you prefer and set a spend limit in their console as well as the ceiling here. - Verify: One section generates and the model identifier is stored on the draft version - [ ] **The current text of the consumer reviews and testimonials rule** · free - Why: This build refuses a whole category of output because of it, and the refusal list should be written from the rule rather than from a summary of it. - Get it: Read the rule text and the Commission's business guidance, note the date you read it, and record the current civil penalty figure. - Verify: The refusal patterns in the code map to named sections of the rule - [ ] **Three to five pieces of your own writing per project** · free - Why: The voice constraints are derived from these, and without them the output is generic marketing language with your product name in it. - Get it: Collect writing you were happy with: emails, posts, an about page. - Verify: Constraints are derived and differ between two different sample sets - [ ] **The claim patterns the extractor recognises** · free - Why: The extractor is deterministic on purpose, which means its coverage is exactly the list you wrote, so the list is a design decision rather than an implementation detail. - Get it: Write down the patterns for numbers, superlatives, guarantees, comparisons, deadlines and quoted speech. - Verify: The list is in a readable file and the fixture document produces the expected claim count ## 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: - Testimonials, reviews, ratings and case studies in any form. - A stock template library. - Automated publishing to advertising platforms. - A quality score for generated copy. - team governance and integrations - vendor-managed prompt and quality tuning - proprietary models or classifiers - brand-trained workflows If one of those is essential to you, that is the reason to keep paying for ClosersCopy, and the README should say so rather than pretend.
# Build brief · ClosersCopy 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 copywriting workspace with reusable frameworks, versioned drafts and a claims check that runs before anything is marked ready to replace ClosersCopy. 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 Sales copy is the genre where a language model will invent a customer, a statistic and a deadline in the same paragraph, and the first two of those are regulated in the United States. The Federal Trade Commission's rule on the use of consumer reviews and testimonials, 16 CFR Part 465, took effect on 21 October 2024. It prohibits creating, buying, selling or disseminating reviews or testimonials that misrepresent that they were written by someone who does not exist, which the Commission's own guidance applies to AI-generated fake reviews, or by someone who never had the experience described. Section 465.5 requires a clear and conspicuous disclosure when a testimonial comes from a company insider. Knowing violations carry civil penalties, and the maximum amount is adjusted, so look it up rather than repeating a figure from a blog post. So this generator never writes a testimonial, a customer quote, a review, a star rating, a named case study or a 'what our users say' block. Those are inputs you supply with an attribution, or they do not appear at all. If the model returns testimonial-shaped text anyway, the draft is refused, not edited down. The second rule is about the rest of it: every number, superlative, guarantee, comparison and deadline in a draft is flagged and has to be resolved against a source you name before the draft can be marked ready. An unresolved claim is not a warning badge, it is a blocked state. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 with TypeScript, single user, running on your own machine · drafts and client details are not something to put through a shared workspace for a solo build - Storage: SQLite with append-only draft versions · regeneration is the normal operation here and overwriting loses the version you preferred - Generation: One model call per section with the framework version recorded · a framework is the product and a prompt buried in code is not a framework - Claims: A deterministic extractor plus a blocked ready state · a badge is ignored on the second day and a blocked state is not - Voice: Constraints derived from your own writing samples, checked after generation · a tone slider does nothing measurable and a vocabulary check does ### Data model (create this before Phase 1) - frameworks(id, name, description, created_at) - framework_versions(id, framework_id, version, sections_json, published_at) · immutable, and every draft records which one it used - projects(id, name, audience, offer, created_at) · the brief, kept separate from the copy - drafts(id, project_id, framework_version_id, title, created_at, state) · state is writing, blocked or ready - draft_versions(id, draft_id, ordinal, body_md, model_id, prompt_version, created_at, note) · append-only, never updated - claims(id, draft_version_id, kind, sentence, offset, state, source_url, source_note, resolved_at) · kind is number, superlative, guarantee, comparison, deadline or quoted_speech - voice_samples(id, project_id, body, words, banned_terms_json) · your own writing, not a personality setting - A draft moves to ready only when every claim row on its latest version is resolved. That is a query, not a checkbox, and it is the only path to the ready state. ### Phase 1 · A framework is data, not a prompt in a file Build: Model a framework as an ordered list of named sections, each with a purpose, an instruction, a length target and an optional example. Publishing a framework writes an immutable version row. Editing a published framework starts a new version. A draft stores the version it was made from. Trap: Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. Done when: Publishing a framework twice produces two version rows and the first is unchanged, verified by hash. A draft records its framework version and regenerating it uses that version unless the user explicitly upgrades, tested. A framework section with an empty instruction is refused at publish with the section named. The framework editor and the generator read the same schema, so a section kind cannot exist in one and not the other. Do not build yet: No generation, versioning of drafts, claims checking or voice work yet. ### Phase 2 · Drafts that accumulate instead of overwrite Build: Store each generation and each manual save as a new draft version row with the model identifier and prompt version. Show a word-level difference between any two versions. Let a version be restored by creating a new version from it rather than by rewinding. Trap: Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. Done when: No code path updates a draft version row, enforced by a database trigger and tested. Regenerating produces a new version and the previous body is byte-identical to what it was, asserted. A word-level difference between any two versions renders correctly on a fixture pair. Restoring an older version creates a new version whose body matches the restored one exactly. Do not build yet: No claims checking, voice constraints, export or cost ceiling yet. ### Phase 3 · The claims check, and the shapes that are refused outright Build: After each generation, scan the body for sentences containing numbers and percentages, superlatives, guarantee language, comparisons to named competitors, time-pressure phrases and quoted speech. Write a claim row per sentence. Separately detect testimonial and review shapes and refuse the whole generation with the offending sentence shown. Trap: Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. Done when: A generation containing a quoted customer sentence, a star rating or a named case study is refused and no draft version is written, tested with fixtures for each shape. Every sentence containing a digit, a superlative or guarantee language produces a claim row, tested against a fixture document with a known expected count. A draft cannot enter the ready state while any claim on its latest version is unresolved, enforced by the state transition and tested. Resolving a claim requires either a source URL or a written note, and an empty resolution is refused. Do not build yet: No voice constraints, export, cost ceiling or deletion yet. ### Phase 4 · Voice from your own writing Build: Take three to five pieces of your own writing per project. Derive from them a banned-term list, a preferred-term list, a sentence-length distribution and a reading-level band. Pass the constraints into the prompt, then check the output against them and report every violation with its sentence. Trap: A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. Done when: Generating without any voice samples is allowed but reports that no constraints were applied, so the difference is visible. A banned term appearing in generated output is reported with the sentence and the draft cannot be marked ready until it is removed or the term is unbanned deliberately. The reading level and mean sentence length of the output are computed and shown against the sample band. Changing the sample set changes the derived constraints, tested by comparing two sample sets. Do not build yet: No export, cost ceiling or deletion yet. ### Phase 5 · Getting copy out, and keeping the bill small Build: Export a draft as Markdown with a front matter block naming the framework version, model identifier, prompt version and the resolved claims with their sources. Export a whole project as a folder. Track spend per generation against a monthly ceiling. Deleting a project removes its drafts, versions, claims and voice samples in one transaction. Trap: Exporting only the final text separates the copy from the sources that were used to resolve its claims, so six months later nobody can tell whether the forty percent figure in the headline was checked or invented, which is precisely the record the rule zero exists to create. Put the resolved claims and their sources in the exported file. Done when: An exported Markdown file contains the framework version, model identifier, prompt version and every resolved claim with its source, checked against a real draft. Exporting a project produces a folder that reads correctly without this application. Reaching the monthly ceiling blocks generation with the figure shown and leaves editing, claim resolution and export working. Deleting a project removes every related row in one transaction, verified by querying each table afterwards. Do not build yet: Nothing after this. ### Out of scope (and why) - Generating testimonials, reviews, ratings or named case studies in any form, because the rule that covers them is a trade regulation with civil penalties attached and no build shortcut is worth that. - A stock library of proven templates, which is a licensing and maintenance commitment rather than a weekend of code and is most of what the lifetime deal is selling. - Automated publishing to advertising platforms, each of which has its own policy review that your copy has to pass on its own terms. - A quality score for generated copy, which would be a number with no basis until you have conversion data, and by then you have the conversion data. ### README must contain - That the generator never produces testimonials, reviews or case studies, with the rule and its effective date named - That a draft cannot be marked ready while any extracted claim is unresolved, and what counts as a resolution - That draft versions are append-only and how to compare and restore them - That voice constraints come from your own writing samples and are checked after generation, not requested in a prompt - That exports carry the framework version, model identifier and resolved claims with their sources
# Agent instructions · ClosersCopy indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js 15 with TypeScript, single user, running on your own machine, SQLite with append-only draft versions, One model call per section with the framework version recorded, A deterministic extractor plus a blocked ready state, Constraints derived from your own writing samples, checked after generation. 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 - Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. - Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. - Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. - A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds.
# Build plan · ClosersCopy A copywriting workspace whose hard problems are refusing to manufacture the social proof that the genre invites, forcing every factual claim to be resolved before a draft can ship, and keeping a personal voice from a corpus rather than from a slider. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A framework is data, not a prompt in a file Frameworks are versioned ordered sections with their own instructions, and every draft records the version it used. ### Steps 1. Model sections with purpose, instruction and length A framework you can read is a framework you can fix. Files: `src/frameworks/schema.ts` 2. Freeze a framework on publish So a draft from March is still explicable in December. Files: `src/frameworks/publish.ts`, `tests/framework-version.test.ts` 3. Stamp the version on every draft And require an explicit action to move a draft to a newer one. Files: `src/drafts/create.ts` ### Done when - [ ] Two publishes produce two immutable versions - [ ] A draft records and reuses its framework version - [ ] An empty section instruction is refused at publish - [ ] Editor and generator share one schema ### Watch out - Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. ## Phase 2 · Drafts that accumulate instead of overwrite Every generation and every edit adds a version, and any two versions can be compared. ### Steps 1. Append versions, never update Enforced in the schema so it cannot be forgotten in a route. Files: `src/db/schema.sql`, `tests/append-only.test.ts` 2. Record model identifier and prompt version per version The only way to explain a quality change later. Files: `src/drafts/generate.ts` 3. Diff at word level and restore by appending A linear history is one you can reason about. Files: `src/drafts/diff.ts`, `src/drafts/restore.ts` ### Done when - [ ] Draft version updates are refused by the database - [ ] Regeneration leaves the previous body untouched - [ ] Word-level diff renders on a fixture pair - [ ] Restore appends rather than rewinds ### Watch out - Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. ## Phase 3 · The claims check, and the shapes that are refused outright Factual claims are extracted deterministically and block the ready state until resolved, and testimonial-shaped output is rejected. ### Steps 1. Extract claims deterministically from a readable pattern list So the same draft always produces the same rows. Files: `src/claims/extract.ts`, `fixtures/claims.md` 2. Refuse testimonial and review shapes at generation Refuse the generation, do not edit the sentence out. Files: `src/claims/refuse.ts`, `tests/refuse.test.ts` 3. Gate the ready state on a query, not a checkbox The state transition runs the query itself. Files: `src/drafts/state.ts`, `tests/ready-gate.test.ts` ### Done when - [ ] Testimonial-shaped output is refused with the sentence shown - [ ] Claim rows match the expected count on a fixture document - [ ] Ready is impossible while a claim is unresolved - [ ] An empty resolution is refused ### Watch out - Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. ## Phase 4 · Voice from your own writing Tone is a set of measurable constraints derived from samples you supply, checked after generation. ### Steps 1. Derive constraints from samples, not from a slider Banned terms, preferred terms, sentence length, reading level. Files: `src/voice/derive.ts`, `tests/voice.test.ts` 2. Check output against the constraints after generation The prompt is a request; the check is the enforcement. Files: `src/voice/check.ts` 3. Report violations by sentence and block ready A list of offending sentences, not a score. Files: `src/voice/report.ts` ### Done when - [ ] Generation with no samples says so explicitly - [ ] A banned term blocks the ready state with its sentence shown - [ ] Reading level and sentence length are shown against the band - [ ] Different sample sets produce different constraints ### Watch out - A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. ## Not in this build - Testimonials, reviews, ratings and case studies in any form. - A stock template library. - Automated publishing to advertising platforms. - A quality score for generated copy. ## After v1, if you want it - A per-project claim library, so a figure checked once is reusable with its source attached - A regression set of briefs run against a new model before you switch, compared by the claim and voice checks - An import path for copy written elsewhere, so it goes through the same claims check before it ships
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Frameworks, projects, drafts, versions, claims and voice samples. DATABASE_PATH=./data/copy.db # Required · secret. The text model. Give it a provider-side spend limit too. MODEL_API_KEY=sk-...redacted # Required. Stored on every draft version, because output quality changes with it. MODEL_ID=a text model identifier # Required. Generation stops here. Editing, claim resolution and export continue. MONTHLY_COST_CEILING=5.00 # Required. The readable pattern list. Extending it is the normal maintenance of this app. CLAIM_PATTERNS_PATH=./config/claim-patterns.json # Required. Markdown with front matter carrying framework, model and resolved claims. EXPORT_DIR=./exports
# ClosersCopy · product brief ## Problem The visible AI copywriting loop is buildable, but a credible replacement needs more than the first screen. ClosersCopy earns its keep through model, data, workflow, so expect a weekend or multi-day build and a narrower personal scope. ## Product outcome A copy workspace that will not invent a customer, will not let a draft reach ready with an unchecked number in it, keeps every version you generated, and exports the copy with the record of how its claims were resolved. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - OpenAI API key in .env - Node.js 22 - SQLite database ## Explicit non-goals for v1 - Testimonials, reviews, ratings and case studies in any form. - A stock template library. - Automated publishing to advertising platforms. - A quality score for generated copy. - team governance and integrations - vendor-managed prompt and quality tuning - proprietary models or classifiers - brand-trained workflows ## Success criteria - Testimonial-shaped generations are confirmed refused with no version written - The ready state is confirmed impossible with an unresolved claim - Draft version updates are confirmed refused by the database - Banned terms from voice samples are confirmed to block ready - Exports are confirmed to carry framework, model and resolved claims
# Build brief · ClosersCopy 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 copywriting workspace with reusable frameworks, versioned drafts and a claims check that runs before anything is marked ready to replace ClosersCopy. 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 Sales copy is the genre where a language model will invent a customer, a statistic and a deadline in the same paragraph, and the first two of those are regulated in the United States. The Federal Trade Commission's rule on the use of consumer reviews and testimonials, 16 CFR Part 465, took effect on 21 October 2024. It prohibits creating, buying, selling or disseminating reviews or testimonials that misrepresent that they were written by someone who does not exist, which the Commission's own guidance applies to AI-generated fake reviews, or by someone who never had the experience described. Section 465.5 requires a clear and conspicuous disclosure when a testimonial comes from a company insider. Knowing violations carry civil penalties, and the maximum amount is adjusted, so look it up rather than repeating a figure from a blog post. So this generator never writes a testimonial, a customer quote, a review, a star rating, a named case study or a 'what our users say' block. Those are inputs you supply with an attribution, or they do not appear at all. If the model returns testimonial-shaped text anyway, the draft is refused, not edited down. The second rule is about the rest of it: every number, superlative, guarantee, comparison and deadline in a draft is flagged and has to be resolved against a source you name before the draft can be marked ready. An unresolved claim is not a warning badge, it is a blocked state. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 with TypeScript, single user, running on your own machine · drafts and client details are not something to put through a shared workspace for a solo build - Storage: SQLite with append-only draft versions · regeneration is the normal operation here and overwriting loses the version you preferred - Generation: One model call per section with the framework version recorded · a framework is the product and a prompt buried in code is not a framework - Claims: A deterministic extractor plus a blocked ready state · a badge is ignored on the second day and a blocked state is not - Voice: Constraints derived from your own writing samples, checked after generation · a tone slider does nothing measurable and a vocabulary check does ### Data model (create this before Phase 1) - frameworks(id, name, description, created_at) - framework_versions(id, framework_id, version, sections_json, published_at) · immutable, and every draft records which one it used - projects(id, name, audience, offer, created_at) · the brief, kept separate from the copy - drafts(id, project_id, framework_version_id, title, created_at, state) · state is writing, blocked or ready - draft_versions(id, draft_id, ordinal, body_md, model_id, prompt_version, created_at, note) · append-only, never updated - claims(id, draft_version_id, kind, sentence, offset, state, source_url, source_note, resolved_at) · kind is number, superlative, guarantee, comparison, deadline or quoted_speech - voice_samples(id, project_id, body, words, banned_terms_json) · your own writing, not a personality setting - A draft moves to ready only when every claim row on its latest version is resolved. That is a query, not a checkbox, and it is the only path to the ready state. ### Phase 1 · A framework is data, not a prompt in a file Build: Model a framework as an ordered list of named sections, each with a purpose, an instruction, a length target and an optional example. Publishing a framework writes an immutable version row. Editing a published framework starts a new version. A draft stores the version it was made from. Trap: Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. Done when: Publishing a framework twice produces two version rows and the first is unchanged, verified by hash. A draft records its framework version and regenerating it uses that version unless the user explicitly upgrades, tested. A framework section with an empty instruction is refused at publish with the section named. The framework editor and the generator read the same schema, so a section kind cannot exist in one and not the other. Do not build yet: No generation, versioning of drafts, claims checking or voice work yet. ### Phase 2 · Drafts that accumulate instead of overwrite Build: Store each generation and each manual save as a new draft version row with the model identifier and prompt version. Show a word-level difference between any two versions. Let a version be restored by creating a new version from it rather than by rewinding. Trap: Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. Done when: No code path updates a draft version row, enforced by a database trigger and tested. Regenerating produces a new version and the previous body is byte-identical to what it was, asserted. A word-level difference between any two versions renders correctly on a fixture pair. Restoring an older version creates a new version whose body matches the restored one exactly. Do not build yet: No claims checking, voice constraints, export or cost ceiling yet. ### Phase 3 · The claims check, and the shapes that are refused outright Build: After each generation, scan the body for sentences containing numbers and percentages, superlatives, guarantee language, comparisons to named competitors, time-pressure phrases and quoted speech. Write a claim row per sentence. Separately detect testimonial and review shapes and refuse the whole generation with the offending sentence shown. Trap: Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. Done when: A generation containing a quoted customer sentence, a star rating or a named case study is refused and no draft version is written, tested with fixtures for each shape. Every sentence containing a digit, a superlative or guarantee language produces a claim row, tested against a fixture document with a known expected count. A draft cannot enter the ready state while any claim on its latest version is unresolved, enforced by the state transition and tested. Resolving a claim requires either a source URL or a written note, and an empty resolution is refused. Do not build yet: No voice constraints, export, cost ceiling or deletion yet. ### Phase 4 · Voice from your own writing Build: Take three to five pieces of your own writing per project. Derive from them a banned-term list, a preferred-term list, a sentence-length distribution and a reading-level band. Pass the constraints into the prompt, then check the output against them and report every violation with its sentence. Trap: A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. Done when: Generating without any voice samples is allowed but reports that no constraints were applied, so the difference is visible. A banned term appearing in generated output is reported with the sentence and the draft cannot be marked ready until it is removed or the term is unbanned deliberately. The reading level and mean sentence length of the output are computed and shown against the sample band. Changing the sample set changes the derived constraints, tested by comparing two sample sets. Do not build yet: No export, cost ceiling or deletion yet. ### Phase 5 · Getting copy out, and keeping the bill small Build: Export a draft as Markdown with a front matter block naming the framework version, model identifier, prompt version and the resolved claims with their sources. Export a whole project as a folder. Track spend per generation against a monthly ceiling. Deleting a project removes its drafts, versions, claims and voice samples in one transaction. Trap: Exporting only the final text separates the copy from the sources that were used to resolve its claims, so six months later nobody can tell whether the forty percent figure in the headline was checked or invented, which is precisely the record the rule zero exists to create. Put the resolved claims and their sources in the exported file. Done when: An exported Markdown file contains the framework version, model identifier, prompt version and every resolved claim with its source, checked against a real draft. Exporting a project produces a folder that reads correctly without this application. Reaching the monthly ceiling blocks generation with the figure shown and leaves editing, claim resolution and export working. Deleting a project removes every related row in one transaction, verified by querying each table afterwards. Do not build yet: Nothing after this. ### Out of scope (and why) - Generating testimonials, reviews, ratings or named case studies in any form, because the rule that covers them is a trade regulation with civil penalties attached and no build shortcut is worth that. - A stock library of proven templates, which is a licensing and maintenance commitment rather than a weekend of code and is most of what the lifetime deal is selling. - Automated publishing to advertising platforms, each of which has its own policy review that your copy has to pass on its own terms. - A quality score for generated copy, which would be a number with no basis until you have conversion data, and by then you have the conversion data. ### README must contain - That the generator never produces testimonials, reviews or case studies, with the rule and its effective date named - That a draft cannot be marked ready while any extracted claim is unresolved, and what counts as a resolution - That draft versions are append-only and how to compare and restore them - That voice constraints come from your own writing samples and are checked after generation, not requested in a prompt - That exports carry the framework version, model identifier and resolved claims with their sources
# Architecture · ClosersCopy ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js 15 with TypeScript, single user, running on your own machine | drafts and client details are not something to put through a shared workspace for a solo build | | Storage | SQLite with append-only draft versions | regeneration is the normal operation here and overwriting loses the version you preferred | | Generation | One model call per section with the framework version recorded | a framework is the product and a prompt buried in code is not a framework | | Claims | A deterministic extractor plus a blocked ready state | a badge is ignored on the second day and a blocked state is not | | Voice | Constraints derived from your own writing samples, checked after generation | a tone slider does nothing measurable and a vocabulary check does | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Frameworks | Versioned section definitions. | Any structure, provided drafts stamp the version they used. | | Drafts | Append-only versions, diff and restore. | Any store that refuses updates to a version row. | | Claims | Deterministic extraction, refusal shapes and the ready gate. | Not swappable for a model-based checker, which approves confidently and records nothing. | | Voice | Constraints derived from samples and post-generation checking. | Any measures, provided the check runs after generation. | | Export | Markdown with provenance and project deletion. | Any format that carries the claims and their sources. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_PATH` · required · Frameworks, projects, drafts, versions, claims and voice samples. - `MODEL_API_KEY` · required, secret · The text model. Give it a provider-side spend limit too. - `MODEL_ID` · required · Stored on every draft version, because output quality changes with it. - `MONTHLY_COST_CEILING` · required · Generation stops here. Editing, claim resolution and export continue. - `CLAIM_PATTERNS_PATH` · required · The readable pattern list. Extending it is the normal maintenance of this app. - `EXPORT_DIR` · required · Markdown with front matter carrying framework, model and resolved claims. ## 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 · ClosersCopy product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Next.js 15 with TypeScript, single user, running on your own machine, SQLite with append-only draft versions, One model call per section with the framework version recorded, A deterministic extractor plus a blocked ready state, Constraints derived from your own writing samples, checked after generation. - 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 - Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. - Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. - Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. - A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. - Exporting only the final text separates the copy from the sources that were used to resolve its claims, so six months later nobody can tell whether the forty percent figure in the headline was checked or invented, which is precisely the record the rule zero exists to create. Put the resolved claims and their sources in the exported file.
# Delivery milestones · ClosersCopy Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A framework is data, not a prompt in a file Frameworks are versioned ordered sections with their own instructions, and every draft records the version it used. ### Steps 1. Model sections with purpose, instruction and length A framework you can read is a framework you can fix. Files: `src/frameworks/schema.ts` 2. Freeze a framework on publish So a draft from March is still explicable in December. Files: `src/frameworks/publish.ts`, `tests/framework-version.test.ts` 3. Stamp the version on every draft And require an explicit action to move a draft to a newer one. Files: `src/drafts/create.ts` ### Done when - [ ] Two publishes produce two immutable versions - [ ] A draft records and reuses its framework version - [ ] An empty section instruction is refused at publish - [ ] Editor and generator share one schema ### Watch out - Keeping the prompt text in the code means that when you improve it, every previous draft silently becomes unreproducible and you can no longer tell whether last month's better output came from a better brief or a better prompt. Version the framework as data, stamp the version on the draft, and let an old draft be regenerated against the framework it was written under. ## M2 · Drafts that accumulate instead of overwrite Every generation and every edit adds a version, and any two versions can be compared. ### Steps 1. Append versions, never update Enforced in the schema so it cannot be forgotten in a route. Files: `src/db/schema.sql`, `tests/append-only.test.ts` 2. Record model identifier and prompt version per version The only way to explain a quality change later. Files: `src/drafts/generate.ts` 3. Diff at word level and restore by appending A linear history is one you can reason about. Files: `src/drafts/diff.ts`, `src/drafts/restore.ts` ### Done when - [ ] Draft version updates are refused by the database - [ ] Regeneration leaves the previous body untouched - [ ] Word-level diff renders on a fixture pair - [ ] Restore appends rather than rewinds ### Watch out - Regenerating in place means the version somebody actually liked is gone the moment they press the button again, and in this genre people press it a dozen times before deciding the third one was best. Append every version, never update one, and make restore an append too so the history stays linear. ## M3 · The claims check, and the shapes that are refused outright Factual claims are extracted deterministically and block the ready state until resolved, and testimonial-shaped output is rejected. ### Steps 1. Extract claims deterministically from a readable pattern list So the same draft always produces the same rows. Files: `src/claims/extract.ts`, `fixtures/claims.md` 2. Refuse testimonial and review shapes at generation Refuse the generation, do not edit the sentence out. Files: `src/claims/refuse.ts`, `tests/refuse.test.ts` 3. Gate the ready state on a query, not a checkbox The state transition runs the query itself. Files: `src/drafts/state.ts`, `tests/ready-gate.test.ts` ### Done when - [ ] Testimonial-shaped output is refused with the sentence shown - [ ] Claim rows match the expected count on a fixture document - [ ] Ready is impossible while a claim is unresolved - [ ] An empty resolution is refused ### Watch out - Asking a second model call to check the first one for false claims produces a confident approval and no record, which is worse than no check at all because it looks like one. Use a deterministic extractor with a pattern list you can read and extend, so the same draft always produces the same claim rows and a missed pattern is a bug you can fix. ## M4 · Voice from your own writing Tone is a set of measurable constraints derived from samples you supply, checked after generation. ### Steps 1. Derive constraints from samples, not from a slider Banned terms, preferred terms, sentence length, reading level. Files: `src/voice/derive.ts`, `tests/voice.test.ts` 2. Check output against the constraints after generation The prompt is a request; the check is the enforcement. Files: `src/voice/check.ts` 3. Report violations by sentence and block ready A list of offending sentences, not a score. Files: `src/voice/report.ts` ### Done when - [ ] Generation with no samples says so explicitly - [ ] A banned term blocks the ready state with its sentence shown - [ ] Reading level and sentence length are shown against the band - [ ] Different sample sets produce different constraints ### Watch out - A tone dropdown labelled friendly or authoritative changes nothing you can measure and gives the user the feeling of control without any of it, so the output drifts back to generic marketing language within three sections. Derive constraints from real samples and check the generated text against them afterwards, because the check is the part that actually holds. ## M5 · Getting copy out, and keeping the bill small (production only) Drafts export as plain files with their provenance, spend has a ceiling, and a project can be deleted properly. ### Steps 1. Export copy together with its provenance The claims and sources travel with the text or the record is lost. Files: `src/export/draft.ts`, `tests/export.test.ts` 2. Cap monthly spend and degrade to editing Everything except generation keeps working at the ceiling. Files: `src/cost/ceiling.ts` 3. Delete a project completely in one transaction Including voice samples, which are your own writing. Files: `src/projects/delete.ts`, `tests/delete.test.ts` ### Done when - [ ] Exports carry framework, model, prompt and resolved claims - [ ] A project folder reads without the application - [ ] The ceiling blocks generation only - [ ] Project deletion leaves no rows behind ### Watch out - Exporting only the final text separates the copy from the sources that were used to resolve its claims, so six months later nobody can tell whether the forty percent figure in the headline was checked or invented, which is precisely the record the rule zero exists to create. Put the resolved claims and their sources in the exported file.
# Operations · ClosersCopy ## Backup The database, which holds every version and every claim resolution. The resolutions are the part you cannot recreate, because they represent a person having checked something. ## Restore Restore and confirm that drafts in the ready state still have zero unresolved claims. A restore that loses claim rows can silently promote a blocked draft, so check that query first. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Spend against the ceiling, the refusal rate for testimonial shapes, and the number of claims resolved with a note rather than a source. A rising note-only rate means the check is being waved through. ## Incident checklist The incident is a published claim that was not true. The draft versions, the claim rows and the exported front matter together show what was asserted, when, and what it was checked against. 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 - [ ] Testimonial-shaped generations are confirmed refused with no version written - [ ] The ready state is confirmed impossible with an unresolved claim - [ ] Draft version updates are confirmed refused by the database - [ ] Banned terms from voice samples are confirmed to block ready - [ ] Exports are confirmed to carry framework, model and resolved claims ## Launch constraint Do not market omitted ClosersCopy 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. Frameworks, projects, drafts, versions, claims and voice samples. DATABASE_PATH=./data/copy.db # Required · secret. The text model. Give it a provider-side spend limit too. MODEL_API_KEY=sk-...redacted # Required. Stored on every draft version, because output quality changes with it. MODEL_ID=a text model identifier # Required. Generation stops here. Editing, claim resolution and export continue. MONTHLY_COST_CEILING=5.00 # Required. The readable pattern list. Extending it is the normal maintenance of this app. CLAIM_PATTERNS_PATH=./config/claim-patterns.json # Required. Markdown with front matter carrying framework, model and resolved claims. EXPORT_DIR=./exports
$ choose a build depth, inspect the files, then open the complete pack in your agent
ClosersCopy: Customers pay for tuned workflows, predictable quality, governance, and a product team absorbing model churn rather than for the text box alone.
xteam governance and integrations
xvendor-managed prompt and quality tuning
xproprietary models or classifiers
xbrand-trained workflows
Don't feel like building it? These folks already made it free.
all 3 free alternatives to ClosersCopy →· no votes, no pay-to-list · just what's real
ClosersCopy pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| superpower hero lifetime | custom | — | Unlimited AI writing; unlimited SEO audits and updates; 1 owner + 2 team members. |
| recurring subscription | custom | — | Current public tier name, price, and numeric limits were not exposed on the live site. |
free tierno free tier
billingone-time lifetime offer; recurring billing exists but its live tier names and prices are not publicly exposed
verified 2026-08-12 · source ↗
Vibecode ClosersCopy
Kinda. The core of ClosersCopy is buildable in a weekend with the prompt on this page, but there are real gaps: team governance and integrations, vendor-managed prompt and quality tuning. Read the honest list above before committing.
How much does ClosersCopy cost?
ClosersCopy'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 ClosersCopy?
Honestly: team governance and integrations; vendor-managed prompt and quality tuning; proprietary models or classifiers; brand-trained workflows. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to ClosersCopy?
Yes: AnythingLLM (A local-first AI desk with reusable agents, documents, memory, and workflows; less campaign theatre, more settings.) Witsy (System-wide selected-text commands, reusable experts, a scratchpad, and document context; bring the model and skip the subscription.) Open WebUI (A self-hosted AI workbench with saved prompts, knowledge, web search, and multi-model comparison; powerful, not especially house-trained.) All 3 curated free alternatives are at vibecodeit.com/closerscopy/alternatives. The prompt is for when you want it exactly your way.