@termwright/mcp 0.2.0 → 0.3.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 +109 -28
- package/dist/bin.js +2 -1
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-IPNUAUAN.js → chunk-45LJP5X3.js} +595 -482
- package/dist/chunk-45LJP5X3.js.map +1 -0
- package/dist/chunk-EU4HGMLE.js +629 -0
- package/dist/chunk-EU4HGMLE.js.map +1 -0
- package/dist/docs-85fGFORb.d.ts +13 -0
- package/dist/docs.d.ts +1 -0
- package/dist/docs.js +7 -0
- package/dist/docs.js.map +1 -0
- package/dist/index.d.ts +50 -28
- package/dist/index.js +9 -7
- package/package.json +11 -8
- package/dist/chunk-2J5WHI6X.js +0 -2000
- package/dist/chunk-2J5WHI6X.js.map +0 -1
- package/dist/chunk-36C7A7DW.js +0 -2685
- package/dist/chunk-36C7A7DW.js.map +0 -1
- package/dist/chunk-3PLOAM2C.js +0 -2427
- package/dist/chunk-3PLOAM2C.js.map +0 -1
- package/dist/chunk-57GYK2EF.js +0 -2991
- package/dist/chunk-57GYK2EF.js.map +0 -1
- package/dist/chunk-ABLJBL5P.js +0 -2687
- package/dist/chunk-ABLJBL5P.js.map +0 -1
- package/dist/chunk-BOOUADRN.js +0 -1938
- package/dist/chunk-BOOUADRN.js.map +0 -1
- package/dist/chunk-BPWIETN5.js +0 -2983
- package/dist/chunk-BPWIETN5.js.map +0 -1
- package/dist/chunk-CMQB5G7R.js +0 -2968
- package/dist/chunk-CMQB5G7R.js.map +0 -1
- package/dist/chunk-I4B53KZ7.js +0 -2955
- package/dist/chunk-I4B53KZ7.js.map +0 -1
- package/dist/chunk-IPNUAUAN.js.map +0 -1
- package/dist/chunk-KZWL2S6E.js +0 -2869
- package/dist/chunk-KZWL2S6E.js.map +0 -1
- package/dist/chunk-LB2QBYW4.js +0 -2686
- package/dist/chunk-LB2QBYW4.js.map +0 -1
- package/dist/chunk-MR3AXSXL.js +0 -1977
- package/dist/chunk-MR3AXSXL.js.map +0 -1
- package/dist/chunk-NVSZXEZU.js +0 -2688
- package/dist/chunk-NVSZXEZU.js.map +0 -1
- package/dist/chunk-PD2WKAFE.js +0 -2531
- package/dist/chunk-PD2WKAFE.js.map +0 -1
- package/dist/chunk-PGY4ZDLD.js +0 -1843
- package/dist/chunk-PGY4ZDLD.js.map +0 -1
- package/dist/chunk-QDIAASH7.js +0 -2982
- package/dist/chunk-QDIAASH7.js.map +0 -1
- package/dist/chunk-UZWFLJGG.js +0 -2873
- package/dist/chunk-UZWFLJGG.js.map +0 -1
- package/dist/chunk-VFYTROYG.js +0 -2825
- package/dist/chunk-VFYTROYG.js.map +0 -1
- package/dist/chunk-ZTHKAJKT.js +0 -2981
- package/dist/chunk-ZTHKAJKT.js.map +0 -1
- package/dist/chunk-ZZULGRRE.js +0 -2991
- package/dist/chunk-ZZULGRRE.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/http-security.ts","../src/sdk-facade.ts","../src/cli.ts"],"sourcesContent":["/**\n * Server wiring: tools onto an `McpServer`, and the two transports.\n *\n * Both transports share one code path — `createTermwrightMcpServer(store)` — so\n * a tool behaves identically over stdio and over Streamable HTTP. What differs\n * is only who owns the session key: stdio has exactly one implicit session,\n * HTTP keys sessions by `Mcp-Session-Id` in our {@link SessionRegistry}.\n */\nimport { createServer } from 'node:http';\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\nimport { randomBytes, randomUUID } from 'node:crypto';\nimport { CrashContextError, describeCrash } from './crash.js';\nimport { renderErrorPayload, toErrorPayload, usageError } from './errors.js';\nimport {\n BoundedRateLimiter,\n admitHttpRequest,\n isLoopbackHost,\n normalizeAllowedOrigins,\n} from './http-security.js';\nimport type { HttpRateLimitOptions } from './http-security.js';\nimport {\n InMemoryTransport,\n connectTransport,\n McpServer,\n StdioServerTransport,\n StreamableHTTPServerTransport,\n isInitializeRequest,\n} from './sdk-facade.js';\nimport type { CallToolResult, Transport } from './sdk-facade.js';\nimport { SessionRegistry, closeSessionStores, createSessionStores } from './sessions.js';\nimport type { SessionStores } from './sessions.js';\nimport { TOOLS } from './registry.js';\nimport type { ToolContext, ToolOutcome } from './tool-kit.js';\nimport { SERVER_NAME, SERVER_VERSION } from './version.js';\n\n/** Server-level instructions shown to hosts that surface them. */\nconst INSTRUCTIONS =\n 'Drive terminal programs the way a person would. terminal.launch starts a program and returns a ' +\n 'handle; terminal.snapshot gives compact refs plus visible text; act with terminal.click / press / ' +\n 'type; wait with terminal.wait_for; poll cheaply with terminal.capture_since using the revision a ' +\n 'snapshot returned. Refs like semantic:n8@42 are only valid at semantic revision 42 — re-snapshot after the ' +\n 'screen changes. Programs without a termwright adapter report semanticTree: unavailable; target ' +\n 'them by text instead of by role.';\n\n/** Builds the `CallToolResult` for a successful handler outcome. */\nfunction successResult(outcome: ToolOutcome<Record<string, unknown>>): CallToolResult {\n return {\n content: [\n { type: 'text', text: outcome.text },\n ...(outcome.images ?? []).map((image) => ({\n type: 'image' as const,\n data: image.data,\n mimeType: image.mimeType,\n })),\n ],\n structuredContent: outcome.data,\n };\n}\n\n/**\n * Attaches the crash report when the failure happened against a terminal whose\n * program died on its own.\n *\n * This lives here, once, rather than in every acting tool: the server is the\n * only place that sees both the raw arguments (which name the terminal) and\n * every thrown error. Without it a locator that never resolved because the\n * child was gone reports a bare `timeout`, which tells an agent to wait longer\n * — precisely the wrong move.\n */\nfunction withCrashContext(context: ToolContext, args: unknown, error: unknown): unknown {\n if (error instanceof CrashContextError) return error;\n const id = (args as { terminal?: unknown } | null)?.terminal;\n if (typeof id !== 'string') return error;\n const report = context.terminals.find(id)?.harness.crashReport();\n return report === undefined || report === null\n ? error\n : new CrashContextError(error, describeCrash(report));\n}\n\n/** `_meta` key carrying the structured error payload of a failed tool call. */\nexport const ERROR_META_KEY = 'io.termwright/error';\n\n/**\n * Builds the `CallToolResult` for a failure.\n *\n * The payload travels in `_meta`, not in `structuredContent`: a client that has\n * seen the tool list validates `structuredContent` against the success\n * `outputSchema`, and an error object would be rejected before the agent ever\n * saw it. The text content carries the same information in readable form —\n * typed kind, message, the driver's suggestion and its bounded candidates.\n */\nfunction errorResult(error: unknown): CallToolResult {\n const payload = toErrorPayload(error);\n return {\n isError: true,\n content: [{ type: 'text', text: renderErrorPayload(payload) }],\n _meta: { [ERROR_META_KEY]: payload },\n };\n}\n\n/** Registers every tool from {@link TOOLS} on a fresh `McpServer`. */\nexport function createTermwrightMcpServer(stores: SessionStores): McpServer {\n const server = new McpServer(\n { name: SERVER_NAME, version: SERVER_VERSION },\n { capabilities: { tools: {} }, instructions: INSTRUCTIONS },\n );\n const context: ToolContext = { terminals: stores.terminals, traces: stores.traces };\n\n for (const tool of TOOLS) {\n server.registerTool(\n tool.name,\n {\n title: tool.title,\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n annotations: tool.annotations,\n },\n async (args: unknown): Promise<CallToolResult> => {\n try {\n return successResult(await tool.handler(context, args as never));\n } catch (error) {\n return errorResult(withCrashContext(context, args, error));\n }\n },\n );\n }\n return server;\n}\n\n/** A running server plus the handle needed to shut it down. */\nexport interface RunningServer {\n readonly server: McpServer;\n readonly stores: SessionStores;\n close(): Promise<void>;\n}\n\n/** Options shared by the transports. */\nexport interface ServeOptions {\n /** Root for `variant: \"full\"` snapshot dumps. */\n readonly storageDir?: string;\n readonly maxSessions?: number;\n}\n\n/** Connects a server to a transport and returns its lifecycle handle. */\nasync function connect(stores: SessionStores, transport: Transport): Promise<RunningServer> {\n const server = createTermwrightMcpServer(stores);\n try {\n await connectTransport(server, transport);\n } catch (error) {\n const cleanup = await Promise.allSettled([closeSessionStores(stores), server.close()]);\n const failures = cleanup.flatMap((result) =>\n result.status === 'rejected' ? [result.reason] : [],\n );\n if (failures.length > 0)\n throw new AggregateError([error, ...failures], 'MCP transport startup and rollback failed');\n throw error;\n }\n return {\n server,\n stores,\n close: async (): Promise<void> => {\n const results = await Promise.allSettled([closeSessionStores(stores), server.close()]);\n const failures = results.flatMap((result) =>\n result.status === 'rejected' ? [result.reason] : [],\n );\n if (failures.length > 0)\n throw new AggregateError(failures, 'MCP transport failed to close cleanly');\n },\n };\n}\n\n/** Serves the tools over stdio — the transport an MCP host spawns. */\nexport async function serveStdio(options: ServeOptions = {}): Promise<RunningServer> {\n const stores = createSessionStores({ sessionKey: 'stdio', storageDir: options.storageDir });\n return connect(stores, new StdioServerTransport());\n}\n\n/**\n * An in-process client/server pair over `InMemoryTransport`. Used by this\n * package's tests, and by anything embedding the tools without a socket.\n */\nexport async function serveInMemory(\n options: ServeOptions & { readonly sessionKey?: string } = {},\n): Promise<RunningServer & { readonly clientTransport: Transport }> {\n const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();\n const stores = createSessionStores({\n sessionKey: options.sessionKey ?? 'in-memory',\n storageDir: options.storageDir,\n });\n const running = await connect(stores, serverTransport);\n return { ...running, clientTransport };\n}\n\n/** A listening Streamable HTTP server. */\nexport interface HttpServerHandle {\n readonly http: Server;\n readonly registry: SessionRegistry<{\n transport: StreamableHTTPServerTransport;\n server: McpServer;\n }>;\n readonly port: number;\n /** Per-launch bearer required by every HTTP request. Never put it in a URL. */\n readonly authToken: string;\n close(): Promise<void>;\n}\n\n/** Default idle ceiling for an HTTP session. */\nexport const DEFAULT_IDLE_TTL_MS = 10 * 60_000;\n\n/** Options for {@link serveHttp}. */\nexport interface HttpServeOptions extends ServeOptions {\n readonly port?: number;\n readonly host?: string;\n /**\n * Explicit acknowledgement that a non-loopback bind exposes process launch,\n * terminal input and filesystem-backed trace tools to the network.\n */\n readonly allowNonLoopback?: boolean;\n /**\n * Browser origins allowed to call the endpoint. Non-browser MCP clients omit\n * `Origin`; any presented origin is rejected unless it appears here exactly.\n */\n readonly allowedOrigins?: readonly string[];\n /** Per-peer request ceiling. The limiter's identity map is bounded too. */\n readonly rateLimit?: HttpRateLimitOptions;\n /** Path the MCP endpoint listens on. Defaults to `/mcp`. */\n readonly path?: string;\n /**\n * Milliseconds a session may sit idle before it is torn down. Defaults to\n * {@link DEFAULT_IDLE_TTL_MS}; `0` disables expiry.\n */\n readonly idleTtlMs?: number;\n /** Injectable clock, for tests. */\n readonly now?: () => number;\n /** Where an expiry is reported. Defaults to stderr. */\n readonly log?: (message: string) => void;\n}\n\nfunction sendJson(response: ServerResponse, status: number, body: unknown): void {\n const text = JSON.stringify(body);\n response.writeHead(status, { 'content-type': 'application/json' });\n response.end(text);\n}\n\nasync function readBody(request: IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of request) {\n const buffer = Buffer.from(chunk as Buffer);\n size += buffer.byteLength;\n if (size > 4 * 1024 * 1024) throw new Error('request body too large');\n chunks.push(buffer);\n }\n if (chunks.length === 0) return undefined;\n return JSON.parse(Buffer.concat(chunks).toString('utf8'));\n}\n\n/**\n * Serves the tools over Streamable HTTP with multi-session support.\n *\n * Sessions live in {@link SessionRegistry}: `initialize` mints a session id and\n * registers a store together with its transport, every later request is routed\n * by its `Mcp-Session-Id` header, and `DELETE` (or transport close) disposes the\n * session's terminals. The ceiling is enforced here, before a transport exists.\n */\nexport async function serveHttp(options: HttpServeOptions = {}): Promise<HttpServerHandle> {\n const path = options.path ?? '/mcp';\n const host = options.host ?? '127.0.0.1';\n if (!isLoopbackHost(host) && options.allowNonLoopback !== true) {\n throw usageError(\n `refusing non-loopback MCP HTTP bind ${JSON.stringify(host)} without allowNonLoopback`,\n 'keep the default loopback bind, or explicitly acknowledge the remote trust boundary',\n );\n }\n const authToken = randomBytes(32).toString('base64url');\n const allowedOrigins = normalizeAllowedOrigins(options.allowedOrigins);\n const authenticatedRateLimiter = new BoundedRateLimiter(options.rateLimit);\n const preflightRateLimiter = new BoundedRateLimiter(options.rateLimit);\n const now = options.now ?? Date.now;\n // stdout may be a protocol stream elsewhere; server-level notes go to stderr.\n const log = options.log ?? ((message: string): void => void process.stderr.write(`${message}\\n`));\n const registry = new SessionRegistry<{\n transport: StreamableHTTPServerTransport;\n server: McpServer;\n }>({\n ...(options.maxSessions === undefined ? {} : { maxSessions: options.maxSessions }),\n ...(options.storageDir === undefined ? {} : { storageDir: options.storageDir }),\n ...(options.now === undefined ? {} : { now: options.now }),\n idleTtlMs: options.idleTtlMs ?? DEFAULT_IDLE_TTL_MS,\n disposeAttachment: async (attachment) => {\n await attachment.transport.close();\n },\n onExpired: (key) => {\n log(`termwright: session ${key} expired after idling; terminals and traces released`);\n },\n onBackgroundError: (error) => {\n log(\n `termwright: idle session cleanup failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n },\n });\n\n const http = createServer((request, response) => {\n void (async () => {\n try {\n // Security admission is intentionally before URL routing, body reads,\n // session lookup/touch and initialize. A rejected peer cannot allocate\n // an MCP session, refresh one it guessed, or make us buffer its body.\n if (\n !admitHttpRequest(request, response, {\n token: authToken,\n allowedOrigins,\n authenticatedRateLimiter,\n preflightRateLimiter,\n now,\n })\n )\n return;\n const url = new URL(request.url ?? '/', 'http://localhost');\n if (url.pathname !== path) {\n sendJson(response, 404, { error: 'not found' });\n return;\n }\n const sessionId = request.headers['mcp-session-id'];\n const key = Array.isArray(sessionId) ? sessionId[0] : sessionId;\n\n if (request.method === 'DELETE') {\n if (key !== undefined) await registry.delete(key);\n response.writeHead(204).end();\n return;\n }\n\n const body = request.method === 'POST' ? await readBody(request) : undefined;\n\n if (key !== undefined) {\n const session = registry.get(key);\n if (session === undefined) {\n sendJson(response, 404, { error: 'unknown session', kind: 'no-session' });\n return;\n }\n // Every request that names a session is proof the client is alive.\n registry.touch(key);\n await session.attachment.transport.handleRequest(request, response, body);\n return;\n }\n\n if (request.method !== 'POST' || !isInitializeRequest(body)) {\n sendJson(response, 400, { error: 'missing Mcp-Session-Id', kind: 'usage' });\n return;\n }\n\n const newKey = randomUUID();\n const session = registry.create(newKey, (stores) => {\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => newKey,\n });\n const server = createTermwrightMcpServer(stores);\n transport.onclose = (): void => {\n void registry.delete(newKey).catch((error) => {\n log(\n `termwright: session ${newKey} transport cleanup failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n });\n };\n return { transport, server };\n });\n await connectTransport(session.attachment.server, session.attachment.transport);\n await session.attachment.transport.handleRequest(request, response, body);\n } catch (error) {\n const payload = toErrorPayload(error);\n if (!response.headersSent)\n sendJson(response, 500, { error: payload.message, kind: payload.kind });\n else response.end();\n }\n })();\n });\n\n try {\n await new Promise<void>((resolve, reject) => {\n const onError = (error: Error): void => {\n http.off('listening', onListening);\n reject(error);\n };\n const onListening = (): void => {\n http.off('error', onError);\n resolve();\n };\n http.once('error', onError);\n http.once('listening', onListening);\n http.listen(options.port ?? 0, host);\n });\n } catch (error) {\n await registry.closeAll().catch((cleanup) => {\n throw new AggregateError([error, cleanup], 'MCP HTTP bind and rollback both failed');\n });\n throw error;\n }\n registry.startIdleSweeper();\n const address = http.address();\n const port = typeof address === 'object' && address !== null ? address.port : (options.port ?? 0);\n\n return {\n http,\n registry,\n port,\n authToken,\n close: async (): Promise<void> => {\n registry.stopIdleSweeper();\n const results = await Promise.allSettled([\n registry.closeAll(),\n new Promise<void>((resolve, reject) => {\n http.close((error) => (error === undefined ? resolve() : reject(error)));\n }),\n ]);\n const failures = results.flatMap((result) =>\n result.status === 'rejected' ? [result.reason] : [],\n );\n if (failures.length > 0)\n throw new AggregateError(failures, 'MCP HTTP server failed to close cleanly');\n },\n };\n}\n","/**\n * Security policy for the Streamable HTTP transport.\n *\n * This module deliberately knows nothing about MCP messages or sessions. It is\n * the admission boundary in front of both: rate limit the peer, apply the\n * browser Origin policy, then authenticate the bearer. Only an admitted\n * request may reach routing, body parsing or the session registry.\n */\nimport { timingSafeEqual } from 'node:crypto';\nimport { isIP } from 'node:net';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\n/** Defaults are conservative for an interactive protocol and cheap to raise explicitly. */\nexport const DEFAULT_HTTP_RATE_LIMIT = Object.freeze({\n windowMs: 60_000,\n maxRequests: 120,\n maxClients: 1_024,\n});\n\n/** Bounded fixed-window admission policy, keyed by the TCP peer address. */\nexport interface HttpRateLimitOptions {\n readonly windowMs?: number;\n readonly maxRequests?: number;\n readonly maxClients?: number;\n}\n\ninterface RateBucket {\n readonly startedAt: number;\n requests: number;\n}\n\nexport interface RateLimitDecision {\n readonly allowed: boolean;\n readonly retryAfterSeconds: number;\n}\n\n/**\n * A rate limiter whose own memory is bounded. When all client slots are active,\n * a new identity is refused instead of evicting an active bucket and thereby\n * letting an address-rotation attack reset counters.\n */\nexport class BoundedRateLimiter {\n readonly #windowMs: number;\n readonly #maxRequests: number;\n readonly #maxClients: number;\n readonly #buckets = new Map<string, RateBucket>();\n\n constructor(options: HttpRateLimitOptions = {}) {\n this.#windowMs = positiveInteger(\n options.windowMs ?? DEFAULT_HTTP_RATE_LIMIT.windowMs,\n 'rateLimit.windowMs',\n );\n this.#maxRequests = positiveInteger(\n options.maxRequests ?? DEFAULT_HTTP_RATE_LIMIT.maxRequests,\n 'rateLimit.maxRequests',\n );\n this.#maxClients = positiveInteger(\n options.maxClients ?? DEFAULT_HTTP_RATE_LIMIT.maxClients,\n 'rateLimit.maxClients',\n );\n }\n\n /** Visible for deterministic tests and operational diagnostics. */\n get size(): number {\n return this.#buckets.size;\n }\n\n admit(identity: string, now: number): RateLimitDecision {\n if (!Number.isFinite(now)) throw new TypeError('rate-limit clock must return a finite number');\n let bucket = this.#buckets.get(identity);\n if (bucket !== undefined && now - bucket.startedAt >= this.#windowMs) {\n this.#buckets.delete(identity);\n bucket = undefined;\n }\n\n if (bucket === undefined) {\n if (this.#buckets.size >= this.#maxClients) this.#purgeExpired(now);\n if (this.#buckets.size >= this.#maxClients) {\n return { allowed: false, retryAfterSeconds: this.#retryForOldest(now) };\n }\n this.#buckets.set(identity, { startedAt: now, requests: 1 });\n return { allowed: true, retryAfterSeconds: 0 };\n }\n\n if (bucket.requests >= this.#maxRequests) {\n return {\n allowed: false,\n retryAfterSeconds: secondsUntil(bucket.startedAt + this.#windowMs, now),\n };\n }\n bucket.requests += 1;\n return { allowed: true, retryAfterSeconds: 0 };\n }\n\n #purgeExpired(now: number): void {\n for (const [identity, bucket] of this.#buckets) {\n if (now - bucket.startedAt >= this.#windowMs) this.#buckets.delete(identity);\n }\n }\n\n #retryForOldest(now: number): number {\n let oldestExpiry = Number.POSITIVE_INFINITY;\n for (const bucket of this.#buckets.values()) {\n oldestExpiry = Math.min(oldestExpiry, bucket.startedAt + this.#windowMs);\n }\n return Number.isFinite(oldestExpiry) ? secondsUntil(oldestExpiry, now) : 1;\n }\n}\n\n/** Exact origins accepted from browser-like clients. Missing Origin is allowed. */\nexport function normalizeAllowedOrigins(origins: readonly string[] = []): ReadonlySet<string> {\n const normalized = new Set<string>();\n for (const candidate of origins) {\n let url: URL;\n try {\n url = new URL(candidate);\n } catch {\n throw new TypeError(`allowed origin is not a URL: ${JSON.stringify(candidate)}`);\n }\n if (\n (url.protocol !== 'http:' && url.protocol !== 'https:') ||\n url.username !== '' ||\n url.password !== '' ||\n url.pathname !== '/' ||\n url.search !== '' ||\n url.hash !== ''\n ) {\n throw new TypeError(\n `allowed origin must be an HTTP(S) origin without path, query or credentials: ${JSON.stringify(candidate)}`,\n );\n }\n normalized.add(url.origin);\n }\n return normalized;\n}\n\n/** True only for an explicit loopback address or the conventional localhost name. */\nexport function isLoopbackHost(host: string): boolean {\n const lower = host.toLowerCase().replace(/^\\[|\\]$/gu, '');\n if (lower === 'localhost' || lower === '::1') return true;\n if (isIP(lower) === 4) return lower.split('.')[0] === '127';\n return /^::ffff:127(?:\\.\\d{1,3}){3}$/u.test(lower);\n}\n\nexport interface HttpAdmissionOptions {\n readonly token: string;\n readonly allowedOrigins: ReadonlySet<string>;\n readonly authenticatedRateLimiter: BoundedRateLimiter;\n readonly preflightRateLimiter: BoundedRateLimiter;\n readonly now: () => number;\n}\n\n/**\n * Applies the complete admission policy and writes the rejection response.\n * Returns only after headers have been decided; it never consumes request data.\n */\nexport function admitHttpRequest(\n request: IncomingMessage,\n response: ServerResponse,\n options: HttpAdmissionOptions,\n): boolean {\n const identity = request.socket.remoteAddress ?? '<unknown-peer>';\n const origin = request.headers.origin;\n if (origin !== undefined && (Array.isArray(origin) || !options.allowedOrigins.has(origin))) {\n sendSecurityError(response, 403, 'origin is not allowed');\n return false;\n }\n if (typeof origin === 'string') {\n response.setHeader('access-control-allow-origin', origin);\n response.setHeader('access-control-expose-headers', 'Mcp-Session-Id');\n response.setHeader('vary', 'Origin');\n if (request.method === 'OPTIONS') {\n if (!admitRate(identity, response, options.preflightRateLimiter, options.now)) return false;\n return admitPreflight(request, response);\n }\n }\n\n if (!bearerMatches(request.headers.authorization, options.token)) {\n sendSecurityError(response, 401, 'missing or invalid bearer token', {\n 'www-authenticate': 'Bearer realm=\"termwright-mcp\"',\n });\n return false;\n }\n if (!admitRate(identity, response, options.authenticatedRateLimiter, options.now)) return false;\n return true;\n}\n\nfunction admitRate(\n identity: string,\n response: ServerResponse,\n limiter: BoundedRateLimiter,\n now: () => number,\n): boolean {\n const rate = limiter.admit(identity, now());\n if (rate.allowed) return true;\n sendSecurityError(response, 429, 'rate limit exceeded', {\n 'retry-after': String(rate.retryAfterSeconds),\n });\n return false;\n}\n\nconst CORS_METHODS = new Set(['GET', 'POST', 'DELETE']);\nconst CORS_HEADERS = new Set([\n 'accept',\n 'authorization',\n 'content-type',\n 'last-event-id',\n 'mcp-protocol-version',\n 'mcp-session-id',\n]);\n\n/** A preflight authorizes no MCP operation, but remains origin- and rate-limited. */\nfunction admitPreflight(request: IncomingMessage, response: ServerResponse): false {\n const method = request.headers['access-control-request-method'];\n const requestedHeaders = (request.headers['access-control-request-headers'] ?? '')\n .split(',')\n .map((header) => header.trim().toLowerCase())\n .filter((header) => header !== '');\n if (\n typeof method !== 'string' ||\n !CORS_METHODS.has(method.toUpperCase()) ||\n requestedHeaders.some((header) => !CORS_HEADERS.has(header))\n ) {\n sendSecurityError(response, 403, 'CORS preflight is not allowed');\n return false;\n }\n response.writeHead(204, {\n 'access-control-allow-methods': [...CORS_METHODS].join(', '),\n 'access-control-allow-headers': [...CORS_HEADERS].join(', '),\n 'access-control-max-age': '600',\n });\n response.end();\n return false;\n}\n\nfunction bearerMatches(header: string | undefined, expected: string): boolean {\n if (header === undefined || !header.startsWith('Bearer ')) return false;\n const provided = header.slice('Bearer '.length);\n const left = Buffer.from(provided, 'utf8');\n const right = Buffer.from(expected, 'utf8');\n return left.length === right.length && timingSafeEqual(left, right);\n}\n\nfunction sendSecurityError(\n response: ServerResponse,\n status: number,\n error: string,\n headers: Readonly<Record<string, string>> = {},\n): void {\n const text = JSON.stringify({ error });\n // Rejections never reuse the connection: an unauthenticated peer that\n // declared a large body cannot keep streaming it after admission failed.\n response.writeHead(status, {\n 'cache-control': 'no-store',\n connection: 'close',\n 'content-type': 'application/json',\n ...headers,\n });\n response.end(text);\n}\n\nfunction positiveInteger(value: number, name: string): number {\n if (!Number.isSafeInteger(value) || value <= 0)\n throw new TypeError(`${name} must be a positive safe integer`);\n return value;\n}\n\nfunction secondsUntil(deadline: number, now: number): number {\n return Math.max(1, Math.ceil((deadline - now) / 1_000));\n}\n","/**\n * The single module in this package that is allowed to import\n * `@modelcontextprotocol/sdk`.\n *\n * Everything else — tools, server wiring, CLI, tests — imports from here. The\n * SDK v2 package split is therefore a change to this one file: re-point the\n * specifiers, keep the exported names, and no tool handler moves.\n *\n * Nothing SDK-shaped leaks into the package's own public surface\n * (`src/index.ts`) beyond what a host needs to connect a transport.\n */\nimport type { Client as ClientType } from '@modelcontextprotocol/sdk/client/index.js';\nimport type { McpServer as McpServerType } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nexport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nexport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nexport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nexport { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';\nexport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nexport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';\nexport { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';\n\nexport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\n\n/**\n * Connects a server to a transport.\n *\n * The SDK is compiled without `exactOptionalPropertyTypes`, so its transport\n * classes declare `onclose?: (() => void) | undefined` while the `Transport`\n * interface declares `onclose?: () => void`. The two are the same thing at\n * runtime; the single cast that reconciles them lives here, in the SDK\n * boundary, rather than in every call site.\n */\nexport async function connectTransport(server: McpServerType, transport: unknown): Promise<void> {\n await server.connect(transport as Parameters<McpServerType['connect']>[0]);\n}\n\n/** The client-side counterpart of {@link connectTransport}, with the same rationale. */\nexport async function connectClient(client: ClientType, transport: unknown): Promise<void> {\n await client.connect(transport as Parameters<ClientType['connect']>[0]);\n}\n\nexport type { CallToolResult, ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';\n","/**\n * `termwright-mcp` — the standalone binary for this package.\n *\n * Commands: serve over stdio (the default), serve Streamable HTTP, print\n * `agent-context`, print `usage`. Exit codes follow the taxonomy in\n * CONTRACTS.md §MCP: 0 ok / 1 assertion / 2 usage / 3 no-session / 4 ipc /\n * 5 internal. With `--json`, failures are one JSON object carrying `kind`.\n *\n * The umbrella `termwright` CLI (task #10) reuses {@link runCli} and the\n * generators it calls rather than spawning this binary.\n */\nimport { buildAgentContext, buildUsage } from './agent-context.js';\nimport { buildAgentSkill, writeAgentSkill } from './agent-skill.js';\nimport { EXIT_CODES, exitCodeFor, toErrorPayload, usageError } from './errors.js';\nimport { serveHttp, serveStdio } from './server.js';\nimport { SERVER_NAME, SERVER_VERSION } from './version.js';\n\n/** Where the CLI writes. Injectable so tests never touch the real streams. */\nexport interface CliIo {\n readonly out: (text: string) => void;\n readonly err: (text: string) => void;\n}\n\nconst defaultIo: CliIo = {\n out: (text) => process.stdout.write(`${text}\\n`),\n err: (text) => process.stderr.write(`${text}\\n`),\n};\n\nexport interface ParsedArgs {\n readonly command: 'serve' | 'agent-context' | 'usage' | 'skill' | 'help' | 'version';\n readonly json: boolean;\n readonly http: boolean;\n readonly port: number | undefined;\n readonly host: string | undefined;\n readonly allowNonLoopback: boolean;\n readonly showAuthToken: boolean;\n /** Destination directory for `skill`; without it the package goes to stdout. */\n readonly out: string | undefined;\n}\n\nexport function parseArgs(argv: readonly string[]): ParsedArgs {\n let command: ParsedArgs['command'] = 'serve';\n let json = false;\n let http = false;\n let port: number | undefined;\n let host: string | undefined;\n let allowNonLoopback = false;\n let showAuthToken = false;\n let out: string | undefined;\n\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index] ?? '';\n switch (arg) {\n case '--json':\n json = true;\n break;\n case '--http':\n http = true;\n break;\n case '--port': {\n const value = Number(argv[index + 1]);\n if (!Number.isInteger(value) || value < 0 || value > 65_535) {\n throw usageError('--port needs an integer between 0 and 65535');\n }\n port = value;\n index += 1;\n break;\n }\n case '--host':\n host = argv[index + 1];\n if (host === undefined) throw usageError('--host needs a value');\n index += 1;\n break;\n case '--allow-non-loopback':\n allowNonLoopback = true;\n break;\n case '--show-auth-token':\n showAuthToken = true;\n break;\n case '--out':\n out = argv[index + 1];\n if (out === undefined) throw usageError('--out needs a directory');\n index += 1;\n break;\n case '--help':\n case '-h':\n command = 'help';\n break;\n case '--version':\n case '-v':\n command = 'version';\n break;\n case 'serve':\n case 'stdio':\n command = 'serve';\n break;\n case 'agent-context':\n command = 'agent-context';\n break;\n case 'usage':\n command = 'usage';\n break;\n case 'skill':\n command = 'skill';\n break;\n default:\n throw usageError(\n `unknown argument ${JSON.stringify(arg)}`,\n 'run `termwright-mcp usage` for the one-screen cheat sheet',\n );\n }\n }\n return { command, json, http, port, host, allowNonLoopback, showAuthToken, out };\n}\n\n/** Startup lines are pure so the secret-disclosure policy is regression tested without opening a server. */\nexport function httpStartupMessages(\n args: Pick<ParsedArgs, 'host' | 'showAuthToken'>,\n handle: { readonly port: number; readonly authToken: string },\n): readonly string[] {\n return [\n `${SERVER_NAME} MCP listening on http://${args.host ?? '127.0.0.1'}:${handle.port}/mcp`,\n args.showAuthToken\n ? `${SERVER_NAME} MCP bearer token: ${handle.authToken}`\n : `${SERVER_NAME} MCP bearer token hidden; restart with --show-auth-token to disclose it explicitly`,\n ];\n}\n\n/**\n * Runs the CLI and resolves with the process exit code. Serving blocks until\n * the transport closes, so `serve` only resolves on shutdown.\n */\nexport async function runCli(argv: readonly string[], io: CliIo = defaultIo): Promise<number> {\n // Read before parsing: a failure on a later argument must still honour --json.\n let json = argv.includes('--json');\n try {\n const args = parseArgs(argv);\n json = args.json;\n switch (args.command) {\n case 'version':\n io.out(\n json ? JSON.stringify({ name: SERVER_NAME, version: SERVER_VERSION }) : SERVER_VERSION,\n );\n return EXIT_CODES.ok;\n case 'help':\n case 'usage':\n io.out(json ? JSON.stringify(buildAgentContext()) : buildUsage());\n return EXIT_CODES.ok;\n case 'agent-context':\n io.out(JSON.stringify(buildAgentContext(), null, json ? 0 : 2));\n return EXIT_CODES.ok;\n case 'skill': {\n if (args.out === undefined) {\n const files = buildAgentSkill();\n io.out(\n json\n ? JSON.stringify(Object.fromEntries(files.map((file) => [file.path, file.contents])))\n : files.map((file) => `=== ${file.path}\\n${file.contents}`).join('\\n'),\n );\n return EXIT_CODES.ok;\n }\n const written = await writeAgentSkill(args.out);\n io.out(json ? JSON.stringify({ written }) : written.join('\\n'));\n return EXIT_CODES.ok;\n }\n case 'serve': {\n if (args.http) {\n const handle = await serveHttp({\n ...(args.port === undefined ? {} : { port: args.port }),\n ...(args.host === undefined ? {} : { host: args.host }),\n ...(args.allowNonLoopback ? { allowNonLoopback: true } : {}),\n });\n // stderr, never stdout: stdout may be a protocol stream.\n for (const line of httpStartupMessages(args, handle)) io.err(line);\n await new Promise<void>((resolve) => {\n handle.http.on('close', resolve);\n });\n return EXIT_CODES.ok;\n }\n const running = await serveStdio();\n await new Promise<void>((resolve) => {\n const shutdown = (): void => {\n void running.close().then(resolve, resolve);\n };\n process.once('SIGINT', shutdown);\n process.once('SIGTERM', shutdown);\n running.server.server.onclose = shutdown;\n });\n return EXIT_CODES.ok;\n }\n }\n } catch (error) {\n const payload = toErrorPayload(error);\n io.err(json ? JSON.stringify(payload) : `${payload.kind}: ${payload.message}`);\n if (!json && payload.suggestion !== undefined) io.err(`suggestion: ${payload.suggestion}`);\n return exitCodeFor(payload.kind);\n }\n}\n\n/** Entry point for the `termwright-mcp` bin. */\nexport async function main(): Promise<void> {\n process.exitCode = await runCli(process.argv.slice(2));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAQA,SAAS,oBAAoB;AAE7B,SAAS,aAAa,kBAAkB;;;ACFxC,SAAS,uBAAuB;AAChC,SAAS,YAAY;AAId,IAAM,0BAA0B,OAAO,OAAO;AAAA,EACnD,UAAU;AAAA,EACV,aAAa;AAAA,EACb,YAAY;AACd,CAAC;AAwBM,IAAM,qBAAN,MAAyB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW,oBAAI,IAAwB;AAAA,EAEhD,YAAY,UAAgC,CAAC,GAAG;AAC9C,SAAK,YAAY;AAAA,MACf,QAAQ,YAAY,wBAAwB;AAAA,MAC5C;AAAA,IACF;AACA,SAAK,eAAe;AAAA,MAClB,QAAQ,eAAe,wBAAwB;AAAA,MAC/C;AAAA,IACF;AACA,SAAK,cAAc;AAAA,MACjB,QAAQ,cAAc,wBAAwB;AAAA,MAC9C;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,OAAe;AACjB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,MAAM,UAAkB,KAAgC;AACtD,QAAI,CAAC,OAAO,SAAS,GAAG,EAAG,OAAM,IAAI,UAAU,8CAA8C;AAC7F,QAAI,SAAS,KAAK,SAAS,IAAI,QAAQ;AACvC,QAAI,WAAW,UAAa,MAAM,OAAO,aAAa,KAAK,WAAW;AACpE,WAAK,SAAS,OAAO,QAAQ;AAC7B,eAAS;AAAA,IACX;AAEA,QAAI,WAAW,QAAW;AACxB,UAAI,KAAK,SAAS,QAAQ,KAAK,YAAa,MAAK,cAAc,GAAG;AAClE,UAAI,KAAK,SAAS,QAAQ,KAAK,aAAa;AAC1C,eAAO,EAAE,SAAS,OAAO,mBAAmB,KAAK,gBAAgB,GAAG,EAAE;AAAA,MACxE;AACA,WAAK,SAAS,IAAI,UAAU,EAAE,WAAW,KAAK,UAAU,EAAE,CAAC;AAC3D,aAAO,EAAE,SAAS,MAAM,mBAAmB,EAAE;AAAA,IAC/C;AAEA,QAAI,OAAO,YAAY,KAAK,cAAc;AACxC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,mBAAmB,aAAa,OAAO,YAAY,KAAK,WAAW,GAAG;AAAA,MACxE;AAAA,IACF;AACA,WAAO,YAAY;AACnB,WAAO,EAAE,SAAS,MAAM,mBAAmB,EAAE;AAAA,EAC/C;AAAA,EAEA,cAAc,KAAmB;AAC/B,eAAW,CAAC,UAAU,MAAM,KAAK,KAAK,UAAU;AAC9C,UAAI,MAAM,OAAO,aAAa,KAAK,UAAW,MAAK,SAAS,OAAO,QAAQ;AAAA,IAC7E;AAAA,EACF;AAAA,EAEA,gBAAgB,KAAqB;AACnC,QAAI,eAAe,OAAO;AAC1B,eAAW,UAAU,KAAK,SAAS,OAAO,GAAG;AAC3C,qBAAe,KAAK,IAAI,cAAc,OAAO,YAAY,KAAK,SAAS;AAAA,IACzE;AACA,WAAO,OAAO,SAAS,YAAY,IAAI,aAAa,cAAc,GAAG,IAAI;AAAA,EAC3E;AACF;AAGO,SAAS,wBAAwB,UAA6B,CAAC,GAAwB;AAC5F,QAAM,aAAa,oBAAI,IAAY;AACnC,aAAW,aAAa,SAAS;AAC/B,QAAI;AACJ,QAAI;AACF,YAAM,IAAI,IAAI,SAAS;AAAA,IACzB,QAAQ;AACN,YAAM,IAAI,UAAU,gCAAgC,KAAK,UAAU,SAAS,CAAC,EAAE;AAAA,IACjF;AACA,QACG,IAAI,aAAa,WAAW,IAAI,aAAa,YAC9C,IAAI,aAAa,MACjB,IAAI,aAAa,MACjB,IAAI,aAAa,OACjB,IAAI,WAAW,MACf,IAAI,SAAS,IACb;AACA,YAAM,IAAI;AAAA,QACR,gFAAgF,KAAK,UAAU,SAAS,CAAC;AAAA,MAC3G;AAAA,IACF;AACA,eAAW,IAAI,IAAI,MAAM;AAAA,EAC3B;AACA,SAAO;AACT;AAGO,SAAS,eAAe,MAAuB;AACpD,QAAM,QAAQ,KAAK,YAAY,EAAE,QAAQ,aAAa,EAAE;AACxD,MAAI,UAAU,eAAe,UAAU,MAAO,QAAO;AACrD,MAAI,KAAK,KAAK,MAAM,EAAG,QAAO,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM;AACtD,SAAO,gCAAgC,KAAK,KAAK;AACnD;AAcO,SAAS,iBACd,SACA,UACA,SACS;AACT,QAAM,WAAW,QAAQ,OAAO,iBAAiB;AACjD,QAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,WAAc,MAAM,QAAQ,MAAM,KAAK,CAAC,QAAQ,eAAe,IAAI,MAAM,IAAI;AAC1F,sBAAkB,UAAU,KAAK,uBAAuB;AACxD,WAAO;AAAA,EACT;AACA,MAAI,OAAO,WAAW,UAAU;AAC9B,aAAS,UAAU,+BAA+B,MAAM;AACxD,aAAS,UAAU,iCAAiC,gBAAgB;AACpE,aAAS,UAAU,QAAQ,QAAQ;AACnC,QAAI,QAAQ,WAAW,WAAW;AAChC,UAAI,CAAC,UAAU,UAAU,UAAU,QAAQ,sBAAsB,QAAQ,GAAG,EAAG,QAAO;AACtF,aAAO,eAAe,SAAS,QAAQ;AAAA,IACzC;AAAA,EACF;AAEA,MAAI,CAAC,cAAc,QAAQ,QAAQ,eAAe,QAAQ,KAAK,GAAG;AAChE,sBAAkB,UAAU,KAAK,mCAAmC;AAAA,MAClE,oBAAoB;AAAA,IACtB,CAAC;AACD,WAAO;AAAA,EACT;AACA,MAAI,CAAC,UAAU,UAAU,UAAU,QAAQ,0BAA0B,QAAQ,GAAG,EAAG,QAAO;AAC1F,SAAO;AACT;AAEA,SAAS,UACP,UACA,UACA,SACA,KACS;AACT,QAAM,OAAO,QAAQ,MAAM,UAAU,IAAI,CAAC;AAC1C,MAAI,KAAK,QAAS,QAAO;AACzB,oBAAkB,UAAU,KAAK,uBAAuB;AAAA,IACtD,eAAe,OAAO,KAAK,iBAAiB;AAAA,EAC9C,CAAC;AACD,SAAO;AACT;AAEA,IAAM,eAAe,oBAAI,IAAI,CAAC,OAAO,QAAQ,QAAQ,CAAC;AACtD,IAAM,eAAe,oBAAI,IAAI;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,SAAS,eAAe,SAA0B,UAAiC;AACjF,QAAM,SAAS,QAAQ,QAAQ,+BAA+B;AAC9D,QAAM,oBAAoB,QAAQ,QAAQ,gCAAgC,KAAK,IAC5E,MAAM,GAAG,EACT,IAAI,CAAC,WAAW,OAAO,KAAK,EAAE,YAAY,CAAC,EAC3C,OAAO,CAAC,WAAW,WAAW,EAAE;AACnC,MACE,OAAO,WAAW,YAClB,CAAC,aAAa,IAAI,OAAO,YAAY,CAAC,KACtC,iBAAiB,KAAK,CAAC,WAAW,CAAC,aAAa,IAAI,MAAM,CAAC,GAC3D;AACA,sBAAkB,UAAU,KAAK,+BAA+B;AAChE,WAAO;AAAA,EACT;AACA,WAAS,UAAU,KAAK;AAAA,IACtB,gCAAgC,CAAC,GAAG,YAAY,EAAE,KAAK,IAAI;AAAA,IAC3D,gCAAgC,CAAC,GAAG,YAAY,EAAE,KAAK,IAAI;AAAA,IAC3D,0BAA0B;AAAA,EAC5B,CAAC;AACD,WAAS,IAAI;AACb,SAAO;AACT;AAEA,SAAS,cAAc,QAA4B,UAA2B;AAC5E,MAAI,WAAW,UAAa,CAAC,OAAO,WAAW,SAAS,EAAG,QAAO;AAClE,QAAM,WAAW,OAAO,MAAM,UAAU,MAAM;AAC9C,QAAM,OAAO,OAAO,KAAK,UAAU,MAAM;AACzC,QAAM,QAAQ,OAAO,KAAK,UAAU,MAAM;AAC1C,SAAO,KAAK,WAAW,MAAM,UAAU,gBAAgB,MAAM,KAAK;AACpE;AAEA,SAAS,kBACP,UACA,QACA,OACA,UAA4C,CAAC,GACvC;AACN,QAAM,OAAO,KAAK,UAAU,EAAE,MAAM,CAAC;AAGrC,WAAS,UAAU,QAAQ;AAAA,IACzB,iBAAiB;AAAA,IACjB,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,GAAG;AAAA,EACL,CAAC;AACD,WAAS,IAAI,IAAI;AACnB;AAEA,SAAS,gBAAgB,OAAe,MAAsB;AAC5D,MAAI,CAAC,OAAO,cAAc,KAAK,KAAK,SAAS;AAC3C,UAAM,IAAI,UAAU,GAAG,IAAI,kCAAkC;AAC/D,SAAO;AACT;AAEA,SAAS,aAAa,UAAkB,KAAqB;AAC3D,SAAO,KAAK,IAAI,GAAG,KAAK,MAAM,WAAW,OAAO,GAAK,CAAC;AACxD;;;AC/PA,SAAS,iBAAiB;AAC1B,SAAS,4BAA4B;AACrC,SAAS,qCAAqC;AAC9C,SAAS,yBAAyB;AAClC,SAAS,cAAc;AACvB,SAAS,qCAAqC;AAC9C,SAAS,2BAA2B;AAapC,eAAsB,iBAAiB,QAAuB,WAAmC;AAC/F,QAAM,OAAO,QAAQ,SAAoD;AAC3E;;;AFCA,IAAM,eACJ;AAQF,SAAS,cAAc,SAA+D;AACpF,SAAO;AAAA,IACL,SAAS;AAAA,MACP,EAAE,MAAM,QAAQ,MAAM,QAAQ,KAAK;AAAA,MACnC,IAAI,QAAQ,UAAU,CAAC,GAAG,IAAI,CAAC,WAAW;AAAA,QACxC,MAAM;AAAA,QACN,MAAM,MAAM;AAAA,QACZ,UAAU,MAAM;AAAA,MAClB,EAAE;AAAA,IACJ;AAAA,IACA,mBAAmB,QAAQ;AAAA,EAC7B;AACF;AAYA,SAAS,iBAAiB,SAAsB,MAAe,OAAyB;AACtF,MAAI,iBAAiB,kBAAmB,QAAO;AAC/C,QAAM,KAAM,MAAwC;AACpD,MAAI,OAAO,OAAO,SAAU,QAAO;AACnC,QAAM,SAAS,QAAQ,UAAU,KAAK,EAAE,GAAG,QAAQ,YAAY;AAC/D,SAAO,WAAW,UAAa,WAAW,OACtC,QACA,IAAI,kBAAkB,OAAO,cAAc,MAAM,CAAC;AACxD;AAGO,IAAM,iBAAiB;AAW9B,SAAS,YAAY,OAAgC;AACnD,QAAM,UAAU,eAAe,KAAK;AACpC,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,mBAAmB,OAAO,EAAE,CAAC;AAAA,IAC7D,OAAO,EAAE,CAAC,cAAc,GAAG,QAAQ;AAAA,EACrC;AACF;AAGO,SAAS,0BAA0B,QAAkC;AAC1E,QAAM,SAAS,IAAI;AAAA,IACjB,EAAE,MAAM,aAAa,SAAS,eAAe;AAAA,IAC7C,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,GAAG,cAAc,aAAa;AAAA,EAC5D;AACA,QAAM,UAAuB,EAAE,WAAW,OAAO,WAAW,QAAQ,OAAO,OAAO;AAElF,aAAW,QAAQ,OAAO;AACxB,WAAO;AAAA,MACL,KAAK;AAAA,MACL;AAAA,QACE,OAAO,KAAK;AAAA,QACZ,aAAa,KAAK;AAAA,QAClB,aAAa,KAAK;AAAA,QAClB,cAAc,KAAK;AAAA,QACnB,aAAa,KAAK;AAAA,MACpB;AAAA,MACA,OAAO,SAA2C;AAChD,YAAI;AACF,iBAAO,cAAc,MAAM,KAAK,QAAQ,SAAS,IAAa,CAAC;AAAA,QACjE,SAAS,OAAO;AACd,iBAAO,YAAY,iBAAiB,SAAS,MAAM,KAAK,CAAC;AAAA,QAC3D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAiBA,eAAe,QAAQ,QAAuB,WAA8C;AAC1F,QAAM,SAAS,0BAA0B,MAAM;AAC/C,MAAI;AACF,UAAM,iBAAiB,QAAQ,SAAS;AAAA,EAC1C,SAAS,OAAO;AACd,UAAM,UAAU,MAAM,QAAQ,WAAW,CAAC,mBAAmB,MAAM,GAAG,OAAO,MAAM,CAAC,CAAC;AACrF,UAAM,WAAW,QAAQ;AAAA,MAAQ,CAAC,WAChC,OAAO,WAAW,aAAa,CAAC,OAAO,MAAM,IAAI,CAAC;AAAA,IACpD;AACA,QAAI,SAAS,SAAS;AACpB,YAAM,IAAI,eAAe,CAAC,OAAO,GAAG,QAAQ,GAAG,2CAA2C;AAC5F,UAAM;AAAA,EACR;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,YAA2B;AAChC,YAAM,UAAU,MAAM,QAAQ,WAAW,CAAC,mBAAmB,MAAM,GAAG,OAAO,MAAM,CAAC,CAAC;AACrF,YAAM,WAAW,QAAQ;AAAA,QAAQ,CAAC,WAChC,OAAO,WAAW,aAAa,CAAC,OAAO,MAAM,IAAI,CAAC;AAAA,MACpD;AACA,UAAI,SAAS,SAAS;AACpB,cAAM,IAAI,eAAe,UAAU,uCAAuC;AAAA,IAC9E;AAAA,EACF;AACF;AAGA,eAAsB,WAAW,UAAwB,CAAC,GAA2B;AACnF,QAAM,SAAS,oBAAoB,EAAE,YAAY,SAAS,YAAY,QAAQ,WAAW,CAAC;AAC1F,SAAO,QAAQ,QAAQ,IAAI,qBAAqB,CAAC;AACnD;AAMA,eAAsB,cACpB,UAA2D,CAAC,GACM;AAClE,QAAM,CAAC,iBAAiB,eAAe,IAAI,kBAAkB,iBAAiB;AAC9E,QAAM,SAAS,oBAAoB;AAAA,IACjC,YAAY,QAAQ,cAAc;AAAA,IAClC,YAAY,QAAQ;AAAA,EACtB,CAAC;AACD,QAAM,UAAU,MAAM,QAAQ,QAAQ,eAAe;AACrD,SAAO,EAAE,GAAG,SAAS,gBAAgB;AACvC;AAgBO,IAAM,sBAAsB,KAAK;AA+BxC,SAAS,SAAS,UAA0B,QAAgB,MAAqB;AAC/E,QAAM,OAAO,KAAK,UAAU,IAAI;AAChC,WAAS,UAAU,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC;AACjE,WAAS,IAAI,IAAI;AACnB;AAEA,eAAe,SAAS,SAA4C;AAClE,QAAM,SAAmB,CAAC;AAC1B,MAAI,OAAO;AACX,mBAAiB,SAAS,SAAS;AACjC,UAAM,SAAS,OAAO,KAAK,KAAe;AAC1C,YAAQ,OAAO;AACf,QAAI,OAAO,IAAI,OAAO,KAAM,OAAM,IAAI,MAAM,wBAAwB;AACpE,WAAO,KAAK,MAAM;AAAA,EACpB;AACA,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,KAAK,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM,CAAC;AAC1D;AAUA,eAAsB,UAAU,UAA4B,CAAC,GAA8B;AACzF,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,OAAO,QAAQ,QAAQ;AAC7B,MAAI,CAAC,eAAe,IAAI,KAAK,QAAQ,qBAAqB,MAAM;AAC9D,UAAM;AAAA,MACJ,uCAAuC,KAAK,UAAU,IAAI,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AACA,QAAM,YAAY,YAAY,EAAE,EAAE,SAAS,WAAW;AACtD,QAAM,iBAAiB,wBAAwB,QAAQ,cAAc;AACrE,QAAM,2BAA2B,IAAI,mBAAmB,QAAQ,SAAS;AACzE,QAAM,uBAAuB,IAAI,mBAAmB,QAAQ,SAAS;AACrE,QAAM,MAAM,QAAQ,OAAO,KAAK;AAEhC,QAAM,MAAM,QAAQ,QAAQ,CAAC,YAA0B,KAAK,QAAQ,OAAO,MAAM,GAAG,OAAO;AAAA,CAAI;AAC/F,QAAM,WAAW,IAAI,gBAGlB;AAAA,IACD,GAAI,QAAQ,gBAAgB,SAAY,CAAC,IAAI,EAAE,aAAa,QAAQ,YAAY;AAAA,IAChF,GAAI,QAAQ,eAAe,SAAY,CAAC,IAAI,EAAE,YAAY,QAAQ,WAAW;AAAA,IAC7E,GAAI,QAAQ,QAAQ,SAAY,CAAC,IAAI,EAAE,KAAK,QAAQ,IAAI;AAAA,IACxD,WAAW,QAAQ,aAAa;AAAA,IAChC,mBAAmB,OAAO,eAAe;AACvC,YAAM,WAAW,UAAU,MAAM;AAAA,IACnC;AAAA,IACA,WAAW,CAAC,QAAQ;AAClB,UAAI,uBAAuB,GAAG,sDAAsD;AAAA,IACtF;AAAA,IACA,mBAAmB,CAAC,UAAU;AAC5B;AAAA,QACE,4CAA4C,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MACpG;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,OAAO,aAAa,CAAC,SAAS,aAAa;AAC/C,UAAM,YAAY;AAChB,UAAI;AAIF,YACE,CAAC,iBAAiB,SAAS,UAAU;AAAA,UACnC,OAAO;AAAA,UACP;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED;AACF,cAAM,MAAM,IAAI,IAAI,QAAQ,OAAO,KAAK,kBAAkB;AAC1D,YAAI,IAAI,aAAa,MAAM;AACzB,mBAAS,UAAU,KAAK,EAAE,OAAO,YAAY,CAAC;AAC9C;AAAA,QACF;AACA,cAAM,YAAY,QAAQ,QAAQ,gBAAgB;AAClD,cAAM,MAAM,MAAM,QAAQ,SAAS,IAAI,UAAU,CAAC,IAAI;AAEtD,YAAI,QAAQ,WAAW,UAAU;AAC/B,cAAI,QAAQ,OAAW,OAAM,SAAS,OAAO,GAAG;AAChD,mBAAS,UAAU,GAAG,EAAE,IAAI;AAC5B;AAAA,QACF;AAEA,cAAM,OAAO,QAAQ,WAAW,SAAS,MAAM,SAAS,OAAO,IAAI;AAEnE,YAAI,QAAQ,QAAW;AACrB,gBAAMA,WAAU,SAAS,IAAI,GAAG;AAChC,cAAIA,aAAY,QAAW;AACzB,qBAAS,UAAU,KAAK,EAAE,OAAO,mBAAmB,MAAM,aAAa,CAAC;AACxE;AAAA,UACF;AAEA,mBAAS,MAAM,GAAG;AAClB,gBAAMA,SAAQ,WAAW,UAAU,cAAc,SAAS,UAAU,IAAI;AACxE;AAAA,QACF;AAEA,YAAI,QAAQ,WAAW,UAAU,CAAC,oBAAoB,IAAI,GAAG;AAC3D,mBAAS,UAAU,KAAK,EAAE,OAAO,0BAA0B,MAAM,QAAQ,CAAC;AAC1E;AAAA,QACF;AAEA,cAAM,SAAS,WAAW;AAC1B,cAAM,UAAU,SAAS,OAAO,QAAQ,CAAC,WAAW;AAClD,gBAAM,YAAY,IAAI,8BAA8B;AAAA,YAClD,oBAAoB,MAAM;AAAA,UAC5B,CAAC;AACD,gBAAM,SAAS,0BAA0B,MAAM;AAC/C,oBAAU,UAAU,MAAY;AAC9B,iBAAK,SAAS,OAAO,MAAM,EAAE,MAAM,CAAC,UAAU;AAC5C;AAAA,gBACE,uBAAuB,MAAM,8BAA8B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,cACnH;AAAA,YACF,CAAC;AAAA,UACH;AACA,iBAAO,EAAE,WAAW,OAAO;AAAA,QAC7B,CAAC;AACD,cAAM,iBAAiB,QAAQ,WAAW,QAAQ,QAAQ,WAAW,SAAS;AAC9E,cAAM,QAAQ,WAAW,UAAU,cAAc,SAAS,UAAU,IAAI;AAAA,MAC1E,SAAS,OAAO;AACd,cAAM,UAAU,eAAe,KAAK;AACpC,YAAI,CAAC,SAAS;AACZ,mBAAS,UAAU,KAAK,EAAE,OAAO,QAAQ,SAAS,MAAM,QAAQ,KAAK,CAAC;AAAA,YACnE,UAAS,IAAI;AAAA,MACpB;AAAA,IACF,GAAG;AAAA,EACL,CAAC;AAED,MAAI;AACF,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,YAAM,UAAU,CAAC,UAAuB;AACtC,aAAK,IAAI,aAAa,WAAW;AACjC,eAAO,KAAK;AAAA,MACd;AACA,YAAM,cAAc,MAAY;AAC9B,aAAK,IAAI,SAAS,OAAO;AACzB,gBAAQ;AAAA,MACV;AACA,WAAK,KAAK,SAAS,OAAO;AAC1B,WAAK,KAAK,aAAa,WAAW;AAClC,WAAK,OAAO,QAAQ,QAAQ,GAAG,IAAI;AAAA,IACrC,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,SAAS,SAAS,EAAE,MAAM,CAAC,YAAY;AAC3C,YAAM,IAAI,eAAe,CAAC,OAAO,OAAO,GAAG,wCAAwC;AAAA,IACrF,CAAC;AACD,UAAM;AAAA,EACR;AACA,WAAS,iBAAiB;AAC1B,QAAM,UAAU,KAAK,QAAQ;AAC7B,QAAM,OAAO,OAAO,YAAY,YAAY,YAAY,OAAO,QAAQ,OAAQ,QAAQ,QAAQ;AAE/F,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,YAA2B;AAChC,eAAS,gBAAgB;AACzB,YAAM,UAAU,MAAM,QAAQ,WAAW;AAAA,QACvC,SAAS,SAAS;AAAA,QAClB,IAAI,QAAc,CAAC,SAAS,WAAW;AACrC,eAAK,MAAM,CAAC,UAAW,UAAU,SAAY,QAAQ,IAAI,OAAO,KAAK,CAAE;AAAA,QACzE,CAAC;AAAA,MACH,CAAC;AACD,YAAM,WAAW,QAAQ;AAAA,QAAQ,CAAC,WAChC,OAAO,WAAW,aAAa,CAAC,OAAO,MAAM,IAAI,CAAC;AAAA,MACpD;AACA,UAAI,SAAS,SAAS;AACpB,cAAM,IAAI,eAAe,UAAU,yCAAyC;AAAA,IAChF;AAAA,EACF;AACF;;;AG/YA,IAAM,YAAmB;AAAA,EACvB,KAAK,CAAC,SAAS,QAAQ,OAAO,MAAM,GAAG,IAAI;AAAA,CAAI;AAAA,EAC/C,KAAK,CAAC,SAAS,QAAQ,OAAO,MAAM,GAAG,IAAI;AAAA,CAAI;AACjD;AAcO,SAAS,UAAU,MAAqC;AAC7D,MAAI,UAAiC;AACrC,MAAI,OAAO;AACX,MAAI,OAAO;AACX,MAAI;AACJ,MAAI;AACJ,MAAI,mBAAmB;AACvB,MAAI,gBAAgB;AACpB,MAAI;AAEJ,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,MAAM,KAAK,KAAK,KAAK;AAC3B,YAAQ,KAAK;AAAA,MACX,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK;AACH,eAAO;AACP;AAAA,MACF,KAAK,UAAU;AACb,cAAM,QAAQ,OAAO,KAAK,QAAQ,CAAC,CAAC;AACpC,YAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,KAAK,QAAQ,OAAQ;AAC3D,gBAAM,WAAW,6CAA6C;AAAA,QAChE;AACA,eAAO;AACP,iBAAS;AACT;AAAA,MACF;AAAA,MACA,KAAK;AACH,eAAO,KAAK,QAAQ,CAAC;AACrB,YAAI,SAAS,OAAW,OAAM,WAAW,sBAAsB;AAC/D,iBAAS;AACT;AAAA,MACF,KAAK;AACH,2BAAmB;AACnB;AAAA,MACF,KAAK;AACH,wBAAgB;AAChB;AAAA,MACF,KAAK;AACH,cAAM,KAAK,QAAQ,CAAC;AACpB,YAAI,QAAQ,OAAW,OAAM,WAAW,yBAAyB;AACjE,iBAAS;AACT;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,kBAAU;AACV;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,kBAAU;AACV;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,kBAAU;AACV;AAAA,MACF,KAAK;AACH,kBAAU;AACV;AAAA,MACF,KAAK;AACH,kBAAU;AACV;AAAA,MACF,KAAK;AACH,kBAAU;AACV;AAAA,MACF;AACE,cAAM;AAAA,UACJ,oBAAoB,KAAK,UAAU,GAAG,CAAC;AAAA,UACvC;AAAA,QACF;AAAA,IACJ;AAAA,EACF;AACA,SAAO,EAAE,SAAS,MAAM,MAAM,MAAM,MAAM,kBAAkB,eAAe,IAAI;AACjF;AAGO,SAAS,oBACd,MACA,QACmB;AACnB,SAAO;AAAA,IACL,GAAG,WAAW,4BAA4B,KAAK,QAAQ,WAAW,IAAI,OAAO,IAAI;AAAA,IACjF,KAAK,gBACD,GAAG,WAAW,sBAAsB,OAAO,SAAS,KACpD,GAAG,WAAW;AAAA,EACpB;AACF;AAMA,eAAsB,OAAO,MAAyB,KAAY,WAA4B;AAE5F,MAAI,OAAO,KAAK,SAAS,QAAQ;AACjC,MAAI;AACF,UAAM,OAAO,UAAU,IAAI;AAC3B,WAAO,KAAK;AACZ,YAAQ,KAAK,SAAS;AAAA,MACpB,KAAK;AACH,WAAG;AAAA,UACD,OAAO,KAAK,UAAU,EAAE,MAAM,aAAa,SAAS,eAAe,CAAC,IAAI;AAAA,QAC1E;AACA,eAAO,WAAW;AAAA,MACpB,KAAK;AAAA,MACL,KAAK;AACH,WAAG,IAAI,OAAO,KAAK,UAAU,kBAAkB,CAAC,IAAI,WAAW,CAAC;AAChE,eAAO,WAAW;AAAA,MACpB,KAAK;AACH,WAAG,IAAI,KAAK,UAAU,kBAAkB,GAAG,MAAM,OAAO,IAAI,CAAC,CAAC;AAC9D,eAAO,WAAW;AAAA,MACpB,KAAK,SAAS;AACZ,YAAI,KAAK,QAAQ,QAAW;AAC1B,gBAAM,QAAQ,gBAAgB;AAC9B,aAAG;AAAA,YACD,OACI,KAAK,UAAU,OAAO,YAAY,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAClF,MAAM,IAAI,CAAC,SAAS,OAAO,KAAK,IAAI;AAAA,EAAK,KAAK,QAAQ,EAAE,EAAE,KAAK,IAAI;AAAA,UACzE;AACA,iBAAO,WAAW;AAAA,QACpB;AACA,cAAM,UAAU,MAAM,gBAAgB,KAAK,GAAG;AAC9C,WAAG,IAAI,OAAO,KAAK,UAAU,EAAE,QAAQ,CAAC,IAAI,QAAQ,KAAK,IAAI,CAAC;AAC9D,eAAO,WAAW;AAAA,MACpB;AAAA,MACA,KAAK,SAAS;AACZ,YAAI,KAAK,MAAM;AACb,gBAAM,SAAS,MAAM,UAAU;AAAA,YAC7B,GAAI,KAAK,SAAS,SAAY,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK;AAAA,YACrD,GAAI,KAAK,SAAS,SAAY,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK;AAAA,YACrD,GAAI,KAAK,mBAAmB,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAAA,UAC5D,CAAC;AAED,qBAAW,QAAQ,oBAAoB,MAAM,MAAM,EAAG,IAAG,IAAI,IAAI;AACjE,gBAAM,IAAI,QAAc,CAAC,YAAY;AACnC,mBAAO,KAAK,GAAG,SAAS,OAAO;AAAA,UACjC,CAAC;AACD,iBAAO,WAAW;AAAA,QACpB;AACA,cAAM,UAAU,MAAM,WAAW;AACjC,cAAM,IAAI,QAAc,CAAC,YAAY;AACnC,gBAAM,WAAW,MAAY;AAC3B,iBAAK,QAAQ,MAAM,EAAE,KAAK,SAAS,OAAO;AAAA,UAC5C;AACA,kBAAQ,KAAK,UAAU,QAAQ;AAC/B,kBAAQ,KAAK,WAAW,QAAQ;AAChC,kBAAQ,OAAO,OAAO,UAAU;AAAA,QAClC,CAAC;AACD,eAAO,WAAW;AAAA,MACpB;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,UAAU,eAAe,KAAK;AACpC,OAAG,IAAI,OAAO,KAAK,UAAU,OAAO,IAAI,GAAG,QAAQ,IAAI,KAAK,QAAQ,OAAO,EAAE;AAC7E,QAAI,CAAC,QAAQ,QAAQ,eAAe,OAAW,IAAG,IAAI,eAAe,QAAQ,UAAU,EAAE;AACzF,WAAO,YAAY,QAAQ,IAAI;AAAA,EACjC;AACF;AAGA,eAAsB,OAAsB;AAC1C,UAAQ,WAAW,MAAM,OAAO,QAAQ,KAAK,MAAM,CAAC,CAAC;AACvD;","names":["session"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** One file of the emitted package, keyed by its path inside the directory. */
|
|
2
|
+
interface SkillFile {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly contents: string;
|
|
5
|
+
}
|
|
6
|
+
/** Renders the committed tool inventories from the same registry the server exposes. */
|
|
7
|
+
declare function renderMcpToolSurfaceMarkdown(): string;
|
|
8
|
+
/** Builds the agent-skill package in memory. */
|
|
9
|
+
declare function buildAgentSkill(): readonly SkillFile[];
|
|
10
|
+
/** Writes the package into `directory`, creating it if needed. Returns the paths written. */
|
|
11
|
+
declare function writeAgentSkill(directory: string): Promise<readonly string[]>;
|
|
12
|
+
|
|
13
|
+
export { type SkillFile as S, buildAgentSkill as b, renderMcpToolSurfaceMarkdown as r, writeAgentSkill as w };
|
package/dist/docs.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { r as renderMcpToolSurfaceMarkdown } from './docs-85fGFORb.js';
|
package/dist/docs.js
ADDED
package/dist/docs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { TermwrightErrorCode, TerminalHarness, AppLogEvent, ExitStatus, EnvMode, LaunchOptions, AppLogSource,
|
|
1
|
+
import { TermwrightErrorCode, TerminalHarness, SemanticLocatorRef, AppLogEvent, ExitStatus, EnvMode, LaunchOptions, AppLogSource, AnyLocator } from '@termwright/driver';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
export { S as SkillFile, b as buildAgentSkill, w as writeAgentSkill } from './docs-85fGFORb.js';
|
|
3
4
|
import { SemanticSnapshot, Rect, SemanticState, SemanticNode, SemanticRole } from '@termwright/protocol';
|
|
4
5
|
export { Rect, SEMANTIC_ROLES, SemanticNode, SemanticRole, SemanticSnapshot, SemanticState } from '@termwright/protocol';
|
|
5
6
|
import { Server } from 'node:http';
|
|
@@ -151,16 +152,6 @@ declare function buildAgentContext(): AgentContext;
|
|
|
151
152
|
/** The one-screen cheat sheet printed by `termwright-mcp usage`. */
|
|
152
153
|
declare function buildUsage(): string;
|
|
153
154
|
|
|
154
|
-
/** One file of the emitted package, keyed by its path inside the directory. */
|
|
155
|
-
interface SkillFile {
|
|
156
|
-
readonly path: string;
|
|
157
|
-
readonly contents: string;
|
|
158
|
-
}
|
|
159
|
-
/** Builds the agent-skill package in memory. */
|
|
160
|
-
declare function buildAgentSkill(): readonly SkillFile[];
|
|
161
|
-
/** Writes the package into `directory`, creating it if needed. Returns the paths written. */
|
|
162
|
-
declare function writeAgentSkill(directory: string): Promise<readonly string[]>;
|
|
163
|
-
|
|
164
155
|
/** Where the CLI writes. Injectable so tests never touch the real streams. */
|
|
165
156
|
interface CliIo {
|
|
166
157
|
readonly out: (text: string) => void;
|
|
@@ -186,8 +177,6 @@ declare function main(): Promise<void>;
|
|
|
186
177
|
|
|
187
178
|
/** The driver's view of the visible grid. */
|
|
188
179
|
type ScreenSnapshot = ReturnType<TerminalHarness['screen']>;
|
|
189
|
-
/** Session capabilities as reported after the semantic handshake window. */
|
|
190
|
-
type SessionCapabilities = ReturnType<TerminalHarness['capabilities']>;
|
|
191
180
|
/** State flags an agent may filter on; the value type follows {@link SemanticState}. */
|
|
192
181
|
declare const FILTERABLE_STATES: readonly ["disabled", "focused", "selected", "checked", "expanded", "modal", "busy", "hidden", "readonly"];
|
|
193
182
|
/** Signals `terminal.signal` accepts, mirroring `TerminalHarness['signal']`. */
|
|
@@ -225,20 +214,20 @@ declare function diffSemantic(before: SemanticSnapshot | null, after: SemanticSn
|
|
|
225
214
|
* ```
|
|
226
215
|
* Terminal t1 100x30 revision 42
|
|
227
216
|
* semanticTree: available
|
|
228
|
-
* dialog "Permission" ref=n7@42 bounds=(8,20,40,9) modal
|
|
229
|
-
* button "Approve" ref=n8@42 bounds=(14,23,11,1) focused
|
|
217
|
+
* dialog "Permission" ref=semantic:n7@42 bounds=(8,20,40,9) modal
|
|
218
|
+
* button "Approve" ref=semantic:n8@42 bounds=(14,23,11,1) focused
|
|
230
219
|
* visible text:
|
|
231
220
|
* <grid text>
|
|
232
221
|
* ```
|
|
233
222
|
*
|
|
234
223
|
* `bounds` is `(row,column,width,height)` — the field order of the protocol's
|
|
235
|
-
* `Rect`. Refs are
|
|
224
|
+
* `Rect`. Refs are `semantic:<nodeId>@<semanticRevision>`, byte-identical to the refs the
|
|
236
225
|
* driver puts on `ResolvedTarget`, so a ref can be quoted back to any tool.
|
|
237
226
|
*/
|
|
238
227
|
|
|
239
228
|
/** One line of the ref list, plus the structured fields behind it. */
|
|
240
229
|
interface RefEntry {
|
|
241
|
-
readonly ref:
|
|
230
|
+
readonly ref: SemanticLocatorRef;
|
|
242
231
|
readonly role: string;
|
|
243
232
|
readonly name: string;
|
|
244
233
|
readonly depth: number;
|
|
@@ -246,10 +235,12 @@ interface RefEntry {
|
|
|
246
235
|
readonly flags: readonly string[];
|
|
247
236
|
readonly testId?: string;
|
|
248
237
|
readonly value?: string;
|
|
238
|
+
readonly applicationScroll?: string;
|
|
239
|
+
readonly paintedRegion?: string;
|
|
249
240
|
}
|
|
250
|
-
/** Formats
|
|
251
|
-
declare function formatRef(nodeId: string, revision: number):
|
|
252
|
-
/** Splits
|
|
241
|
+
/** Formats an explicitly semantic ref for a node observed at `revision`. */
|
|
242
|
+
declare function formatRef(nodeId: string, revision: number): SemanticLocatorRef;
|
|
243
|
+
/** Splits a semantic ref back into its parts; screen refs are rejected. */
|
|
253
244
|
declare function parseRef(ref: string): {
|
|
254
245
|
readonly nodeId: string;
|
|
255
246
|
readonly revision: number;
|
|
@@ -294,6 +285,13 @@ interface CompactSnapshotOptions {
|
|
|
294
285
|
/** Renders the normative compact snapshot. */
|
|
295
286
|
declare function formatCompactSnapshot(options: CompactSnapshotOptions): string;
|
|
296
287
|
|
|
288
|
+
/** Bounded fixed-window admission policy, keyed by the TCP peer address. */
|
|
289
|
+
interface HttpRateLimitOptions {
|
|
290
|
+
readonly windowMs?: number;
|
|
291
|
+
readonly maxRequests?: number;
|
|
292
|
+
readonly maxClients?: number;
|
|
293
|
+
}
|
|
294
|
+
|
|
297
295
|
/**
|
|
298
296
|
* The application's own log, on the session timeline.
|
|
299
297
|
*
|
|
@@ -357,6 +355,8 @@ declare class LogBuffer {
|
|
|
357
355
|
get size(): number;
|
|
358
356
|
/** Records one driver event. */
|
|
359
357
|
append(event: AppLogEvent): void;
|
|
358
|
+
/** Advances the cursor for source events that were explicitly reported lost. */
|
|
359
|
+
omit(count: number): void;
|
|
360
360
|
/**
|
|
361
361
|
* Everything after `cursor`, newest-biased and bounded.
|
|
362
362
|
*
|
|
@@ -459,6 +459,8 @@ interface TerminalEntry {
|
|
|
459
459
|
readonly directory: string;
|
|
460
460
|
readonly command: readonly string[];
|
|
461
461
|
exit: ExitStatus | null;
|
|
462
|
+
/** Backend/lifecycle failure is not a fabricated process exit. */
|
|
463
|
+
exitFailure?: unknown;
|
|
462
464
|
closed: boolean;
|
|
463
465
|
history: RevisionRecord[];
|
|
464
466
|
/** The application's own log, buffered for `terminal.capture_since`. */
|
|
@@ -540,6 +542,8 @@ interface RegisteredSession<T> {
|
|
|
540
542
|
readonly attachment: T;
|
|
541
543
|
/** Clock reading of the last request that named this session. */
|
|
542
544
|
lastSeenAt: number;
|
|
545
|
+
/** One shared cleanup transaction; retained on failure so callers observe it. */
|
|
546
|
+
closing?: Promise<void>;
|
|
543
547
|
}
|
|
544
548
|
/** Options for {@link SessionRegistry}. */
|
|
545
549
|
interface SessionRegistryOptions<T> {
|
|
@@ -556,6 +560,8 @@ interface SessionRegistryOptions<T> {
|
|
|
556
560
|
readonly disposeAttachment?: (attachment: T) => Promise<void> | void;
|
|
557
561
|
/** Called after an idle session was torn down, for the server log. */
|
|
558
562
|
readonly onExpired?: (key: string) => void;
|
|
563
|
+
/** Receives managed sweeper failures; never an unhandled rejection. */
|
|
564
|
+
readonly onBackgroundError?: (error: unknown) => void;
|
|
559
565
|
}
|
|
560
566
|
/**
|
|
561
567
|
* Sessions keyed by `Mcp-Session-Id` (or `stdio` for the stdio transport).
|
|
@@ -633,12 +639,26 @@ interface HttpServerHandle {
|
|
|
633
639
|
server: McpServer;
|
|
634
640
|
}>;
|
|
635
641
|
readonly port: number;
|
|
642
|
+
/** Per-launch bearer required by every HTTP request. Never put it in a URL. */
|
|
643
|
+
readonly authToken: string;
|
|
636
644
|
close(): Promise<void>;
|
|
637
645
|
}
|
|
638
646
|
/** Options for {@link serveHttp}. */
|
|
639
647
|
interface HttpServeOptions extends ServeOptions {
|
|
640
648
|
readonly port?: number;
|
|
641
649
|
readonly host?: string;
|
|
650
|
+
/**
|
|
651
|
+
* Explicit acknowledgement that a non-loopback bind exposes process launch,
|
|
652
|
+
* terminal input and filesystem-backed trace tools to the network.
|
|
653
|
+
*/
|
|
654
|
+
readonly allowNonLoopback?: boolean;
|
|
655
|
+
/**
|
|
656
|
+
* Browser origins allowed to call the endpoint. Non-browser MCP clients omit
|
|
657
|
+
* `Origin`; any presented origin is rejected unless it appears here exactly.
|
|
658
|
+
*/
|
|
659
|
+
readonly allowedOrigins?: readonly string[];
|
|
660
|
+
/** Per-peer request ceiling. The limiter's identity map is bounded too. */
|
|
661
|
+
readonly rateLimit?: HttpRateLimitOptions;
|
|
642
662
|
/** Path the MCP endpoint listens on. Defaults to `/mcp`. */
|
|
643
663
|
readonly path?: string;
|
|
644
664
|
/**
|
|
@@ -696,7 +716,7 @@ interface ScreenshotRequest {
|
|
|
696
716
|
*
|
|
697
717
|
* Failures are typed rather than thrown as raw errors: a scale out of range is
|
|
698
718
|
* `usage`, an image over the ceiling is `capacity`, and a renderer that cannot
|
|
699
|
-
* run at all (no font, no rasteriser) is `
|
|
719
|
+
* run at all (no font, no rasteriser) is `capability-unavailable` — each with the
|
|
700
720
|
* next thing to try.
|
|
701
721
|
*/
|
|
702
722
|
declare function renderScreenshot(frame: ScreenFrame, request?: ScreenshotRequest): ScreenshotImage;
|
|
@@ -785,16 +805,18 @@ declare const TRACE_TOOLS: readonly ToolDefinition[];
|
|
|
785
805
|
* from zod-parsed arguments, where an absent key is present-and-undefined.
|
|
786
806
|
*/
|
|
787
807
|
interface TargetInput {
|
|
788
|
-
/** A ref
|
|
808
|
+
/** A domain-tagged ref: `semantic:n8@42`, or `screen:1,2,9,1@7`. */
|
|
789
809
|
readonly ref?: string | undefined;
|
|
790
|
-
/**
|
|
810
|
+
/** Termwright Semantic Selector Language, e.g. `dialog button#approve:focused`. */
|
|
791
811
|
readonly selector?: string | undefined;
|
|
792
812
|
readonly role?: SemanticRole | undefined;
|
|
793
813
|
/** Accessible name; `/…/flags` is read as a regular expression. */
|
|
794
814
|
readonly name?: string | undefined;
|
|
795
815
|
readonly testId?: string | undefined;
|
|
796
|
-
/**
|
|
816
|
+
/** Text carried by a semantic node. Requires a semantic tree. */
|
|
797
817
|
readonly text?: string | undefined;
|
|
818
|
+
/** Text rendered in the physical terminal grid. */
|
|
819
|
+
readonly screenText?: string | undefined;
|
|
798
820
|
/** Label text (`labelledBy`, else name). */
|
|
799
821
|
readonly label?: string | undefined;
|
|
800
822
|
readonly exact?: boolean | undefined;
|
|
@@ -809,18 +831,18 @@ interface TargetInput {
|
|
|
809
831
|
declare function textOrRegExp(value: string): string | RegExp;
|
|
810
832
|
/**
|
|
811
833
|
* Builds the locator described by `input`. Precedence is `ref`, `selector`,
|
|
812
|
-
* `testId`, `role`, `label`, `text` — the order from most to least specific.
|
|
834
|
+
* `testId`, `role`, `label`, `text`, `screenText` — the order from most to least specific.
|
|
813
835
|
*
|
|
814
836
|
* Every branch hands straight to a driver factory; nothing here matches, waits
|
|
815
837
|
* or decides staleness.
|
|
816
838
|
*/
|
|
817
|
-
declare function buildLocator(harness: TerminalHarness, input: TargetInput):
|
|
839
|
+
declare function buildLocator(harness: TerminalHarness, input: TargetInput): AnyLocator;
|
|
818
840
|
|
|
819
841
|
/** Identity reported over MCP and by `--version`. Kept in step with package.json. */
|
|
820
842
|
declare const SERVER_NAME = "termwright";
|
|
821
843
|
/** Package version. */
|
|
822
|
-
declare const SERVER_VERSION = "0.1
|
|
844
|
+
declare const SERVER_VERSION = "0.3.1";
|
|
823
845
|
/** Version of the `agent-context` document shape (independent of the package). */
|
|
824
846
|
declare const AGENT_CONTEXT_VERSION = 1;
|
|
825
847
|
|
|
826
|
-
export { AGENT_CONTEXT_VERSION, type AgentContext, type AgentContextTool, type CliIo, type CompactSnapshotOptions, EXIT_CODES, type ErrorKind, type ErrorPayload, FILTERABLE_STATES, type HttpServeOptions, type HttpServerHandle, type JsonSchema, type LaunchRequest, MCP_LIMITS, McpError, type OpenTrace, type RefEntry, type RegisteredSession, type RevisionRecord, type RowChange, type RunningServer, SCREENSHOT_LIMITS, SERVER_NAME, SERVER_VERSION, SIGNALS, type ScreenSnapshot, type ScreenshotImage, type ScreenshotRequest, type ServeOptions,
|
|
848
|
+
export { AGENT_CONTEXT_VERSION, type AgentContext, type AgentContextTool, type CliIo, type CompactSnapshotOptions, EXIT_CODES, type ErrorKind, type ErrorPayload, FILTERABLE_STATES, type HttpRateLimitOptions, type HttpServeOptions, type HttpServerHandle, type JsonSchema, type LaunchRequest, MCP_LIMITS, McpError, type OpenTrace, type RefEntry, type RegisteredSession, type RevisionRecord, type RowChange, type RunningServer, SCREENSHOT_LIMITS, SERVER_NAME, SERVER_VERSION, SIGNALS, type ScreenSnapshot, type ScreenshotImage, type ScreenshotRequest, type ServeOptions, SessionRegistry, type SessionStores, type SubtreeChange, TERMINAL_TOOLS, TOOLS, TRACE_LIMITS, TRACE_TOOLS, type TargetInput, type TerminalEntry, TerminalStore, type TerminalStoreOptions, type ToolContext, type ToolDefinition, type ToolOutcome, TraceStore, type TraceStoreOptions, buildAgentContext, buildLocator, buildUsage, closeSessionStores, createSessionStores, createTermwrightMcpServer, defineTool, diffRows, diffSemantic, exitCodeFor, formatBounds, formatCompactSnapshot, formatNodeLine, formatRef, main, noSessionError, parseRef, refEntries, renderErrorPayload, renderScreenshot, runCli, serveHttp, serveInMemory, serveStdio, stateFlags, textOrRegExp, toErrorPayload, toRefEntry, toolByName, usageError, walkSnapshot };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTermwrightMcpServer,
|
|
3
|
+
main,
|
|
4
|
+
runCli,
|
|
5
|
+
serveHttp,
|
|
6
|
+
serveInMemory,
|
|
7
|
+
serveStdio
|
|
8
|
+
} from "./chunk-EU4HGMLE.js";
|
|
1
9
|
import {
|
|
2
10
|
AGENT_CONTEXT_VERSION,
|
|
3
11
|
EXIT_CODES,
|
|
@@ -22,7 +30,6 @@ import {
|
|
|
22
30
|
buildUsage,
|
|
23
31
|
closeSessionStores,
|
|
24
32
|
createSessionStores,
|
|
25
|
-
createTermwrightMcpServer,
|
|
26
33
|
defineTool,
|
|
27
34
|
diffRows,
|
|
28
35
|
diffSemantic,
|
|
@@ -31,16 +38,11 @@ import {
|
|
|
31
38
|
formatCompactSnapshot,
|
|
32
39
|
formatNodeLine,
|
|
33
40
|
formatRef,
|
|
34
|
-
main,
|
|
35
41
|
noSessionError,
|
|
36
42
|
parseRef,
|
|
37
43
|
refEntries,
|
|
38
44
|
renderErrorPayload,
|
|
39
45
|
renderScreenshot,
|
|
40
|
-
runCli,
|
|
41
|
-
serveHttp,
|
|
42
|
-
serveInMemory,
|
|
43
|
-
serveStdio,
|
|
44
46
|
stateFlags,
|
|
45
47
|
textOrRegExp,
|
|
46
48
|
toErrorPayload,
|
|
@@ -49,7 +51,7 @@ import {
|
|
|
49
51
|
usageError,
|
|
50
52
|
walkSnapshot,
|
|
51
53
|
writeAgentSkill
|
|
52
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-45LJP5X3.js";
|
|
53
55
|
export {
|
|
54
56
|
AGENT_CONTEXT_VERSION,
|
|
55
57
|
EXIT_CODES,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@termwright/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "thin MCP server over the public driver API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
12
|
"engines": {
|
|
13
|
-
"node": "
|
|
13
|
+
"node": "^22.0.0 || ^24.0.0"
|
|
14
14
|
},
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
|
@@ -27,14 +27,17 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
29
29
|
"zod": "^4.0.0",
|
|
30
|
-
"@termwright/driver": "0.
|
|
31
|
-
"@termwright/
|
|
32
|
-
"@termwright/
|
|
33
|
-
"@termwright/
|
|
30
|
+
"@termwright/driver": "0.3.1",
|
|
31
|
+
"@termwright/screenshot": "0.3.1",
|
|
32
|
+
"@termwright/trace": "0.3.1",
|
|
33
|
+
"@termwright/protocol": "0.3.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@termwright/resource-broker": "0.3.1"
|
|
34
37
|
},
|
|
35
38
|
"scripts": {
|
|
36
|
-
"build": "tsup src/index.ts src/bin.ts --format esm --dts --sourcemap",
|
|
39
|
+
"build": "tsup src/index.ts src/bin.ts src/docs.ts --format esm --dts --sourcemap --clean",
|
|
37
40
|
"typecheck": "tsc --noEmit",
|
|
38
|
-
"test": "
|
|
41
|
+
"test": "pnpm --dir ../.. test -- -- --run packages/mcp"
|
|
39
42
|
}
|
|
40
43
|
}
|