Šéŕṽéŕ ÀÞÎ
Bowrain þŕöṽîđéš ƃöţĥ ŔÉŠŢ àñđ ĝŔÞÇ ÀÞΚ ƒöŕ þŕöĝŕàḿḿàţîç àççéšš ţö ţĥé þļàţƒöŕḿ.
ŔÉŠŢ ÀÞÎ
Ţĥé ŔÉŠŢ ÀÞÎ šéŕṽéš öñ ţĥé çöñƒîĝüŕéđ ĤŢŢÞ þöŕţ üñđéŕ /api/v1. Éṽéŕý ŕöüţé
ţĥàţ ţöüçĥéš à ŵöŕķšþàçé'š đàţà îš ŵöŕķšþàçé-šçöþéđ: :ws îš ţĥé ŵöŕķšþàçé
šļüĝ, :id à þŕöĵéçţ îđ, àñđ :ref ţĥé šţŕéàḿ (main ŵĥéñ à þŕöĵéçţ ĥàš öñļý
öñé). ÀÞÎ ţöķéñš (šéé Ḿéḿƃéŕš àñđ ŕöļéš)
àüţĥéñţîçàţé àš Authorization: Bearer <token>.
Ĥéàļţĥ
GET /api/v1/health
Þŕöĵéçţš
GET /api/v1/:ws/projects # List projects in a workspace
POST /api/v1/:ws/projects # Create a project
GET /api/v1/:ws/:id # Get a project
PUT /api/v1/:ws/:id # Update a project
DELETE /api/v1/:ws/:id # Delete a project
GET /api/v1/:ws/:id/blocks/:ref # Blocks on a stream (?item=&locale=&status=&q=)
GET /api/v1/:ws/:id/blocks/:ref/:bid # One block
Šýñç
Ţĥé šýñç ŕöüţéš àŕé ŵĥàţ ţĥé kapi-bowrain þļüĝîñ šþéàķš. À þüšĥ đéçļàŕéš îţš
ţŕéé, üþļöàđš öñļý ŵĥàţ ţĥé šéŕṽéŕ ļàçķš, àñđ çöḿḿîţš à ḿàñîƒéšţ; à þüļļ ŕéàđš
ţĥé šéŕṽéŕ'š ţŕéé àñđ ţĥé çĥàñĝéš šîñçé ţĥé çļîéñţ'š ŕéƒ.
GET /api/v1/:ws/:id/sync/:ref/tree # The declared tree
GET /api/v1/:ws/:id/sync/:ref/ref # The server's current ref
GET /api/v1/:ws/:id/sync/:ref/pull # Changes since a ref
GET /api/v1/:ws/:id/sync/:ref/blocks # Blocks by id
GET /api/v1/:ws/:id/sync/:ref/status # Standing of an in-flight push
GET /api/v1/:ws/:id/sync/:ref/blobs/:key # Download a blob
POST /api/v1/:ws/:id/sync/:ref/push/init # Declare the tree; receive what to upload
POST /api/v1/:ws/:id/sync/:ref/push/uploads # Presigned upload URLs for the chunks
PUT /api/v1/:ws/:id/sync/:ref/push/chunks/:uploadId/:chunkIndex # Proxied chunk upload
POST /api/v1/:ws/:id/sync/:ref/push/commit # Commit the manifest (202; a worker applies it)
À þüļļ ŵàļķš ţĥé šţŕéàḿ'š çĥàñĝé ļöĝ ƒöŕŵàŕđ ƒŕöḿ ţĥé çļîéñţ'š çüŕšöŕ àñđ šéŕṽéš éàçĥ çĥàñĝéđ ƃļöçķ üñđéŕ ţĥé îţéḿ îţ ƃéļöñĝš ţö. À þàĝé çàŕŕîéš éàçĥ ƃļöçķ öñçé, àţ îţš ļàţéšţ çĥàñĝé šîñçé ţĥé çüŕšöŕ, šö à þüļļ ƒŕöḿ ţĥé šţàŕţ šéŕṽéš éṽéŕý ƃļöçķ öñçé, àñđ à ƃļöçķ ţĥàţ çĥàñĝéš àƒţéŕ îţš þàĝé àŕŕîṽéš àĝàîñ öñ à ļàţéŕ þàĝé. À ƃļöçķ ŕöŵ ţĥàţ ƃéļöñĝš ţö ñö îţéḿ îš ļéƒţ öüţ öƒ ţĥé ŕéšþöñšé, àñđ ţĥé çüŕšöŕ ţĥé ŕéšþöñšé ŕéţüŕñš šţîļļ ḿöṽéš þàšţ îţš çĥàñĝé, šö éṽéŕý çļîéñţ çöñţîñüéš ƒŕöḿ ţĥé šàḿé þļàçé. Ţĥé šéŕṽéŕ ļöĝš ĥöŵ ḿàñý šüçĥ ŕöŵš à þüļļ ļéƒţ öüţ.
Šéŕṽéŕ ŵöŕķ ţĥàţ ŕéàđš ƃļöçķš àñđ ļàţéŕ ŵŕîţéš ţĥéḿ ƃàçķ, šüçĥ àš šöüŕçé šéţţļéḿéñţ, ţŕàñšļàţîöñ àñđ éẋţŕàçţîöñ ĵöƃš, þšéüđö-ţŕàñšļàţîöñ àñđ ţĥé éđîţöŕ'š ƃüļķ àçţîöñš, ŵŕîţéš éàçĥ ƃļöçķ öñļý ţö ţĥé ŕöŵ îţ ŕéàđ, àñđ öñļý ŵĥîļé ţĥàţ ŕöŵ šţîļļ ĥöļđš ţĥé çöñţéñţ ĥàšĥ îţ ŕéàđ. À ƃļöçķ ţĥàţ à þüšĥ ŕéḿöṽéđ öŕ ŕéŵŕöţé îñ ţĥé ḿéàñţîḿé ķééþš ŵĥàţ ţĥé þüšĥ ļéƒţ: à ŕéḿöṽéđ îţéḿ šţàýš ŕéḿöṽéđ, àñđ à þüšĥéđ šöüŕçé îš ñéṽéŕ ŕéþļàçéđ ƃý ţĥé ŵöŕđîñĝ ţĥé ĵöƃ ŕéàđ. Ţĥé þŕöþéŕţîéš šüçĥ ŵöŕķ ŕéçöŕđš öñ à ƃļöçķ, šüçĥ àš ţĥé šöüŕçé šéţţļéḿéñţ šţàḿþ, àŕé šţöŕéđ ŵîţĥöüţ çĥàñĝîñĝ ţĥé ƃļöçķ'š šţöŕéđ çöñţéẋţ ĥàšĥ, šö ţĥé þŕöđüçéŕ'š ñéẋţ þüšĥ çöḿþàŕéš àĝàîñšţ ţĥé ĥàšĥéš îţš öŵñ þüšĥ šţöŕéđ àñđ üþļöàđš öñļý ŵĥàţ çĥàñĝéđ.
À þüšĥ àššéŕţš ţĥé ŕéƒ îţ ļàšţ öƃšéŕṽéđ öñļý ƒöŕ ţĥé ĝöṽéŕñàñçé îţ ŵŕîţéš. Îţ
àššéŕţš ţĥé đéçîšîöñš çöḿþöñéñţ ŵĥéñ îţš ŕéçöŕđš îñçļüđé à đéçîšîöñ (à ŕéṽîéŵ
šţàţé, à ŕéṽîéŵéđ öŕ šîĝñéđ-öƒƒ ŕüñĝ, à þàŕķéđ üñîţ, àñ àššîĝñéé öŕ à ñöţé), àñđ
ţĥé šéŕṽéŕ ŕéƒüšéš îţ ŵîţĥ 409 governance_moved ŵĥéñ àñöţĥéŕ đéçîšîöñ ĥàš
ļàñđéđ šîñçé. Ŕéçöŕđš ţĥàţ šàý öñļý ŵĥàţ ŵàš þŕöđüçéđ ƒöŕ à üñîţ àššéŕţ ñöţĥîñĝ
àñđ ḿéŕĝé ƃý ŕéçöŕđ ţîḿé. Ţĥé đéçîšîöñš çöḿþöñéñţ ƒöļđš đéçîšîöñš àļöñé, šö ţĥé
ŕéçöŕđš à šéŕṽéŕ ŕüñ ŵŕîţéš ŵĥîļé îţ đŕàƒţš ļéàṽé îţ ŵĥéŕé à çļîéñţ ŕéàđ îţ. Ţĥé
ŵöŕķéŕ ḿàķéš ţĥé šàḿé àššéŕţîöñ àĝàîñ îñšîđé ţĥé þüšĥ'š ţŕàñšàçţîöñ, àĝàîñšţ ţĥé
ļéđĝéŕ àš îţ šţööđ ƃéƒöŕé ţĥé þüšĥ ŵŕöţé àñýţĥîñĝ, šö ŕöŵš ţĥé þüšĥ îţšéļƒ
çĥàñĝéš ñéṽéŕ çöüñţ àĝàîñšţ îţ.
À ŕéḿöṽàļ ţàķéš à ƒîļé'š çöñţéñţ àñđ ļéàṽéš ţĥé đéçîšîöñš îţ ĥöļđš šţàñđîñĝ. Ţĥé îţéḿ ŕöŵ šţàýš àš ţĥé àñçĥöŕ ţĥöšé ŕöŵš àŕé ķéýéđ öñ, ƃéçàüšé ţĥé þŕöđüçéŕ'š çöḿḿîţţéđ ŕéçöŕđ šţîļļ ĥöļđš ţĥé đéçîšîöñš àñđ à þŕöđüçéŕ šéñđš ţĥàţ ŕéçöŕđ àĝàîñ öñļý ŵĥéñ îţš ƒöļđ ḿöṽéš: ŕöŵš đŕöþþéđ ĥéŕé ŵöüļđ ĥàṽé ñöţĥîñĝ ţö ƃŕîñĝ ţĥéḿ ƃàçķ. Çöñţéñţ þüšĥéđ ţö ţĥàţ þàţĥ àĝàîñ ļàñđš öñ ţĥé šàḿé îţéḿ, àñđ ţĥé ļéđĝéŕ šţîļļ ĥöļđš ţĥöšé đéçîšîöñš. Àñ îţéḿ ĥöļđîñĝ ñö đéçîšîöñ îš ŕéḿöṽéđ öüţŕîĝĥţ, àñđ ţĥé ţŕéé ţĥé šéŕṽéŕ šéŕṽéš ļéàṽéš öüţ àñ îţéḿ ĥöļđîñĝ ñö ƃļöçķš, šö à þŕöđüçéŕ îš ñéṽéŕ ţöļđ àƃöüţ à ƒîļé îţ đöéš ñöţ ĥàṽé.
À šéŕṽéŕ ŕüñ ƃéĝîñš ƃý ŵàîţîñĝ ƒöŕ ţĥé þŕöĵéçţ'š þüšĥéš ţĥàţ àŕé šţîļļ ǫüéüéđ öŕ ƃéîñĝ àþþļîéđ, üþ ţö à ļîḿîţ, àñđ šéţţļéš šöüŕçé öñļý àƒţéŕ ţĥéḿ. À çļîéñţ çöñƒîŕḿš à þüšĥ ƒöŕ à ƒéŵ šéçöñđš ƃéƒöŕé îţ ḿàý šţàŕţ à ŕüñ, àñđ à ļàŕĝé þüšĥ àþþļîéš ƒöŕ ļöñĝéŕ, šö šéţţļéḿéñţ ŕéàđš ŵĥàţ ţĥé þüšĥ ŵŕöţé ŕàţĥéŕ ţĥàñ ţĥé ƃļöçķš ţĥé þüšĥ îš àƃöüţ ţö çĥàñĝé öŕ ŕéḿöṽé.
À ŕüñ'š éṽéñţ šţŕéàḿ šéñđš à çöḿḿéñţ ƒŕàḿé éṽéŕý 15 šéçöñđš ŵĥîļé à šţàĝé ŵöŕķš
îñ šîļéñçé, ŵĥîçĥ îš ŵéļļ îñšîđé ţĥé 60 šéçöñđš àñý çöññéçţîöñ ḿàý šîţ îđļé
ƃéţŵééñ à çļîéñţ àñđ ţĥîš šéŕṽéŕ. Ţĥé çöḿḿéñţ çàŕŕîéš ñö îđ àñđ ñö đàţà, šö îţ
ḿöṽéš ñö ŕéšüḿé þöîñţ àñđ ñö çļîéñţ ŕéñđéŕš îţ. À çļîéñţ ŵĥöšé çöññéçţîöñ đŕöþš
ŕéšüḿéš ƒŕöḿ ţĥé ļàšţ éṽéñţ îđ îţ ŕéçéîṽéđ, ţĥŕöüĝĥ ţĥé Last-Event-ID ĥéàđéŕ
öŕ ?after=<seq>, àñđ ţĥé šéŕṽéŕ ŕéþļàýš ţĥé þéŕšîšţéđ éṽéñţš þàšţ ţĥàţ þöîñţ.
À ŕüñ îţšéļƒ ƃéļöñĝš ţö ţĥé šéŕṽéŕ: îţ ĝöéš öñ, àñđ îţš éṽéñţš àŕé ŕéçöŕđéđ,
ŵĥéţĥéŕ öŕ ñöţ àñýöñé îš ŵàţçĥîñĝ ţĥé šţŕéàḿ.
À þüšĥ àþþļîéš üñđéŕ à ŵŕîţé ļöçķ öñ îţš þŕöĵéçţ šţŕéàḿ, àñđ šö đöéš ŕéḿöṽîñĝ à ƒîļé ƒŕöḿ ţĥé éđîţöŕ. Ţĥé šéŕṽéŕ'š ƃļöçķ ŵŕîţéš öñ ţĥàţ šţŕéàḿ, ŵŕîţé-ƃàçķš àḿöñĝ ţĥéḿ, šĥàŕé ţĥé ļöçķ ŵîţĥ öñé àñöţĥéŕ. À ƃļöçķ ŵŕîţé ŵàîţš ƒöŕ àñ àþþļýîñĝ þüšĥ ţö çöḿḿîţ àñđ à þüšĥ ŵàîţš ƒöŕ ţĥé ƃļöçķ ŵŕîţéš àļŕéàđý îñ þŕöĝŕéšš, àñđ ƃöţĥ çöḿþļéţé. À ŵŕîţé-ƃàçķ ţĥàţ ŵàîţéđ šķîþš ţĥé ƃļöçķš ţĥé þüšĥ ŕéḿöṽéđ öŕ çĥàñĝéđ.
À šîñĝļé îñţéŕàçţîṽé ŵŕîţé ţö öñé ƃļöçķ, šüçĥ àš à ţàŕĝéţ šàṽé îñ ţĥé éđîţöŕ, à
ŕéṽîéŵ đéçîšîöñ öŕ ţĥé update_block ţööļ, çĥàñĝéš ţĥé ƃļöçķ ŵĥîļé ţĥé šţöŕé
ĥöļđš îţš ŕöŵ. Ţĥé éđîţöŕ'š ƃļöçķš þàýļöàđ çàŕŕîéš à ŕéṽîšîöñ ƒöŕ éàçĥ ţàŕĝéţ
ļöçàļé (target_revisions), àñđ get_block ŕéţüŕñš ţĥé šàḿé ṽàļüéš àš
revisions. À ŵŕîţé ţĥàţ šéñđš ţĥé ŕéṽîšîöñ îţ ŕéàđ àš base_revision îš
ŕéƒüšéđ ŵĥéñ ţĥàţ ţàŕĝéţ ĥàš ḿöṽéđ šîñçé: ţĥé éđîţöŕ ŕöüţéš àñšŵéŕ 409 ŵîţĥ
code: "block_changed" àñđ ţĥé ƃļöçķ àš îţ ñöŵ šţàñđš îñ current, àñđ
update_block ŕéţüŕñš àñ éŕŕöŕ ñàḿîñĝ ţĥé çüŕŕéñţ ŵöŕđîñĝ. Ñöţĥîñĝ îš ŵŕîţţéñ,
šö ţĥé ŵŕîţéŕ đéçîđéš àĝàîñ öñ ţĥé ŵöŕđîñĝ ţĥàţ šţàñđš. À ŕöļļƃàçķ ţàķéš ţĥé šàḿé
base_revision. Àþþŕöṽîñĝ à šöüŕçé þŕöþöšàļ îš ŕéƒüšéđ ţĥé šàḿé ŵàý ŵĥéñ ţĥé
ƃļöçķ'š šöüŕçé ñö ļöñĝéŕ ḿàţçĥéš ţĥé šöüŕçé ţĥé þŕöþöšàļ ŵàš ḿàđé àĝàîñšţ, àñđ
ţĥé þŕöþöšàļ šţàýš öþéñ. Éñţîţý ḿàŕķš ļàñđ öñ ţĥé ƃļöçķ îñ ţĥé šţŕéàḿ ţĥé ŕöüţé
ñàḿéš, àñđ à ƃļöçķ ŕéḿöṽéđ îñ ţĥé ḿéàñţîḿé šţàýš ŕéḿöṽéđ, àš îţ đöéš ƒöŕ à
ŕöļļƃàçķ, à ƃàţçĥ ŕéṽéŕţ öŕ à ƒļöŵ ŕüñ öṽéŕ ţĥé þŕöĵéçţ'š ƃļöçķš.
Ţĥé šàḿé ŕöüţéš éẋîšţ üñđéŕ /api/v1/projects/:id/sync/:ref/... ƒöŕ à þŕöĵéçţ
ţĥàţ ĥàš ñöţ ýéţ ƃééñ çļàîḿéđ îñţö à ŵöŕķšþàçé, àüţĥéñţîçàţéđ ƃý îţš çļàîḿ ţöķéñ.
Šéé kapi push ƒöŕ ţĥé þŕöţöçöļ àš à çļîéñţ šééš îţ.
Çöññéçţöŕš
Çöññéçţöŕ îñšţàñçéš àŕé ŵöŕķšþàçé-šçöþéđ àñđ ŕéǫüîŕé ţĥé ḿàñàĝé-çöññéçţöŕš þéŕḿîššîöñ. Šéé Çöññéçţöŕš ƒöŕ šéţüþ ĝüîđéš.
GET /api/v1/:ws/connectors # List active connectors
POST /api/v1/:ws/connectors # Add connector
PUT /api/v1/:ws/connectors/:id # Update connector
DELETE /api/v1/:ws/connectors/:id # Remove connector
GET /api/v1/:ws/connectors/:id/status # Sync status
GET /api/v1/:ws/connectors/:id/content # Browse available content
POST /api/v1/:ws/connectors/:id/fetch # Fetch content from the external system
POST /api/v1/:ws/connectors/:id/publish # Publish results back
Ƒļöŵš àñđ àüţöḿàţîöñ
GET /api/v1/:ws/:id/flows # List flows (built-in catalog + project flows)
POST /api/v1/:ws/:id/flows # Create a project flow
GET /api/v1/:ws/:id/flows/:flowId # Get one flow
PUT /api/v1/:ws/:id/flows/:flowId # Replace a project flow
DELETE /api/v1/:ws/:id/flows/:flowId # Delete a project flow
/api/v1/:ws/:id/automations # Automation rules (see Automation)
Ŕüñš àŕé šţàŕţéđ àñđ öƃšéŕṽéđ ţĥŕöüĝĥ ţĥé çöñṽéŕĝéñçé ŕöüţéš üñđéŕ à þŕöĵéçţ;
ţĥé Ŕüñš ṽîéŵ àñđ kapi up àŕé ţĥéîŕ çļîéñţš. Šéé
Šéŕṽéŕ-šîđé ƒļöŵš àñđ Àüţöḿàţîöñ.
Ŕéṽîéŵ àñđ đéļîṽéŕý
POST /api/v1/:ws/:id/review/approve-passing # Bulk-approve every block passing checks, checked terminology and the voice bar
GET /api/v1/projects/:id/ship.json # Public per-locale ship manifest
GET /api/v1/:ws/audit-log/verify # Verify the workspace audit chain
Ŵéƃĥööķš (îñƃöüñđ)
POST /api/webhooks/forge/:configID # Repository push webhook (token mode)
POST /api/webhooks/github-app # GitHub App webhook
Ŕüññîñĝ ţĥé Šéŕṽéŕ
bin/bowrain-server --port 8080 --host 0.0.0.0 \
--database-url postgres://bowrain:password@localhost/bowrain
Ţĥé šéŕṽéŕ ŕéǫüîŕéš ÞöšţĝŕéŠǪĻ. Šéé Çöñƒîĝüŕàţîöñ ƒöŕ ţĥé çöḿþļéţé éñṽîŕöñḿéñţ-ṽàŕîàƃļé àñđ ƒļàĝ ŕéƒéŕéñçé.
ĝŔÞÇ ÀÞÎ
Ţĥé ĝŔÞÇ ÀÞÎ þŕöṽîđéš šţŕéàḿîñĝ àççéšš. Îţ îš ḿüļţîþļéẋéđ öñţö ţĥé šàḿé ĤŢŢÞ
þöŕţ àš ţĥé ŔÉŠŢ ÀÞÎ üšîñĝ ĥ2ç (çļéàŕţéẋţ ĤŢŢÞ/2): ŕéǫüéšţš çàŕŕýîñĝ
Content-Type: application/grpc àŕé ŕöüţéđ ţö ţĥé ĝŔÞÇ ĥàñđļéŕ, éṽéŕýţĥîñĝ éļšé
ţö ţĥé ŔÉŠŢ ĥàñđļéŕ. Ţĥéŕé îš ñö šéþàŕàţé ĝŔÞÇ þöŕţ öŕ ŢĻŠ ƒļàĝ; ţĥé šéŕṽéŕ
ŕüñš ƃéĥîñđ à ŢĻŠ-ţéŕḿîñàţîñĝ ŕéṽéŕšé þŕöẋý îñ þŕöđüçţîöñ (šéé
Šéļƒ-Ĥöšţîñĝ), ŵĥîçĥ ŕöüţéš /neokapi.*
ţö ţĥé šéŕṽéŕ.
Šéŕṽîçé Đéƒîñîţîöñ
Ţĥé NeokapiService þŕöṽîđéš ţĥéšé ŔÞÇš:
service NeokapiService {
rpc CreateProject(CreateProjectRequest) returns (ProjectResponse);
rpc GetProject(GetProjectRequest) returns (ProjectResponse);
rpc ListProjects(ListProjectsRequest) returns (ListProjectsResponse);
rpc CreateVersion(CreateVersionRequest) returns (VersionResponse);
rpc ListVersions(ListVersionsRequest) returns (ListVersionsResponse);
rpc PullContent(PullContentRequest) returns (PullContentResponse);
rpc PushContent(PushContentRequest) returns (PushContentResponse);
rpc ExecuteFlow(ExecuteFlowRequest) returns (stream FlowProgressResponse);
rpc Subscribe(SubscribeRequest) returns (stream EventResponse);
}
Šţŕéàḿîñĝ
Ţŵö ŔÞÇš üšé šéŕṽéŕ-šîđé šţŕéàḿîñĝ:
- ÉẋéçüţéƑļöŵ: šţŕéàḿš þŕöĝŕéšš üþđàţéš đüŕîñĝ ƒļöŵ éẋéçüţîöñ
- Šüƃšçŕîƃé: šţŕéàḿš éṽéñţš ḿàţçĥîñĝ ţĥé šüƃšçŕîþţîöñ ƒîļţéŕ
Ƃļöçķ çöñţéñţ đöéš ñöţ ţŕàṽéļ ţĥîš šéŕṽîçé. Îţ ḿöṽéš öṽéŕ ţĥé çàñöñîçàļ
neokapi.content.v1 šýñç ŵîŕé (core/proto/sync/v1/sync.proto), ŵĥîçĥ
çàŕŕîéš ŕüñš, öṽéŕļàýš, šéĝḿéñţàţîöñ àñđ šöüŕçé-ļöçàļé ļöššļéššļý.
Çļîéñţ Éẋàḿþļé
// Production: connect through the TLS-terminating proxy (port 443).
conn, err := grpc.NewClient("bowrain.example.com:443",
grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")),
)
// Local dev: the server speaks cleartext h2c on its HTTP port.
// conn, err := grpc.NewClient("localhost:8080",
// grpc.WithTransportCredentials(insecure.NewCredentials()),
// )
client := serverv1.NewNeokapiServiceClient(conn)
// Stream flow-execution progress
stream, _ := client.ExecuteFlow(ctx, &serverv1.ExecuteFlowRequest{
ProjectId: "proj-1",
FlowConfig: "name: qa\ntools:\n - case-transform",
})
for {
resp, err := stream.Recv()
if err == io.EOF {
break
}
fmt.Println(resp.Stage, resp.Message)
}
Þŕöţö Ƒîļé Ļöçàţîöñ
Ţĥé þŕöţö đéƒîñîţîöñš àŕé àţ bowrain/proto/v1/neokapi_service.proto. Ĝéñéŕàţé Ĝö çöđé ŵîţĥ:
make proto