Automatic setup
What `veefore` finds in your project, what it creates, how it changes your code, and how to do any part of it by hand instead.
veefore, run in an agent project with no command, does the whole integration:
sign in, scan, set up, change the code, verify. Each step shows what it is about
to do and asks before doing it. Each is also a command of its own, so you can
run, skip or repeat any part.
| Step | Command on its own | Changes anything? |
|---|---|---|
| Sign in through the browser | veefore login |
Stores a session token |
| Scan the project | veefore scan |
No |
| Create agent, tools, policy | veefore init |
In Veefore, after you confirm |
Wrap tools in guard() |
veefore integrate |
Your files, after you confirm |
| Verify | veefore integrate --check |
No |
| Reverse the code change | veefore undo |
Restores your files |
| Take Veefore out entirely | veefore remove |
Your files, then disconnects the project |
#The scan
Static and local. The project's code is read, never run, and nothing is sent anywhere.
Frameworks recognised: Vercel AI SDK, OpenAI function tools, OpenAI Agents SDK (TypeScript and Python), LangChain.js and LangChain, CrewAI, and MCP servers in both languages.
No framework: tools written as plain data, the way you hand them to a model
yourself: an object or dict with name, description and an input schema
(parameters, input_schema or inputSchema), in TypeScript, JavaScript or
Python. All three keys must be on the same object, so a config block that only
has a name is not mistaken for a tool.
For each tool it reports:
- The external hosts it calls, from
fetch,axios,requests,httpxand base-URL assignments. LLM provider endpoints are recognised as the model's and left out. These become the tool's destination allowlist. - The credentials it reads, by environment variable name only. Model keys
such as
OPENAI_API_KEYare listed separately and never touched. - A risk level, from the tool's name. A high-impact word anywhere
(
refund,delete,transfer,deploy, …) makes itHIGHanddangerous. Otherwise the leading verb decides:lookupOrderreads,placeOrderwrites. Unknown intent isMEDIUM, neverLOW. A risk the definition states itself (risk: 'critical') can raise the rating but never lower it.
What it will not do: open .env or any other hidden file, read a secret value,
or descend into node_modules, virtualenvs, build output or tests.
bashveefore scan # what setup would find, without signing in
veefore scan --json # the scan and the plan it implies, for scripts
Detection is pattern-based and can miss an unusual definition. Anything it misses can be registered in the console under Tools.
#What setup creates
After one confirmation, through the same public API the console uses:
- A capability per tool,
tool.<key>, so authorizing one tool grants nothing else. Existing ones are reused. - The agent, with an Ed25519 key pair generated on your machine. Only the
public key is sent; the private key goes to
.env.localand nowhere else. - Each tool, registered and then authorized for this agent only. Registration on its own grants nothing.
- A starter policy from the safe-defaults template, bound to the workspace,
with the environment in
OBSERVE. Tools flagged dangerous require approval under it once you enforce.
Running it again in another project reuses capabilities and tools that already exist rather than duplicating them.
#Client-executed tools
Setup registers tools as kind CLIENT: Veefore decides, your code
executes. Every call is authenticated, checked against policy, risk and
approval, and recorded; when it is permitted, guard() runs your function.
The credential stays in your code, so this is a control only while calls go
through guard(). A CLIENT tool cannot be given an endpoint or a brokered
credential, so it never claims more protection than it has.
To close that gap, broker the credential and
re-register the tool as HTTP. Veefore then performs the call itself with a key
your agent never holds, and guard() returns that result without running your
function, so nothing executes twice.
#The code change
veefore integrate (offered automatically at the end of setup) wraps the
function that performs each registered tool.
diff+import { guard } from '@veefore/sdk';
export const tools = {
lookupOrder: tool({
description: 'Look up an order',
- execute: async ({ orderId }) => {
+ execute: guard('lookup_order', async ({ orderId }) => {
return orders.get(orderId);
- },
+ }),
}),
};
diff from langchain_core.tools import tool
+from veefore import guard
@tool
+@guard("lookup_invoice")
def lookup_invoice(invoice_id: str) -> dict:
"""Fetch an invoice by id."""
@guard goes directly above def, below the framework's decorator. It uses
functools.wraps, so @tool, @function_tool and @mcp.tool() still see the
original name, signature, type hints and docstring.
The rules the rewrite follows:
- Only registered tools are touched, and only the function is wrapped. Your function bodies are never edited.
- JavaScript/TypeScript: only
asyncfunctions.guard()returns a Promise, so wrapping a synchronous function would change what it returns. - It never guesses. A tool it cannot place with certainty is listed with the exact change to make (see below).
- Re-running changes nothing. Already-guarded tools are left alone. If your
file already uses the name
guard, the import is aliased.
#Safety
- You see a unified diff of every file and are asked once.
- A baseline typecheck runs first, so errors your project already had are not blamed on the change.
- Every changed file is backed up to
.veefore/backups/. - Then your own checks run: the project's TypeScript compiler (
--noEmit),node --checkfor plain JavaScript, or a Python syntax parse. If one newly fails, every file is restored automatically.
bashveefore integrate --dry-run # show the diff, write nothing
veefore undo # restore the last change
veefore undo leaves alone any file you edited after the change and lists it;
--force restores it anyway, losing your later edits.
#Tools defined as data
When tools are plain definitions, no one function performs each tool: your code runs whichever tool the model picked, usually in one dispatch function. One guard there covers every tool routed through it.
veefore integrate finds that place and adds the guard itself when the shape
is unambiguous: a function named like executeTool, runTool or
handle_tool_call, whose argument carries the tool's name (call.tool,
call.name), with exactly one awaited call inside that receives it.
diff+import { guard } from '@veefore/sdk';
…
export async function executeTool(call: ToolCall): Promise<ToolResult> {
…
- result = await executor.execute(call);
+ result = await guard(call.tool, (_args: typeof call.params) => executor.execute(call))(call.params);
It goes through the same diff, backup, typecheck and rollback as every other
change. In a monorepo, each changed file is typechecked by its own package's
tsconfig.json.
When the place is found but the shape is not certain, or the project is Python, you get the same three choices as below: make the change by hand from exact instructions naming the file and function, or have AI make it with your own key or with Veefore AI. An AI change here is accepted only if it adds a single guard keyed by the tool name and nothing else.
tsimport { guard } from '@veefore/sdk';
// before: const result = await runTool(call);
const result = await guard(call.name, (_args: typeof call.args) => runTool(call))(call.args);
pythonfrom veefore import guard
# before: result = run_tool(call.name, call.args)
result = guard(call.name, describe=lambda _args: {"parameters": _args})(
lambda _args: run_tool(call.name, call.args))(call.args)
The first argument must be the tool's Veefore key, which is its name when that
is lower_snake_case. A refusal throws, so the guard belongs where your code
already turns errors into a tool result for the model.
veefore integrate --check then reports those tools as guarded at the
dispatch point. It checks that the guard exists, not that every path reaches
it, so keep it where every tool call passes.
#Tools the rewrite will not change
A method-style execute, a synchronous function, an OpenAI tool that is only a
schema, or LangChain Tool(func=…). For these you choose:
textHow would you like to handle them?
1 I will change them myself (show me exactly what to change)
2 Use AI with my own Anthropic or OpenAI key (code goes only to that provider)
3 Use Veefore AI (code is sent to Veefore, then to its model provider)
4 Skip for now
Or choose up front with --assist=manual, --assist=own, --assist=veefore
or --assist=off. Without a terminal to ask, the default is manual.
#By hand
Nothing is sent anywhere. For each tool you get the file, the line, the import to add and the exact code to change it to:
textsrc/agent.ts
Add once, with the other imports:
import { guard } from '@veefore/sdk';
lookup_order near line 8 — execute is written as a method
// before: async execute(args) { … }
execute: guard('lookup_order', async (args) => { … }),
When you are done:
bashveefore integrate --check
It lists every registered tool as guarded or not, and exits 1 if any are not,
so it also works as a CI check.
#With AI
Both AI options work the same way:
- Secrets are replaced with placeholders before anything leaves your machine, and restored locally afterwards: provider keys (Stripe, OpenAI, Anthropic, AWS, GitHub, Slack, Google), JWTs, passwords inside URLs, private keys, and random-looking string literals.
- You see what is sent and to whom, and must agree: which files, the destination, the model, and how many secrets were removed. Only the files that need the change are sent.
- The reply is untrusted. It is accepted only if it keeps every token of
your file, adds no new string, adds almost nothing beyond the
guard()change and actually guards the requested tools. A rejected or declined reply falls back to the manual instructions. - An accepted change is marked "proposed by AI" in the diff and goes through the same confirmation, backup, checks and rollback as everything else.
Your own key. Uses ANTHROPIC_API_KEY or OPENAI_API_KEY from your shell,
or a key you paste, held in memory for that run only. It lists the models your
key can use and you pick one (VEEFORE_AI_MODEL preselects). Your code goes from
your machine straight to that provider, never through Veefore.
bashANTHROPIC_API_KEY=… veefore integrate --assist=own
Veefore AI. Available when it is enabled for your Veefore, with the models its operator chose. Your code goes to Veefore and then to its model provider. It is used for that request only and never stored or logged; the audit trail records who asked, which model, which tools and the file size. Rate limited per person.
#Removing Veefore from a project
bashveefore remove --dry-run # show what would change
veefore remove
It offers to revoke the agent if it is still active, takes every guard()
back out of the code, and then disconnects the project by deleting
veefore.config.json and the Veefore block in .env.local, including the
private key. The code change gets the same diff, confirmation, backup, checks
and rollback as adding it. Unlike veefore undo, it rewrites the code instead
of restoring an old copy, so edits you made after setup are kept.
Revoked in the console? The console cannot change files on a developer's
machine, so revoking an agent there stops it at once, and every guarded call is
refused from then on, but leaves the code as it is. The next time anyone runs
veefore in that project, it notices and offers two ways forward: remove
Veefore from the code, or set the project up again with a new agent. The
guarded code keeps working with the new agent, because the tool keys are the
same. veefore integrate refuses to add guards for a revoked agent and points
at the same two options.
#Running veefore again
In a project that is already connected, veefore shows the agent, the
environment and the agent's status in Veefore, then offers: check status,
protect tools in the code, re-run setup, remove Veefore from the project, or
sign out.