Service scaffolding
"REST CRUD for User with email (required) + role" is enough to start a service. Bowire turns the sentence into a small spec: one entity, its fields and a protocol. From the spec it generates:
| File | What it is |
|---|---|
openapi.yaml or <service>.proto |
The contract: list, get, create, update, delete |
Program.cs, <Service>.csproj (+ <Service>Service.cs for gRPC) |
A runnable C# stub that keeps its data in memory — replace the store, keep the routes |
bowire/<entities>.collection.json |
A workbench collection: every operation, plus an unknown id and a create without its required fields |
bowire/<entities>.smoke-test.json |
A smoke test for bowire test |
bowire-scaffold.json |
The spec itself, to edit and generate again |
README.md |
How to run and try it |
The stub runs with dotnet run, and the smoke test passes against it as generated.
The model writes the spec, never the code
The assistant only reads the sentence into a spec. The files come from templates checked in to Kuestenlogik.Bowire.Scaffold, which is why a 3B local model is enough and a different model changes at most the field list, never the shape of the code. The same spec gives the same bytes on every machine.
Which model is asked:
- The configured provider, when it is local (Ollama, LM Studio).
- When the configured provider is a cloud one, a local Ollama or LM Studio that answers first, and the cloud provider only when the local one fails.
Bowire:Ai:Scaffold:PreferLocal=falseasks the configured provider only. - When no model gives a usable spec — none configured, none reachable, or an answer that is not a spec — a deterministic parser reads the sentence. The dialog and the CLI say which one wrote the spec, and why the others did not.
The parser understands the shape above: the protocol from rest / grpc, the entity from the word after for, the fields from the list after with, split on commas, + and and. A type word next to a field sets its type (total: double, paid bool, year int), and required, (required) or a trailing * makes it required. German works too: für, mit, und, Pflicht.
In the workbench
Sources → + Scaffold a service, or Scaffold a service… in the command palette.
- Describe — the sentence and, optionally, the protocol. Propose asks the assistant; Read without AI asks the parser.
- Spec — the spec as JSON. Fix what the model got wrong here, before a file exists.
- Files — every generated file, each one editable.
Add to workspace writes the files to scaffold/<Service>/ in the workspace folder, uploads the schema the way Upload schema files does — so the service shows up in discovery at once — and adds the collection. The requests that need an id read it from the environment variable <entity>Id: create one, copy its id into the variable, and get, update and delete follow it.
On the command line
bowire scaffold "REST CRUD for User with email (required) + role" -o users
cd users && dotnet run
bowire test bowire/users.smoke-test.json
| Option | |
|---|---|
--protocol, -p |
rest or grpc; wins over the sentence |
--ai |
Let the assistant write the spec — local model first, then the configured provider (Bowire__Ai__*, or what Settings → AI saved). Without it the parser reads the sentence. |
--print-spec |
Print the spec and stop |
--spec <file> |
Generate from a spec file instead of a sentence |
--base-url |
Where the stub listens; default http://localhost:5000 |
--out, -o |
The folder; default ./<Service> |
--force |
Write into a folder that is not empty |
--print-spec, an edit, then --spec is the command-line version of the dialog's spec step.
The spec
{
"entity": "User",
"protocol": "rest",
"fields": [
{ "name": "email", "type": "string", "required": true },
{ "name": "role", "type": "string" }
],
"service": "Users",
"baseUrl": "http://localhost:5000"
}
| Field | |
|---|---|
entity |
Singular; brought into PascalCase |
protocol |
rest (OpenAPI 3 + minimal API) or grpc (proto3 + Grpc.AspNetCore with server reflection) |
fields |
Up to 40; camelCase on the wire. Types: string, int, long, double, bool, datetime, uuid. A field called id is dropped — every entity gets one. |
service |
Default: the entity's plural |
baseUrl |
Default http://localhost:5000 |
In the proto, datetime and uuid are strings (ISO 8601, UUID), and every scalar other than a string is optional, so a required number can be told apart from a zero.
Not yet
C# is the only stub language; REST and gRPC the only protocols. More languages and protocols are templates next to the existing ones.