@fugood/buttress-server 2.25.1-beta.0 → 2.25.1-beta.1

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/README.md CHANGED
@@ -8,6 +8,41 @@ A high-performance RPC server for managing GGML LLM generators with configurable
8
8
  npm install -g @fugood/buttress-server
9
9
  ```
10
10
 
11
+ ### Standalone binary (no Node.js required)
12
+
13
+ A self-contained executable built with `bun build --compile`:
14
+
15
+ ```bash
16
+ curl -fsSL https://cdn.bricks.tools/bricks-buttress/release/install.sh | sh
17
+ # beta channel
18
+ curl -fsSL https://cdn.bricks.tools/bricks-buttress/beta/install.sh | sh -s -- --beta
19
+ ```
20
+
21
+ Windows (PowerShell):
22
+
23
+ ```powershell
24
+ irm https://cdn.bricks.tools/bricks-buttress/release/install.ps1 | iex
25
+ ```
26
+
27
+ The installer detects the host's supported GGML accelerator (CUDA > Vulkan >
28
+ Snapdragon, falling back to the default CPU/Metal build) and downloads only
29
+ the native modules the binary needs — the ggml llama.node / whisper.node
30
+ variant packages plus onnxruntime, sharp, and oxc-transform prebuilds — into a
31
+ `node_modules` sidecar next to the executable. It installs to
32
+ `~/.bricks-cli/bin` (shared with the BRICKS CLI, so one PATH entry covers
33
+ both). Override detection with
34
+ `--ggml-variant=default|cuda|vulkan|snapdragon|all`.
35
+
36
+ Build the distribution locally from this package:
37
+
38
+ ```bash
39
+ bun run build:dist -- --target=darwin-arm64 # or --platform=linux, etc.
40
+ ```
41
+
42
+ See `scripts/build-distribution.js` for how native modules are swapped to
43
+ sidecar loaders at bundle time, and `scripts/unix/install.sh` /
44
+ `scripts/windows/install.ps1` for the host detection.
45
+
11
46
  ## Quick Start
12
47
 
13
48
  ### Using CLI
@@ -160,6 +195,8 @@ Most ggml-llm `[generators.model]` keys can also live in `[runtime]` as defaults
160
195
  | `kv_unified` | boolean | Use a unified KV cache across sequences |
161
196
  | `swa_full` | boolean | Materialize full attention even for sliding-window layers |
162
197
  | `ctx_shift` | boolean | Allow llama.cpp's rolling context shift |
198
+ | `state_cache_budget_mb` | number | Memory budget of the cross-turn KV prefix cache for recurrent / hybrid models (default `160`, `0` disables) |
199
+ | `state_cache_max_checkpoints` | number | Snapshot count cap for that cache (default `8`, `0` = unlimited); the memory budget takes precedence |
163
200
  | `use_mmap`, `use_mlock` | boolean | Memory-mapping / locking |
164
201
  | `no_extra_bufts` | boolean | Disable extra compute buffer types |
165
202
  | `cpu_mask`, `cpu_strict` | string / boolean | CPU affinity (advanced) |
@@ -242,7 +279,7 @@ Loads a GGUF LLM. Runtime keys above can be overridden per-generator under `[gen
242
279
  | `n_gpu_layers` | number\|`"auto"` | Layers offloaded to GPU (default `"auto"`) |
243
280
  | `n_batch` | number | Prompt batch size (default `512`) |
244
281
  | `n_ubatch`, `n_threads`, `n_parallel`, `n_cpu_moe` | number | Same semantics as the `[runtime]` defaults |
245
- | `flash_attn_type`, `cache_type_k`, `cache_type_v`, `kv_unified`, `swa_full`, `ctx_shift`, `use_mmap`, `use_mlock`, `no_extra_bufts`, `cpu_mask`, `cpu_strict`, `devices` | various | Per-model overrides for the `[runtime]` defaults |
282
+ | `flash_attn_type`, `cache_type_k`, `cache_type_v`, `kv_unified`, `swa_full`, `ctx_shift`, `state_cache_budget_mb`, `state_cache_max_checkpoints`, `use_mmap`, `use_mlock`, `no_extra_bufts`, `cpu_mask`, `cpu_strict`, `devices` | various | Per-model overrides for the `[runtime]` defaults |
246
283
 
247
284
  **Multimodal (mtmd)** — auto-downloads the matching `mmproj-*.gguf` from the same repo and calls `initMultimodal`:
248
285
 
@@ -459,6 +496,14 @@ Buttress server for remote inference with GGML backends.
459
496
 
460
497
  Usage:
461
498
  bricks-buttress [options]
499
+ bricks-buttress update [--check] [-y] [--channel <release|beta>]
500
+
501
+ Commands:
502
+ update Update bricks-buttress to the latest version.
503
+ Standalone binary installs re-run the CDN
504
+ installer (refreshing the native-module
505
+ sidecar); npm/bun installs update the package.
506
+ `--check` only reports whether an update exists.
462
507
 
463
508
  Options:
464
509
  -h, --help Show this help message
@@ -581,13 +626,35 @@ For faster authoring feedback, opt into eager reloading with `[functions] hot_re
581
626
  | -------- | ------- |
582
627
  | `GET /functions` | List callable functions with their JSON Schemas |
583
628
  | `POST /functions/<name>` | Run one; JSON body = input object, response is `{ "result": … }`. A multipart body stages its file fields inline (see below) |
584
- | `POST /functions/<name>?stream=1` | Same, as SSE: `progress` events (from `context.emit`) then `result` or `error` |
629
+ | `GET /functions/<name>?…` | Run one with the query string as its input (see below) |
630
+ | `POST /functions/<name>?stream=1` | Same, as SSE: `progress` events (from `context.emit`) then `result` or `error`. `GET` streams too |
585
631
  | `POST /functions/mcp` | MCP over Streamable HTTP (stateless; `GET`/`DELETE` return 405) |
586
632
  | `GET /functions/files/<path>` | Download a file a function wrote to its `tempDir` — functions hand out these URLs via `context.fileUrl` |
587
633
  | `POST /functions/upload` | Stage an input file on the server (multipart, `file` field) → `{ "path", "url", "name", "size" }` |
588
634
 
589
635
  Errors come back as `{ "error": { "code", "message" } }` with `FUNCTION_NOT_FOUND` (404), `FUNCTION_TIMEOUT` (504), `FUNCTION_FAILED` (500) or `FUNCTION_FILE_NOT_FOUND` (404).
590
636
 
637
+ #### Calling with GET
638
+
639
+ `GET /functions/<name>` runs the same call with no body at all — the query string is the input, which is what a browser address bar, a webhook, an `EventSource` (it cannot set headers, hence `?token=…`) or a bare `curl` can produce without ceremony:
640
+
641
+ ```bash
642
+ curl '<base>/functions/weather?city=Taipei&days=3&units=metric'
643
+ ```
644
+
645
+ Query values are strings, so the function's declared `meta.parameters` schema doubles as the coercion table: declared `number`/`integer`, `boolean`, `array` and `object` properties are converted, everything else stays a string. A value that does not fit its declared type is passed through verbatim rather than turned into `NaN` — the handler still owns validation.
646
+
647
+ | Declared type | Query form |
648
+ | ------------- | ---------- |
649
+ | `number` / `integer` | `?n=3` |
650
+ | `boolean` | `?verbose=true`, `?verbose=1`, or a bare `?verbose`; `false`/`0` for the other side |
651
+ | `array` | `?tag=a&tag=b`, `?tag=a,b`, or `?tag=["a","b"]` (items coerce by `items`) |
652
+ | `object` | `?filter={"lang":"en"}` |
653
+
654
+ For exact types regardless of the schema, pass the whole object as JSON in `input` — plain parameters overlay it, exactly like a multipart call: `?input={"n":3}&note=hi`. `stream`, `token` and `access_token` steer the request and never reach the handler.
655
+
656
+ GET is offered for every function; HTTP asks that a GET be safe to repeat and only you know whether yours is, so pick the method that matches what the function does. Responses are `no-store`.
657
+
591
658
  Downloads carry the same auth as function calls, only ever serve files under the functions scratch root (anything else — traversal, directories — is a 404), and stay available until the ~24h scratch sweep. Typical flow: a function returns `{ url: context.fileUrl(outPath) }` and the caller (or MCP agent) fetches `<base><url>` with its existing `Authorization` header — see `config/function-samples/text-to-speech.ts` for the end-to-end shape.
592
659
 
593
660
  Uploads are the same idea in the other direction, for functions whose input is a media file (`transcribe-media` and friends). The direct route is a multipart **call**: post `multipart/form-data` to `POST /functions/<name>` and every file field is staged into the call's scratch directory with its server-local path injected into the input under the field's name — one request uploads and runs:
@@ -618,13 +685,15 @@ allow_unauthenticated = true # or BUTTRESS_FUNCTIONS_ALLOW_UNAUTHENTICATED=1
618
685
 
619
686
  Only do that on a trusted network — it lets anyone who can reach the port run every function.
620
687
 
621
- Requests carrying a browser `Origin` header are rejected regardless of the above, so a web page you happen to visit cannot reach this surface. To allow a browser client, list its origin explicitly:
688
+ Browser-initiated cross-site requests are rejected regardless of the above, so a web page you happen to visit cannot reach this surface — that covers both requests carrying an `Origin` header and no-CORS loads that send none (`<img src>`, `<script src>`, a prefetch of a `GET` call), which browsers mark with `Sec-Fetch-Site`. To allow a browser client, list its origin explicitly:
622
689
 
623
690
  ```toml
624
691
  [functions]
625
692
  cors_allowed_origins = ["http://localhost:3000"] # or "*" to allow any origin
626
693
  ```
627
694
 
695
+ A listed origin only helps requests that carry one; a no-CORS load has no origin to match, so only `"*"` lets those through.
696
+
628
697
  #### Custom auth (`_auth.ts`)
629
698
 
630
699
  Dropping an `_auth.ts` (or `.js`) into the functions directory puts your own logic in front of every `/functions` endpoint. The file has the same shape as a function file — `meta` plus a default-exported handler — and `meta.mode` picks how it composes with the workspace auth above:
@@ -0,0 +1 @@
1
+ export declare const runUpdateCommand: (args: string[]) => Promise<never>;
@@ -25,6 +25,8 @@ export type FunctionsService = {
25
25
  config: FunctionsConfig;
26
26
  dir: string;
27
27
  list: () => Promise<FunctionSummary[]>;
28
+ /** One function's summary; rejects when it is unknown or fails to load. */
29
+ describe: (name: string) => Promise<FunctionSummary>;
28
30
  call: (name: string, input: any, options: CallOptions) => Promise<any>;
29
31
  /**
30
32
  * The operator's `_auth` function, or null when none exists. Rejects while
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The shared record contract behind the transport input parsers.
3
+ *
4
+ * `parseQueryInput` and `stageMultipartInput` both fill an input object with
5
+ * externally chosen field names, so plain `input[field] = value` is wrong: a
6
+ * field literally named `__proto__` would hit `Object.prototype`'s legacy
7
+ * setter instead of becoming an input value — silently dropped for strings,
8
+ * and a swapped prototype for object-like values.
9
+ */
10
+ /** Set an externally named field as a plain own data property. */
11
+ export declare const setInputField: (input: Record<string, any>, field: string, value: any) => void;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Query-string input for `GET /functions/<name>`.
3
+ *
4
+ * The sibling of `stageMultipartInput`: same idea, other transport. An optional
5
+ * `input` parameter carries a JSON object for exact types, and every other
6
+ * parameter overlays it — so `?input={"n":3}&note=hi` and a multipart body with
7
+ * the same two fields produce the same input object.
8
+ *
9
+ * Query values are always strings, so a bare `?n=3` would hand a handler `"3"`.
10
+ * The function's declared `meta.parameters` schema is the only thing that says
11
+ * otherwise, so it doubles as the coercion table: declared numbers, booleans,
12
+ * arrays and objects are converted, and anything undeclared stays a string.
13
+ * Coercion never rejects — a value that does not fit its declared type is
14
+ * passed through verbatim rather than turned into `NaN` (the handler, which
15
+ * owns validation, sees what the caller actually sent).
16
+ */
17
+ /** A caller mistake in the query shape — reported as 400, not 500. */
18
+ export declare class QueryInputError extends Error {
19
+ constructor(message: string);
20
+ }
21
+ /**
22
+ * Turn a query string into a function input object.
23
+ *
24
+ * `parameters` is the function's declared JSON Schema, when it has one; without
25
+ * it every value stays a string.
26
+ */
27
+ export declare const parseQueryInput: (search: URLSearchParams, parameters?: Record<string, any>) => Record<string, any>;
@@ -28,6 +28,7 @@ export declare const createFunctionsRegistry: (config: FunctionsConfig) => {
28
28
  config: FunctionsConfig;
29
29
  scan: () => Promise<void>;
30
30
  get: (name: string) => Promise<LoadedFunction>;
31
+ describe: (name: string) => Promise<FunctionSummary>;
31
32
  list: () => Promise<FunctionSummary[]>;
32
33
  entries: Map<string, Entry>;
33
34
  };
@@ -9,6 +9,6 @@
9
9
  export declare const TYPES_FILE_NAME = "buttress-functions.d.ts";
10
10
  export declare const TSCONFIG_FILE_NAME = "tsconfig.json";
11
11
  export declare const EXAMPLE_FILE_NAME = "_example.ts";
12
- export declare const TYPES_TEMPLATE = "// Generated by @fugood/buttress-server \u2014 do not edit.\n// Rewritten on every server start to match the running version.\n//\n// Local functions are EXPERIMENTAL: this contract may change between\n// releases \u2014 after a server upgrade, re-read this file for the current shape.\n\n/** Result of `context.spawn(...)`. */\ntype ButtressSpawnResult = {\n /** Exit code, or null when the process was killed by a signal. */\n code: number | null\n signal: NodeJS.Signals | null\n stdout: string | Buffer\n stderr: string | Buffer\n /** True when output hit `maxBuffer` and capture stopped early. */\n truncated: boolean\n}\n\ntype ButtressSpawnOptions = {\n cwd?: string\n /** Merged over the server's own environment. */\n env?: Record<string, string | undefined>\n /** Written to stdin, which is then closed. */\n input?: string | Uint8Array\n /** 'utf8' (default) yields strings; 'buffer' yields Buffers. */\n encoding?: 'utf8' | 'buffer'\n /** Per-stream capture cap in bytes (default 8MB). */\n maxBuffer?: number\n onStdout?: (chunk: Buffer) => void\n onStderr?: (chunk: Buffer) => void\n}\n\ntype ButtressCompletionResult = {\n content: string\n reasoning_content?: string\n tool_calls?: any[]\n interrupted?: boolean\n usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number }\n}\n\ntype ButtressFunctionContext = {\n /**\n * Run a child process. Resolves once it exits \u2014 a non-zero `code` is a\n * normal resolution, so check it yourself. Rejects only when the process\n * cannot be started. Every process a call spawns is killed when the call\n * ends or times out.\n */\n spawn: (\n command: string,\n args?: string[],\n options?: ButtressSpawnOptions,\n ) => Promise<ButtressSpawnResult>\n\n buttress: {\n /**\n * Run a chat completion on this server's LLM generator. `messages` are\n * rendered with the model's own chat template, and thinking is off unless\n * you pass `enable_thinking: true` (which fills `reasoning_content`).\n */\n completion: (options: {\n /** Configured `[[generators]]` model; defaults to the first one. */\n model?: string\n messages?: { role: string; content: any }[]\n prompt?: string\n /** Cap on generated tokens. Unbounded when omitted. */\n max_tokens?: number\n temperature?: number\n /** Emit the model's reasoning into `reasoning_content` (default false). */\n enable_thinking?: boolean\n /** Called for every streamed token event. */\n onToken?: (event: { token?: string; content?: string; [key: string]: any }) => void\n /** Any other backend sampling option (`top_p`, `stop`, `tools`, \u2026). */\n [param: string]: any\n }) => Promise<ButtressCompletionResult>\n\n /** Transcribe audio with this server's STT generator. */\n transcribe: (options: {\n /** Must match a configured STT model (`repo_id` or `repo_id:filename`). */\n model?: string\n filePath?: string\n audioData?: Uint8Array | Buffer\n options?: Record<string, any>\n }) => Promise<any>\n\n /**\n * Synthesize speech with this server's TTS generator (onnx-tts). The WAV\n * is written into `tempDir`; return `fileUrl(path)` to let callers\n * download it.\n */\n synthesize: (options: {\n /** Must match a configured TTS model when given. */\n model?: string\n text: string\n /** Backend options; `options.speaker` picks a registered voice. */\n options?: Record<string, any>\n }) => Promise<{ path: string; sampling_rate: number; channels: number }>\n }\n\n fetch: typeof fetch\n /** Server-side log, prefixed with the function name. */\n log: (...args: unknown[]) => void\n /** Emit a progress event. Delivered only to SSE callers; a no-op otherwise. */\n emit: (event: string, data?: unknown) => void\n /** Aborted when the call times out or the caller disconnects. */\n signal: AbortSignal\n env: Record<string, string | undefined>\n /** The `[functions.config]` table from the server config. */\n config: Record<string, any>\n /** Per-call scratch directory, created on first access. */\n tempDir: string\n /**\n * Download URL path (`/functions/files/...`) for a file inside `tempDir`;\n * relative input resolves against it. Callers fetch the URL with the same\n * auth as any function call. Scratch dirs are swept after ~24h.\n */\n fileUrl: (target: string) => string\n /** Absolute path of this functions directory. */\n dir: string\n /** Helper libraries: _, lodash, moment, math, mathjs, voca, chroma, json5, qs, bytes, ms, nanoid, md5. */\n libs: Record<string, any>\n}\n\ntype ButtressFunctionMeta = {\n /** Shown to MCP clients in `tools/list`. */\n description?: string\n /** JSON Schema for the input object, passed to MCP verbatim. */\n parameters?: Record<string, any>\n /** Deadline for this function: ms, or a duration string like \"10m\". */\n timeout?: number | string\n}\n\n// --- Custom auth ------------------------------------------------------------\n// Drop an `_auth.ts` into this directory to gate the /functions endpoints\n// with your own logic. It exports the same shape as a function file:\n//\n// export const meta: ButtressAuthMeta = { mode: 'both' }\n// export default async function (\n// request: ButtressAuthRequest,\n// context: ButtressAuthContext,\n// ): Promise<ButtressAuthResult> { ... }\n//\n// While an `_auth` file exists but fails to load, every call is rejected.\n\ntype ButtressAuthMeta = {\n /**\n * 'both' (default): runs after the built-in workspace auth passes, as an\n * extra gate. 'override': replaces workspace auth \u2014 this function is the\n * only authority (a presented workspace token is still verified into\n * `request.workspaceAuth` so you can choose to honor it).\n */\n mode?: 'override' | 'both'\n}\n\ntype ButtressAuthRequest = {\n method: string\n path: string\n /** Function name for `POST /functions/<name>`; undefined for list and MCP. */\n name?: string\n headers: Record<string, string | undefined>\n query: Record<string, string | undefined>\n /** Raw bearer token (Authorization header or `?token=`), if any. */\n token: string | null\n workspaceAuth: {\n /** Whether this server is bound to a workspace. */\n bound: boolean\n /** Whether the caller presented a valid workspace access token. */\n authenticated: boolean\n identity: {\n workspaceId: string\n subjectType: 'ws' | 'dev'\n subjectId: string\n jti?: string\n exp: number\n } | null\n }\n}\n\ntype ButtressAuthContext = {\n /** Server-side log, prefixed with \"_auth\". */\n log: (...args: unknown[]) => void\n fetch: typeof fetch\n env: Record<string, string | undefined>\n /** The `[functions.config]` table, same as `context.config` in functions. */\n config: Record<string, any>\n /** Absolute path of this functions directory. */\n dir: string\n /** Same helper libraries functions get. */\n libs: Record<string, any>\n}\n\n/** Only `true` (or `{ ok: true }`) allows the request; anything else denies. */\ntype ButtressAuthResult =\n | boolean\n | {\n ok: boolean\n /** Response status for a denial, 400-499 (default 403). */\n status?: number\n /** Message returned to the caller on denial. */\n error?: string\n }\n";
12
+ export declare const TYPES_TEMPLATE = "// Generated by @fugood/buttress-server \u2014 do not edit.\n// Rewritten on every server start to match the running version.\n//\n// Local functions are EXPERIMENTAL: this contract may change between\n// releases \u2014 after a server upgrade, re-read this file for the current shape.\n\n/** Result of `context.spawn(...)`. */\ntype ButtressSpawnResult = {\n /** Exit code, or null when the process was killed by a signal. */\n code: number | null\n signal: NodeJS.Signals | null\n stdout: string | Buffer\n stderr: string | Buffer\n /** True when output hit `maxBuffer` and capture stopped early. */\n truncated: boolean\n}\n\ntype ButtressSpawnOptions = {\n cwd?: string\n /** Merged over the server's own environment. */\n env?: Record<string, string | undefined>\n /** Written to stdin, which is then closed. */\n input?: string | Uint8Array\n /** 'utf8' (default) yields strings; 'buffer' yields Buffers. */\n encoding?: 'utf8' | 'buffer'\n /** Per-stream capture cap in bytes (default 8MB). */\n maxBuffer?: number\n onStdout?: (chunk: Buffer) => void\n onStderr?: (chunk: Buffer) => void\n}\n\ntype ButtressCompletionResult = {\n content: string\n reasoning_content?: string\n tool_calls?: any[]\n interrupted?: boolean\n usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number }\n}\n\ntype ButtressFunctionContext = {\n /**\n * Run a child process. Resolves once it exits \u2014 a non-zero `code` is a\n * normal resolution, so check it yourself. Rejects only when the process\n * cannot be started. Every process a call spawns is killed when the call\n * ends or times out.\n */\n spawn: (\n command: string,\n args?: string[],\n options?: ButtressSpawnOptions,\n ) => Promise<ButtressSpawnResult>\n\n buttress: {\n /**\n * Run a chat completion on this server's LLM generator. `messages` are\n * rendered with the model's own chat template, and thinking is off unless\n * you pass `enable_thinking: true` (which fills `reasoning_content`).\n */\n completion: (options: {\n /** Configured `[[generators]]` model; defaults to the first one. */\n model?: string\n messages?: { role: string; content: any }[]\n prompt?: string\n /** Cap on generated tokens. Unbounded when omitted. */\n max_tokens?: number\n temperature?: number\n /** Emit the model's reasoning into `reasoning_content` (default false). */\n enable_thinking?: boolean\n /** Called for every streamed token event. */\n onToken?: (event: { token?: string; content?: string; [key: string]: any }) => void\n /** Any other backend sampling option (`top_p`, `stop`, `tools`, \u2026). */\n [param: string]: any\n }) => Promise<ButtressCompletionResult>\n\n /** Transcribe audio with this server's STT generator. */\n transcribe: (options: {\n /** Must match a configured STT model (`repo_id` or `repo_id:filename`). */\n model?: string\n filePath?: string\n audioData?: Uint8Array | Buffer\n options?: Record<string, any>\n }) => Promise<any>\n\n /**\n * Synthesize speech with this server's TTS generator (onnx-tts). The WAV\n * is written into `tempDir`; return `fileUrl(path)` to let callers\n * download it.\n */\n synthesize: (options: {\n /** Must match a configured TTS model when given. */\n model?: string\n text: string\n /** Backend options; `options.speaker` picks a registered voice. */\n options?: Record<string, any>\n }) => Promise<{ path: string; sampling_rate: number; channels: number }>\n }\n\n fetch: typeof fetch\n /** Server-side log, prefixed with the function name. */\n log: (...args: unknown[]) => void\n /** Emit a progress event. Delivered only to SSE callers; a no-op otherwise. */\n emit: (event: string, data?: unknown) => void\n /** Aborted when the call times out or the caller disconnects. */\n signal: AbortSignal\n env: Record<string, string | undefined>\n /** The `[functions.config]` table from the server config. */\n config: Record<string, any>\n /** Per-call scratch directory, created on first access. */\n tempDir: string\n /**\n * Download URL path (`/functions/files/...`) for a file inside `tempDir`;\n * relative input resolves against it. Callers fetch the URL with the same\n * auth as any function call. Scratch dirs are swept after ~24h.\n */\n fileUrl: (target: string) => string\n /** Absolute path of this functions directory. */\n dir: string\n /** Helper libraries: _, lodash, moment, math, mathjs, voca, chroma, json5, qs, bytes, ms, nanoid, md5. */\n libs: Record<string, any>\n}\n\ntype ButtressFunctionMeta = {\n /** Shown to MCP clients in `tools/list`. */\n description?: string\n /** JSON Schema for the input object, passed to MCP verbatim. */\n parameters?: Record<string, any>\n /** Deadline for this function: ms, or a duration string like \"10m\". */\n timeout?: number | string\n}\n\n// --- Custom auth ------------------------------------------------------------\n// Drop an `_auth.ts` into this directory to gate the /functions endpoints\n// with your own logic. It exports the same shape as a function file:\n//\n// export const meta: ButtressAuthMeta = { mode: 'both' }\n// export default async function (\n// request: ButtressAuthRequest,\n// context: ButtressAuthContext,\n// ): Promise<ButtressAuthResult> { ... }\n//\n// While an `_auth` file exists but fails to load, every call is rejected.\n\ntype ButtressAuthMeta = {\n /**\n * 'both' (default): runs after the built-in workspace auth passes, as an\n * extra gate. 'override': replaces workspace auth \u2014 this function is the\n * only authority (a presented workspace token is still verified into\n * `request.workspaceAuth` so you can choose to honor it).\n */\n mode?: 'override' | 'both'\n}\n\ntype ButtressAuthRequest = {\n method: string\n path: string\n /** Function name for `GET`/`POST /functions/<name>`; undefined for list and MCP. */\n name?: string\n headers: Record<string, string | undefined>\n query: Record<string, string | undefined>\n /** Raw bearer token (Authorization header or `?token=`), if any. */\n token: string | null\n workspaceAuth: {\n /** Whether this server is bound to a workspace. */\n bound: boolean\n /** Whether the caller presented a valid workspace access token. */\n authenticated: boolean\n identity: {\n workspaceId: string\n subjectType: 'ws' | 'dev'\n subjectId: string\n jti?: string\n exp: number\n } | null\n }\n}\n\ntype ButtressAuthContext = {\n /** Server-side log, prefixed with \"_auth\". */\n log: (...args: unknown[]) => void\n fetch: typeof fetch\n env: Record<string, string | undefined>\n /** The `[functions.config]` table, same as `context.config` in functions. */\n config: Record<string, any>\n /** Absolute path of this functions directory. */\n dir: string\n /** Same helper libraries functions get. */\n libs: Record<string, any>\n}\n\n/** Only `true` (or `{ ok: true }`) allows the request; anything else denies. */\ntype ButtressAuthResult =\n | boolean\n | {\n ok: boolean\n /** Response status for a denial, 400-499 (default 403). */\n status?: number\n /** Message returned to the caller on denial. */\n error?: string\n }\n";
13
13
  export declare const TSCONFIG_TEMPLATE = "{\n // Editor support for Buttress local functions.\n // Install @types/node here for typings on \"node:*\" imports.\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"Bundler\",\n \"lib\": [\"ES2023\"],\n \"strict\": true,\n \"noEmit\": true,\n \"allowJs\": true,\n \"skipLibCheck\": true\n },\n \"include\": [\"**/*.ts\", \"**/*.js\"]\n}\n";
14
- export declare const EXAMPLE_TEMPLATE = "// Example Buttress local function.\n//\n// Rename (or copy) this file to expose it: files starting with \"_\" are\n// ignored. The tool name is the file name \u2014 \"video-duration.ts\" becomes the\n// tool \"video-duration\", callable over MCP and at\n// POST /functions/video-duration.\n\nexport const meta: ButtressFunctionMeta = {\n description: 'Report the duration of a video file using ffprobe',\n parameters: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'Absolute path to a video file' },\n },\n required: ['path'],\n },\n timeout: '2m',\n}\n\nexport default async function (\n { path }: { path: string },\n context: ButtressFunctionContext,\n): Promise<{ seconds: number }> {\n const { code, stdout, stderr } = await context.spawn('ffprobe', [\n '-v',\n 'error',\n '-show_entries',\n 'format=duration',\n '-of',\n 'default=noprint_wrappers=1:nokey=1',\n path,\n ])\n\n if (code !== 0) throw new Error(`ffprobe failed (${code}): ${stderr}`)\n\n return { seconds: Number(String(stdout).trim()) }\n}\n";
14
+ export declare const EXAMPLE_TEMPLATE = "// Example Buttress local function.\n//\n// Rename (or copy) this file to expose it: files starting with \"_\" are\n// ignored. The tool name is the file name \u2014 \"video-duration.ts\" becomes the\n// tool \"video-duration\", callable over MCP, at POST /functions/video-duration\n// with a JSON body, and at GET /functions/video-duration?path=... with the\n// input in the query string.\n\nexport const meta: ButtressFunctionMeta = {\n description: 'Report the duration of a video file using ffprobe',\n parameters: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'Absolute path to a video file' },\n },\n required: ['path'],\n },\n timeout: '2m',\n}\n\nexport default async function (\n { path }: { path: string },\n context: ButtressFunctionContext,\n): Promise<{ seconds: number }> {\n const { code, stdout, stderr } = await context.spawn('ffprobe', [\n '-v',\n 'error',\n '-show_entries',\n 'format=duration',\n '-of',\n 'default=noprint_wrappers=1:nokey=1',\n path,\n ])\n\n if (code !== 0) throw new Error(`ffprobe failed (${code}): ${stderr}`)\n\n return { seconds: Number(String(stdout).trim()) }\n}\n";
@@ -159,7 +159,7 @@ export type WorkspaceAuthInfo = {
159
159
  export type AuthRequestInfo = {
160
160
  method: string;
161
161
  path: string;
162
- /** Function name for `POST /functions/<name>`; undefined for list and MCP. */
162
+ /** Function name for `GET`/`POST /functions/<name>`; undefined for list and MCP. */
163
163
  name?: string;
164
164
  headers: Record<string, string | undefined>;
165
165
  query: Record<string, any>;
package/lib/index.d.ts CHANGED
@@ -2,13 +2,14 @@ import type { AnyElysia } from 'elysia';
2
2
  import * as backendCore from '@fugood/buttress-backend-core';
3
3
  import { AutodiscoverService } from './autodiscover';
4
4
  import type { Config } from './types';
5
+ import { compareVersions } from './utils/update';
5
6
  import type { FunctionsConfig, FunctionsService } from './functions';
6
7
  export { startModelDownload } from '@fugood/buttress-backend-core';
7
8
  export { processConfig } from './utils/config';
8
9
  export { resolveFunctionsConfig } from './functions';
9
10
  export type { FunctionsConfig, FunctionsService } from './functions';
10
11
  export declare const checkForUpdates: () => Promise<string | null>;
11
- export declare const compareVersions: (current: string, latest: string) => boolean;
12
+ export { compareVersions };
12
13
  export declare const logUpdateMessage: (latestVersion: string) => void;
13
14
  export declare const checkAndNotifyUpdates: () => Promise<void>;
14
15
  export type Backend = typeof backendCore;