Vibecode PromptDrive
track this build4 phases, 12 steps, beginner friendly0%The core loop, save a prompt, file it in a folder, tag it, find it, fill in variables, copy it into a chat, is a small CRUD app around one SQLite table and ships in one sitting. What the subscription actually sells is the multiplayer part: shared folders with permissions, comments that let a team iterate on a prompt, and a Chrome extension that surfaces the library inside ChatGPT, Claude, and Gemini.
You are building a lean indie version of PromptDrive. 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 ===== # PromptDrive · indie build A personal prompt library whose hard problems are filling variables without producing a broken prompt, keeping the version of a prompt that used to work, and copying to the clipboard from a browser that will not give you the clipboard API. Estimated effort: **one sitting**. 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 | One Node process serving plain HTML, CSS and JavaScript, no build step | a filing cabinet should still open in three years without reinstalling a toolchain | | Storage | One SQLite file beside the application, schema created on first run | backup is copying a file, which is the correct amount of ceremony here | | Search | Query as you type over titles, bodies, notes and tags | the library is only useful if finding beats rewriting | | Variables | Placeholders parsed from the body into a generated form, with required checks | an unfilled placeholder pasted into a chat is the failure mode of every prompt library | | History | Every save keeps the previous body, with a restore | you will edit a prompt that worked and want it back | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 20 or newer** · free - Why: Runs the single process that serves the library. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v20 or higher - [ ] **A SQLite driver for Node** · free - Why: The whole library is one file, and a synchronous driver keeps the code simple for a single user. - Get it: Install a maintained driver as a dependency. - Verify: The database is created on first run and the schema matches - [ ] **How you will reach the app from other devices** · free - Why: One-click copying needs a trustworthy origin, and a plain address on your network is not one, which decides whether you need a certificate. - Get it: Decide between using it only on the machine itself, or setting up a hostname with a certificate for phone access. - Verify: The copy button either works or shows the fallback with an explanation on every device you use - [ ] **Twenty prompts you actually use** · free - Why: A library built against five invented examples gets the variable handling and the search wrong, because real prompts are long and contain awkward characters. - Get it: Paste in the prompts you currently keep in notes, documents or chat history. - Verify: Search, filling and copying all behave with the real ones ## 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: - Team sharing, comments and permissions. - A browser extension for chat tools. - Running prompts against models. - Public share links. - team sharing, comments, and permissions - the Chrome extension inside ChatGPT, Claude, and Gemini - running prompts against models from the same workspace with your own API keys - public share links with unique URLs - someone else's servers & support If one of those is essential to you, that is the reason to keep paying for PromptDrive, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · PromptDrive 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 local prompt library with typed variables, version history and a copy button that works from your phone too to replace PromptDrive. 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. ### Stack (fixed, do not substitute) - Runtime: One Node process serving plain HTML, CSS and JavaScript, no build step · a filing cabinet should still open in three years without reinstalling a toolchain - Storage: One SQLite file beside the application, schema created on first run · backup is copying a file, which is the correct amount of ceremony here - Search: Query as you type over titles, bodies, notes and tags · the library is only useful if finding beats rewriting - Variables: Placeholders parsed from the body into a generated form, with required checks · an unfilled placeholder pasted into a chat is the failure mode of every prompt library - History: Every save keeps the previous body, with a restore · you will edit a prompt that worked and want it back ### Data model (create this before Phase 1) - prompts(id, title, body, notes, folder_id, created_at, updated_at, last_used_at, use_count) - versions(id, prompt_id, ordinal, body, notes, saved_at, label) · the previous body, every time, because editing is destructive - folders(id, name, parent_id, position) - tags(id, name) and prompt_tags(prompt_id, tag_id) · flat tags, because a second hierarchy is a second filing system - variables(prompt_id, name, label, default_value, required, ordinal) · derived from the body on save and editable - The variables table is derived, so renaming a placeholder in the body renames it here without a migration or a stale row. ### Phase 1 · A library you can search faster than you can retype Build: Prompts with a title, body, notes, one folder and flat tags, stored in SQLite created on first run. Search filters as you type across all four fields. Keyboard throughout: a key focuses search, arrows move through results, one key opens and one copies. Seed with a few examples. Trap: Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. Done when: Typing in search filters results within a stated number of milliseconds on a library of a thousand prompts, measured with generated data. Every action in the list is reachable from the keyboard without the mouse. The database and schema are created on first run with no manual step. A search matching nothing shows an empty state that offers to create a prompt with that title. Do not build yet: No variables, copying, versions or export yet. ### Phase 2 · Variables that fail loudly Build: Parse placeholders out of the body, generate one input per distinct name, and update the filled text live. A placeholder used twice fills once and substitutes in both places. Mark fields required by default and block copying while any required field is empty. Handle braces in the body that are not placeholders rather than mangling them. Trap: Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. Done when: A prompt with the same placeholder twice fills once and substitutes in both positions, tested. A value containing something that looks like a placeholder is inserted literally and not substituted again, tested. Copying is blocked while a required field is empty and the blocked field is named. Braces in the body that are not placeholders survive filling unchanged, tested with a code sample. Do not build yet: No copying, versions or export yet. ### Phase 3 · Copying, including from the phone on the sofa Build: Copy with the clipboard API where it exists. It is declared as available only in a secure context, and the specification treats a loopback address as trustworthy while a plain address on your local network is not, so opening the app at a machine's network address over plain http leaves the clipboard object simply undefined rather than failing with a nice error. Detect that, fall back to selecting the text so the platform copy gesture works, and say which happened. Trap: Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. Done when: Copying works on the local machine and the confirmation appears, tested. Loading the app at a network address over plain http falls back to selectable text with an explanation instead of throwing, tested in a browser at that address. The fallback path is exercised by a test that simulates the clipboard object being absent. The README states why a certificate or a loopback address is needed for one-click copying. Do not build yet: No version history or export yet. ### Phase 4 · The version that used to work Build: Every save writes the previous body to the versions table. Show the history with dates, a difference against the current body, and a one-click restore that is itself a new save. Let a version be labelled, so the one that worked can be marked as such. Trap: Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. Done when: Saving an edited prompt creates a version row containing the previous body, tested. The history shows a readable difference between any version and the current body. Restoring a version writes a new version rather than deleting anything, so the restore itself is reversible, tested. A version can be labelled and the label survives later edits. Do not build yet: No export, import or backup documentation yet. ### Phase 5 · Out of this app and into the next one Build: Export everything as one JSON file including folders, tags, variables and version history, and import it back into an empty database. Also export a folder as Markdown, one file per prompt, with the variables listed in front matter. Document the backup as copying the database file, and state what a copy taken while the app is running may miss. Trap: An export that drops version history looks complete because the prompts are all there, and the loss is discovered months later when somebody wants the wording from before a rewrite. Round-trip the export in a test that compares every table, including versions, rather than eyeballing the file. Done when: Exporting and importing into an empty database reproduces prompts, folders, tags, variables and versions, compared table by table in a test. A Markdown export of a folder produces one readable file per prompt with its variables listed. Importing a file exported by an older schema version either upgrades it or refuses with a clear message, tested. The README names the file to copy for a backup and how to take it safely. Do not build yet: Nothing after this. ### Out of scope (and why) - Team sharing, comments and permissions, which is the entire reason the paid product charges per seat and is a different application. - A browser extension that injects the library into chat tools, which is a per-site maintenance burden that breaks whenever any of them ships a change. - Running prompts against models from inside the library, which turns a filing cabinet into a chat client with an API bill. - Public share links, which need hosting, access control and an abuse story for a feature one person does not need. ### README must contain - That the library is one SQLite file and backing up means copying it, with the safe way to do that - Why one-click copying needs a loopback address or a certificate, and what the fallback does otherwise - How placeholders are parsed, that substitution happens in one pass, and what happens to braces that are not placeholders - That every save keeps the previous body and how to restore one - That there are no accounts, no network calls and no telemetry, and how to verify that by watching the network ===== AGENTS.md ===== # Agent instructions · PromptDrive indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: One Node process serving plain HTML, CSS and JavaScript, no build step, One SQLite file beside the application, schema created on first run, Query as you type over titles, bodies, notes and tags, Placeholders parsed from the body into a generated form, with required checks, Every save keeps the previous body, with a restore. 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 - Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. - Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. - Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. - Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. ===== BUILD_PLAN.md ===== # Build plan · PromptDrive A personal prompt library whose hard problems are filling variables without producing a broken prompt, keeping the version of a prompt that used to work, and copying to the clipboard from a browser that will not give you the clipboard API. 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 library you can search faster than you can retype Finding a prompt takes one keystroke to focus and a few to filter. ### Steps 1. Create the schema on first run A setup step is a reason not to open the app. Files: `src/db.js`, `src/schema.sql` 2. Filter as you type, measured Instant or it loses to a text file. Files: `src/public/search.js`, `tests/search.test.js` 3. Make every action keyboard reachable This tool is used between two other windows. Files: `src/public/keys.js` ### Done when - [ ] Search filters within the stated time at a thousand prompts - [ ] Every list action works from the keyboard - [ ] First run creates the database unaided - [ ] An empty result offers to create that prompt ### Watch out - Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. ## Phase 2 · Variables that fail loudly A filled prompt never contains a leftover placeholder, and the form matches the body. ### Steps 1. Derive the variable list from the body on save One source of truth, so a rename cannot leave a stale field. Files: `src/variables.js`, `tests/variables.test.js` 2. Substitute in one scan, never repeatedly A second pass substitutes the values themselves. Files: `src/fill.js`, `tests/fill.test.js` 3. Block the copy while a required field is empty Name the field, because the user is mid-task. Files: `src/public/fill.js` ### Done when - [ ] A repeated placeholder fills once and appears twice - [ ] A value containing a placeholder is inserted literally - [ ] Copying is blocked with the empty field named - [ ] Non-placeholder braces survive unchanged ### Watch out - Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. ## Phase 3 · Copying, including from the phone on the sofa The copy button works on the machine and over the network, or explains itself. ### Steps 1. Feature-detect before calling the clipboard On an insecure origin the object is not there at all. Files: `src/public/copy.js`, `tests/copy.test.js` 2. Fall back to selecting the filled text The platform copy gesture always works. Files: `src/public/copy.js` 3. Explain the difference where it happens Not in a documentation file the user will not read. Files: `src/templates/prompt.html` ### Done when - [ ] One-click copy works locally with a confirmation - [ ] A network address falls back rather than throwing - [ ] The absent-clipboard path is covered by a test - [ ] The README explains the secure origin requirement ### Watch out - Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. ## Phase 4 · The version that used to work Editing a prompt never loses the wording that was working yesterday. ### Steps 1. Write a version row on every save Cheap now, priceless the day you need it. Files: `src/versions.js`, `tests/versions.test.js` 2. Show a difference, not two blocks of text Prompt edits are usually a handful of words. Files: `src/public/diff.js` 3. Make restore a new save So restoring the wrong one is also reversible. Files: `src/versions.js` ### Done when - [ ] Every save stores the previous body - [ ] History shows a readable difference - [ ] Restore creates a version rather than deleting - [ ] Version labels survive later edits ### Watch out - Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. ## Not in this build - Team sharing, comments and permissions. - A browser extension for chat tools. - Running prompts against models. - Public share links. ## After v1, if you want it - Typed variables with a select list, which removes most of the remaining filling mistakes - A duplicate detector over prompt bodies, since libraries accumulate near-identical entries - A quick-open window bound to a system shortcut, which is the only thing an extension really gave you ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The whole library. Copy this file to back it up. DATABASE_PATH=./data/prompts.sqlite # Required. Bound to the loopback address by default, which is also what makes copying work. PORT=4321 # Required. Change it to reach the app from a phone, and read the note about the clipboard first. BIND_ADDRESS=127.0.0.1 # Required. Where JSON and Markdown exports are written. EXPORT_DIR=./data/exports
You are building a lean indie version of PromptDrive. 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 ===== # PromptDrive · indie build A personal prompt library whose hard problems are filling variables without producing a broken prompt, keeping the version of a prompt that used to work, and copying to the clipboard from a browser that will not give you the clipboard API. Estimated effort: **one sitting**. 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 | One Node process serving plain HTML, CSS and JavaScript, no build step | a filing cabinet should still open in three years without reinstalling a toolchain | | Storage | One SQLite file beside the application, schema created on first run | backup is copying a file, which is the correct amount of ceremony here | | Search | Query as you type over titles, bodies, notes and tags | the library is only useful if finding beats rewriting | | Variables | Placeholders parsed from the body into a generated form, with required checks | an unfilled placeholder pasted into a chat is the failure mode of every prompt library | | History | Every save keeps the previous body, with a restore | you will edit a prompt that worked and want it back | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 20 or newer** · free - Why: Runs the single process that serves the library. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v20 or higher - [ ] **A SQLite driver for Node** · free - Why: The whole library is one file, and a synchronous driver keeps the code simple for a single user. - Get it: Install a maintained driver as a dependency. - Verify: The database is created on first run and the schema matches - [ ] **How you will reach the app from other devices** · free - Why: One-click copying needs a trustworthy origin, and a plain address on your network is not one, which decides whether you need a certificate. - Get it: Decide between using it only on the machine itself, or setting up a hostname with a certificate for phone access. - Verify: The copy button either works or shows the fallback with an explanation on every device you use - [ ] **Twenty prompts you actually use** · free - Why: A library built against five invented examples gets the variable handling and the search wrong, because real prompts are long and contain awkward characters. - Get it: Paste in the prompts you currently keep in notes, documents or chat history. - Verify: Search, filling and copying all behave with the real ones ## 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: - Team sharing, comments and permissions. - A browser extension for chat tools. - Running prompts against models. - Public share links. - team sharing, comments, and permissions - the Chrome extension inside ChatGPT, Claude, and Gemini - running prompts against models from the same workspace with your own API keys - public share links with unique URLs - someone else's servers & support If one of those is essential to you, that is the reason to keep paying for PromptDrive, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · PromptDrive 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 local prompt library with typed variables, version history and a copy button that works from your phone too to replace PromptDrive. 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. ### Stack (fixed, do not substitute) - Runtime: One Node process serving plain HTML, CSS and JavaScript, no build step · a filing cabinet should still open in three years without reinstalling a toolchain - Storage: One SQLite file beside the application, schema created on first run · backup is copying a file, which is the correct amount of ceremony here - Search: Query as you type over titles, bodies, notes and tags · the library is only useful if finding beats rewriting - Variables: Placeholders parsed from the body into a generated form, with required checks · an unfilled placeholder pasted into a chat is the failure mode of every prompt library - History: Every save keeps the previous body, with a restore · you will edit a prompt that worked and want it back ### Data model (create this before Phase 1) - prompts(id, title, body, notes, folder_id, created_at, updated_at, last_used_at, use_count) - versions(id, prompt_id, ordinal, body, notes, saved_at, label) · the previous body, every time, because editing is destructive - folders(id, name, parent_id, position) - tags(id, name) and prompt_tags(prompt_id, tag_id) · flat tags, because a second hierarchy is a second filing system - variables(prompt_id, name, label, default_value, required, ordinal) · derived from the body on save and editable - The variables table is derived, so renaming a placeholder in the body renames it here without a migration or a stale row. ### Phase 1 · A library you can search faster than you can retype Build: Prompts with a title, body, notes, one folder and flat tags, stored in SQLite created on first run. Search filters as you type across all four fields. Keyboard throughout: a key focuses search, arrows move through results, one key opens and one copies. Seed with a few examples. Trap: Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. Done when: Typing in search filters results within a stated number of milliseconds on a library of a thousand prompts, measured with generated data. Every action in the list is reachable from the keyboard without the mouse. The database and schema are created on first run with no manual step. A search matching nothing shows an empty state that offers to create a prompt with that title. Do not build yet: No variables, copying, versions or export yet. ### Phase 2 · Variables that fail loudly Build: Parse placeholders out of the body, generate one input per distinct name, and update the filled text live. A placeholder used twice fills once and substitutes in both places. Mark fields required by default and block copying while any required field is empty. Handle braces in the body that are not placeholders rather than mangling them. Trap: Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. Done when: A prompt with the same placeholder twice fills once and substitutes in both positions, tested. A value containing something that looks like a placeholder is inserted literally and not substituted again, tested. Copying is blocked while a required field is empty and the blocked field is named. Braces in the body that are not placeholders survive filling unchanged, tested with a code sample. Do not build yet: No copying, versions or export yet. ### Phase 3 · Copying, including from the phone on the sofa Build: Copy with the clipboard API where it exists. It is declared as available only in a secure context, and the specification treats a loopback address as trustworthy while a plain address on your local network is not, so opening the app at a machine's network address over plain http leaves the clipboard object simply undefined rather than failing with a nice error. Detect that, fall back to selecting the text so the platform copy gesture works, and say which happened. Trap: Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. Done when: Copying works on the local machine and the confirmation appears, tested. Loading the app at a network address over plain http falls back to selectable text with an explanation instead of throwing, tested in a browser at that address. The fallback path is exercised by a test that simulates the clipboard object being absent. The README states why a certificate or a loopback address is needed for one-click copying. Do not build yet: No version history or export yet. ### Phase 4 · The version that used to work Build: Every save writes the previous body to the versions table. Show the history with dates, a difference against the current body, and a one-click restore that is itself a new save. Let a version be labelled, so the one that worked can be marked as such. Trap: Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. Done when: Saving an edited prompt creates a version row containing the previous body, tested. The history shows a readable difference between any version and the current body. Restoring a version writes a new version rather than deleting anything, so the restore itself is reversible, tested. A version can be labelled and the label survives later edits. Do not build yet: No export, import or backup documentation yet. ### Phase 5 · Out of this app and into the next one Build: Export everything as one JSON file including folders, tags, variables and version history, and import it back into an empty database. Also export a folder as Markdown, one file per prompt, with the variables listed in front matter. Document the backup as copying the database file, and state what a copy taken while the app is running may miss. Trap: An export that drops version history looks complete because the prompts are all there, and the loss is discovered months later when somebody wants the wording from before a rewrite. Round-trip the export in a test that compares every table, including versions, rather than eyeballing the file. Done when: Exporting and importing into an empty database reproduces prompts, folders, tags, variables and versions, compared table by table in a test. A Markdown export of a folder produces one readable file per prompt with its variables listed. Importing a file exported by an older schema version either upgrades it or refuses with a clear message, tested. The README names the file to copy for a backup and how to take it safely. Do not build yet: Nothing after this. ### Out of scope (and why) - Team sharing, comments and permissions, which is the entire reason the paid product charges per seat and is a different application. - A browser extension that injects the library into chat tools, which is a per-site maintenance burden that breaks whenever any of them ships a change. - Running prompts against models from inside the library, which turns a filing cabinet into a chat client with an API bill. - Public share links, which need hosting, access control and an abuse story for a feature one person does not need. ### README must contain - That the library is one SQLite file and backing up means copying it, with the safe way to do that - Why one-click copying needs a loopback address or a certificate, and what the fallback does otherwise - How placeholders are parsed, that substitution happens in one pass, and what happens to braces that are not placeholders - That every save keeps the previous body and how to restore one - That there are no accounts, no network calls and no telemetry, and how to verify that by watching the network ===== AGENTS.md ===== # Agent instructions · PromptDrive indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: One Node process serving plain HTML, CSS and JavaScript, no build step, One SQLite file beside the application, schema created on first run, Query as you type over titles, bodies, notes and tags, Placeholders parsed from the body into a generated form, with required checks, Every save keeps the previous body, with a restore. 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 - Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. - Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. - Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. - Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. ===== BUILD_PLAN.md ===== # Build plan · PromptDrive A personal prompt library whose hard problems are filling variables without producing a broken prompt, keeping the version of a prompt that used to work, and copying to the clipboard from a browser that will not give you the clipboard API. 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 library you can search faster than you can retype Finding a prompt takes one keystroke to focus and a few to filter. ### Steps 1. Create the schema on first run A setup step is a reason not to open the app. Files: `src/db.js`, `src/schema.sql` 2. Filter as you type, measured Instant or it loses to a text file. Files: `src/public/search.js`, `tests/search.test.js` 3. Make every action keyboard reachable This tool is used between two other windows. Files: `src/public/keys.js` ### Done when - [ ] Search filters within the stated time at a thousand prompts - [ ] Every list action works from the keyboard - [ ] First run creates the database unaided - [ ] An empty result offers to create that prompt ### Watch out - Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. ## Phase 2 · Variables that fail loudly A filled prompt never contains a leftover placeholder, and the form matches the body. ### Steps 1. Derive the variable list from the body on save One source of truth, so a rename cannot leave a stale field. Files: `src/variables.js`, `tests/variables.test.js` 2. Substitute in one scan, never repeatedly A second pass substitutes the values themselves. Files: `src/fill.js`, `tests/fill.test.js` 3. Block the copy while a required field is empty Name the field, because the user is mid-task. Files: `src/public/fill.js` ### Done when - [ ] A repeated placeholder fills once and appears twice - [ ] A value containing a placeholder is inserted literally - [ ] Copying is blocked with the empty field named - [ ] Non-placeholder braces survive unchanged ### Watch out - Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. ## Phase 3 · Copying, including from the phone on the sofa The copy button works on the machine and over the network, or explains itself. ### Steps 1. Feature-detect before calling the clipboard On an insecure origin the object is not there at all. Files: `src/public/copy.js`, `tests/copy.test.js` 2. Fall back to selecting the filled text The platform copy gesture always works. Files: `src/public/copy.js` 3. Explain the difference where it happens Not in a documentation file the user will not read. Files: `src/templates/prompt.html` ### Done when - [ ] One-click copy works locally with a confirmation - [ ] A network address falls back rather than throwing - [ ] The absent-clipboard path is covered by a test - [ ] The README explains the secure origin requirement ### Watch out - Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. ## Phase 4 · The version that used to work Editing a prompt never loses the wording that was working yesterday. ### Steps 1. Write a version row on every save Cheap now, priceless the day you need it. Files: `src/versions.js`, `tests/versions.test.js` 2. Show a difference, not two blocks of text Prompt edits are usually a handful of words. Files: `src/public/diff.js` 3. Make restore a new save So restoring the wrong one is also reversible. Files: `src/versions.js` ### Done when - [ ] Every save stores the previous body - [ ] History shows a readable difference - [ ] Restore creates a version rather than deleting - [ ] Version labels survive later edits ### Watch out - Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. ## Not in this build - Team sharing, comments and permissions. - A browser extension for chat tools. - Running prompts against models. - Public share links. ## After v1, if you want it - Typed variables with a select list, which removes most of the remaining filling mistakes - A duplicate detector over prompt bodies, since libraries accumulate near-identical entries - A quick-open window bound to a system shortcut, which is the only thing an extension really gave you ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The whole library. Copy this file to back it up. DATABASE_PATH=./data/prompts.sqlite # Required. Bound to the loopback address by default, which is also what makes copying work. PORT=4321 # Required. Change it to reach the app from a phone, and read the note about the clipboard first. BIND_ADDRESS=127.0.0.1 # Required. Where JSON and Markdown exports are written. EXPORT_DIR=./data/exports
You are building a production product version of PromptDrive. 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 ===== # PromptDrive · product brief ## Problem The core loop, save a prompt, file it in a folder, tag it, find it, fill in variables, copy it into a chat, is a small CRUD app around one SQLite table and ships in one sitting. What the subscription actually sells is the multiplayer part: shared folders with permissions, comments that let a team iterate on a prompt, and a Chrome extension that surfaces the library inside ChatGPT, Claude, and Gemini. ## Product outcome A prompt library that finds a prompt faster than retyping it, never produces a prompt with an unfilled placeholder, keeps the version that used to work, and copies or explains why it cannot. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node.js 20+ - a browser ## Explicit non-goals for v1 - Team sharing, comments and permissions. - A browser extension for chat tools. - Running prompts against models. - Public share links. - team sharing, comments, and permissions - the Chrome extension inside ChatGPT, Claude, and Gemini - running prompts against models from the same workspace with your own API keys - public share links with unique URLs - someone else's servers & support ## Success criteria - Search is confirmed fast at a thousand prompts - A repeated placeholder is confirmed to fill once and appear twice - The absent-clipboard fallback is confirmed to explain itself - Every save is confirmed to store the previous body - Export and import are confirmed to reproduce every table ===== BRIEF.md ===== # Build brief · PromptDrive 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 local prompt library with typed variables, version history and a copy button that works from your phone too to replace PromptDrive. 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. ### Stack (fixed, do not substitute) - Runtime: One Node process serving plain HTML, CSS and JavaScript, no build step · a filing cabinet should still open in three years without reinstalling a toolchain - Storage: One SQLite file beside the application, schema created on first run · backup is copying a file, which is the correct amount of ceremony here - Search: Query as you type over titles, bodies, notes and tags · the library is only useful if finding beats rewriting - Variables: Placeholders parsed from the body into a generated form, with required checks · an unfilled placeholder pasted into a chat is the failure mode of every prompt library - History: Every save keeps the previous body, with a restore · you will edit a prompt that worked and want it back ### Data model (create this before Phase 1) - prompts(id, title, body, notes, folder_id, created_at, updated_at, last_used_at, use_count) - versions(id, prompt_id, ordinal, body, notes, saved_at, label) · the previous body, every time, because editing is destructive - folders(id, name, parent_id, position) - tags(id, name) and prompt_tags(prompt_id, tag_id) · flat tags, because a second hierarchy is a second filing system - variables(prompt_id, name, label, default_value, required, ordinal) · derived from the body on save and editable - The variables table is derived, so renaming a placeholder in the body renames it here without a migration or a stale row. ### Phase 1 · A library you can search faster than you can retype Build: Prompts with a title, body, notes, one folder and flat tags, stored in SQLite created on first run. Search filters as you type across all four fields. Keyboard throughout: a key focuses search, arrows move through results, one key opens and one copies. Seed with a few examples. Trap: Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. Done when: Typing in search filters results within a stated number of milliseconds on a library of a thousand prompts, measured with generated data. Every action in the list is reachable from the keyboard without the mouse. The database and schema are created on first run with no manual step. A search matching nothing shows an empty state that offers to create a prompt with that title. Do not build yet: No variables, copying, versions or export yet. ### Phase 2 · Variables that fail loudly Build: Parse placeholders out of the body, generate one input per distinct name, and update the filled text live. A placeholder used twice fills once and substitutes in both places. Mark fields required by default and block copying while any required field is empty. Handle braces in the body that are not placeholders rather than mangling them. Trap: Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. Done when: A prompt with the same placeholder twice fills once and substitutes in both positions, tested. A value containing something that looks like a placeholder is inserted literally and not substituted again, tested. Copying is blocked while a required field is empty and the blocked field is named. Braces in the body that are not placeholders survive filling unchanged, tested with a code sample. Do not build yet: No copying, versions or export yet. ### Phase 3 · Copying, including from the phone on the sofa Build: Copy with the clipboard API where it exists. It is declared as available only in a secure context, and the specification treats a loopback address as trustworthy while a plain address on your local network is not, so opening the app at a machine's network address over plain http leaves the clipboard object simply undefined rather than failing with a nice error. Detect that, fall back to selecting the text so the platform copy gesture works, and say which happened. Trap: Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. Done when: Copying works on the local machine and the confirmation appears, tested. Loading the app at a network address over plain http falls back to selectable text with an explanation instead of throwing, tested in a browser at that address. The fallback path is exercised by a test that simulates the clipboard object being absent. The README states why a certificate or a loopback address is needed for one-click copying. Do not build yet: No version history or export yet. ### Phase 4 · The version that used to work Build: Every save writes the previous body to the versions table. Show the history with dates, a difference against the current body, and a one-click restore that is itself a new save. Let a version be labelled, so the one that worked can be marked as such. Trap: Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. Done when: Saving an edited prompt creates a version row containing the previous body, tested. The history shows a readable difference between any version and the current body. Restoring a version writes a new version rather than deleting anything, so the restore itself is reversible, tested. A version can be labelled and the label survives later edits. Do not build yet: No export, import or backup documentation yet. ### Phase 5 · Out of this app and into the next one Build: Export everything as one JSON file including folders, tags, variables and version history, and import it back into an empty database. Also export a folder as Markdown, one file per prompt, with the variables listed in front matter. Document the backup as copying the database file, and state what a copy taken while the app is running may miss. Trap: An export that drops version history looks complete because the prompts are all there, and the loss is discovered months later when somebody wants the wording from before a rewrite. Round-trip the export in a test that compares every table, including versions, rather than eyeballing the file. Done when: Exporting and importing into an empty database reproduces prompts, folders, tags, variables and versions, compared table by table in a test. A Markdown export of a folder produces one readable file per prompt with its variables listed. Importing a file exported by an older schema version either upgrades it or refuses with a clear message, tested. The README names the file to copy for a backup and how to take it safely. Do not build yet: Nothing after this. ### Out of scope (and why) - Team sharing, comments and permissions, which is the entire reason the paid product charges per seat and is a different application. - A browser extension that injects the library into chat tools, which is a per-site maintenance burden that breaks whenever any of them ships a change. - Running prompts against models from inside the library, which turns a filing cabinet into a chat client with an API bill. - Public share links, which need hosting, access control and an abuse story for a feature one person does not need. ### README must contain - That the library is one SQLite file and backing up means copying it, with the safe way to do that - Why one-click copying needs a loopback address or a certificate, and what the fallback does otherwise - How placeholders are parsed, that substitution happens in one pass, and what happens to braces that are not placeholders - That every save keeps the previous body and how to restore one - That there are no accounts, no network calls and no telemetry, and how to verify that by watching the network ===== ARCHITECTURE.md ===== # Architecture · PromptDrive ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | One Node process serving plain HTML, CSS and JavaScript, no build step | a filing cabinet should still open in three years without reinstalling a toolchain | | Storage | One SQLite file beside the application, schema created on first run | backup is copying a file, which is the correct amount of ceremony here | | Search | Query as you type over titles, bodies, notes and tags | the library is only useful if finding beats rewriting | | Variables | Placeholders parsed from the body into a generated form, with required checks | an unfilled placeholder pasted into a chat is the failure mode of every prompt library | | History | Every save keeps the previous body, with a restore | you will edit a prompt that worked and want it back | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Library | Prompts, folders, tags and search. | Any store, provided search stays fast enough to type into. | | Variables | Parsing, the generated form and one-pass substitution. | Any syntax, provided substitution never runs twice over its own output. | | Clipboard | Feature detection and the selection fallback. | Not swappable for a direct call, which throws on an insecure origin. | | Versions | Previous bodies, differences and restore. | Any history, provided restore is itself reversible. | | Portability | JSON round trip and Markdown export. | Any format, provided versions are included and the round trip is tested. | ## 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 · The whole library. Copy this file to back it up. - `PORT` · required · Bound to the loopback address by default, which is also what makes copying work. - `BIND_ADDRESS` · required · Change it to reach the app from a phone, and read the note about the clipboard first. - `EXPORT_DIR` · required · Where JSON and Markdown exports are written. ## 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 · PromptDrive product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: One Node process serving plain HTML, CSS and JavaScript, no build step, One SQLite file beside the application, schema created on first run, Query as you type over titles, bodies, notes and tags, Placeholders parsed from the body into a generated form, with required checks, Every save keeps the previous body, with a restore. - 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 - Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. - Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. - Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. - Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. - An export that drops version history looks complete because the prompts are all there, and the loss is discovered months later when somebody wants the wording from before a rewrite. Round-trip the export in a test that compares every table, including versions, rather than eyeballing the file. ===== MILESTONES.md ===== # Delivery milestones · PromptDrive Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A library you can search faster than you can retype Finding a prompt takes one keystroke to focus and a few to filter. ### Steps 1. Create the schema on first run A setup step is a reason not to open the app. Files: `src/db.js`, `src/schema.sql` 2. Filter as you type, measured Instant or it loses to a text file. Files: `src/public/search.js`, `tests/search.test.js` 3. Make every action keyboard reachable This tool is used between two other windows. Files: `src/public/keys.js` ### Done when - [ ] Search filters within the stated time at a thousand prompts - [ ] Every list action works from the keyboard - [ ] First run creates the database unaided - [ ] An empty result offers to create that prompt ### Watch out - Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. ## M2 · Variables that fail loudly A filled prompt never contains a leftover placeholder, and the form matches the body. ### Steps 1. Derive the variable list from the body on save One source of truth, so a rename cannot leave a stale field. Files: `src/variables.js`, `tests/variables.test.js` 2. Substitute in one scan, never repeatedly A second pass substitutes the values themselves. Files: `src/fill.js`, `tests/fill.test.js` 3. Block the copy while a required field is empty Name the field, because the user is mid-task. Files: `src/public/fill.js` ### Done when - [ ] A repeated placeholder fills once and appears twice - [ ] A value containing a placeholder is inserted literally - [ ] Copying is blocked with the empty field named - [ ] Non-placeholder braces survive unchanged ### Watch out - Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. ## M3 · Copying, including from the phone on the sofa The copy button works on the machine and over the network, or explains itself. ### Steps 1. Feature-detect before calling the clipboard On an insecure origin the object is not there at all. Files: `src/public/copy.js`, `tests/copy.test.js` 2. Fall back to selecting the filled text The platform copy gesture always works. Files: `src/public/copy.js` 3. Explain the difference where it happens Not in a documentation file the user will not read. Files: `src/templates/prompt.html` ### Done when - [ ] One-click copy works locally with a confirmation - [ ] A network address falls back rather than throwing - [ ] The absent-clipboard path is covered by a test - [ ] The README explains the secure origin requirement ### Watch out - Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. ## M4 · The version that used to work Editing a prompt never loses the wording that was working yesterday. ### Steps 1. Write a version row on every save Cheap now, priceless the day you need it. Files: `src/versions.js`, `tests/versions.test.js` 2. Show a difference, not two blocks of text Prompt edits are usually a handful of words. Files: `src/public/diff.js` 3. Make restore a new save So restoring the wrong one is also reversible. Files: `src/versions.js` ### Done when - [ ] Every save stores the previous body - [ ] History shows a readable difference - [ ] Restore creates a version rather than deleting - [ ] Version labels survive later edits ### Watch out - Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. ## M5 · Out of this app and into the next one (production only) The whole library moves as files, and the backup story is one line. ### Steps 1. Round-trip every table, versions included The history is the part an export usually loses. Files: `src/export.js`, `tests/roundtrip.test.js` 2. Export Markdown for humans as well as JSON One file per prompt travels anywhere. Files: `src/markdown.js` 3. Refuse an import you cannot read Better than a partial import nobody notices. Files: `src/import.js` ### Done when - [ ] Export and import reproduce every table - [ ] Markdown export gives one readable file per prompt - [ ] An unreadable import is refused with a reason - [ ] The README names the backup file and method ### Watch out - An export that drops version history looks complete because the prompts are all there, and the loss is discovered months later when somebody wants the wording from before a rewrite. Round-trip the export in a test that compares every table, including versions, rather than eyeballing the file. ===== OPERATIONS.md ===== # Operations · PromptDrive ## Backup Copy the database file on a schedule and keep a few generations. For a file this small there is no reason to be clever, and the export gives you a second format that does not depend on SQLite at all. ## Restore Copy the file back, open the app, and check the prompt count and one version history. If versions are missing you restored an export rather than the database. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring None worth running for one user. The suite covers what matters: search speed at a thousand prompts, single-pass substitution, the absent-clipboard path, and the export round trip. ## Incident checklist The realistic incident is a prompt edited into uselessness, and the version history is the entire answer. The second is a copy button that stopped working, which is almost always the origin rather than the code. 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 - [ ] Search is confirmed fast at a thousand prompts - [ ] A repeated placeholder is confirmed to fill once and appear twice - [ ] The absent-clipboard fallback is confirmed to explain itself - [ ] Every save is confirmed to store the previous body - [ ] Export and import are confirmed to reproduce every table ## Launch constraint Do not market omitted PromptDrive 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. The whole library. Copy this file to back it up. DATABASE_PATH=./data/prompts.sqlite # Required. Bound to the loopback address by default, which is also what makes copying work. PORT=4321 # Required. Change it to reach the app from a phone, and read the note about the clipboard first. BIND_ADDRESS=127.0.0.1 # Required. Where JSON and Markdown exports are written. EXPORT_DIR=./data/exports
# PromptDrive · indie build A personal prompt library whose hard problems are filling variables without producing a broken prompt, keeping the version of a prompt that used to work, and copying to the clipboard from a browser that will not give you the clipboard API. Estimated effort: **one sitting**. 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 | One Node process serving plain HTML, CSS and JavaScript, no build step | a filing cabinet should still open in three years without reinstalling a toolchain | | Storage | One SQLite file beside the application, schema created on first run | backup is copying a file, which is the correct amount of ceremony here | | Search | Query as you type over titles, bodies, notes and tags | the library is only useful if finding beats rewriting | | Variables | Placeholders parsed from the body into a generated form, with required checks | an unfilled placeholder pasted into a chat is the failure mode of every prompt library | | History | Every save keeps the previous body, with a restore | you will edit a prompt that worked and want it back | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 20 or newer** · free - Why: Runs the single process that serves the library. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v20 or higher - [ ] **A SQLite driver for Node** · free - Why: The whole library is one file, and a synchronous driver keeps the code simple for a single user. - Get it: Install a maintained driver as a dependency. - Verify: The database is created on first run and the schema matches - [ ] **How you will reach the app from other devices** · free - Why: One-click copying needs a trustworthy origin, and a plain address on your network is not one, which decides whether you need a certificate. - Get it: Decide between using it only on the machine itself, or setting up a hostname with a certificate for phone access. - Verify: The copy button either works or shows the fallback with an explanation on every device you use - [ ] **Twenty prompts you actually use** · free - Why: A library built against five invented examples gets the variable handling and the search wrong, because real prompts are long and contain awkward characters. - Get it: Paste in the prompts you currently keep in notes, documents or chat history. - Verify: Search, filling and copying all behave with the real ones ## 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: - Team sharing, comments and permissions. - A browser extension for chat tools. - Running prompts against models. - Public share links. - team sharing, comments, and permissions - the Chrome extension inside ChatGPT, Claude, and Gemini - running prompts against models from the same workspace with your own API keys - public share links with unique URLs - someone else's servers & support If one of those is essential to you, that is the reason to keep paying for PromptDrive, and the README should say so rather than pretend.
# Build brief · PromptDrive 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 local prompt library with typed variables, version history and a copy button that works from your phone too to replace PromptDrive. 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. ### Stack (fixed, do not substitute) - Runtime: One Node process serving plain HTML, CSS and JavaScript, no build step · a filing cabinet should still open in three years without reinstalling a toolchain - Storage: One SQLite file beside the application, schema created on first run · backup is copying a file, which is the correct amount of ceremony here - Search: Query as you type over titles, bodies, notes and tags · the library is only useful if finding beats rewriting - Variables: Placeholders parsed from the body into a generated form, with required checks · an unfilled placeholder pasted into a chat is the failure mode of every prompt library - History: Every save keeps the previous body, with a restore · you will edit a prompt that worked and want it back ### Data model (create this before Phase 1) - prompts(id, title, body, notes, folder_id, created_at, updated_at, last_used_at, use_count) - versions(id, prompt_id, ordinal, body, notes, saved_at, label) · the previous body, every time, because editing is destructive - folders(id, name, parent_id, position) - tags(id, name) and prompt_tags(prompt_id, tag_id) · flat tags, because a second hierarchy is a second filing system - variables(prompt_id, name, label, default_value, required, ordinal) · derived from the body on save and editable - The variables table is derived, so renaming a placeholder in the body renames it here without a migration or a stale row. ### Phase 1 · A library you can search faster than you can retype Build: Prompts with a title, body, notes, one folder and flat tags, stored in SQLite created on first run. Search filters as you type across all four fields. Keyboard throughout: a key focuses search, arrows move through results, one key opens and one copies. Seed with a few examples. Trap: Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. Done when: Typing in search filters results within a stated number of milliseconds on a library of a thousand prompts, measured with generated data. Every action in the list is reachable from the keyboard without the mouse. The database and schema are created on first run with no manual step. A search matching nothing shows an empty state that offers to create a prompt with that title. Do not build yet: No variables, copying, versions or export yet. ### Phase 2 · Variables that fail loudly Build: Parse placeholders out of the body, generate one input per distinct name, and update the filled text live. A placeholder used twice fills once and substitutes in both places. Mark fields required by default and block copying while any required field is empty. Handle braces in the body that are not placeholders rather than mangling them. Trap: Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. Done when: A prompt with the same placeholder twice fills once and substitutes in both positions, tested. A value containing something that looks like a placeholder is inserted literally and not substituted again, tested. Copying is blocked while a required field is empty and the blocked field is named. Braces in the body that are not placeholders survive filling unchanged, tested with a code sample. Do not build yet: No copying, versions or export yet. ### Phase 3 · Copying, including from the phone on the sofa Build: Copy with the clipboard API where it exists. It is declared as available only in a secure context, and the specification treats a loopback address as trustworthy while a plain address on your local network is not, so opening the app at a machine's network address over plain http leaves the clipboard object simply undefined rather than failing with a nice error. Detect that, fall back to selecting the text so the platform copy gesture works, and say which happened. Trap: Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. Done when: Copying works on the local machine and the confirmation appears, tested. Loading the app at a network address over plain http falls back to selectable text with an explanation instead of throwing, tested in a browser at that address. The fallback path is exercised by a test that simulates the clipboard object being absent. The README states why a certificate or a loopback address is needed for one-click copying. Do not build yet: No version history or export yet. ### Phase 4 · The version that used to work Build: Every save writes the previous body to the versions table. Show the history with dates, a difference against the current body, and a one-click restore that is itself a new save. Let a version be labelled, so the one that worked can be marked as such. Trap: Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. Done when: Saving an edited prompt creates a version row containing the previous body, tested. The history shows a readable difference between any version and the current body. Restoring a version writes a new version rather than deleting anything, so the restore itself is reversible, tested. A version can be labelled and the label survives later edits. Do not build yet: No export, import or backup documentation yet. ### Phase 5 · Out of this app and into the next one Build: Export everything as one JSON file including folders, tags, variables and version history, and import it back into an empty database. Also export a folder as Markdown, one file per prompt, with the variables listed in front matter. Document the backup as copying the database file, and state what a copy taken while the app is running may miss. Trap: An export that drops version history looks complete because the prompts are all there, and the loss is discovered months later when somebody wants the wording from before a rewrite. Round-trip the export in a test that compares every table, including versions, rather than eyeballing the file. Done when: Exporting and importing into an empty database reproduces prompts, folders, tags, variables and versions, compared table by table in a test. A Markdown export of a folder produces one readable file per prompt with its variables listed. Importing a file exported by an older schema version either upgrades it or refuses with a clear message, tested. The README names the file to copy for a backup and how to take it safely. Do not build yet: Nothing after this. ### Out of scope (and why) - Team sharing, comments and permissions, which is the entire reason the paid product charges per seat and is a different application. - A browser extension that injects the library into chat tools, which is a per-site maintenance burden that breaks whenever any of them ships a change. - Running prompts against models from inside the library, which turns a filing cabinet into a chat client with an API bill. - Public share links, which need hosting, access control and an abuse story for a feature one person does not need. ### README must contain - That the library is one SQLite file and backing up means copying it, with the safe way to do that - Why one-click copying needs a loopback address or a certificate, and what the fallback does otherwise - How placeholders are parsed, that substitution happens in one pass, and what happens to braces that are not placeholders - That every save keeps the previous body and how to restore one - That there are no accounts, no network calls and no telemetry, and how to verify that by watching the network
# Agent instructions · PromptDrive indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: One Node process serving plain HTML, CSS and JavaScript, no build step, One SQLite file beside the application, schema created on first run, Query as you type over titles, bodies, notes and tags, Placeholders parsed from the body into a generated form, with required checks, Every save keeps the previous body, with a restore. 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 - Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. - Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. - Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. - Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing.
# Build plan · PromptDrive A personal prompt library whose hard problems are filling variables without producing a broken prompt, keeping the version of a prompt that used to work, and copying to the clipboard from a browser that will not give you the clipboard API. 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 library you can search faster than you can retype Finding a prompt takes one keystroke to focus and a few to filter. ### Steps 1. Create the schema on first run A setup step is a reason not to open the app. Files: `src/db.js`, `src/schema.sql` 2. Filter as you type, measured Instant or it loses to a text file. Files: `src/public/search.js`, `tests/search.test.js` 3. Make every action keyboard reachable This tool is used between two other windows. Files: `src/public/keys.js` ### Done when - [ ] Search filters within the stated time at a thousand prompts - [ ] Every list action works from the keyboard - [ ] First run creates the database unaided - [ ] An empty result offers to create that prompt ### Watch out - Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. ## Phase 2 · Variables that fail loudly A filled prompt never contains a leftover placeholder, and the form matches the body. ### Steps 1. Derive the variable list from the body on save One source of truth, so a rename cannot leave a stale field. Files: `src/variables.js`, `tests/variables.test.js` 2. Substitute in one scan, never repeatedly A second pass substitutes the values themselves. Files: `src/fill.js`, `tests/fill.test.js` 3. Block the copy while a required field is empty Name the field, because the user is mid-task. Files: `src/public/fill.js` ### Done when - [ ] A repeated placeholder fills once and appears twice - [ ] A value containing a placeholder is inserted literally - [ ] Copying is blocked with the empty field named - [ ] Non-placeholder braces survive unchanged ### Watch out - Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. ## Phase 3 · Copying, including from the phone on the sofa The copy button works on the machine and over the network, or explains itself. ### Steps 1. Feature-detect before calling the clipboard On an insecure origin the object is not there at all. Files: `src/public/copy.js`, `tests/copy.test.js` 2. Fall back to selecting the filled text The platform copy gesture always works. Files: `src/public/copy.js` 3. Explain the difference where it happens Not in a documentation file the user will not read. Files: `src/templates/prompt.html` ### Done when - [ ] One-click copy works locally with a confirmation - [ ] A network address falls back rather than throwing - [ ] The absent-clipboard path is covered by a test - [ ] The README explains the secure origin requirement ### Watch out - Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. ## Phase 4 · The version that used to work Editing a prompt never loses the wording that was working yesterday. ### Steps 1. Write a version row on every save Cheap now, priceless the day you need it. Files: `src/versions.js`, `tests/versions.test.js` 2. Show a difference, not two blocks of text Prompt edits are usually a handful of words. Files: `src/public/diff.js` 3. Make restore a new save So restoring the wrong one is also reversible. Files: `src/versions.js` ### Done when - [ ] Every save stores the previous body - [ ] History shows a readable difference - [ ] Restore creates a version rather than deleting - [ ] Version labels survive later edits ### Watch out - Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. ## Not in this build - Team sharing, comments and permissions. - A browser extension for chat tools. - Running prompts against models. - Public share links. ## After v1, if you want it - Typed variables with a select list, which removes most of the remaining filling mistakes - A duplicate detector over prompt bodies, since libraries accumulate near-identical entries - A quick-open window bound to a system shortcut, which is the only thing an extension really gave you
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The whole library. Copy this file to back it up. DATABASE_PATH=./data/prompts.sqlite # Required. Bound to the loopback address by default, which is also what makes copying work. PORT=4321 # Required. Change it to reach the app from a phone, and read the note about the clipboard first. BIND_ADDRESS=127.0.0.1 # Required. Where JSON and Markdown exports are written. EXPORT_DIR=./data/exports
# PromptDrive · product brief ## Problem The core loop, save a prompt, file it in a folder, tag it, find it, fill in variables, copy it into a chat, is a small CRUD app around one SQLite table and ships in one sitting. What the subscription actually sells is the multiplayer part: shared folders with permissions, comments that let a team iterate on a prompt, and a Chrome extension that surfaces the library inside ChatGPT, Claude, and Gemini. ## Product outcome A prompt library that finds a prompt faster than retyping it, never produces a prompt with an unfilled placeholder, keeps the version that used to work, and copies or explains why it cannot. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node.js 20+ - a browser ## Explicit non-goals for v1 - Team sharing, comments and permissions. - A browser extension for chat tools. - Running prompts against models. - Public share links. - team sharing, comments, and permissions - the Chrome extension inside ChatGPT, Claude, and Gemini - running prompts against models from the same workspace with your own API keys - public share links with unique URLs - someone else's servers & support ## Success criteria - Search is confirmed fast at a thousand prompts - A repeated placeholder is confirmed to fill once and appear twice - The absent-clipboard fallback is confirmed to explain itself - Every save is confirmed to store the previous body - Export and import are confirmed to reproduce every table
# Build brief · PromptDrive 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 local prompt library with typed variables, version history and a copy button that works from your phone too to replace PromptDrive. 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. ### Stack (fixed, do not substitute) - Runtime: One Node process serving plain HTML, CSS and JavaScript, no build step · a filing cabinet should still open in three years without reinstalling a toolchain - Storage: One SQLite file beside the application, schema created on first run · backup is copying a file, which is the correct amount of ceremony here - Search: Query as you type over titles, bodies, notes and tags · the library is only useful if finding beats rewriting - Variables: Placeholders parsed from the body into a generated form, with required checks · an unfilled placeholder pasted into a chat is the failure mode of every prompt library - History: Every save keeps the previous body, with a restore · you will edit a prompt that worked and want it back ### Data model (create this before Phase 1) - prompts(id, title, body, notes, folder_id, created_at, updated_at, last_used_at, use_count) - versions(id, prompt_id, ordinal, body, notes, saved_at, label) · the previous body, every time, because editing is destructive - folders(id, name, parent_id, position) - tags(id, name) and prompt_tags(prompt_id, tag_id) · flat tags, because a second hierarchy is a second filing system - variables(prompt_id, name, label, default_value, required, ordinal) · derived from the body on save and editable - The variables table is derived, so renaming a placeholder in the body renames it here without a migration or a stale row. ### Phase 1 · A library you can search faster than you can retype Build: Prompts with a title, body, notes, one folder and flat tags, stored in SQLite created on first run. Search filters as you type across all four fields. Keyboard throughout: a key focuses search, arrows move through results, one key opens and one copies. Seed with a few examples. Trap: Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. Done when: Typing in search filters results within a stated number of milliseconds on a library of a thousand prompts, measured with generated data. Every action in the list is reachable from the keyboard without the mouse. The database and schema are created on first run with no manual step. A search matching nothing shows an empty state that offers to create a prompt with that title. Do not build yet: No variables, copying, versions or export yet. ### Phase 2 · Variables that fail loudly Build: Parse placeholders out of the body, generate one input per distinct name, and update the filled text live. A placeholder used twice fills once and substitutes in both places. Mark fields required by default and block copying while any required field is empty. Handle braces in the body that are not placeholders rather than mangling them. Trap: Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. Done when: A prompt with the same placeholder twice fills once and substitutes in both positions, tested. A value containing something that looks like a placeholder is inserted literally and not substituted again, tested. Copying is blocked while a required field is empty and the blocked field is named. Braces in the body that are not placeholders survive filling unchanged, tested with a code sample. Do not build yet: No copying, versions or export yet. ### Phase 3 · Copying, including from the phone on the sofa Build: Copy with the clipboard API where it exists. It is declared as available only in a secure context, and the specification treats a loopback address as trustworthy while a plain address on your local network is not, so opening the app at a machine's network address over plain http leaves the clipboard object simply undefined rather than failing with a nice error. Detect that, fall back to selecting the text so the platform copy gesture works, and say which happened. Trap: Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. Done when: Copying works on the local machine and the confirmation appears, tested. Loading the app at a network address over plain http falls back to selectable text with an explanation instead of throwing, tested in a browser at that address. The fallback path is exercised by a test that simulates the clipboard object being absent. The README states why a certificate or a loopback address is needed for one-click copying. Do not build yet: No version history or export yet. ### Phase 4 · The version that used to work Build: Every save writes the previous body to the versions table. Show the history with dates, a difference against the current body, and a one-click restore that is itself a new save. Let a version be labelled, so the one that worked can be marked as such. Trap: Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. Done when: Saving an edited prompt creates a version row containing the previous body, tested. The history shows a readable difference between any version and the current body. Restoring a version writes a new version rather than deleting anything, so the restore itself is reversible, tested. A version can be labelled and the label survives later edits. Do not build yet: No export, import or backup documentation yet. ### Phase 5 · Out of this app and into the next one Build: Export everything as one JSON file including folders, tags, variables and version history, and import it back into an empty database. Also export a folder as Markdown, one file per prompt, with the variables listed in front matter. Document the backup as copying the database file, and state what a copy taken while the app is running may miss. Trap: An export that drops version history looks complete because the prompts are all there, and the loss is discovered months later when somebody wants the wording from before a rewrite. Round-trip the export in a test that compares every table, including versions, rather than eyeballing the file. Done when: Exporting and importing into an empty database reproduces prompts, folders, tags, variables and versions, compared table by table in a test. A Markdown export of a folder produces one readable file per prompt with its variables listed. Importing a file exported by an older schema version either upgrades it or refuses with a clear message, tested. The README names the file to copy for a backup and how to take it safely. Do not build yet: Nothing after this. ### Out of scope (and why) - Team sharing, comments and permissions, which is the entire reason the paid product charges per seat and is a different application. - A browser extension that injects the library into chat tools, which is a per-site maintenance burden that breaks whenever any of them ships a change. - Running prompts against models from inside the library, which turns a filing cabinet into a chat client with an API bill. - Public share links, which need hosting, access control and an abuse story for a feature one person does not need. ### README must contain - That the library is one SQLite file and backing up means copying it, with the safe way to do that - Why one-click copying needs a loopback address or a certificate, and what the fallback does otherwise - How placeholders are parsed, that substitution happens in one pass, and what happens to braces that are not placeholders - That every save keeps the previous body and how to restore one - That there are no accounts, no network calls and no telemetry, and how to verify that by watching the network
# Architecture · PromptDrive ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | One Node process serving plain HTML, CSS and JavaScript, no build step | a filing cabinet should still open in three years without reinstalling a toolchain | | Storage | One SQLite file beside the application, schema created on first run | backup is copying a file, which is the correct amount of ceremony here | | Search | Query as you type over titles, bodies, notes and tags | the library is only useful if finding beats rewriting | | Variables | Placeholders parsed from the body into a generated form, with required checks | an unfilled placeholder pasted into a chat is the failure mode of every prompt library | | History | Every save keeps the previous body, with a restore | you will edit a prompt that worked and want it back | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Library | Prompts, folders, tags and search. | Any store, provided search stays fast enough to type into. | | Variables | Parsing, the generated form and one-pass substitution. | Any syntax, provided substitution never runs twice over its own output. | | Clipboard | Feature detection and the selection fallback. | Not swappable for a direct call, which throws on an insecure origin. | | Versions | Previous bodies, differences and restore. | Any history, provided restore is itself reversible. | | Portability | JSON round trip and Markdown export. | Any format, provided versions are included and the round trip is tested. | ## 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 · The whole library. Copy this file to back it up. - `PORT` · required · Bound to the loopback address by default, which is also what makes copying work. - `BIND_ADDRESS` · required · Change it to reach the app from a phone, and read the note about the clipboard first. - `EXPORT_DIR` · required · Where JSON and Markdown exports are written. ## 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 · PromptDrive product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: One Node process serving plain HTML, CSS and JavaScript, no build step, One SQLite file beside the application, schema created on first run, Query as you type over titles, bodies, notes and tags, Placeholders parsed from the body into a generated form, with required checks, Every save keeps the previous body, with a restore. - 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 - Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. - Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. - Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. - Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. - An export that drops version history looks complete because the prompts are all there, and the loss is discovered months later when somebody wants the wording from before a rewrite. Round-trip the export in a test that compares every table, including versions, rather than eyeballing the file.
# Delivery milestones · PromptDrive Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A library you can search faster than you can retype Finding a prompt takes one keystroke to focus and a few to filter. ### Steps 1. Create the schema on first run A setup step is a reason not to open the app. Files: `src/db.js`, `src/schema.sql` 2. Filter as you type, measured Instant or it loses to a text file. Files: `src/public/search.js`, `tests/search.test.js` 3. Make every action keyboard reachable This tool is used between two other windows. Files: `src/public/keys.js` ### Done when - [ ] Search filters within the stated time at a thousand prompts - [ ] Every list action works from the keyboard - [ ] First run creates the database unaided - [ ] An empty result offers to create that prompt ### Watch out - Reloading the whole result list from the server on every keystroke makes search feel laggy at exactly the moment it needs to feel instant, and the tool loses to a text file. Keep the query snappy with an index, render results without a full page load, and measure the keystroke to update time rather than assuming it. ## M2 · Variables that fail loudly A filled prompt never contains a leftover placeholder, and the form matches the body. ### Steps 1. Derive the variable list from the body on save One source of truth, so a rename cannot leave a stale field. Files: `src/variables.js`, `tests/variables.test.js` 2. Substitute in one scan, never repeatedly A second pass substitutes the values themselves. Files: `src/fill.js`, `tests/fill.test.js` 3. Block the copy while a required field is empty Name the field, because the user is mid-task. Files: `src/public/fill.js` ### Done when - [ ] A repeated placeholder fills once and appears twice - [ ] A value containing a placeholder is inserted literally - [ ] Copying is blocked with the empty field named - [ ] Non-placeholder braces survive unchanged ### Watch out - Substituting with a naive replacement pass lets a value that itself contains a placeholder get substituted on the next pass, so a prompt filled with text pasted from another prompt comes out with fragments replaced in ways nobody asked for. Substitute once over the original body in a single scan, never repeatedly until nothing changes. ## M3 · Copying, including from the phone on the sofa The copy button works on the machine and over the network, or explains itself. ### Steps 1. Feature-detect before calling the clipboard On an insecure origin the object is not there at all. Files: `src/public/copy.js`, `tests/copy.test.js` 2. Fall back to selecting the filled text The platform copy gesture always works. Files: `src/public/copy.js` 3. Explain the difference where it happens Not in a documentation file the user will not read. Files: `src/templates/prompt.html` ### Done when - [ ] One-click copy works locally with a confirmation - [ ] A network address falls back rather than throwing - [ ] The absent-clipboard path is covered by a test - [ ] The README explains the secure origin requirement ### Watch out - Calling the clipboard write directly assumes the object exists, so on a phone loading the app at a local network address the call throws on the missing object rather than rejecting a promise, and a catch written around a rejected promise never runs. Check for the object before calling, provide the selection fallback, and show a message explaining why the button behaves differently there. ## M4 · The version that used to work Editing a prompt never loses the wording that was working yesterday. ### Steps 1. Write a version row on every save Cheap now, priceless the day you need it. Files: `src/versions.js`, `tests/versions.test.js` 2. Show a difference, not two blocks of text Prompt edits are usually a handful of words. Files: `src/public/diff.js` 3. Make restore a new save So restoring the wrong one is also reversible. Files: `src/versions.js` ### Done when - [ ] Every save stores the previous body - [ ] History shows a readable difference - [ ] Restore creates a version rather than deleting - [ ] Version labels survive later edits ### Watch out - Overwriting the body on save means the afternoon spent tightening a prompt is gone when the tightened version turns out worse, and the only record of the good wording was in a chat window that has since scrolled away. Keep the previous body on every save, which for a library this size costs nothing. ## M5 · Out of this app and into the next one (production only) The whole library moves as files, and the backup story is one line. ### Steps 1. Round-trip every table, versions included The history is the part an export usually loses. Files: `src/export.js`, `tests/roundtrip.test.js` 2. Export Markdown for humans as well as JSON One file per prompt travels anywhere. Files: `src/markdown.js` 3. Refuse an import you cannot read Better than a partial import nobody notices. Files: `src/import.js` ### Done when - [ ] Export and import reproduce every table - [ ] Markdown export gives one readable file per prompt - [ ] An unreadable import is refused with a reason - [ ] The README names the backup file and method ### Watch out - An export that drops version history looks complete because the prompts are all there, and the loss is discovered months later when somebody wants the wording from before a rewrite. Round-trip the export in a test that compares every table, including versions, rather than eyeballing the file.
# Operations · PromptDrive ## Backup Copy the database file on a schedule and keep a few generations. For a file this small there is no reason to be clever, and the export gives you a second format that does not depend on SQLite at all. ## Restore Copy the file back, open the app, and check the prompt count and one version history. If versions are missing you restored an export rather than the database. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring None worth running for one user. The suite covers what matters: search speed at a thousand prompts, single-pass substitution, the absent-clipboard path, and the export round trip. ## Incident checklist The realistic incident is a prompt edited into uselessness, and the version history is the entire answer. The second is a copy button that stopped working, which is almost always the origin rather than the code. 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 - [ ] Search is confirmed fast at a thousand prompts - [ ] A repeated placeholder is confirmed to fill once and appear twice - [ ] The absent-clipboard fallback is confirmed to explain itself - [ ] Every save is confirmed to store the previous body - [ ] Export and import are confirmed to reproduce every table ## Launch constraint Do not market omitted PromptDrive 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. The whole library. Copy this file to back it up. DATABASE_PATH=./data/prompts.sqlite # Required. Bound to the loopback address by default, which is also what makes copying work. PORT=4321 # Required. Change it to reach the app from a phone, and read the note about the clipboard first. BIND_ADDRESS=127.0.0.1 # Required. Where JSON and Markdown exports are written. EXPORT_DIR=./data/exports
$ choose a build depth, inspect the files, then open the complete pack in your agent
Teams pay for the shared workspace: private folders someone else maintains, comments that turn a prompt into a living document, permissions, and an extension that follows the team into whichever chat AI they use. A local library covers one person fine; the per-seat fee is really about keeping five people on the same page.
xteam sharing, comments, and permissions
xthe Chrome extension inside ChatGPT, Claude, and Gemini
xrunning prompts against models from the same workspace with your own API keys
xpublic share links with unique URLs
xsomeone else's servers & support
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
PromptDrive pricing
team$5/mo · monthly per user · $60/yr
free tierThe free Personal plan includes unlimited prompts, public sharing links, and the Chrome extension.
verified 2026-08-10 · source ↗
Is PromptDrive free?
The free Personal plan includes unlimited prompts, public sharing links, and the Chrome extension. Paid is Team at $5/mo (checked 2026-08-10).
Vibecode PromptDrive
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal PromptDrive replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does PromptDrive cost?
PromptDrive costs about $5/month (Team, checked 2026-08-10), which is $60 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing PromptDrive?
Honestly: team sharing, comments, and permissions; the Chrome extension inside ChatGPT, Claude, and Gemini; running prompts against models from the same workspace with your own API keys; public share links with unique URLs; someone else's servers & support. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to PromptDrive?
Yes: Langfuse (An open source LLM engineering platform whose prompt management gives a team versioned, shared prompts, if you are happy running the whole stack for that one feature.) The prompt is for when you want it exactly your way.