fix: 修复类型问题(#267) (#271)

* fix: 修复 Bun 的 polyfill 问题

* fix: 类型修复完成

* feat: 统一所有包的类型文件

* fix: 修复构建问题
This commit is contained in:
claude-code-best
2026-04-15 10:54:00 +08:00
committed by GitHub
parent 2273a0bcfe
commit 1a4e9702c2
39 changed files with 187 additions and 131 deletions

View File

@@ -55,6 +55,8 @@ bun run health
# Check unused exports # Check unused exports
bun run check:unused bun run check:unused
bun run typecheck
# Remote Control Server # Remote Control Server
bun run rcs bun run rcs

View File

@@ -88,8 +88,27 @@ for (const file of files) {
} }
} }
// Also patch unguarded globalThis.Bun destructuring from third-party deps
// (e.g. @anthropic-ai/sandbox-runtime) so Node.js doesn't crash at import time.
let bunPatched = 0
const BUN_DESTRUCTURE = /var \{([^}]+)\} = globalThis\.Bun;?/g
const BUN_DESTRUCTURE_SAFE = 'var {$1} = typeof globalThis.Bun !== "undefined" ? globalThis.Bun : {};'
for (const file of files) {
if (!file.endsWith('.js')) continue
const filePath = join(outdir, file)
const content = await readFile(filePath, 'utf-8')
if (BUN_DESTRUCTURE.test(content)) {
await writeFile(
filePath,
content.replace(BUN_DESTRUCTURE, BUN_DESTRUCTURE_SAFE),
)
bunPatched++
}
}
BUN_DESTRUCTURE.lastIndex = 0
console.log( console.log(
`Bundled ${result.outputs.length} files to ${outdir}/ (patched ${patched} for Node.js compat)`, `Bundled ${result.outputs.length} files to ${outdir}/ (patched ${patched} for import.meta.require, ${bunPatched} for Bun destructure)`,
) )
// Step 4: Copy native .node addon files (audio-capture) // Step 4: Copy native .node addon files (audio-capture)
@@ -119,46 +138,7 @@ const cliNode = join(outdir, 'cli-node.js')
await writeFile(cliBun, '#!/usr/bin/env bun\nimport "./cli.js"\n') await writeFile(cliBun, '#!/usr/bin/env bun\nimport "./cli.js"\n')
// Node.js entry needs a Bun API polyfill because Bun.build({ target: 'bun' }) await writeFile(cliNode, '#!/usr/bin/env node\nimport "./cli.js"\n')
// emits globalThis.Bun references (e.g. Bun.$ shell tag in computer-use-input,
// Bun.which in chunk-ys6smqg9) that crash at import time under plain Node.js.
const NODE_BUN_POLYFILL = `#!/usr/bin/env node
// Bun API polyfill for Node.js runtime
if (typeof globalThis.Bun === "undefined") {
const { execFileSync } = await import("child_process");
const { resolve, delimiter } = await import("path");
const { accessSync, constants: { X_OK } } = await import("fs");
function which(bin) {
const isWin = process.platform === "win32";
const pathExt = isWin ? (process.env.PATHEXT || ".EXE").split(";") : [""];
for (const dir of (process.env.PATH || "").split(delimiter)) {
for (const ext of pathExt) {
const candidate = resolve(dir, bin + ext);
try { accessSync(candidate, X_OK); return candidate; } catch {}
}
}
return null;
}
// Bun.$ is the shell template tag (e.g. $\`osascript ...\`). Only used by
// computer-use-input/darwin — stub it so the top-level destructuring
// \`var { $ } = globalThis.Bun\` doesn't crash.
function $(parts, ...args) {
throw new Error("Bun.$ shell API is not available in Node.js. Use Bun runtime for this feature.");
}
function hash(data, seed) {
let h = ((seed || 0) ^ 0x811c9dc5) >>> 0;
for (let i = 0; i < data.length; i++) {
h ^= data.charCodeAt(i);
h = Math.imul(h, 0x01000193) >>> 0;
}
return h;
}
globalThis.Bun = { which, $, hash };
}
import "./cli.js"
`
await writeFile(cliNode, NODE_BUN_POLYFILL)
// NOTE: when new Bun-specific globals appear in bundled output, add them here.
// Make both executable // Make both executable
const { chmodSync } = await import('fs') const { chmodSync } = await import('fs')

View File

@@ -58,10 +58,11 @@
"@sentry/node": "^10.47.0", "@sentry/node": "^10.47.0",
"@smithy/core": "^3.23.13", "@smithy/core": "^3.23.13",
"@smithy/node-http-handler": "^4.5.1", "@smithy/node-http-handler": "^4.5.1",
"@types/bun": "^1.3.11", "@types/bun": "^1.3.12",
"@types/cacache": "^20.0.1", "@types/cacache": "^20.0.1",
"@types/he": "^1.2.3", "@types/he": "^1.2.3",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^25.6.0",
"@types/picomatch": "^4.0.3", "@types/picomatch": "^4.0.3",
"@types/plist": "^3.0.5", "@types/plist": "^3.0.5",
"@types/proper-lockfile": "^4.1.4", "@types/proper-lockfile": "^4.1.4",

View File

@@ -1,6 +1,6 @@
{ {
"name": "claude-code-best", "name": "claude-code-best",
"version": "1.3.6", "version": "1.3.7",
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal", "description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
"type": "module", "type": "module",
"author": "claude-code-best <claude-code-best@proton.me>", "author": "claude-code-best <claude-code-best@proton.me>",
@@ -52,6 +52,7 @@
"health": "bun run scripts/health-check.ts", "health": "bun run scripts/health-check.ts",
"postinstall": "node scripts/postinstall.cjs && node scripts/setup-chrome-mcp.mjs", "postinstall": "node scripts/postinstall.cjs && node scripts/setup-chrome-mcp.mjs",
"docs:dev": "npx mintlify dev", "docs:dev": "npx mintlify dev",
"typecheck": "tsc --noEmit",
"rcs": "bun run scripts/rcs.ts" "rcs": "bun run scripts/rcs.ts"
}, },
"dependencies": { "dependencies": {
@@ -59,10 +60,6 @@
"@claude-code-best/mcp-chrome-bridge": "^2.0.7" "@claude-code-best/mcp-chrome-bridge": "^2.0.7"
}, },
"devDependencies": { "devDependencies": {
"@types/he": "^1.2.3",
"@langfuse/otel": "^5.1.0",
"@langfuse/tracing": "^5.1.0",
"@types/lodash-es": "^4.17.12",
"@alcalzone/ansi-tokenize": "^0.3.0", "@alcalzone/ansi-tokenize": "^0.3.0",
"@ant/claude-for-chrome-mcp": "workspace:*", "@ant/claude-for-chrome-mcp": "workspace:*",
"@ant/computer-use-input": "workspace:*", "@ant/computer-use-input": "workspace:*",
@@ -76,9 +73,6 @@
"@anthropic-ai/sdk": "^0.80.0", "@anthropic-ai/sdk": "^0.80.0",
"@anthropic-ai/vertex-sdk": "^0.14.4", "@anthropic-ai/vertex-sdk": "^0.14.4",
"@anthropic/ink": "workspace:*", "@anthropic/ink": "workspace:*",
"@claude-code-best/builtin-tools": "workspace:*",
"@claude-code-best/agent-tools": "workspace:*",
"@claude-code-best/mcp-client": "workspace:*",
"@aws-sdk/client-bedrock": "^3.1020.0", "@aws-sdk/client-bedrock": "^3.1020.0",
"@aws-sdk/client-bedrock-runtime": "^3.1020.0", "@aws-sdk/client-bedrock-runtime": "^3.1020.0",
"@aws-sdk/client-sts": "^3.1020.0", "@aws-sdk/client-sts": "^3.1020.0",
@@ -86,8 +80,13 @@
"@aws-sdk/credential-providers": "^3.1020.0", "@aws-sdk/credential-providers": "^3.1020.0",
"@azure/identity": "^4.13.1", "@azure/identity": "^4.13.1",
"@biomejs/biome": "^2.4.10", "@biomejs/biome": "^2.4.10",
"@claude-code-best/agent-tools": "workspace:*",
"@claude-code-best/builtin-tools": "workspace:*",
"@claude-code-best/mcp-client": "workspace:*",
"@commander-js/extra-typings": "^14.0.0", "@commander-js/extra-typings": "^14.0.0",
"@growthbook/growthbook": "^1.6.5", "@growthbook/growthbook": "^1.6.5",
"@langfuse/otel": "^5.1.0",
"@langfuse/tracing": "^5.1.0",
"@modelcontextprotocol/sdk": "^1.29.0", "@modelcontextprotocol/sdk": "^1.29.0",
"@opentelemetry/api": "^1.9.1", "@opentelemetry/api": "^1.9.1",
"@opentelemetry/api-logs": "^0.214.0", "@opentelemetry/api-logs": "^0.214.0",
@@ -110,8 +109,11 @@
"@sentry/node": "^10.47.0", "@sentry/node": "^10.47.0",
"@smithy/core": "^3.23.13", "@smithy/core": "^3.23.13",
"@smithy/node-http-handler": "^4.5.1", "@smithy/node-http-handler": "^4.5.1",
"@types/bun": "^1.3.11", "@types/bun": "^1.3.12",
"@types/cacache": "^20.0.1", "@types/cacache": "^20.0.1",
"@types/he": "^1.2.3",
"@types/lodash-es": "^4.17.12",
"@types/node": "^25.6.0",
"@types/picomatch": "^4.0.3", "@types/picomatch": "^4.0.3",
"@types/plist": "^3.0.5", "@types/plist": "^3.0.5",
"@types/proper-lockfile": "^4.1.4", "@types/proper-lockfile": "^4.1.4",

View File

@@ -0,0 +1,5 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -5,9 +5,12 @@
* mouse and keyboard via CoreGraphics events and System Events. * mouse and keyboard via CoreGraphics events and System Events.
*/ */
import { $ } from 'bun' import { execFile, execFileSync } from 'child_process'
import { promisify } from 'util'
import type { FrontmostAppInfo, InputBackend } from '../types.js' import type { FrontmostAppInfo, InputBackend } from '../types.js'
const execFileAsync = promisify(execFile)
const KEY_MAP: Record<string, number> = { const KEY_MAP: Record<string, number> = {
return: 36, enter: 36, tab: 48, space: 49, delete: 51, backspace: 51, return: 36, enter: 36, tab: 48, space: 49, delete: 51, backspace: 51,
escape: 53, esc: 53, escape: 53, esc: 53,
@@ -25,13 +28,17 @@ const MODIFIER_MAP: Record<string, string> = {
} }
async function osascript(script: string): Promise<string> { async function osascript(script: string): Promise<string> {
const result = await $`osascript -e ${script}`.quiet().nothrow().text() const { stdout } = await execFileAsync('osascript', ['-e', script], {
return result.trim() encoding: 'utf-8',
})
return stdout.trim()
} }
async function jxa(script: string): Promise<string> { async function jxa(script: string): Promise<string> {
const result = await $`osascript -l JavaScript -e ${script}`.quiet().nothrow().text() const { stdout } = await execFileAsync('osascript', ['-l', 'JavaScript', '-e', script], {
return result.trim() encoding: 'utf-8',
})
return stdout.trim()
} }
function buildMouseJxa(eventType: string, x: number, y: number, btn: number, clickState?: number): string { function buildMouseJxa(eventType: string, x: number, y: number, btn: number, clickState?: number): string {
@@ -115,19 +122,14 @@ export const typeText: InputBackend['typeText'] = async (text) => {
export const getFrontmostAppInfo: InputBackend['getFrontmostAppInfo'] = () => { export const getFrontmostAppInfo: InputBackend['getFrontmostAppInfo'] = () => {
try { try {
const result = Bun.spawnSync({ const output = execFileSync('osascript', ['-e', `
cmd: ['osascript', '-e', ` tell application "System Events"
tell application "System Events" set frontApp to first application process whose frontmost is true
set frontApp to first application process whose frontmost is true set appName to name of frontApp
set appName to name of frontApp set bundleId to bundle identifier of frontApp
set bundleId to bundle identifier of frontApp return bundleId & "|" & appName
return bundleId & "|" & appName end tell
end tell `], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim()
`],
stdout: 'pipe',
stderr: 'pipe',
})
const output = new TextDecoder().decode(result.stdout).trim()
if (!output || !output.includes('|')) return null if (!output || !output.includes('|')) return null
const [bundleId, appName] = output.split('|', 2) const [bundleId, appName] = output.split('|', 2)
return { bundleId: bundleId!, appName: appName! } return { bundleId: bundleId!, appName: appName! }

View File

@@ -0,0 +1,5 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,5 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -274,4 +274,9 @@ export const screenshot: ScreenshotAPI = {
if (displayId !== undefined) args.push('-D', String(displayId)) if (displayId !== undefined) args.push('-D', String(displayId))
return captureScreenToBase64(args) return captureScreenToBase64(args)
}, },
captureWindowTarget(_titleOrHwnd: string | number): ScreenshotResult | null {
// Window capture not supported on macOS via this backend
return null
},
} }

View File

@@ -275,4 +275,9 @@ export const screenshot: ScreenshotAPI = {
return { base64: '', width: 0, height: 0 } return { base64: '', width: 0, height: 0 }
} }
}, },
captureWindowTarget(_titleOrHwnd: string | number): ScreenshotResult | null {
// Window capture not supported on Linux via this backend
return null
},
} }

View File

@@ -76,6 +76,7 @@ export interface ScreenshotAPI {
x: number, y: number, w: number, h: number, x: number, y: number, w: number, h: number,
outW: number, outH: number, quality: number, displayId?: number, outW: number, outH: number, quality: number, displayId?: number,
): Promise<ScreenshotResult> ): Promise<ScreenshotResult>
captureWindowTarget(titleOrHwnd: string | number): ScreenshotResult | null
} }
export interface SwiftBackend { export interface SwiftBackend {

View File

@@ -0,0 +1,5 @@
{
"extends": "../../../tsconfig.json",
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,5 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -1,6 +1,6 @@
import { describe, expect, test } from 'bun:test' import { describe, expect, test } from 'bun:test'
import type { CoreTool, Tool, Tools, AnyObject, ToolResult, ValidationResult, PermissionResult } from '@claude-code-best/agent-tools' import type { CoreTool, Tool, Tools, AnyObject, ToolResult, ValidationResult, PermissionResult } from '@claude-code-best/agent-tools'
import type { Tool as HostTool } from '../../src/Tool.js' import type { Tool as HostTool } from '../../../../src/Tool.js'
describe('agent-tools compatibility', () => { describe('agent-tools compatibility', () => {
test('CoreTool structural compatibility with host Tool', () => { test('CoreTool structural compatibility with host Tool', () => {
@@ -27,7 +27,7 @@ describe('agent-tools compatibility', () => {
} }
// This assignment should work if HostTool structurally extends CoreTool // This assignment should work if HostTool structurally extends CoreTool
const coreTool: CoreTool = mockHostTool as CoreTool const coreTool: CoreTool = mockHostTool as unknown as CoreTool
expect(coreTool.name).toBe('test') expect(coreTool.name).toBe('test')
expect(coreTool.isEnabled()).toBe(true) expect(coreTool.isEnabled()).toBe(true)
}) })

View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -72,18 +72,18 @@ describe("detectColorMode", () => {
describe("detectLanguage", () => { describe("detectLanguage", () => {
test("detects language from file extension", () => { test("detects language from file extension", () => {
expect(detectLanguage("index.ts")).toBe("ts"); expect(detectLanguage("index.ts", null)).toBe("ts");
expect(detectLanguage("main.py")).toBe("py"); expect(detectLanguage("main.py", null)).toBe("py");
expect(detectLanguage("style.css")).toBe("css"); expect(detectLanguage("style.css", null)).toBe("css");
}); });
test("detects language from known filenames", () => { test("detects language from known filenames", () => {
expect(detectLanguage("Makefile")).toBe("makefile"); expect(detectLanguage("Makefile", null)).toBe("makefile");
expect(detectLanguage("Dockerfile")).toBe("dockerfile"); expect(detectLanguage("Dockerfile", null)).toBe("dockerfile");
}); });
test("returns null for unknown extensions", () => { test("returns null for unknown extensions", () => {
expect(detectLanguage("file.xyz123")).toBeNull(); expect(detectLanguage("file.xyz123", null)).toBeNull();
}); });
}); });

View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -38,7 +38,7 @@ describe('InProcessTransport', () => {
let received: JSONRPCMessage | null = null let received: JSONRPCMessage | null = null
client.onmessage = (msg) => { received = msg } client.onmessage = (msg) => { received = msg }
await server.send({ jsonrpc: '2.0', result: 42, id: 1 }) await server.send({ jsonrpc: '2.0', result: 42, id: 1 } as any)
await new Promise(resolve => setTimeout(resolve, 10)) await new Promise(resolve => setTimeout(resolve, 10))

View File

@@ -57,9 +57,9 @@ describe('discoverTools', () => {
expect(tool.name).toBe('mcp__my-server__search') expect(tool.name).toBe('mcp__my-server__search')
expect(tool.mcpInfo).toEqual({ serverName: 'my-server', toolName: 'search' }) expect(tool.mcpInfo).toEqual({ serverName: 'my-server', toolName: 'search' })
expect(tool.isMcp).toBe(true) expect(tool.isMcp).toBe(true)
expect(tool.isReadOnly()).toBe(true) expect(tool.isReadOnly({} as any)).toBe(true)
expect(tool.userFacingName()).toBe('Search Items') expect(tool.userFacingName(undefined)).toBe('Search Items')
expect(await tool.description()).toBe('Search for items') expect(await tool.description({} as any, { isNonInteractiveSession: false, toolPermissionContext: {}, tools: [] })).toBe('Search for items')
}) })
test('respects skipPrefix option', async () => { test('respects skipPrefix option', async () => {

View File

@@ -65,7 +65,7 @@ describe('createMcpManager', () => {
const result = await manager.connect('test-server', { command: 'npx', args: [] }) const result = await manager.connect('test-server', { command: 'npx', args: [] })
expect(result.type).toBe('connected') expect(result.type).toBe('connected')
expect(connectedEvent).toBe('test-server') expect(connectedEvent as unknown as string).toBe('test-server')
}) })
test('disconnect calls cleanup and emits disconnected', async () => { test('disconnect calls cleanup and emits disconnected', async () => {

View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -75,7 +75,7 @@ describe("Disconnect Monitor Logic", () => {
}); });
test("session becomes inactive when updatedAt is too old", () => { test("session becomes inactive when updatedAt is too old", () => {
const session = storeCreateSession({ status: "idle" }); const session = storeCreateSession({});
storeUpdateSession(session.id, { status: "running" }); storeUpdateSession(session.id, { status: "running" });
const timeoutMs = 300 * 1000 * 2; // 2x disconnect timeout const timeoutMs = 300 * 1000 * 2; // 2x disconnect timeout

View File

@@ -14,14 +14,14 @@ app.post("/bridge", acceptCliHeaders, apiKeyAuth, async (c) => {
/** DELETE /v1/environments/bridge/:id — Deregister */ /** DELETE /v1/environments/bridge/:id — Deregister */
app.delete("/bridge/:id", acceptCliHeaders, apiKeyAuth, async (c) => { app.delete("/bridge/:id", acceptCliHeaders, apiKeyAuth, async (c) => {
const envId = c.req.param("id"); const envId = c.req.param("id")!;
deregisterEnvironment(envId); deregisterEnvironment(envId);
return c.json({ status: "ok" }, 200); return c.json({ status: "ok" }, 200);
}); });
/** POST /v1/environments/:id/bridge/reconnect — Reconnect */ /** POST /v1/environments/:id/bridge/reconnect — Reconnect */
app.post("/:id/bridge/reconnect", acceptCliHeaders, apiKeyAuth, async (c) => { app.post("/:id/bridge/reconnect", acceptCliHeaders, apiKeyAuth, async (c) => {
const envId = c.req.param("id"); const envId = c.req.param("id")!;
reconnectEnvironment(envId); reconnectEnvironment(envId);
const { reconnectWorkForEnvironment } = await import("../../services/work-dispatch"); const { reconnectWorkForEnvironment } = await import("../../services/work-dispatch");
await reconnectWorkForEnvironment(envId); await reconnectWorkForEnvironment(envId);

View File

@@ -7,7 +7,7 @@ const app = new Hono();
/** GET /v1/environments/:id/work/poll — Long-poll for work */ /** GET /v1/environments/:id/work/poll — Long-poll for work */
app.get("/:id/work/poll", acceptCliHeaders, apiKeyAuth, async (c) => { app.get("/:id/work/poll", acceptCliHeaders, apiKeyAuth, async (c) => {
const envId = c.req.param("id"); const envId = c.req.param("id")!;
updatePollTime(envId); updatePollTime(envId);
const result = await pollWork(envId); const result = await pollWork(envId);
if (!result) { if (!result) {
@@ -19,21 +19,21 @@ app.get("/:id/work/poll", acceptCliHeaders, apiKeyAuth, async (c) => {
/** POST /v1/environments/:id/work/:workId/ack — Acknowledge work */ /** POST /v1/environments/:id/work/:workId/ack — Acknowledge work */
app.post("/:id/work/:workId/ack", acceptCliHeaders, apiKeyAuth, async (c) => { app.post("/:id/work/:workId/ack", acceptCliHeaders, apiKeyAuth, async (c) => {
const workId = c.req.param("workId"); const workId = c.req.param("workId")!;
ackWork(workId); ackWork(workId);
return c.json({ status: "ok" }, 200); return c.json({ status: "ok" }, 200);
}); });
/** POST /v1/environments/:id/work/:workId/stop — Stop work */ /** POST /v1/environments/:id/work/:workId/stop — Stop work */
app.post("/:id/work/:workId/stop", acceptCliHeaders, apiKeyAuth, async (c) => { app.post("/:id/work/:workId/stop", acceptCliHeaders, apiKeyAuth, async (c) => {
const workId = c.req.param("workId"); const workId = c.req.param("workId")!;
stopWork(workId); stopWork(workId);
return c.json({ status: "ok" }, 200); return c.json({ status: "ok" }, 200);
}); });
/** POST /v1/environments/:id/work/:workId/heartbeat — Heartbeat */ /** POST /v1/environments/:id/work/:workId/heartbeat — Heartbeat */
app.post("/:id/work/:workId/heartbeat", acceptCliHeaders, apiKeyAuth, async (c) => { app.post("/:id/work/:workId/heartbeat", acceptCliHeaders, apiKeyAuth, async (c) => {
const workId = c.req.param("workId"); const workId = c.req.param("workId")!;
const result = heartbeatWork(workId); const result = heartbeatWork(workId);
return c.json(result, 200); return c.json(result, 200);
}); });

View File

@@ -38,7 +38,7 @@ app.post("/", acceptCliHeaders, apiKeyAuth, async (c) => {
/** GET /v1/sessions/:id — Get session */ /** GET /v1/sessions/:id — Get session */
app.get("/:id", acceptCliHeaders, apiKeyAuth, async (c) => { app.get("/:id", acceptCliHeaders, apiKeyAuth, async (c) => {
const session = getSession(c.req.param("id")); const session = getSession(c.req.param("id")!);
if (!session) { if (!session) {
return c.json({ error: { type: "not_found", message: "Session not found" } }, 404); return c.json({ error: { type: "not_found", message: "Session not found" } }, 404);
} }
@@ -49,16 +49,16 @@ app.get("/:id", acceptCliHeaders, apiKeyAuth, async (c) => {
app.patch("/:id", acceptCliHeaders, apiKeyAuth, async (c) => { app.patch("/:id", acceptCliHeaders, apiKeyAuth, async (c) => {
const body = await c.req.json(); const body = await c.req.json();
if (body.title) { if (body.title) {
updateSessionTitle(c.req.param("id"), body.title); updateSessionTitle(c.req.param("id")!, body.title);
} }
const session = getSession(c.req.param("id")); const session = getSession(c.req.param("id")!);
return c.json(session, 200); return c.json(session, 200);
}); });
/** POST /v1/sessions/:id/archive — Archive session */ /** POST /v1/sessions/:id/archive — Archive session */
app.post("/:id/archive", acceptCliHeaders, apiKeyAuth, async (c) => { app.post("/:id/archive", acceptCliHeaders, apiKeyAuth, async (c) => {
try { try {
archiveSession(c.req.param("id")); archiveSession(c.req.param("id")!);
} catch { } catch {
return c.json({ status: "ok" }, 409); return c.json({ status: "ok" }, 409);
} }
@@ -67,7 +67,7 @@ app.post("/:id/archive", acceptCliHeaders, apiKeyAuth, async (c) => {
/** POST /v1/sessions/:id/events — Send event to session */ /** POST /v1/sessions/:id/events — Send event to session */
app.post("/:id/events", acceptCliHeaders, apiKeyAuth, async (c) => { app.post("/:id/events", acceptCliHeaders, apiKeyAuth, async (c) => {
const sessionId = c.req.param("id"); const sessionId = c.req.param("id")!;
const body = await c.req.json(); const body = await c.req.json();
const events = body.events const events = body.events

View File

@@ -15,7 +15,7 @@ app.post("/", acceptCliHeaders, apiKeyAuth, async (c) => {
/** POST /v1/code/sessions/:id/bridge — Get connection info + worker JWT */ /** POST /v1/code/sessions/:id/bridge — Get connection info + worker JWT */
app.post("/:id/bridge", acceptCliHeaders, apiKeyAuth, async (c) => { app.post("/:id/bridge", acceptCliHeaders, apiKeyAuth, async (c) => {
const sessionId = c.req.param("id"); const sessionId = c.req.param("id")!;
const session = getSession(sessionId); const session = getSession(sessionId);
if (!session) { if (!session) {
return c.json({ error: { type: "not_found", message: "Session not found" } }, 404); return c.json({ error: { type: "not_found", message: "Session not found" } }, 404);

View File

@@ -7,7 +7,7 @@ const app = new Hono();
/** SSE /v1/code/sessions/:id/worker/events/stream — SSE event stream */ /** SSE /v1/code/sessions/:id/worker/events/stream — SSE event stream */
app.get("/:id/worker/events/stream", acceptCliHeaders, sessionIngressAuth, async (c) => { app.get("/:id/worker/events/stream", acceptCliHeaders, sessionIngressAuth, async (c) => {
const sessionId = c.req.param("id"); const sessionId = c.req.param("id")!;
const session = getSession(sessionId); const session = getSession(sessionId);
if (!session) { if (!session) {
return c.json({ error: { type: "not_found", message: "Session not found" } }, 404); return c.json({ error: { type: "not_found", message: "Session not found" } }, 404);

View File

@@ -7,7 +7,7 @@ const app = new Hono();
/** POST /v1/code/sessions/:id/worker/events — Write events */ /** POST /v1/code/sessions/:id/worker/events — Write events */
app.post("/:id/worker/events", acceptCliHeaders, sessionIngressAuth, async (c) => { app.post("/:id/worker/events", acceptCliHeaders, sessionIngressAuth, async (c) => {
const sessionId = c.req.param("id"); const sessionId = c.req.param("id")!;
const body = await c.req.json(); const body = await c.req.json();
const events = Array.isArray(body) ? body : [body]; const events = Array.isArray(body) ? body : [body];
@@ -22,7 +22,7 @@ app.post("/:id/worker/events", acceptCliHeaders, sessionIngressAuth, async (c) =
/** PUT /v1/code/sessions/:id/worker/state — Report worker state */ /** PUT /v1/code/sessions/:id/worker/state — Report worker state */
app.put("/:id/worker/state", acceptCliHeaders, sessionIngressAuth, async (c) => { app.put("/:id/worker/state", acceptCliHeaders, sessionIngressAuth, async (c) => {
const sessionId = c.req.param("id"); const sessionId = c.req.param("id")!;
const body = await c.req.json(); const body = await c.req.json();
if (body.status) { if (body.status) {

View File

@@ -6,7 +6,7 @@ const app = new Hono();
/** POST /v1/code/sessions/:id/worker/register — Register worker */ /** POST /v1/code/sessions/:id/worker/register — Register worker */
app.post("/:id/worker/register", acceptCliHeaders, apiKeyAuth, async (c) => { app.post("/:id/worker/register", acceptCliHeaders, apiKeyAuth, async (c) => {
const sessionId = c.req.param("id"); const sessionId = c.req.param("id")!;
const session = getSession(sessionId); const session = getSession(sessionId);
if (!session) { if (!session) {
return c.json({ error: { type: "not_found", message: "Session not found" } }, 404); return c.json({ error: { type: "not_found", message: "Session not found" } }, 404);

View File

@@ -8,7 +8,7 @@ import { storeIsSessionOwner } from "../../store";
const app = new Hono(); const app = new Hono();
function checkOwnership(c: { get: (key: string) => string | undefined }, sessionId: string) { function checkOwnership(c: { get: (key: string) => string | undefined }, sessionId: string) {
const uuid = c.get("uuid"); const uuid = c.get("uuid")!;
if (!storeIsSessionOwner(sessionId, uuid)) { if (!storeIsSessionOwner(sessionId, uuid)) {
return { error: true, session: null }; return { error: true, session: null };
} }

View File

@@ -11,7 +11,7 @@ const app = new Hono();
/** POST /web/sessions — Create a session from web UI */ /** POST /web/sessions — Create a session from web UI */
app.post("/sessions", uuidAuth, async (c) => { app.post("/sessions", uuidAuth, async (c) => {
const uuid = c.get("uuid"); const uuid = c.get("uuid")!;
const body = await c.req.json(); const body = await c.req.json();
const session = createSession({ const session = createSession({
environment_id: body.environment_id || null, environment_id: body.environment_id || null,
@@ -37,21 +37,21 @@ app.post("/sessions", uuidAuth, async (c) => {
/** GET /web/sessions — List sessions owned by the requesting UUID */ /** GET /web/sessions — List sessions owned by the requesting UUID */
app.get("/sessions", uuidAuth, async (c) => { app.get("/sessions", uuidAuth, async (c) => {
const uuid = c.get("uuid"); const uuid = c.get("uuid")!;
const sessions = storeListSessionsByOwnerUuid(uuid); const sessions = storeListSessionsByOwnerUuid(uuid);
return c.json(sessions, 200); return c.json(sessions, 200);
}); });
/** GET /web/sessions/all — List sessions owned by the requesting UUID (unowned sessions excluded) */ /** GET /web/sessions/all — List sessions owned by the requesting UUID (unowned sessions excluded) */
app.get("/sessions/all", uuidAuth, async (c) => { app.get("/sessions/all", uuidAuth, async (c) => {
const uuid = c.get("uuid"); const uuid = c.get("uuid")!;
const sessions = listSessionSummariesByOwnerUuid(uuid); const sessions = listSessionSummariesByOwnerUuid(uuid);
return c.json(sessions, 200); return c.json(sessions, 200);
}); });
/** GET /web/sessions/:id — Session detail */ /** GET /web/sessions/:id — Session detail */
app.get("/sessions/:id", uuidAuth, async (c) => { app.get("/sessions/:id", uuidAuth, async (c) => {
const uuid = c.get("uuid"); const uuid = c.get("uuid")!;
const sessionId = c.req.param("id")!; const sessionId = c.req.param("id")!;
if (!storeIsSessionOwner(sessionId, uuid)) { if (!storeIsSessionOwner(sessionId, uuid)) {
return c.json({ error: { type: "forbidden", message: "Not your session" } }, 403); return c.json({ error: { type: "forbidden", message: "Not your session" } }, 403);
@@ -65,7 +65,7 @@ app.get("/sessions/:id", uuidAuth, async (c) => {
/** GET /web/sessions/:id/history — Historical events for session */ /** GET /web/sessions/:id/history — Historical events for session */
app.get("/sessions/:id/history", uuidAuth, async (c) => { app.get("/sessions/:id/history", uuidAuth, async (c) => {
const uuid = c.get("uuid"); const uuid = c.get("uuid")!;
const sessionId = c.req.param("id")!; const sessionId = c.req.param("id")!;
if (!storeIsSessionOwner(sessionId, uuid)) { if (!storeIsSessionOwner(sessionId, uuid)) {
return c.json({ error: { type: "forbidden", message: "Not your session" } }, 403); return c.json({ error: { type: "forbidden", message: "Not your session" } }, 403);
@@ -82,7 +82,7 @@ app.get("/sessions/:id/history", uuidAuth, async (c) => {
/** SSE /web/sessions/:id/events — Real-time event stream */ /** SSE /web/sessions/:id/events — Real-time event stream */
app.get("/sessions/:id/events", uuidAuth, async (c) => { app.get("/sessions/:id/events", uuidAuth, async (c) => {
const uuid = c.get("uuid"); const uuid = c.get("uuid")!;
const sessionId = c.req.param("id")!; const sessionId = c.req.param("id")!;
if (!storeIsSessionOwner(sessionId, uuid)) { if (!storeIsSessionOwner(sessionId, uuid)) {
return c.json({ error: { type: "forbidden", message: "Not your session" } }, 403); return c.json({ error: { type: "forbidden", message: "Not your session" } }, 403);

View File

@@ -1,17 +1,5 @@
{ {
"compilerOptions": { "extends": "../../tsconfig.base.json",
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": ".",
"declaration": true,
"resolveJsonModule": true,
"types": ["bun-types"]
},
"include": ["src/**/*.ts"], "include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist", "web"] "exclude": ["node_modules", "dist", "web"]
} }

View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}

15
tsconfig.base.json Normal file
View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"jsx": "react-jsx",
"types": ["bun", "@types/node"]
}
}

View File

@@ -1,22 +1,12 @@
{ {
"extends": "./tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"types": ["bun"],
"paths": { "paths": {
"src/*": ["./src/*"], "src/*": ["./src/*"],
"@claude-code-best/builtin-tools/*": ["./packages/builtin-tools/src/*"], "@claude-code-best/builtin-tools/*": ["./packages/builtin-tools/src/*"],
"@claude-code-best/builtin-tools": ["./packages/builtin-tools/src/index.ts"] "@claude-code-best/builtin-tools": ["./packages/builtin-tools/src/index.ts"]
} }
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "packages/builtin-tools/src/**/*.ts", "packages/builtin-tools/src/**/*.tsx"], "include": ["src/**/*.ts", "src/**/*.tsx", "packages/**/*.ts", "packages/**/*.tsx"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }