@guuey/worker 0.1.0

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 Loqu, Inc.
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,46 @@
1
+ # @guuey/worker
2
+
3
+ The Guuey Worker Protocol v1 types + the tiered worker SDK. Write an agent worker
4
+ in any framework; Guuey runs it, isolates it per end-user, streams it, and scales
5
+ it. You touch only: **read the request → stream your output (text or framework-native events).**
6
+
7
+ ## Zero glue — managed Node base (1a)
8
+
9
+ Export a handler; the base harness wires it to the protocol.
10
+
11
+ ```js
12
+ import { query } from "@anthropic-ai/claude-agent-sdk";
13
+ export default async (turn) => {
14
+ for await (const msg of query({ prompt: turn.input, options: {} })) {
15
+ if (msg.type === "assistant") {
16
+ for (const block of msg.content) {
17
+ if (block.type === "text") turn.text(block.text);
18
+ }
19
+ }
20
+ }
21
+ };
22
+ ```
23
+
24
+ ## Raw — any language, no SDK
25
+
26
+ The protocol is deliberately hand-implementable: read NDJSON control messages on
27
+ **stdin (fd 0)**, write NDJSON events on **fd 3**, log freely on stdout/stderr.
28
+
29
+ ```python
30
+ import sys, json, os
31
+ for line in sys.stdin:
32
+ inv = json.loads(line) # {type:"invoke", input, identity, fs, history, priorMemory?, priorState?}
33
+ if inv.get("type") != "invoke": continue
34
+ # emit framework-native SDK events (the Router normalizes them):
35
+ os.write(3, (json.dumps({"type":"native","framework":"google-adk","event":{...}}) + "\n").encode())
36
+ os.write(3, (json.dumps({"type":"done","stopReason":"end_turn","result":"hi"}) + "\n").encode())
37
+ ```
38
+
39
+ ## The protocol (v1)
40
+
41
+ - **fd 0 (stdin), Router→Worker:** `invoke` · `shutdown`
42
+ - **fd 3, Worker→Router:** `text` · `native` · `done` · `error`
43
+ - **stdout/stderr:** your logs — never the protocol.
44
+ - Context is **pushed** in the invoke (`history` · `priorMemory` · `priorState`).
45
+
46
+ See the design: `docs/superpowers/specs/2026-06-22-worker-platform-northstar-design.md`.
package/dist/emit.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The Worker→Router event emitter (fd 3). Serializes each {@link WorkerEvent} as
3
+ * one NDJSON line to an injected Writable. The default `serve()` (T4) passes the
4
+ * fd-3 stream; tests pass an in-memory stream. Each event is written immediately
5
+ * (one `write` per line) so streaming never stalls behind a buffer.
6
+ */
7
+ import type { JsonValue, StopReason } from "./protocol.js";
8
+ /** Minimal write interface accepted by {@link createEmitter}. `NodeJS.WritableStream` satisfies this. */
9
+ export interface WriteSink {
10
+ write(s: string): void;
11
+ }
12
+ export interface Emitter {
13
+ text(text: string): void;
14
+ done(result: string, stopReason?: StopReason): void;
15
+ error(message: string): void;
16
+ native(framework: string, event: JsonValue): void;
17
+ /** The SDK-version handshake (additive-optional, §8 item B). Emit at most
18
+ * once, before any native/turn event. */
19
+ hello(framework: string, sdkName: string | null, sdkVersion: string | null): void;
20
+ }
21
+ export declare function createEmitter(out: WriteSink): Emitter;
22
+ //# sourceMappingURL=emit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../src/emit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAe,MAAM,eAAe,CAAC;AAExE,yGAAyG;AACzG,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACpD,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IAClD;8CAC0C;IAC1C,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;CACnF;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAWrD"}
package/dist/emit.js ADDED
@@ -0,0 +1,12 @@
1
+ export function createEmitter(out) {
2
+ const write = (ev) => {
3
+ out.write(JSON.stringify(ev) + "\n");
4
+ };
5
+ return {
6
+ text: (text) => write({ type: "text", text }),
7
+ done: (result, stopReason = "end_turn") => write({ type: "done", stopReason, result }),
8
+ error: (message) => write({ type: "error", message }),
9
+ native: (framework, event) => write({ type: "native", framework, event }),
10
+ hello: (framework, sdkName, sdkVersion) => write({ type: "hello", framework, sdkName, sdkVersion }),
11
+ };
12
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @guuey/worker — Guuey Worker Protocol v1 types + the tiered worker SDK.
3
+ * See docs/superpowers/specs/2026-06-22-worker-platform-northstar-design.md §1.
4
+ */
5
+ export { PROTOCOL_VERSION, type ProtocolVersion, type JsonValue, type AuthMode, type Identity, type Fs, type HistoryMessage, type PriorMemoryRecord, type StopReason, type Invoke, type Shutdown, type ControlMessage, type TextEvent, type DoneEvent, type ErrorEvent, type NativeEvent, type WorkerHelloEvent, type WorkerEvent, } from "./protocol.js";
6
+ export { parseControl, parseEvent, isInvoke, isShutdown, isText, isDone, isError, isNative, isHello, } from "./parse.js";
7
+ export { createEmitter, type Emitter, type WriteSink } from "./emit.js";
8
+ export { serve, serveOn, type ServeOptions } from "./serve.js";
9
+ export { Turn, type WorkerHandler } from "./turn.js";
10
+ export { serveNative, serveNativeOn } from "./serve-native.js";
11
+ export type { NativeEmit, NativeHandler, NativeServeInfo } from "./serve-native.js";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,gBAAgB,EAChB,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,EAAE,EACP,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,UAAU,EACf,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,UAAU,EACV,MAAM,EACN,MAAM,EACN,OAAO,EACP,QAAQ,EACR,OAAO,GACR,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AACxE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @guuey/worker — Guuey Worker Protocol v1 types + the tiered worker SDK.
3
+ * See docs/superpowers/specs/2026-06-22-worker-platform-northstar-design.md §1.
4
+ */
5
+ export { PROTOCOL_VERSION, } from "./protocol.js";
6
+ export { parseControl, parseEvent, isInvoke, isShutdown, isText, isDone, isError, isNative, isHello, } from "./parse.js";
7
+ export { createEmitter } from "./emit.js";
8
+ export { serve, serveOn } from "./serve.js";
9
+ export { Turn } from "./turn.js";
10
+ export { serveNative, serveNativeOn } from "./serve-native.js";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared NDJSON line-reader for the control stream (fd 0). Used by both the
3
+ * tier-2 managed loop (`serve.ts`) and the native-streaming loop
4
+ * (`serve-native.ts`) so the two serve tiers read lines identically.
5
+ */
6
+ import type { Readable } from "node:stream";
7
+ /** Async-iterate NDJSON lines off a Readable. */
8
+ export declare function lines(input: Readable): AsyncIterable<string>;
9
+ //# sourceMappingURL=lines.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lines.d.ts","sourceRoot":"","sources":["../src/lines.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,iDAAiD;AACjD,wBAAuB,KAAK,CAAC,KAAK,EAAE,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAcnE"}
package/dist/lines.js ADDED
@@ -0,0 +1,18 @@
1
+ /** Async-iterate NDJSON lines off a Readable. */
2
+ export async function* lines(input) {
3
+ let buf = "";
4
+ for await (const chunk of input) {
5
+ buf += typeof chunk === "string" ? chunk : chunk.toString("utf8");
6
+ let nl = buf.indexOf("\n");
7
+ while (nl !== -1) {
8
+ const line = buf.slice(0, nl).trim();
9
+ buf = buf.slice(nl + 1);
10
+ if (line.length > 0)
11
+ yield line;
12
+ nl = buf.indexOf("\n");
13
+ }
14
+ }
15
+ const tail = buf.trim();
16
+ if (tail.length > 0)
17
+ yield tail;
18
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Tier-1 typed interpreter: validate one NDJSON control line (Router→Worker, fd
3
+ * 0) into a typed {@link ControlMessage}. Hand-rolled guards keep the package
4
+ * dependency-free. A malformed line throws a clear error (never a silent skip).
5
+ */
6
+ import type { ControlMessage, DoneEvent, ErrorEvent, Invoke, NativeEvent, Shutdown, TextEvent, WorkerEvent, WorkerHelloEvent } from "./protocol.js";
7
+ export declare function isInvoke(m: ControlMessage): m is Invoke;
8
+ export declare function isShutdown(m: ControlMessage): m is Shutdown;
9
+ export declare function parseControl(line: string): ControlMessage;
10
+ export declare function isText(e: WorkerEvent): e is TextEvent;
11
+ export declare function isDone(e: WorkerEvent): e is DoneEvent;
12
+ export declare function isError(e: WorkerEvent): e is ErrorEvent;
13
+ export declare function isNative(e: WorkerEvent): e is NativeEvent;
14
+ export declare function isHello(e: WorkerEvent): e is WorkerHelloEvent;
15
+ /**
16
+ * Router-side typed interpreter: validate one NDJSON event line (Worker→Router,
17
+ * fd 3) into a typed {@link WorkerEvent}. Mirror of {@link parseControl}; a
18
+ * malformed line throws (never a silent skip).
19
+ */
20
+ export declare function parseEvent(line: string): WorkerEvent;
21
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAEV,cAAc,EACd,SAAS,EACT,UAAU,EAIV,MAAM,EAEN,WAAW,EAEX,QAAQ,EAER,SAAS,EACT,WAAW,EACX,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAEvB,wBAAgB,QAAQ,CAAC,CAAC,EAAE,cAAc,GAAG,CAAC,IAAI,MAAM,CAEvD;AACD,wBAAgB,UAAU,CAAC,CAAC,EAAE,cAAc,GAAG,CAAC,IAAI,QAAQ,CAE3D;AAyDD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAgCzD;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,WAAW,GAAG,CAAC,IAAI,SAAS,CAErD;AACD,wBAAgB,MAAM,CAAC,CAAC,EAAE,WAAW,GAAG,CAAC,IAAI,SAAS,CAErD;AACD,wBAAgB,OAAO,CAAC,CAAC,EAAE,WAAW,GAAG,CAAC,IAAI,UAAU,CAEvD;AACD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,WAAW,GAAG,CAAC,IAAI,WAAW,CAEzD;AACD,wBAAgB,OAAO,CAAC,CAAC,EAAE,WAAW,GAAG,CAAC,IAAI,gBAAgB,CAE7D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CA6CpD"}
package/dist/parse.js ADDED
@@ -0,0 +1,159 @@
1
+ export function isInvoke(m) {
2
+ return m.type === "invoke";
3
+ }
4
+ export function isShutdown(m) {
5
+ return m.type === "shutdown";
6
+ }
7
+ function isObject(v) {
8
+ return typeof v === "object" && v !== null && !Array.isArray(v);
9
+ }
10
+ function parseIdentity(v) {
11
+ if (!isObject(v) || typeof v.userId !== "string") {
12
+ throw new Error("invoke.identity missing string `userId`");
13
+ }
14
+ const authMode = v.authMode === "authenticated" ? "authenticated" : "anonymous";
15
+ return { userId: v.userId, authMode };
16
+ }
17
+ function parseFs(v) {
18
+ if (!isObject(v) ||
19
+ typeof v.app !== "string" ||
20
+ typeof v.home !== "string" ||
21
+ typeof v.session !== "string") {
22
+ throw new Error("invoke.fs missing string `app`/`home`/`session`");
23
+ }
24
+ return { app: v.app, home: v.home, session: v.session };
25
+ }
26
+ function parseHistory(v) {
27
+ if (v === undefined)
28
+ return [];
29
+ if (!Array.isArray(v))
30
+ throw new Error("invoke.history must be an array");
31
+ return v.map((m) => {
32
+ if (!isObject(m) || typeof m.text !== "string") {
33
+ throw new Error("invoke.history entry missing string `text`");
34
+ }
35
+ return { role: m.role === "agent" ? "agent" : "user", text: m.text };
36
+ });
37
+ }
38
+ /**
39
+ * Parse the optional `priorMemory` push (§1.4). Lenient by design — a malformed
40
+ * entry (no `value`, or a non-object) is DROPPED rather than throwing, so a
41
+ * stray memory record never fails an otherwise-valid turn. Returns `undefined`
42
+ * when absent or when no entry survives (so the field stays off the Invoke).
43
+ */
44
+ function parsePriorMemory(v) {
45
+ if (!Array.isArray(v))
46
+ return undefined;
47
+ const out = [];
48
+ for (const entry of v) {
49
+ if (isObject(entry) && "value" in entry) {
50
+ out.push({
51
+ value: entry.value,
52
+ ...(typeof entry.key === "string" ? { key: entry.key } : {}),
53
+ });
54
+ }
55
+ }
56
+ return out.length > 0 ? out : undefined;
57
+ }
58
+ export function parseControl(line) {
59
+ let raw;
60
+ try {
61
+ raw = JSON.parse(line);
62
+ }
63
+ catch {
64
+ throw new Error(`non-JSON control line: ${line.slice(0, 200)}`);
65
+ }
66
+ if (!isObject(raw)) {
67
+ throw new Error(`control line is not an object: ${line.slice(0, 200)}`);
68
+ }
69
+ switch (raw.type) {
70
+ case "invoke": {
71
+ if (typeof raw.input !== "string")
72
+ throw new Error("invoke missing string `input`");
73
+ const priorMemory = parsePriorMemory(raw.priorMemory);
74
+ return {
75
+ type: "invoke",
76
+ input: raw.input,
77
+ identity: parseIdentity(raw.identity),
78
+ fs: parseFs(raw.fs),
79
+ history: parseHistory(raw.history),
80
+ // §1.4 push-by-value context — additive, both optional. `priorState` uses
81
+ // a `!== undefined` gate (not truthiness) so a falsy blob (null/0/"") is
82
+ // preserved; `priorMemory` is omitted when empty/absent.
83
+ ...(priorMemory ? { priorMemory } : {}),
84
+ ...(raw.priorState !== undefined ? { priorState: raw.priorState } : {}),
85
+ };
86
+ }
87
+ case "shutdown":
88
+ return { type: "shutdown" };
89
+ default:
90
+ throw new Error(`unknown control message type: ${String(raw.type)}`);
91
+ }
92
+ }
93
+ export function isText(e) {
94
+ return e.type === "text";
95
+ }
96
+ export function isDone(e) {
97
+ return e.type === "done";
98
+ }
99
+ export function isError(e) {
100
+ return e.type === "error";
101
+ }
102
+ export function isNative(e) {
103
+ return e.type === "native";
104
+ }
105
+ export function isHello(e) {
106
+ return e.type === "hello";
107
+ }
108
+ /**
109
+ * Router-side typed interpreter: validate one NDJSON event line (Worker→Router,
110
+ * fd 3) into a typed {@link WorkerEvent}. Mirror of {@link parseControl}; a
111
+ * malformed line throws (never a silent skip).
112
+ */
113
+ export function parseEvent(line) {
114
+ let raw;
115
+ try {
116
+ raw = JSON.parse(line);
117
+ }
118
+ catch {
119
+ throw new Error(`non-JSON event line: ${line.slice(0, 200)}`);
120
+ }
121
+ if (!isObject(raw)) {
122
+ throw new Error(`event line is not an object: ${line.slice(0, 200)}`);
123
+ }
124
+ switch (raw.type) {
125
+ case "text": {
126
+ if (typeof raw.text !== "string")
127
+ throw new Error("text event missing string `text`");
128
+ return { type: "text", text: raw.text };
129
+ }
130
+ case "done": {
131
+ const stopReason = raw.stopReason === "max_turns" || raw.stopReason === "error" ? raw.stopReason : "end_turn";
132
+ const result = typeof raw.result === "string" ? raw.result : "";
133
+ return { type: "done", stopReason, result };
134
+ }
135
+ case "error": {
136
+ const message = typeof raw.message === "string" ? raw.message : "unknown worker error";
137
+ return { type: "error", message };
138
+ }
139
+ case "native": {
140
+ if (typeof raw.framework !== "string") {
141
+ throw new Error("native event missing string `framework`");
142
+ }
143
+ if (raw.event === undefined) {
144
+ throw new Error("native event missing `event`");
145
+ }
146
+ return { type: "native", framework: raw.framework, event: raw.event };
147
+ }
148
+ case "hello": {
149
+ if (typeof raw.framework !== "string") {
150
+ throw new Error("hello event missing string `framework`");
151
+ }
152
+ const sdkName = typeof raw.sdkName === "string" ? raw.sdkName : null;
153
+ const sdkVersion = typeof raw.sdkVersion === "string" ? raw.sdkVersion : null;
154
+ return { type: "hello", framework: raw.framework, sdkName, sdkVersion };
155
+ }
156
+ default:
157
+ throw new Error(`unknown event type: ${String(raw.type)}`);
158
+ }
159
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Guuey Worker Protocol v1 — the frozen, versioned types the Router and a Worker
3
+ * exchange. Types ARE the protocol's source of truth (per-language SDKs derive
4
+ * from these). See the north-star design §1.
5
+ *
6
+ * Router → Worker (fd 0 / stdin): ControlMessage (invoke | shutdown)
7
+ * Worker → Router (fd 3): WorkerEvent (text | done | error)
8
+ * stdout (1) + stderr (2): the builder's own logs — not the protocol.
9
+ */
10
+ /** v1 ≡ the worker-facing half of Guuey Router v1 (pinned via `runtime.router`). */
11
+ export declare const PROTOCOL_VERSION: "v1";
12
+ export type ProtocolVersion = typeof PROTOCOL_VERSION;
13
+ /** A JSON value — the precise parse target for NDJSON control/event lines
14
+ * (NOT `unknown`: a parsed line has no static shape yet, but it IS JSON). */
15
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
16
+ [key: string]: JsonValue;
17
+ };
18
+ export type AuthMode = "anonymous" | "authenticated";
19
+ /** Router-vouched end-user identity (the Worker writes zero auth code). */
20
+ export interface Identity {
21
+ userId: string;
22
+ authMode: AuthMode;
23
+ }
24
+ /** The three GuueyFS layer mounts the Worker reads/writes. */
25
+ export interface Fs {
26
+ app: string;
27
+ home: string;
28
+ session: string;
29
+ }
30
+ export interface HistoryMessage {
31
+ role: "user" | "agent";
32
+ text: string;
33
+ }
34
+ /**
35
+ * One prior-memory record pushed by value on the invoke (§1.4). A minimal,
36
+ * dependency-free projection of the Router's `AgMemoryRecord` — the worker reads
37
+ * only `key`/`value` for its `<thread_memory>` preamble. `value` is required
38
+ * (the fold seeds it); `key` is optional (unkeyed records exist).
39
+ */
40
+ export interface PriorMemoryRecord {
41
+ key?: string;
42
+ value: JsonValue;
43
+ }
44
+ export type StopReason = "end_turn" | "max_turns" | "error";
45
+ /** Start a turn. Carries the pushed context (identity, fs, a recent history
46
+ * window — the full transcript is at `/session/.guuey/history.jsonl`).
47
+ *
48
+ * `priorMemory`/`priorState` are the §1.4 push-by-value context the worker
49
+ * renders into its system-prompt preamble (thread memory + working state).
50
+ * Both optional: an early-thread invoke carries neither. */
51
+ export interface Invoke {
52
+ type: "invoke";
53
+ input: string;
54
+ identity: Identity;
55
+ fs: Fs;
56
+ history: HistoryMessage[];
57
+ /** Thread-scoped memory folded from prior turns (the `<thread_memory>` preamble). */
58
+ priorMemory?: PriorMemoryRecord[];
59
+ /** Prior working-state blob carried from the previous turn (the `<working_state>` preamble). */
60
+ priorState?: JsonValue;
61
+ }
62
+ /** Graceful termination (also signalled by stdin EOF). */
63
+ export interface Shutdown {
64
+ type: "shutdown";
65
+ }
66
+ export type ControlMessage = Invoke | Shutdown;
67
+ /** Stream a chunk of assistant output. */
68
+ export interface TextEvent {
69
+ type: "text";
70
+ text: string;
71
+ }
72
+ /** Terminal success. `result` is the turn's final text. */
73
+ export interface DoneEvent {
74
+ type: "done";
75
+ stopReason: StopReason;
76
+ result: string;
77
+ }
78
+ /** Terminal failure. */
79
+ export interface ErrorEvent {
80
+ type: "error";
81
+ message: string;
82
+ }
83
+ /**
84
+ * Pass-through carrier for framework-native SDK events (fd-3).
85
+ * The Worker emits these opaquely; the Router dispatches to the matching
86
+ * `@silverprotocol/<framework>` normalizer.
87
+ *
88
+ * `framework` is typed as `string` rather than the `AgentFramework` enum from
89
+ * `@guuey/config` to keep this package dependency-free. The Router validates
90
+ * `framework` against the real enum before dispatching.
91
+ */
92
+ export interface NativeEvent {
93
+ readonly type: "native";
94
+ /** The framework whose native event this is — Router picks the normalizer. */
95
+ readonly framework: string;
96
+ /** One native SDK event, opaque JSON; only the Router's normalizer reads it. */
97
+ readonly event: JsonValue;
98
+ }
99
+ /**
100
+ * Additive-optional in protocol v1 (the SDK-version handshake, model-release
101
+ * playbook §8 item B). A worker MAY emit this once, before any native/turn
102
+ * event, to report its own SDK provenance. The Router treats it as Router-plane
103
+ * ONLY: never forwarded to the SSE client, never fed to a `@silverprotocol/*`
104
+ * normalizer (it is not an AgJSON-eligible event) — just logged + carried into
105
+ * the invoke's completion telemetry. Absence is fully tolerated (older workers
106
+ * that predate this event, or a builder's own `serve()`-based worker that never
107
+ * emits it): the Router simply has no SDK provenance to log for that invoke.
108
+ */
109
+ export interface WorkerHelloEvent {
110
+ readonly type: "hello";
111
+ /** The framework this worker runs — matches `NativeEvent.framework`. */
112
+ readonly framework: string;
113
+ /** The SDK package name, e.g. `"@anthropic-ai/claude-agent-sdk"`; `null` when unknown/inapplicable. */
114
+ readonly sdkName: string | null;
115
+ /** The SDK's installed version, resolved at RUNTIME (never a hardcoded literal); `null` when unresolvable. */
116
+ readonly sdkVersion: string | null;
117
+ }
118
+ export type WorkerEvent = TextEvent | DoneEvent | ErrorEvent | NativeEvent | WorkerHelloEvent;
119
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,EAAG,IAAa,CAAC;AAC9C,MAAM,MAAM,eAAe,GAAG,OAAO,gBAAgB,CAAC;AAEtD;8EAC8E;AAC9E,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,EAAE,GACX;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEjC,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,eAAe,CAAC;AACrD,2EAA2E;AAC3E,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;CACpB;AACD,8DAA8D;AAC9D,MAAM,WAAW,EAAE;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AACD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd;AACD;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,SAAS,CAAC;CAClB;AACD,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,CAAC;AAI5D;;;;;6DAK6D;AAC7D,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,EAAE,EAAE,EAAE,CAAC;IACP,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,qFAAqF;IACrF,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,gGAAgG;IAChG,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AACD,0DAA0D;AAC1D,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,UAAU,CAAC;CAClB;AACD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,CAAC;AAI/C,0CAA0C;AAC1C,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AACD,2DAA2D;AAC3D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AACD,wBAAwB;AACxB,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AACD;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;CAC3B;AACD;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uGAAuG;IACvG,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,8GAA8G;IAC9G,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AACD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,gBAAgB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Guuey Worker Protocol v1 — the frozen, versioned types the Router and a Worker
3
+ * exchange. Types ARE the protocol's source of truth (per-language SDKs derive
4
+ * from these). See the north-star design §1.
5
+ *
6
+ * Router → Worker (fd 0 / stdin): ControlMessage (invoke | shutdown)
7
+ * Worker → Router (fd 3): WorkerEvent (text | done | error)
8
+ * stdout (1) + stderr (2): the builder's own logs — not the protocol.
9
+ */
10
+ /** v1 ≡ the worker-facing half of Guuey Router v1 (pinned via `runtime.router`). */
11
+ export const PROTOCOL_VERSION = "v1";
@@ -0,0 +1,18 @@
1
+ import type { Invoke, JsonValue } from "./protocol.js";
2
+ import type { ServeOptions } from "./serve.js";
3
+ export interface NativeEmit {
4
+ native(event: JsonValue): void;
5
+ text(chunk: string): void;
6
+ }
7
+ export type NativeHandler = (invoke: Invoke, emit: NativeEmit) => Promise<string | void> | string | void;
8
+ export interface NativeServeInfo {
9
+ framework: string;
10
+ sdkName?: string | null;
11
+ sdkVersion?: string | null;
12
+ }
13
+ export declare function serveNativeOn(handler: NativeHandler, info: NativeServeInfo, opts: ServeOptions): Promise<void>;
14
+ /** The public entry: wire stdin (fd 0) + an fd-3 Writable, then run the loop. */
15
+ export declare function serveNative(handler: NativeHandler, info: NativeServeInfo, opts?: {
16
+ idleMs?: number;
17
+ }): Promise<void>;
18
+ //# sourceMappingURL=serve-native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve-native.d.ts","sourceRoot":"","sources":["../src/serve-native.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AACD,MAAM,MAAM,aAAa,GAAG,CAC1B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,KACb,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;AAC5C,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CACzB,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,eAAe,EACrB,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACzB,OAAO,CAAC,IAAI,CAAC,CAQf"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Native-streaming serve loop: mirrors `serve.ts`'s line-reading, idle-timeout,
3
+ * and shutdown handling exactly, but the per-invoke body emits opaque
4
+ * framework-native events (`emit.native`) alongside text, instead of driving a
5
+ * `Turn`. Lets a template worker emit `hello` → `native`* → `done` per invoke,
6
+ * like `@guuey/host` does.
7
+ */
8
+ import { createWriteStream } from "node:fs";
9
+ import { createEmitter } from "./emit.js";
10
+ import { lines } from "./lines.js";
11
+ import { isInvoke, isShutdown, parseControl } from "./parse.js";
12
+ export async function serveNativeOn(handler, info, opts) {
13
+ const emitter = createEmitter(opts.output);
14
+ const idleMs = opts.idleMs ?? 5 * 60_000;
15
+ let idle;
16
+ const arm = () => {
17
+ if (idle)
18
+ clearTimeout(idle);
19
+ idle = setTimeout(() => opts.input.push(null), idleMs);
20
+ if (idle.unref)
21
+ idle.unref();
22
+ };
23
+ arm();
24
+ for await (const line of lines(opts.input)) {
25
+ arm();
26
+ const msg = parseControl(line);
27
+ if (isShutdown(msg))
28
+ break;
29
+ if (!isInvoke(msg))
30
+ continue;
31
+ emitter.hello(info.framework, info.sdkName ?? null, info.sdkVersion ?? null);
32
+ try {
33
+ const scoped = {
34
+ native: (event) => emitter.native(info.framework, event),
35
+ text: (chunk) => emitter.text(chunk),
36
+ };
37
+ const result = await handler(msg, scoped);
38
+ emitter.done(typeof result === "string" ? result : "", "end_turn");
39
+ }
40
+ catch (e) {
41
+ emitter.error(e instanceof Error ? e.message : String(e));
42
+ }
43
+ }
44
+ if (idle)
45
+ clearTimeout(idle);
46
+ }
47
+ /** The public entry: wire stdin (fd 0) + an fd-3 Writable, then run the loop. */
48
+ export function serveNative(handler, info, opts) {
49
+ // fd 3 is the write end of the pipe the Router created at spawn.
50
+ const output = createWriteStream("", { fd: 3 });
51
+ return serveNativeOn(handler, info, {
52
+ input: process.stdin,
53
+ output,
54
+ ...(opts?.idleMs ? { idleMs: opts.idleMs } : {}),
55
+ });
56
+ }
@@ -0,0 +1,14 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+ import { type WorkerHandler } from "./turn.js";
3
+ export interface ServeOptions {
4
+ input: Readable;
5
+ output: Writable;
6
+ /** Stop the loop after this many ms with no control input (default 5 min). */
7
+ idleMs?: number;
8
+ }
9
+ export declare function serveOn(handler: WorkerHandler, opts: ServeOptions): Promise<void>;
10
+ /** The public entry: wire stdin (fd 0) + an fd-3 Writable, then run the loop. */
11
+ export declare function serve(handler: WorkerHandler, opts?: {
12
+ idleMs?: number;
13
+ }): Promise<void>;
14
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAItD,OAAO,EAAQ,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAErD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,QAAQ,CAAC;IAChB,MAAM,EAAE,QAAQ,CAAC;IACjB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BvF;AAED,iFAAiF;AACjF,wBAAgB,KAAK,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAQvF"}
package/dist/serve.js ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Tier-2 managed loop. Reads the NDJSON control stream (fd 0), drives the handler
3
+ * per `invoke`, emits events on fd 3, and handles `shutdown`/EOF and an idle
4
+ * timeout. `serveOn` is the injectable, unit-testable core; `serve` defaults the
5
+ * streams to `process.stdin` + an fd-3 Writable.
6
+ *
7
+ * Turns are inherently sequential — the handler for an `invoke` is `await`ed
8
+ * inline before the reader advances to the next control line — so no chaining or
9
+ * concurrent reader is needed.
10
+ */
11
+ import { createWriteStream } from "node:fs";
12
+ import { createEmitter } from "./emit.js";
13
+ import { lines } from "./lines.js";
14
+ import { isInvoke, isShutdown, parseControl } from "./parse.js";
15
+ import { Turn } from "./turn.js";
16
+ export async function serveOn(handler, opts) {
17
+ const emit = createEmitter(opts.output);
18
+ const idleMs = opts.idleMs ?? 5 * 60_000;
19
+ let idle;
20
+ const arm = () => {
21
+ if (idle)
22
+ clearTimeout(idle);
23
+ idle = setTimeout(() => opts.input.push(null), idleMs);
24
+ if (idle.unref)
25
+ idle.unref();
26
+ };
27
+ arm();
28
+ for await (const line of lines(opts.input)) {
29
+ arm();
30
+ const msg = parseControl(line);
31
+ if (isShutdown(msg))
32
+ break;
33
+ if (!isInvoke(msg))
34
+ continue;
35
+ // Run the turn inline: turns are sequential, so the next control line is read
36
+ // only after this handler settles.
37
+ let acc = "";
38
+ const turn = new Turn(msg.input, msg.identity, msg.fs, msg.history, emit, (chunk) => {
39
+ acc += chunk;
40
+ });
41
+ try {
42
+ const result = await handler(turn);
43
+ emit.done(typeof result === "string" ? result : acc);
44
+ }
45
+ catch (err) {
46
+ emit.error(err instanceof Error ? err.message : String(err));
47
+ }
48
+ }
49
+ if (idle)
50
+ clearTimeout(idle);
51
+ }
52
+ /** The public entry: wire stdin (fd 0) + an fd-3 Writable, then run the loop. */
53
+ export function serve(handler, opts) {
54
+ // fd 3 is the write end of the pipe the Router created at spawn.
55
+ const output = createWriteStream("", { fd: 3 });
56
+ return serveOn(handler, {
57
+ input: process.stdin,
58
+ output,
59
+ ...(opts?.idleMs ? { idleMs: opts.idleMs } : {}),
60
+ });
61
+ }
package/dist/turn.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The `Turn` handed to a worker handler — the entire surface a builder touches:
3
+ * the pushed context (`input`/`identity`/`fs`/`history`) plus `text()` to stream.
4
+ * Nothing Guuey-specific beyond this.
5
+ */
6
+ import type { Fs, HistoryMessage, Identity } from "./protocol.js";
7
+ import type { Emitter } from "./emit.js";
8
+ export declare class Turn {
9
+ readonly input: string;
10
+ readonly identity: Identity;
11
+ readonly fs: Fs;
12
+ readonly history: HistoryMessage[];
13
+ private readonly emit;
14
+ private readonly onText;
15
+ constructor(input: string, identity: Identity, fs: Fs, history: HistoryMessage[], emit: Emitter, onText: (chunk: string) => void);
16
+ /** Stream a chunk of assistant output (also accumulated into `done.result`). */
17
+ text(chunk: string): void;
18
+ }
19
+ /** A worker handler: do the work, return the final result string (or rely on the
20
+ * accumulated `turn.text(...)`). */
21
+ export type WorkerHandler = (turn: Turn) => Promise<string | void> | string | void;
22
+ //# sourceMappingURL=turn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn.d.ts","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,qBAAa,IAAI;IAEb,QAAQ,CAAC,KAAK,EAAE,MAAM;IACtB,QAAQ,CAAC,QAAQ,EAAE,QAAQ;IAC3B,QAAQ,CAAC,EAAE,EAAE,EAAE;IACf,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE;IAClC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBALd,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,QAAQ,EAClB,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,cAAc,EAAE,EACjB,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI;IAGlD,gFAAgF;IAChF,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;CAI1B;AAED;qCACqC;AACrC,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC"}
package/dist/turn.js ADDED
@@ -0,0 +1,21 @@
1
+ export class Turn {
2
+ input;
3
+ identity;
4
+ fs;
5
+ history;
6
+ emit;
7
+ onText;
8
+ constructor(input, identity, fs, history, emit, onText) {
9
+ this.input = input;
10
+ this.identity = identity;
11
+ this.fs = fs;
12
+ this.history = history;
13
+ this.emit = emit;
14
+ this.onText = onText;
15
+ }
16
+ /** Stream a chunk of assistant output (also accumulated into `done.result`). */
17
+ text(chunk) {
18
+ this.onText(chunk);
19
+ this.emit.text(chunk);
20
+ }
21
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@guuey/worker",
3
+ "version": "0.1.0",
4
+ "description": "Guuey Worker Protocol v1 — the Router↔Worker contract types and the tiered worker SDK (typed interpreter + serve loop).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "LICENSE",
12
+ "README.md"
13
+ ],
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js",
18
+ "default": "./dist/index.js"
19
+ }
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^24.0.0",
23
+ "typescript": "^5.0.0",
24
+ "vitest": "^3.0.0"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "keywords": [
30
+ "guuey",
31
+ "agent",
32
+ "worker",
33
+ "protocol",
34
+ "sdk"
35
+ ],
36
+ "homepage": "https://guuey.com",
37
+ "bugs": {
38
+ "url": "https://github.com/loqu-co/guuey/issues"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/withguuey/guuey-sdks.git",
43
+ "directory": "packages/worker"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc -p tsconfig.build.json",
47
+ "dev": "tsc --watch",
48
+ "typecheck": "tsc --noEmit",
49
+ "test": "vitest run",
50
+ "test:watch": "vitest"
51
+ }
52
+ }