An agent is a small helper program. Someone types words in, your agent thinks about them with an AI model, and sends words back. On Druve, an agent is one file with one function inside. People hire it, run it on their own computer, and pay you for it. This page walks you through the whole thing, start to finish.
How long this takes
About 20 minutes for your first agent, if you already know a little JavaScript. If you have never written code, the browser playground and the AI helpers near the end of this page can write the file for you. You still get to read it and change it.
What is on this page
Skim this now, or come back when a word looks new.
Three ways to make an agent. This page shows the first one.
Whichever way you pick, Steps 6 to 13 are the same for everyone.
You need Node.js on your computer. If you are not sure you have it, open a terminal and type node -v. If you see a version number, you are set. If not, install it from nodejs.org (pick the LTS button).
Then run this one line. It makes a folder with everything you need inside:
$ npx @druve/cli init my-agentnpxmeans "download this tool and run it once." You do not have to install anything first. You will get a folder called my-agent with three files:
index.mjs: your agent. This is the only file that matters.package.json: a list of what your project uses. Leave it alone.README.md: a short note with the next two commands.Open the folder in any code editor (VS Code is free and popular). You are editing a working program from the first second, not a blank page.
Here is the whole starter file, with a comment above each part:
// index.mjs
// This is a whole Druve agent. One file, one function.
import { getModelClient } from '@druve/cli/providers';
export async function handleMessage(input, providerConfig, context) {
// 1. Ask Druve for a ready-to-use AI model.
// The customer already picked which one and typed in their own key.
const model = getModelClient(providerConfig);
// 2. Tell the model what job to do, and give it the customer's text.
const answer = await model.complete(`
You help a small business chase unpaid invoices.
Read the invoice below and write a short, polite payment reminder.
${input}
`);
// 3. Send the words back. That is the whole job.
return answer;
}What each piece means:
import ... from '@druve/cli/providers': borrows one helper from Druve, getModelClient. That is the only thing you import from us.export async function handleMessage(...): the function Druve calls when a customer uses your agent. The name must be exactly handleMessage.input: the words the customer typed. A plain string.providerConfig: a sealed box that says which AI provider the customer chose. You hand it to getModelClient and never open it (Step 5 explains why).context: extra powers, like a web browser your agent can drive. Most agents never use it (Step 9).model.complete(text): sends your instructions plus the customer's input to the model and waits for the reply.return answer: whatever string you return is what the customer sees.That is the entire contract: text in, text out. No server. No deploy. No framework you have to learn. Inside the function you can write whatever code you like, and even use your favorite library, as long as it all ends up in this one file (Step 6 has a tip on that).
The starter chases invoices. Change the words inside model.complete(...) to change the job. That text is called a prompt. A good prompt says three things:
Some ideas people have shipped:
Pick one job and do it well. A narrow agent that always works beats a wide one that sometimes does. It is also much easier to write honest scopes for (Step 7).
You can also do work before or after the model call in normal JavaScript: clean the input, split it into parts, format the answer. If you need data from the internet (a weather API, a price feed), see Step 9 first.
Run your agent on your own computer with a test message:
$ npx @druve/cli dev test index.mjs 'Invoice #42 for $300 is 10 days late'The first time, this uses a free pretend model called echo. It does not think; it just proves your file loads and returns something. No key, no cost, works offline.
To use a real model, set your own key in the terminal first, then run the same command:
$ export ANTHROPIC_API_KEY=your-key-here(OPENAI_API_KEY works too.) This key is only for your own testing. Your customers will use their own. There is no license or login needed while you build.
No terminal handy? The browser playground runs the same file inside your browser tab, with nothing to install. Test runs there are free and unlimited on every plan.
Your customer types their API key into Druve's own setup, on their own computer. It never goes to Druve's servers and it never goes to you. Your code must treat it the same way: you can use the model, but you can never look at, copy, or send the key.
getModelClient(providerConfig) and use what it gives you.providerConfig.credential, or pull it out any other way.fetch() it to any website.An automatic check looks for all of these before you can publish, and tells you exactly what it found so you can fix it. If something slips through and is found later, the listing comes down and the account is closed (Step 13). This is the rule that keeps customers safe enough to run a stranger's code, so it is the one we do not bend on.
Druve reads your agent from GitHub. If you have never used it: make a free account, click New repository, give it a name, and upload index.mjs (drag and drop works). Every time you save a change on GitHub, that is a new commit.
Then open index.mjs on GitHub and copy the address from your browser bar. It looks like this:
https://github.com/your-name/your-repo/blob/main/index.mjsPublic or private, both work
When you publish, Druve downloads the file once, checks it, and keeps its own copy of that exact commit. Customers run Druve's copy. So your repository can be private, and you can keep changing the repository afterwards without changing what customers run.
For a private repository, the wizard has a field called Private repo? Add a read-only token. On GitHub go to Settings, Developer settings, Fine-grained tokens, and make a token for just that one repository with Contents: Read-only. Paste it in. Druve uses it for that one download and does not keep it.
If your agent is more than one file
Druve fetches one file. Use a bundler to squash everything into a single index.mjs, then upload that one. esbuild is the easy one:
$ npx esbuild src/index.mjs --bundle --format=esm --outfile=index.mjsKeep the output readable; minified or scrambled code fails the automatic check.
Go to Build and start the wizard. It saves a draft as you go. Here is what each field is for and how to write it well.
Good
Can: Read invoice text · Draft reminder emails
Cannot: Send email · Read files outside the input · Make payments
Too vague
Can: Anything you need!
Cannot: (blank)
If the code does something the scopes do not say, that is grounds for taking the listing down. Write the scopes to match the code, not the other way around.
claude-sonnet-5. Customers see it as the default, and can still pick any model their own provider offers.Click Publish. In the next few seconds Druve does four things:
eval, no shell commands, no scrambled text hiding what the code does.If the check finds a problem, you see exactly what and where, right in the wizard. Fix it, push to GitHub, and publish again. No penalty, no waiting.
Changing it later
Edit the listing any time. To ship new code, paste the GitHub link again and Druve locks the listing to the new commit. Customers who already hired it keep running the version they hired until you change it. On the Free plan you can have one agent published at a time; Pro and Max lift that.
Most agents call a model and return text. If yours is one of those, skip this step.
Driving a website. If your agent needs to click through a site for the customer, list the site under Browser targets in the wizard, one per line, like app.example.com or *.example.com for a whole domain. Then context gives you a browser:
const browser = await context.getBrowser();
await browser.goto('https://app.example.com/inbox');
await browser.click('#compose');
await browser.type('#body', 'Drafted from: ' + input);
const text = await browser.readText('#thread');
await browser.close();The customer's computer blocks any page outside your list, even a redirect. When they run it in a terminal, each click or typed field asks them to allow it first. So write your code expecting a step to be refused: catch the error and explain, instead of assuming every action went through. Try it locally with:
$ npx @druve/cli dev test index.mjs "try it" --browser-targets example.comFetching outside data. If your code calls fetch() to get something from the internet (a price, the weather), list that site under Data fetch targets the same way. Customers who run your agent right in their browser tab can only reach the sites you declared. Leave both lists empty unless you truly need them; every extra site is something a customer has to trust.
You do not build any of this. It happens on its own once you are live.
druve login opens their browser to Druve so they can sign in. The terminal never sees a password.druve setup asks which AI provider they use and saves their key on their own computer. They can also pick a model here; your recommendation is the default.druve activate claims their license for that one computer.druve runchecks the license with Druve, downloads Druve's copy of your file, and runs it. If they stop paying, the next run stops working.They can also press "Run in browser" on the hire page and skip the install entirely. Either way, your code runs on their machine, with their key, never on Druve's servers.
When someone hires a paid agent, they pay through Druve's PayPal checkout. Druve keeps 5% and you get 95%. A $10 hire means $9.5 to you.
Payouts go to the verified PayPal or Venmo address on your Earnings page, sent by PayPal Payouts. They are sent in batches by a person at Druve, not on a fixed schedule, so allow a few days. That page also shows what is waiting and every payout so far. If you have not verified an address, the money waits until you do. Nothing is lost, but nothing moves either.
You need a PayPal account that can receive money. If you are under 18, the account has to be in a parent or guardian's name; PayPal does not allow accounts for minors.
Publishing is free. Pro and Max are optional plans for developers who want more (more published agents, saved test runs, analytics, Certified reviews included). They never change where your agent ranks. See Pricing.
Certified is a badge on your listing that means a real person at Druve read your exact code and checked it. It is optional. It costs $5 for a Standard review or $12 for Boosted (faster queue, more free re-reviews, notes from the reviewer). Max plans include Standard reviews. The fee pays for the review, not for the badge: many applications do not pass the first time.
The reviewer checks these four things, in this order:
The badge is tied to the exact commit that was reviewed. Every edit is reviewed again: change the code, instructions, scopes, targets, name, or description and the badge pauses until a re-review clears the new version. You get 2 free re-reviews a year on Standard and 4 on Boosted. Price, avatar, and visibility can change freely.
Apply from your agent's edit page once it is published. The reviewer reads Druve's stored copy of your commit, so a private repo is reviewed exactly like a public one.
Code built to hurt a customer means a permanent ban
Agents run on real people's computers. If a file is built to steal keys or passwords, damage or lock files, install something that stays behind, mine crypto, open a back door, or send data to a server you control, the listing comes down, every version is removed, and the account is closed for good with no payout of anything owed.
Hiding code by scrambling or minifying it counts as intent, not style.
Honest mistakes are different. A bug, or an agent that reaches a little further than its scopes by accident, is something to tell us about, and we will sort it out with you.
Two smaller rules that also matter:
/blob/in it), not the repository's front page. If the repo is private, add the read-only token.providerConfig directly, or an eval. Fix it and publish again.handleMessage and it must have export in front.fetch() on a site you did not list under Data fetch targets (Step 9).Publishing from Claude, ChatGPT, or n8n instead of the wizard
Everything on this page also works through Druve's developer tools for AI clients. Claude and ChatGPT connect with a normal "Add connector" sign-in and can create or update a listing for you from a description. n8n uses a Personal Access Token instead; see the n8n setup guide. The same safety check and the same rules apply no matter which door you come in through.
Ready?
Start the wizard and save a draft any time. You only need the code link when you press Publish.
Build an agent