sevok 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hornjs team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,342 @@
1
+ # sevok
2
+
3
+ Web-standard server primitives with context-based handlers, middleware, and runtime adapters for Bun, Deno, Node.js, and stream-based hosts.
4
+
5
+ [简体中文](./README.zh-CN.md) • [GitHub](https://github.com/hornjs/sevok)
6
+
7
+ ## Features
8
+
9
+ - Context-based request handling built on standard `Request` and `Response`
10
+ - Declarative route tables with Bun-style precedence and `fetch` fallback
11
+ - Ordered middleware pipeline with short-circuiting and abort awareness
12
+ - Per-invocation context via `InvocationContext`
13
+ - Typed lifecycle events powered by [`@hornjs/evt`](https://github.com/hornjs/evt)
14
+ - `waitUntil()` support for background tasks during shutdown
15
+ - Runtime adapters for `Bun.serve()`, `Deno.serve()`, Node HTTP/HTTPS/HTTP2, and async event streams
16
+ - Static file middleware with HTML fallback and gzip / Brotli support
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pnpm add sevok
22
+ ```
23
+
24
+ ## Package Exports
25
+
26
+ ```ts
27
+ import { Server, serve } from "sevok";
28
+ import { BunRuntimeAdapter } from "sevok/bun";
29
+ import { DenoRuntimeAdapter } from "sevok/deno";
30
+ import { NodeRuntimeAdapter } from "sevok/node";
31
+ import { log } from "sevok/log";
32
+ import { serveStatic } from "sevok/static";
33
+ import { StreamRuntimeAdapter } from "sevok/stream";
34
+ ```
35
+
36
+ ## Core Concepts
37
+
38
+ ### `Server`
39
+
40
+ `Server` is the runtime-agnostic request kernel. It owns:
41
+
42
+ - middleware composition
43
+ - invocation context setup
44
+ - background task tracking with `waitUntil()`
45
+ - startup and shutdown coordination with a runtime adapter
46
+
47
+ `adapter` is optional. In Bun, Deno, and Node.js environments, `Server` can
48
+ auto-detect the current runtime and lazily load the matching built-in adapter
49
+ with dynamic imports such as `import("sevok/bun")`,
50
+ `import("sevok/deno")`, and `import("sevok/node")`.
51
+
52
+ You usually only need to pass `adapter` when you want to customize native
53
+ runtime options, force a specific adapter, or run in a non-standard host such
54
+ as an async event stream.
55
+
56
+ ### Middleware
57
+
58
+ Middleware receives `(ctx, next)` and can:
59
+
60
+ - return a `Response` immediately
61
+ - call `next(ctx)` and return the downstream response
62
+ - access the request via `ctx.request`
63
+ - use `ctx.set()` / `ctx.get()` to share state
64
+
65
+ ### Routes And Fallbacks
66
+
67
+ `routes` is the primary routing surface. It is matched before `fetch` using
68
+ Bun-style precedence:
69
+
70
+ - exact route
71
+ - parameter route
72
+ - wildcard route
73
+ - catch-all route
74
+
75
+ `fetch` is the fallback handler for unmatched requests. If you omit `fetch`,
76
+ your route table must include `/*`.
77
+
78
+ ### Invocation Context
79
+
80
+ Use `createContextKey()` and `ctx.set()` / `ctx.get()` to share per-invocation state safely across middleware and handlers.
81
+
82
+ ### Lifecycle Events
83
+
84
+ `Server` extends the typed `EventDispatcher` from [`@hornjs/evt`](https://github.com/hornjs/evt) and emits
85
+ three lifecycle events:
86
+
87
+ - `serve`: fired after the runtime adapter reports a listening URL
88
+ - `close`: fired after shutdown completes and `waitUntil()` work has settled
89
+ - `error`: fired when asynchronous adapter initialization fails
90
+
91
+ ```ts
92
+ import { Server, ServerErrorEvent, ServerServeEvent } from "sevok";
93
+
94
+ server.addEventListener("serve", (event: ServerServeEvent) => {
95
+ console.log("server ready", server.url);
96
+ });
97
+
98
+ server.addEventListener("error", (event: ServerErrorEvent) => {
99
+ console.error("server failed", event.error);
100
+ });
101
+ ```
102
+
103
+ ## Basic Example
104
+
105
+ ```ts
106
+ import { Server, createContextKey } from "sevok";
107
+
108
+ const requestIdKey = createContextKey<string>("unknown");
109
+
110
+ const server = new Server({
111
+ middleware: [
112
+ async (ctx, next) => {
113
+ ctx.set(requestIdKey, crypto.randomUUID());
114
+ return next(ctx);
115
+ },
116
+ ],
117
+ routes: {
118
+ "/": async (ctx) => {
119
+ return Response.json({
120
+ id: ctx.get(requestIdKey),
121
+ pathname: new URL(ctx.request.url).pathname,
122
+ });
123
+ },
124
+ },
125
+ fetch: () => new Response("Not Found", { status: 404 }),
126
+ });
127
+
128
+ await server.ready();
129
+ console.log(server.url);
130
+ ```
131
+
132
+ ## Runtime Adapters
133
+
134
+ ### Bun
135
+
136
+ ```ts
137
+ import { Server } from "sevok";
138
+ import { BunRuntimeAdapter } from "sevok/bun";
139
+
140
+ const server = new Server({
141
+ adapter: new BunRuntimeAdapter(),
142
+ middleware: [],
143
+ routes: {
144
+ "/": () => new Response("Hello from Bun"),
145
+ },
146
+ fetch: () => new Response("Not Found", { status: 404 }),
147
+ });
148
+
149
+ await server.ready();
150
+ ```
151
+
152
+ ### Deno
153
+
154
+ ```ts
155
+ import { Server } from "sevok";
156
+ import { DenoRuntimeAdapter } from "sevok/deno";
157
+
158
+ const server = new Server({
159
+ adapter: new DenoRuntimeAdapter(),
160
+ middleware: [],
161
+ routes: {
162
+ "/": () => new Response("Hello from Deno"),
163
+ },
164
+ fetch: () => new Response("Not Found", { status: 404 }),
165
+ });
166
+
167
+ await server.ready();
168
+ ```
169
+
170
+ ### Node.js
171
+
172
+ ```ts
173
+ import { Server } from "sevok";
174
+ import { NodeRuntimeAdapter } from "sevok/node";
175
+
176
+ const server = new Server({
177
+ adapter: new NodeRuntimeAdapter(),
178
+ middleware: [],
179
+ routes: {
180
+ "/": () => new Response("Hello from Node"),
181
+ },
182
+ fetch: () => new Response("Not Found", { status: 404 }),
183
+ });
184
+
185
+ await server.ready();
186
+ ```
187
+
188
+ ### Stream Runtime
189
+
190
+ The stream adapter is useful when a host runtime already exposes fetch events through an async iterator.
191
+
192
+ ```ts
193
+ import { Server } from "sevok";
194
+ import { StreamRuntimeAdapter } from "sevok/stream";
195
+
196
+ async function* stream() {
197
+ while (true) {
198
+ const event = await getNextFetchEvent();
199
+ yield event;
200
+ }
201
+ }
202
+
203
+ const server = new Server({
204
+ adapter: new StreamRuntimeAdapter({
205
+ stream: stream(),
206
+ url: "/worker",
207
+ }),
208
+ middleware: [],
209
+ routes: {
210
+ "/": () => new Response("Hello from stream"),
211
+ },
212
+ fetch: () => new Response("Not Found", { status: 404 }),
213
+ });
214
+
215
+ await server.serve();
216
+ ```
217
+
218
+ ## Static Files
219
+
220
+ Use `serveStatic()` as regular middleware.
221
+
222
+ ```ts
223
+ import { Server } from "sevok";
224
+ import { NodeRuntimeAdapter } from "sevok/node";
225
+ import { serveStatic } from "sevok/static";
226
+
227
+ const server = new Server({
228
+ adapter: new NodeRuntimeAdapter(),
229
+ middleware: [
230
+ serveStatic({
231
+ dir: "./public",
232
+ renderHTML: async ({ html, filename }) => {
233
+ return new Response(html.replace("</body>", `<p>${filename}</p></body>`), {
234
+ headers: { "content-type": "text/html" },
235
+ });
236
+ },
237
+ }),
238
+ ],
239
+ fetch: () => new Response("Not Found", { status: 404 }),
240
+ });
241
+ ```
242
+
243
+ Static resolution rules:
244
+
245
+ - `/` -> `index.html`
246
+ - `/about` -> `about.html`, then `about/index.html`
247
+ - explicit extensions are used as-is
248
+
249
+ If the client accepts compression, Brotli is preferred over gzip when the runtime supports it.
250
+
251
+ ## TLS
252
+
253
+ For Bun, Deno, and Node, TLS material can be provided either as inline PEM strings or file paths:
254
+
255
+ ```ts
256
+ const server = new Server({
257
+ adapter: new NodeRuntimeAdapter(),
258
+ protocol: "https",
259
+ tls: {
260
+ cert: "./certs/dev-cert.pem",
261
+ key: "./certs/dev-key.pem",
262
+ },
263
+ middleware: [],
264
+ fetch: () => new Response("secure"),
265
+ });
266
+ ```
267
+
268
+ If `protocol` is explicitly set to `"https"`, both `cert` and `key` must be present.
269
+
270
+ ## Manual Startup and Shutdown
271
+
272
+ Set `manual: true` when you want explicit lifecycle control.
273
+
274
+ ```ts
275
+ const server = new Server({
276
+ adapter: new NodeRuntimeAdapter(),
277
+ manual: true,
278
+ middleware: [],
279
+ fetch: () => new Response("ok"),
280
+ });
281
+
282
+ await server.serve();
283
+ await server.ready();
284
+
285
+ server.waitUntil?.(
286
+ Promise.resolve().then(() => {
287
+ console.log("background work");
288
+ }),
289
+ );
290
+
291
+ await server.close();
292
+ ```
293
+
294
+ `Server.close()` waits for:
295
+
296
+ - the runtime adapter to close
297
+ - all promises registered via `waitUntil()`
298
+
299
+ You can observe the same transitions through events:
300
+
301
+ ```ts
302
+ server.addEventListener("serve", () => {
303
+ console.log("listening");
304
+ });
305
+
306
+ server.addEventListener("close", () => {
307
+ console.log("closed");
308
+ });
309
+ ```
310
+
311
+ ## API Overview
312
+
313
+ Main export:
314
+
315
+ - `Server`
316
+ - `ServerServeEvent`
317
+ - `ServerCloseEvent`
318
+ - `ServerErrorEvent`
319
+ - `serve`
320
+ - `createContextKey`
321
+ - `InvocationContext`
322
+ - `runMiddleware`
323
+ - `wrapFetch`
324
+
325
+ Subpath exports:
326
+
327
+ - `sevok/bun`
328
+ - `sevok/cli`
329
+ - `sevok/deno`
330
+ - `sevok/log`
331
+ - `sevok/node`
332
+ - `sevok/static`
333
+ - `sevok/stream`
334
+
335
+ ## Development
336
+
337
+ ```bash
338
+ pnpm install
339
+ pnpm lint
340
+ pnpm test
341
+ pnpm build
342
+ ```
@@ -0,0 +1,342 @@
1
+ # sevok
2
+
3
+ 基于 Fetch API 的服务端基础库,提供中间件、调用上下文,以及 Bun、Deno、Node.js、流式事件宿主的运行时适配器。
4
+
5
+ [English](./README.md)
6
+
7
+ ## 特性
8
+
9
+ - 以标准 `Request` / `Response` 为中心的请求处理模型
10
+ - 声明式路由表,支持 Bun 风格优先级与 `fetch` 兜底
11
+ - 有顺序、可短路、支持中止信号的中间件链
12
+ - 基于 `InvocationContext` 的调用级上下文
13
+ - 基于 [`@hornjs/evt`](https://github.com/hornjs/evt) 的类型安全生命周期事件
14
+ - 通过 `waitUntil()` 跟踪后台任务并在关闭时等待完成
15
+ - 支持 `Bun.serve()`、`Deno.serve()`、Node HTTP/HTTPS/HTTP2、异步事件流
16
+ - 提供静态文件中间件,支持 HTML fallback 与 gzip / Brotli 压缩
17
+
18
+ ## 安装
19
+
20
+ ```bash
21
+ pnpm add sevok
22
+ ```
23
+
24
+ ## 导出入口
25
+
26
+ ```ts
27
+ import { Server, serve } from "sevok";
28
+ import { BunRuntimeAdapter } from "sevok/bun";
29
+ import { DenoRuntimeAdapter } from "sevok/deno";
30
+ import { NodeRuntimeAdapter } from "sevok/node";
31
+ import { log } from "sevok/log";
32
+ import { serveStatic } from "sevok/static";
33
+ import { StreamRuntimeAdapter } from "sevok/stream";
34
+ ```
35
+
36
+ ## 核心概念
37
+
38
+ ### `Server`
39
+
40
+ `Server` 是运行时无关的请求内核,负责:
41
+
42
+ - 组合中间件
43
+ - 初始化调用上下文
44
+ - 跟踪 `waitUntil()` 注册的后台任务
45
+ - 协调 runtime adapter 的启动与关闭
46
+
47
+ `adapter` 是可选的。在 Bun、Deno、Node.js 环境下,通常可以不显式传入;
48
+ `Server` 会根据当前运行时自动检测,并通过
49
+ `import("sevok/bun")`、`import("sevok/deno")`、
50
+ `import("sevok/node")` 这类动态导入懒加载内置 adapter。
51
+
52
+ 一般只有这些场景才需要手动指定 `adapter`:
53
+
54
+ - 你需要显式定制 Bun、Deno、Node 的原生 adapter 参数
55
+ - 你想强制使用某个特定 adapter,而不是依赖自动检测
56
+ - 你运行在内置 Bun、Deno、Node 之外的宿主环境中,比如异步事件流
57
+
58
+ ### 中间件
59
+
60
+ 中间件签名为 `(ctx, next)`,可以:
61
+
62
+ - 直接返回一个 `Response`
63
+ - 调用 `next(ctx)` 并返回下游结果
64
+ - 通过 `ctx.request` 访问请求
65
+ - 使用 `ctx.set()` / `ctx.get()` 共享状态
66
+
67
+ ### 路由与兜底处理
68
+
69
+ `routes` 是主路由表,会在 `fetch` 之前按 Bun 风格优先级进行匹配:
70
+
71
+ - 精确路由
72
+ - 参数路由
73
+ - wildcard 路由
74
+ - catch-all 路由
75
+
76
+ `fetch` 是未命中路由时的兜底处理函数。如果省略 `fetch`,那么
77
+ `routes` 必须包含 `/*`。
78
+
79
+ ### 调用上下文
80
+
81
+ 使用 `createContextKey()` 与 `ctx.set()` / `ctx.get()` 可以安全地在中间件和处理函数之间共享调用级状态。
82
+
83
+ ### 生命周期事件
84
+
85
+ `Server` 继承自 [`@hornjs/evt`](https://github.com/hornjs/evt) 的类型化 `EventDispatcher`,目前会发出三个生命周期事件:
86
+
87
+ - `serve`:runtime adapter 报告监听地址后触发
88
+ - `close`:关闭完成且 `waitUntil()` 后台任务全部结束后触发
89
+ - `error`:异步初始化 runtime adapter 失败时触发
90
+
91
+ ```ts
92
+ import { Server, ServerErrorEvent, ServerServeEvent } from "sevok";
93
+
94
+ server.addEventListener("serve", (event: ServerServeEvent) => {
95
+ console.log("server ready", server.url);
96
+ });
97
+
98
+ server.addEventListener("error", (event: ServerErrorEvent) => {
99
+ console.error("server failed", event.error);
100
+ });
101
+ ```
102
+
103
+ ## 基础示例
104
+
105
+ ```ts
106
+ import { Server, createContextKey } from "sevok";
107
+
108
+ const requestIdKey = createContextKey<string>("unknown");
109
+
110
+ const server = new Server({
111
+ middleware: [
112
+ async (ctx, next) => {
113
+ ctx.set(requestIdKey, crypto.randomUUID());
114
+ return next(ctx);
115
+ },
116
+ ],
117
+ routes: {
118
+ "/": async (ctx) => {
119
+ return Response.json({
120
+ id: ctx.get(requestIdKey),
121
+ pathname: new URL(ctx.request.url).pathname,
122
+ });
123
+ },
124
+ },
125
+ fetch: () => new Response("Not Found", { status: 404 }),
126
+ });
127
+
128
+ await server.ready();
129
+ console.log(server.url);
130
+ ```
131
+
132
+ ## Runtime Adapter
133
+
134
+ ### Bun
135
+
136
+ ```ts
137
+ import { Server } from "sevok";
138
+ import { BunRuntimeAdapter } from "sevok/bun";
139
+
140
+ const server = new Server({
141
+ adapter: new BunRuntimeAdapter(),
142
+ middleware: [],
143
+ routes: {
144
+ "/": () => new Response("Hello from Bun"),
145
+ },
146
+ fetch: () => new Response("Not Found", { status: 404 }),
147
+ });
148
+
149
+ await server.ready();
150
+ ```
151
+
152
+ ### Deno
153
+
154
+ ```ts
155
+ import { Server } from "sevok";
156
+ import { DenoRuntimeAdapter } from "sevok/deno";
157
+
158
+ const server = new Server({
159
+ adapter: new DenoRuntimeAdapter(),
160
+ middleware: [],
161
+ routes: {
162
+ "/": () => new Response("Hello from Deno"),
163
+ },
164
+ fetch: () => new Response("Not Found", { status: 404 }),
165
+ });
166
+
167
+ await server.ready();
168
+ ```
169
+
170
+ ### Node.js
171
+
172
+ ```ts
173
+ import { Server } from "sevok";
174
+ import { NodeRuntimeAdapter } from "sevok/node";
175
+
176
+ const server = new Server({
177
+ adapter: new NodeRuntimeAdapter(),
178
+ middleware: [],
179
+ routes: {
180
+ "/": () => new Response("Hello from Node"),
181
+ },
182
+ fetch: () => new Response("Not Found", { status: 404 }),
183
+ });
184
+
185
+ await server.ready();
186
+ ```
187
+
188
+ ### Stream
189
+
190
+ 当宿主环境已经把请求封装成异步事件流时,可以使用 `StreamRuntimeAdapter`。
191
+
192
+ ```ts
193
+ import { Server } from "sevok";
194
+ import { StreamRuntimeAdapter } from "sevok/stream";
195
+
196
+ async function* stream() {
197
+ while (true) {
198
+ const event = await getNextFetchEvent();
199
+ yield event;
200
+ }
201
+ }
202
+
203
+ const server = new Server({
204
+ adapter: new StreamRuntimeAdapter({
205
+ stream: stream(),
206
+ url: "/worker",
207
+ }),
208
+ middleware: [],
209
+ routes: {
210
+ "/": () => new Response("Hello from stream"),
211
+ },
212
+ fetch: () => new Response("Not Found", { status: 404 }),
213
+ });
214
+
215
+ await server.serve();
216
+ ```
217
+
218
+ ## 静态文件
219
+
220
+ `serveStatic()` 可以直接作为普通中间件使用。
221
+
222
+ ```ts
223
+ import { Server } from "sevok";
224
+ import { NodeRuntimeAdapter } from "sevok/node";
225
+ import { serveStatic } from "sevok/static";
226
+
227
+ const server = new Server({
228
+ adapter: new NodeRuntimeAdapter(),
229
+ middleware: [
230
+ serveStatic({
231
+ dir: "./public",
232
+ renderHTML: async ({ html, filename }) => {
233
+ return new Response(html.replace("</body>", `<p>${filename}</p></body>`), {
234
+ headers: { "content-type": "text/html" },
235
+ });
236
+ },
237
+ }),
238
+ ],
239
+ fetch: () => new Response("Not Found", { status: 404 }),
240
+ });
241
+ ```
242
+
243
+ 静态资源解析规则:
244
+
245
+ - `/` -> `index.html`
246
+ - `/about` -> 依次尝试 `about.html`、`about/index.html`
247
+ - 已带扩展名的路径按原样解析
248
+
249
+ 当客户端声明支持压缩时,如果运行时支持,会优先使用 Brotli,否则退回 gzip。
250
+
251
+ ## TLS
252
+
253
+ 在 Bun、Deno、Node 中,TLS 证书既可以传 PEM 内容,也可以传文件路径:
254
+
255
+ ```ts
256
+ const server = new Server({
257
+ adapter: new NodeRuntimeAdapter(),
258
+ protocol: "https",
259
+ tls: {
260
+ cert: "./certs/dev-cert.pem",
261
+ key: "./certs/dev-key.pem",
262
+ },
263
+ middleware: [],
264
+ fetch: () => new Response("secure"),
265
+ });
266
+ ```
267
+
268
+ 如果显式设置了 `protocol: "https"`,那么 `cert` 和 `key` 必须同时提供。
269
+
270
+ ## 手动控制生命周期
271
+
272
+ 如果你想自己控制启动与关闭,可以设置 `manual: true`。
273
+
274
+ ```ts
275
+ const server = new Server({
276
+ adapter: new NodeRuntimeAdapter(),
277
+ manual: true,
278
+ middleware: [],
279
+ fetch: () => new Response("ok"),
280
+ });
281
+
282
+ await server.serve();
283
+ await server.ready();
284
+
285
+ server.waitUntil?.(
286
+ Promise.resolve().then(() => {
287
+ console.log("background work");
288
+ }),
289
+ );
290
+
291
+ await server.close();
292
+ ```
293
+
294
+ `Server.close()` 会等待:
295
+
296
+ - runtime adapter 完成关闭
297
+ - 所有通过 `waitUntil()` 注册的后台任务完成
298
+
299
+ 同样也可以通过事件观察这些阶段:
300
+
301
+ ```ts
302
+ server.addEventListener("serve", () => {
303
+ console.log("listening");
304
+ });
305
+
306
+ server.addEventListener("close", () => {
307
+ console.log("closed");
308
+ });
309
+ ```
310
+
311
+ ## API 概览
312
+
313
+ 主入口导出:
314
+
315
+ - `Server`
316
+ - `ServerServeEvent`
317
+ - `ServerCloseEvent`
318
+ - `ServerErrorEvent`
319
+ - `serve`
320
+ - `createContextKey`
321
+ - `InvocationContext`
322
+ - `runMiddleware`
323
+ - `wrapFetch`
324
+
325
+ 子路径导出:
326
+
327
+ - `sevok/bun`
328
+ - `sevok/cli`
329
+ - `sevok/deno`
330
+ - `sevok/log`
331
+ - `sevok/node`
332
+ - `sevok/static`
333
+ - `sevok/stream`
334
+
335
+ ## 开发
336
+
337
+ ```bash
338
+ pnpm install
339
+ pnpm lint
340
+ pnpm test
341
+ pnpm build
342
+ ```
package/bin/sevok.mjs ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "../dist/cli.mjs";
3
+
4
+ globalThis.__SEVOK_BIN__ = import.meta.url;
5
+
6
+ await main({
7
+ args: process.argv.slice(2),
8
+ usage: {
9
+ command: "sevok",
10
+ docs: "https://sevok.pages.dev",
11
+ issues: "https://github.com/hornjs/sevok/issues",
12
+ },
13
+ });