diff --git a/bun.lock b/bun.lock index d46be047e..1c29f78c8 100644 --- a/bun.lock +++ b/bun.lock @@ -17,20 +17,20 @@ "@ant/computer-use-swift": "workspace:*", "@ant/model-provider": "workspace:*", "@anthropic-ai/bedrock-sdk": "^0.26.4", - "@anthropic-ai/claude-agent-sdk": "^0.2.87", + "@anthropic-ai/claude-agent-sdk": "^0.2.114", "@anthropic-ai/foundry-sdk": "^0.2.3", "@anthropic-ai/mcpb": "^2.1.2", "@anthropic-ai/sandbox-runtime": "^0.0.44", "@anthropic-ai/sdk": "^0.80.0", "@anthropic-ai/vertex-sdk": "^0.14.4", "@anthropic/ink": "workspace:*", - "@aws-sdk/client-bedrock": "^3.1020.0", - "@aws-sdk/client-bedrock-runtime": "^3.1020.0", - "@aws-sdk/client-sts": "^3.1020.0", - "@aws-sdk/credential-provider-node": "^3.972.28", - "@aws-sdk/credential-providers": "^3.1020.0", + "@aws-sdk/client-bedrock": "^3.1032.0", + "@aws-sdk/client-bedrock-runtime": "^3.1032.0", + "@aws-sdk/client-sts": "^3.1032.0", + "@aws-sdk/credential-provider-node": "^3.972.32", + "@aws-sdk/credential-providers": "^3.1032.0", "@azure/identity": "^4.13.1", - "@biomejs/biome": "^2.4.10", + "@biomejs/biome": "^2.4.12", "@claude-code-best/agent-tools": "workspace:*", "@claude-code-best/builtin-tools": "workspace:*", "@claude-code-best/mcp-client": "workspace:*", @@ -41,7 +41,7 @@ "@modelcontextprotocol/sdk": "^1.29.0", "@opentelemetry/api": "^1.9.1", "@opentelemetry/api-logs": "^0.214.0", - "@opentelemetry/core": "^2.6.1", + "@opentelemetry/core": "^2.7.0", "@opentelemetry/exporter-logs-otlp-grpc": "^0.214.0", "@opentelemetry/exporter-logs-otlp-http": "^0.214.0", "@opentelemetry/exporter-logs-otlp-proto": "^0.214.0", @@ -52,14 +52,14 @@ "@opentelemetry/exporter-trace-otlp-grpc": "^0.214.0", "@opentelemetry/exporter-trace-otlp-http": "^0.214.0", "@opentelemetry/exporter-trace-otlp-proto": "^0.214.0", - "@opentelemetry/resources": "^2.6.1", + "@opentelemetry/resources": "^2.7.0", "@opentelemetry/sdk-logs": "^0.214.0", - "@opentelemetry/sdk-metrics": "^2.6.1", - "@opentelemetry/sdk-trace-base": "^2.6.1", + "@opentelemetry/sdk-metrics": "^2.7.0", + "@opentelemetry/sdk-trace-base": "^2.7.0", "@opentelemetry/semantic-conventions": "^1.40.0", - "@sentry/node": "^10.47.0", - "@smithy/core": "^3.23.13", - "@smithy/node-http-handler": "^4.5.1", + "@sentry/node": "^10.49.0", + "@smithy/core": "^3.23.15", + "@smithy/node-http-handler": "^4.5.3", "@types/bun": "^1.3.12", "@types/cacache": "^20.0.1", "@types/he": "^1.2.3", @@ -81,7 +81,7 @@ "asciichart": "^1.5.25", "audio-capture-napi": "workspace:*", "auto-bind": "^5.0.1", - "axios": "^1.14.0", + "axios": "^1.15.0", "bidi-js": "^1.0.3", "cacache": "^20.0.4", "chalk": "^5.6.2", @@ -96,7 +96,7 @@ "execa": "^9.6.1", "fflate": "^0.8.2", "figures": "^6.1.0", - "fuse.js": "^7.1.0", + "fuse.js": "^7.3.0", "get-east-asian-width": "^1.5.0", "google-auth-library": "^10.6.2", "he": "^1.2.0", @@ -106,21 +106,21 @@ "image-processor-napi": "workspace:*", "indent-string": "^5.0.0", "jsonc-parser": "^3.3.1", - "knip": "^6.1.1", - "lodash-es": "^4.17.23", - "lru-cache": "^11.2.7", - "marked": "^17.0.5", + "knip": "^6.4.1", + "lodash-es": "^4.18.1", + "lru-cache": "^11.3.5", + "marked": "^17.0.6", "modifiers-napi": "workspace:*", - "openai": "^6.33.0", + "openai": "^6.34.0", "p-map": "^7.0.4", "picomatch": "^4.0.4", "plist": "^3.1.0", "proper-lockfile": "^4.1.2", "qrcode": "^1.5.4", - "react": "^19.2.4", + "react": "^19.2.5", "react-compiler-runtime": "^1.0.0", "react-reconciler": "^0.33.0", - "rollup": "^4.60.1", + "rollup": "^4.60.2", "semver": "^7.7.4", "sharp": "^0.34.5", "shell-quote": "^1.8.3", @@ -129,10 +129,10 @@ "strip-ansi": "^7.2.0", "supports-hyperlinks": "^4.4.0", "tree-kill": "^1.2.2", - "turndown": "^7.2.2", - "type-fest": "^5.5.0", - "typescript": "^6.0.2", - "undici": "^7.24.6", + "turndown": "^7.2.4", + "type-fest": "^5.6.0", + "typescript": "^6.0.3", + "undici": "^7.25.0", "url-handler-napi": "workspace:*", "usehooks-ts": "^3.1.1", "vite": "^8.0.8", @@ -194,9 +194,10 @@ }, "packages/acp-link": { "name": "acp-link", - "version": "1.0.1", + "version": "1.1.0", "bin": { "acp-link": "dist/cli/bin.js", + "acp-manager": "dist/manager/bin.js", }, "dependencies": { "@agentclientprotocol/sdk": "^0.19.0", @@ -210,6 +211,7 @@ "selfsigned": "^5.5.0", }, "devDependencies": { + "@types/bun": "^1.3.12", "@types/selfsigned": "^2.0.4", "@types/ws": "^8.18.1", }, diff --git a/packages/acp-link/README.md b/packages/acp-link/README.md index 7cd00496a..8f640af71 100644 --- a/packages/acp-link/README.md +++ b/packages/acp-link/README.md @@ -100,6 +100,22 @@ acp-link can register to a Remote Control Server (RCS) for remote access. Set th You can also use `--group ` on the CLI. The CLI flag takes priority over the env var. +## Manager UI + +通过 `--manager` flag 启动独立的管理服务(不启动代理): + +```bash +# 启动 Manager(默认端口 9315) +acp-link --manager + +# 指定端口 +acp-link --manager --port 3210 +``` + +在浏览器打开 `http://localhost:` 即可访问管理界面,创建、停止、删除多个 acp-link 子进程实例并实时查看日志。 + +通过 Manager UI 创建的子进程会自动跳过 Manager UI。 + ## License MIT diff --git a/packages/acp-link/package.json b/packages/acp-link/package.json index 6eb538467..b1898ffb5 100644 --- a/packages/acp-link/package.json +++ b/packages/acp-link/package.json @@ -1,6 +1,6 @@ { "name": "acp-link", - "version": "1.1.0", + "version": "2.0.0", "description": "ACP proxy server that bridges WebSocket clients to ACP agents", "author": "claude-code-best", "type": "module", @@ -15,11 +15,14 @@ "scripts": { "build": "tsc", "dev": "ACP_RCS_URL=http://localhost:3000 ACP_RCS_TOKEN=test-my-key bun run src/cli/bin.ts ccb-bun -- --acp", + "dev:remote": "ACP_RCS_URL=https://remote-control.claude-code-best.win/ ACP_RCS_TOKEN=test-my-key bun run src/cli/bin.ts ccb-bun -- --acp", + "dev:manager": "ACP_RCS_URL=http://localhost:3000 ACP_RCS_TOKEN=test-my-key bun run src/cli/bin.ts --manager", "prepublishOnly": "bun run build" }, "devDependencies": { "@types/selfsigned": "^2.0.4", - "@types/ws": "^8.18.1" + "@types/ws": "^8.18.1", + "@types/bun": "^1.3.12" }, "dependencies": { "@agentclientprotocol/sdk": "^0.19.0", diff --git a/packages/acp-link/src/cli/command.ts b/packages/acp-link/src/cli/command.ts index 4df19cb13..18db7fa9b 100644 --- a/packages/acp-link/src/cli/command.ts +++ b/packages/acp-link/src/cli/command.ts @@ -9,6 +9,8 @@ export const command = buildCommand({ "The agent command is spawned as a subprocess and communicates via stdin/stdout.\n\n" + "Use -- to pass arguments to the agent:\n" + " acp-link /path/to/agent -- --verbose --model gpt-4\n\n" + + "Use --manager to start the Manager Web UI instead:\n" + + " acp-link --manager\n\n" + "For remote access, set ACP_AUTH_TOKEN environment variable or let it auto-generate.", }, parameters: { @@ -40,6 +42,11 @@ export const command = buildCommand({ brief: "Enable HTTPS with auto-generated self-signed certificate", default: false, }, + manager: { + kind: "boolean", + brief: "Start Manager Web UI (no proxy)", + default: false, + }, group: { kind: "parsed", parse: (value: string) => { @@ -59,12 +66,12 @@ export const command = buildCommand({ parse: String, placeholder: "command", }, - minimum: 1, + minimum: 0, }, }, func: async function ( this: LocalContext, - flags: { port: number; host: string; debug: boolean; "no-auth": boolean; https: boolean; group: string | undefined }, + flags: { port: number; host: string; debug: boolean; "no-auth": boolean; https: boolean; manager: boolean; group: string | undefined }, ...args: readonly string[] ) { const port = flags.port; @@ -72,7 +79,21 @@ export const command = buildCommand({ const debug = flags.debug; const noAuth = flags["no-auth"]; const https = flags.https; + const manager = flags.manager; const group = flags.group; + + // Manager mode: start web UI only, no proxy + if (manager) { + const { startManager } = await import("../manager/index.js"); + await startManager(port); + return; + } + + // Proxy mode: agent command is required + if (args.length === 0) { + console.error("Error: agent command is required (or use --manager)"); + process.exit(1); + } const [command, ...agentArgs] = args; const cwd = process.cwd(); diff --git a/packages/acp-link/src/manager/html.ts b/packages/acp-link/src/manager/html.ts new file mode 100644 index 000000000..70f0a6b56 --- /dev/null +++ b/packages/acp-link/src/manager/html.ts @@ -0,0 +1,345 @@ +export const MANAGER_HTML = ` + + + + +ACP Manager + + + +
+

ACP Manager

+
+ +
+
+ + +
+
+ + +
+ +
+ +
+ + + +`; diff --git a/packages/acp-link/src/manager/index.ts b/packages/acp-link/src/manager/index.ts new file mode 100644 index 000000000..e49334d6a --- /dev/null +++ b/packages/acp-link/src/manager/index.ts @@ -0,0 +1,44 @@ +import { Hono } from "hono"; +import { serve } from "@hono/node-server"; +import { ProcessManager } from "./manager.js"; +import { createApp } from "./routes.js"; + +export async function startManager(port: number): Promise { + const manager = new ProcessManager(); + const app = createApp(manager); + + // Health check + app.get("/health", (c) => c.json({ status: "ok" })); + + let shuttingDown = false; + const shutdown = async () => { + if (shuttingDown) return; + shuttingDown = true; + console.log("Shutting down..."); + await manager.shutdownAll(); + process.exit(0); + }; + process.on("SIGTERM", shutdown); + process.on("SIGINT", shutdown); + + const server = serve({ fetch: app.fetch, port }); + server.on("error", (err: NodeJS.ErrnoException) => { + if (err.code === "EADDRINUSE") { + console.error(`\n Error: port ${port} is already in use. Use --port to specify a different port.\n`); + } else { + console.error(`\n Error: ${err.message}\n`); + } + process.exit(1); + }); + + console.log(); + console.log(` 🖥️ ACP Manager`); + console.log(); + console.log(` URL: http://localhost:${port}`); + console.log(); + console.log(` Press Ctrl+C to stop`); + console.log(); + + // Keep running + await new Promise(() => {}); +} diff --git a/packages/acp-link/src/manager/manager.ts b/packages/acp-link/src/manager/manager.ts new file mode 100644 index 000000000..6b2a6ab59 --- /dev/null +++ b/packages/acp-link/src/manager/manager.ts @@ -0,0 +1,233 @@ +import type { AcpInstance, InstanceSummary, LogEntry } from "./types.js"; + +function log(tag: string, msg: string) { + const ts = new Date().toISOString(); + console.log(`[${ts}] [${tag}] ${msg}`); +} + +const MAX_LOG_LINES = 2000; +const SHUTDOWN_TIMEOUT_MS = 5000; + +export class ProcessManager { + private instances = new Map(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private processes = new Map(); + + create(group: string, command: string): AcpInstance { + const id = crypto.randomUUID(); + const instance: AcpInstance = { + id, + group, + command, + status: "running", + pid: undefined, + startTime: Date.now(), + exitCode: null, + logs: [], + subscribers: new Set(), + }; + + const args = this.parseCommand(command); + const fullArgs = ["--group", group, ...args]; + + const proc = Bun.spawn(["acp-link", ...fullArgs], { + stdout: "pipe", + stderr: "pipe", + env: { ...Bun.env, ACP_CHILD: "1" }, + }); + + instance.pid = proc.pid; + this.instances.set(id, instance); + this.processes.set(id, proc); + log("manager", `created instance ${id.slice(0, 8)} group=${group} pid=${proc.pid} cmd="acp-link ${fullArgs.join(" ")}"`); + + this.pipeStream(proc.stdout, id, "stdout"); + this.pipeStream(proc.stderr, id, "stderr"); + + proc.exited.then((code) => { + instance.status = code === 0 ? "stopped" : "failed"; + instance.exitCode = code; + instance.pid = undefined; + this.processes.delete(id); + log("manager", `instance ${id.slice(0, 8)} ${instance.status} exit=${code}`); + this.notifyStatus(instance); + }); + + return instance; + } + + stop(id: string): boolean { + const proc = this.processes.get(id); + if (!proc) return false; + const inst = this.instances.get(id); + log("manager", `stopping instance ${id.slice(0, 8)} pid=${proc.pid}`); + proc.kill("SIGTERM"); + // Immediately mark as stopped to prevent stale state + if (inst) { + inst.status = "stopped"; + } + return true; + } + + remove(id: string): boolean { + const instance = this.instances.get(id); + if (!instance) return false; + if (instance.status === "running") return false; + instance.subscribers.clear(); + this.instances.delete(id); + log("manager", `removed instance ${id.slice(0, 8)} group=${instance.group}`); + return true; + } + + list(): InstanceSummary[] { + return Array.from(this.instances.values()).map(this.toSummary); + } + + get(id: string): AcpInstance | undefined { + return this.instances.get(id); + } + + subscribe(id: string, callback: (entry: LogEntry) => void): () => void { + const instance = this.instances.get(id); + if (!instance) return () => {}; + instance.subscribers.add(callback); + return () => instance.subscribers.delete(callback); + } + + async shutdownAll(): Promise { + const running = Array.from(this.processes.entries()); + if (running.length === 0) return; + + log("manager", `shutting down ${running.length} running instance(s)...`); + for (const [id, proc] of running) { + try { + proc.kill("SIGTERM"); + log("manager", `sent SIGTERM to ${id.slice(0, 8)} pid=${proc.pid}`); + } catch { + // already dead + } + } + + const timeout = new Promise((resolve) => setTimeout(resolve, SHUTDOWN_TIMEOUT_MS)); + await Promise.race([ + Promise.all(running.map(([, proc]) => proc.exited.catch(() => {}))), + timeout, + ]); + + for (const [id, proc] of running) { + try { + proc.kill("SIGKILL"); + log("manager", `sent SIGKILL to ${id.slice(0, 8)}`); + } catch { + // already dead + } + } + log("manager", "all instances shut down"); + } + + private parseCommand(command: string): string[] { + const args: string[] = []; + let current = ""; + let inQuote: string | null = null; + + for (const ch of command) { + if (inQuote) { + if (ch === inQuote) { + inQuote = null; + } else { + current += ch; + } + } else if (ch === '"' || ch === "'") { + inQuote = ch; + } else if (ch === " " || ch === "\t") { + if (current) { + args.push(current); + current = ""; + } + } else { + current += ch; + } + } + if (current) args.push(current); + return args; + } + + private pipeStream( + readable: ReadableStream, + instanceId: string, + stream: "stdout" | "stderr", + ) { + const reader = readable.getReader(); + const decoder = new TextDecoder(); + let buffer = ""; + + const processChunk = () => { + reader + .read() + .then(({ done, value }) => { + if (done) { + if (buffer) this.appendLog(instanceId, buffer, stream); + return; + } + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split("\n"); + buffer = lines.pop() ?? ""; + for (const line of lines) { + if (line) this.appendLog(instanceId, line, stream); + } + processChunk(); + }) + .catch(() => { + // stream ended or error + }); + }; + processChunk(); + } + + private appendLog(instanceId: string, text: string, stream: "stdout" | "stderr") { + const instance = this.instances.get(instanceId); + if (!instance) return; + + const entry: LogEntry = { timestamp: Date.now(), stream, text }; + instance.logs.push(entry); + if (instance.logs.length > MAX_LOG_LINES) { + instance.logs.splice(0, instance.logs.length - MAX_LOG_LINES); + } + + for (const sub of instance.subscribers) { + try { + sub(entry); + } catch { + // subscriber error, remove it + instance.subscribers.delete(sub); + } + } + } + + private notifyStatus(instance: AcpInstance) { + const statusEntry: LogEntry = { + timestamp: Date.now(), + stream: "stderr", + text: `[${instance.status}] exit code: ${instance.exitCode}`, + }; + for (const sub of instance.subscribers) { + try { + sub(statusEntry); + } catch { + instance.subscribers.delete(sub); + } + } + } + + private toSummary(inst: AcpInstance): InstanceSummary { + return { + id: inst.id, + group: inst.group, + command: inst.command, + status: inst.status, + pid: inst.pid, + startTime: inst.startTime, + exitCode: inst.exitCode, + }; + } +} diff --git a/packages/acp-link/src/manager/routes.ts b/packages/acp-link/src/manager/routes.ts new file mode 100644 index 000000000..3b7d752e0 --- /dev/null +++ b/packages/acp-link/src/manager/routes.ts @@ -0,0 +1,153 @@ +import { Hono } from "hono"; +import type { ProcessManager } from "./manager.js"; +import { MANAGER_HTML } from "./html.js"; + +function logReq(method: string, path: string, status?: number) { + const ts = new Date().toISOString(); + const suffix = status != null ? ` -> ${status}` : ""; + console.log(`[${ts}] [http] ${method} ${path}${suffix}`); +} + +export function createApp(manager: ProcessManager): Hono { + const app = new Hono(); + + app.get("/", (c) => { + logReq("GET", "/", 200); + return c.html(MANAGER_HTML); + }); + + app.get("/api/instances", (c) => { + const list = manager.list(); + logReq("GET", "/api/instances", 200); + return c.json(list); + }); + + app.post("/api/instances", async (c) => { + let body: { group?: string; command?: string }; + try { + body = await c.req.json<{ group?: string; command?: string }>(); + } catch { + logReq("POST", "/api/instances", 400); + return c.json({ error: "invalid JSON body" }, 400); + } + if (!body.group?.trim() || !body.command?.trim()) { + logReq("POST", "/api/instances", 400); + return c.json({ error: "group and command are required" }, 400); + } + const instance = manager.create(body.group.trim(), body.command.trim()); + logReq("POST", `/api/instances group=${body.group}`, 201); + return c.json( + { + id: instance.id, + group: instance.group, + command: instance.command, + status: instance.status, + pid: instance.pid, + startTime: instance.startTime, + exitCode: instance.exitCode, + }, + 201, + ); + }); + + app.post("/api/instances/:id/stop", (c) => { + const id = c.req.param("id"); + const inst = manager.get(id); + if (!inst) { + logReq("POST", `/api/instances/${id.slice(0, 8)}/stop`, 404); + return c.json({ error: "not found" }, 404); + } + if (inst.status !== "running") { + logReq("POST", `/api/instances/${id.slice(0, 8)}/stop`, 400); + return c.json({ error: "not running" }, 400); + } + manager.stop(inst.id); + logReq("POST", `/api/instances/${id.slice(0, 8)}/stop`, 200); + return c.json({ ok: true }); + }); + + app.delete("/api/instances/:id", (c) => { + const id = c.req.param("id"); + const inst = manager.get(id); + if (!inst) { + logReq("DELETE", `/api/instances/${id.slice(0, 8)}`, 404); + return c.json({ error: "not found" }, 404); + } + if (inst.status === "running") { + logReq("DELETE", `/api/instances/${id.slice(0, 8)}`, 400); + return c.json({ error: "still running" }, 400); + } + manager.remove(inst.id); + logReq("DELETE", `/api/instances/${id.slice(0, 8)}`, 200); + return c.json({ ok: true }); + }); + + app.get("/api/instances/:id/logs", (c) => { + const id = c.req.param("id"); + const inst = manager.get(id); + if (!inst) { + logReq("GET", `/api/instances/${id.slice(0, 8)}/logs`, 404); + return c.json({ error: "not found" }, 404); + } + logReq("GET", `/api/instances/${id.slice(0, 8)}/logs SSE`); + + const stream = new ReadableStream({ + start(controller) { + const encoder = new TextEncoder(); + + const send = (data: string) => { + try { + controller.enqueue(encoder.encode(data)); + } catch { + // stream closed + } + }; + + // send historical logs + for (const log of inst.logs) { + send(`data: ${JSON.stringify(log)}\n\n`); + } + + // subscribe to new logs + const unsub = manager.subscribe(inst.id, (entry) => { + send(`data: ${JSON.stringify(entry)}\n\n`); + }); + + // keepalive every 15s + const keepalive = setInterval(() => { + send(": keepalive\n\n"); + }, 15000); + + const cleanup = () => { + unsub(); + clearInterval(keepalive); + logReq("SSE", `/api/instances/${id.slice(0, 8)}/logs closed`); + try { + controller.close(); + } catch { + // already closed + } + }; + + c.req.raw.signal.addEventListener("abort", cleanup, { once: true }); + }, + }); + + return new Response(stream, { + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + "X-Accel-Buffering": "no", + }, + }); + }); + + // Catch-all: log unmatched routes for debugging + app.all("*", (c) => { + logReq(c.req.method, c.req.path, 404); + return c.json({ error: "not found", path: c.req.path }, 404); + }); + + return app; +} diff --git a/packages/acp-link/src/manager/types.ts b/packages/acp-link/src/manager/types.ts new file mode 100644 index 000000000..b1984460c --- /dev/null +++ b/packages/acp-link/src/manager/types.ts @@ -0,0 +1,34 @@ +export type InstanceStatus = "running" | "stopped" | "failed"; + +export interface AcpInstance { + id: string; + group: string; + command: string; + status: InstanceStatus; + pid: number | undefined; + startTime: number; + exitCode: number | null; + logs: LogEntry[]; + subscribers: Set<(entry: LogEntry) => void>; +} + +export interface LogEntry { + timestamp: number; + stream: "stdout" | "stderr"; + text: string; +} + +export interface CreateInstanceRequest { + group: string; + command: string; +} + +export interface InstanceSummary { + id: string; + group: string; + command: string; + status: InstanceStatus; + pid: number | undefined; + startTime: number; + exitCode: number | null; +} diff --git a/packages/acp-link/src/server.ts b/packages/acp-link/src/server.ts index 421bb771e..22061915b 100644 --- a/packages/acp-link/src/server.ts +++ b/packages/acp-link/src/server.ts @@ -883,20 +883,16 @@ export async function startServer(config: ServerConfig): Promise { authEnabled: !!AUTH_TOKEN, }, "started"); + // Graceful shutdown — close RCS upstream + const shutdown = async () => { + if (rcsUpstream) { + await rcsUpstream.close(); + } + process.exit(0); + }; + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); + // Keep the server running await new Promise(() => {}); } - -// Graceful shutdown — close RCS upstream on process exit -process.on("SIGINT", async () => { - if (rcsUpstream) { - await rcsUpstream.close(); - } - process.exit(0); -}); -process.on("SIGTERM", async () => { - if (rcsUpstream) { - await rcsUpstream.close(); - } - process.exit(0); -}); diff --git a/packages/acp-link/tsconfig.json b/packages/acp-link/tsconfig.json index 7e355f77d..cff6d9cb6 100644 --- a/packages/acp-link/tsconfig.json +++ b/packages/acp-link/tsconfig.json @@ -3,12 +3,12 @@ // Environment setup & latest features "lib": ["ESNext"], "target": "ES2022", - "module": "NodeNext", + "module": "esnext", "moduleDetection": "force", "allowJs": true, // Node.js module resolution - "moduleResolution": "NodeNext", + "moduleResolution": "bundler", "verbatimModuleSyntax": true, // Output @@ -30,7 +30,8 @@ // Some stricter flags (disabled by default) "noUnusedLocals": false, "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false + "noPropertyAccessFromIndexSignature": false, + "types": ["bun"], }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "src/__tests__"]