getgloss 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -14
- package/dist/cli/index.js +151 -21
- package/dist/cli/index.js.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/server/daemon.js +15 -2
- package/dist/server/daemon.js.map +1 -1
- package/dist/web/CNAME +1 -0
- package/dist/web/assets/index-BQXNNRgM.css +1 -0
- package/dist/web/assets/index-xfsP7fu_.js +186 -0
- package/dist/web/index.html +27 -4
- package/dist/web/install.sh +10 -0
- package/dist/web/logo.svg +14 -0
- package/dist/web/og.png +0 -0
- package/dist/web/prompt.md +5 -4
- package/dist/web/setup/index.html +279 -0
- package/dist/web/setup.md +24 -2
- package/dist/web/skill/SKILL.md +22 -0
- package/package.json +1 -1
- package/skill/SKILL.md +11 -7
- package/dist/web/assets/index-BiGi3rBS.css +0 -1
- package/dist/web/assets/index-GpOF1p41.js +0 -149
package/dist/mcp/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/mcp/index.ts","../../src/cli/lifecycle.ts","../../src/shared/paths.ts","../../package.json","../../src/cli/server-client.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod/v4';\nimport { ensureServer, serverUrl } from '../cli/lifecycle';\nimport { ServerClient } from '../cli/server-client';\nimport { packageVersion } from '../shared/paths';\n\nfunction textResult(value: unknown) {\n return {\n content: [\n {\n type: 'text' as const,\n text: typeof value === 'string' ? value : JSON.stringify(value, null, 2)\n }\n ]\n };\n}\n\nasync function client(): Promise<ServerClient> {\n const info = await ensureServer();\n return new ServerClient(serverUrl(info));\n}\n\nexport async function startMcpServer(): Promise<void> {\n const server = new McpServer({\n name: 'gloss',\n version: packageVersion\n });\n\n server.registerTool(\n 'list_pending_reviews',\n {\n title: 'List pending Gloss reviews',\n description: 'List pending local Gloss review sessions.'\n },\n async () => {\n const api = await client();\n const { reviews } = await api.listReviews();\n return textResult({ reviews: reviews.filter((review) => review.status === 'pending') });\n }\n );\n\n server.registerTool(\n 'get_review',\n {\n title: 'Get Gloss review',\n description: 'Fetch review metadata and diff payload.',\n inputSchema: { id: z.string() }\n },\n async ({ id }) => textResult(await (await client()).getReview(id))\n );\n\n server.registerTool(\n 'watch_review',\n {\n title: 'Watch Gloss review',\n description: 'Block until a review completes, then return feedback.',\n inputSchema: {\n id: z.string(),\n timeout: z.number().optional()\n }\n },\n async ({ id, timeout }) => {\n const api = await client();\n await api.watchReview(id, timeout);\n return textResult(await api.getFeedback(id));\n }\n );\n\n server.registerTool(\n 'get_review_feedback',\n {\n title: 'Get Gloss review feedback',\n description: 'Fetch completed review feedback.',\n inputSchema: { id: z.string() }\n },\n async ({ id }) => textResult(await (await client()).getFeedback(id))\n );\n\n server.registerTool(\n 'mark_review_resolved',\n {\n title: 'Mark Gloss review resolved',\n description: 'Write a resolved marker for a completed review.',\n inputSchema: {\n id: z.string(),\n summary: z.string().optional()\n }\n },\n async ({ id, summary }) => textResult(await (await client()).markResolved(id, summary))\n );\n\n await server.connect(new StdioServerTransport());\n}\n","import { spawn } from 'node:child_process';\nimport { existsSync, openSync } from 'node:fs';\nimport { readFile, rm, writeFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport getPort from 'get-port';\nimport {\n ensureDir,\n globalLogDir,\n globalServerFile,\n globalServerLogFile,\n globalStateDir,\n packageVersion\n} from '../shared/paths';\nimport type { ServerInfo } from '../shared/types';\nimport { ServerClient } from './server-client';\n\nexport async function readServerInfo(): Promise<ServerInfo | null> {\n try {\n return JSON.parse(await readFile(globalServerFile(), 'utf8')) as ServerInfo;\n } catch {\n return null;\n }\n}\n\nexport function serverUrl(info: Pick<ServerInfo, 'port'>): string {\n return `http://localhost:${info.port}`;\n}\n\nexport async function isServerResponsive(info: ServerInfo): Promise<boolean> {\n if (!isPidAlive(info.pid)) {\n return false;\n }\n try {\n const health = await new ServerClient(serverUrl(info)).health();\n return health.ok === true;\n } catch {\n return false;\n }\n}\n\nexport async function ensureServer(options: { port?: number } = {}): Promise<ServerInfo> {\n const existing = await readServerInfo();\n if (existing && (await isServerResponsive(existing))) {\n return existing;\n }\n return startServer(options);\n}\n\nexport async function startServer(options: { port?: number } = {}): Promise<ServerInfo> {\n const existing = await readServerInfo();\n if (existing && (await isServerResponsive(existing))) {\n return existing;\n }\n\n await ensureDir(globalStateDir());\n await ensureDir(globalLogDir());\n const port = options.port ?? (await getPort());\n const daemonPath = fileURLToPath(new URL('../server/daemon.js', import.meta.url));\n if (!existsSync(daemonPath)) {\n throw new Error(`Cannot find server daemon at ${daemonPath}. Run pnpm build first.`);\n }\n\n const logFd = openSync(globalServerLogFile(), 'a');\n const child = spawn(process.execPath, [daemonPath], {\n detached: true,\n env: {\n ...process.env,\n GLOSS_PORT: String(port),\n GLOSS_STATE_DIR: globalStateDir()\n },\n stdio: ['ignore', logFd, logFd]\n });\n child.unref();\n\n const info: ServerInfo = {\n pid: child.pid ?? -1,\n port,\n version: packageVersion,\n startedAt: new Date().toISOString(),\n stateDir: globalStateDir()\n };\n await writeFile(globalServerFile(), `${JSON.stringify(info, null, 2)}\\n`);\n\n const deadline = Date.now() + 8000;\n while (Date.now() < deadline) {\n if (await isServerResponsive(info)) {\n return info;\n }\n await new Promise((resolve) => setTimeout(resolve, 150));\n }\n\n throw new Error(`Server did not become responsive. See ${globalServerLogFile()}`);\n}\n\nexport async function stopServer(): Promise<{ stopped: boolean; info: ServerInfo | null }> {\n const info = await readServerInfo();\n if (!info) {\n return { stopped: false, info: null };\n }\n\n if (isPidAlive(info.pid)) {\n process.kill(info.pid, 'SIGTERM');\n }\n await rm(globalServerFile(), { force: true });\n return { stopped: true, info };\n}\n\nexport async function writeServerInfo(info: ServerInfo): Promise<void> {\n await ensureDir(globalStateDir());\n await writeFile(globalServerFile(), `${JSON.stringify(info, null, 2)}\\n`);\n}\n\nexport function isPidAlive(pid: number): boolean {\n if (pid <= 0) {\n return false;\n }\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n","import { mkdir } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport path from 'node:path';\nimport packageJson from '../../package.json';\n\nexport const packageVersion = packageJson.version;\n\nexport function expandHome(input: string): string {\n if (input === '~') {\n return homedir();\n }\n if (input.startsWith('~/')) {\n return path.join(homedir(), input.slice(2));\n }\n return input;\n}\n\nexport function globalStateDir(): string {\n return expandHome(process.env.GLOSS_STATE_DIR ?? '~/.gloss');\n}\n\nexport function globalServerFile(): string {\n return path.join(globalStateDir(), 'server.json');\n}\n\nexport function globalLogDir(): string {\n return path.join(globalStateDir(), 'logs');\n}\n\nexport function globalServerLogFile(): string {\n return path.join(globalLogDir(), 'server.log');\n}\n\nexport function repoGlossDir(cwd: string): string {\n return path.join(cwd, '.gloss');\n}\n\nexport function reviewsDir(cwd: string): string {\n return path.join(repoGlossDir(cwd), 'reviews');\n}\n\nexport function reviewDir(cwd: string, reviewId: string): string {\n return path.join(reviewsDir(cwd), reviewId);\n}\n\nexport async function ensureDir(dir: string): Promise<void> {\n await mkdir(dir, { recursive: true });\n}\n","{\n \"name\": \"getgloss\",\n \"version\": \"0.1.2\",\n \"description\": \"Local browser-based diff review for coding-agent loops.\",\n \"type\": \"module\",\n \"packageManager\": \"pnpm@10.33.2\",\n \"bin\": {\n \"getgloss\": \"./dist/cli/index.js\",\n \"gloss\": \"./dist/cli/index.js\"\n },\n \"files\": [\n \"dist\",\n \"skill\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"pnpm build:web && pnpm build:node\",\n \"build:web\": \"vite build\",\n \"build:node\": \"tsup\",\n \"check\": \"biome check .\",\n \"format\": \"biome format --write .\",\n \"prepack\": \"pnpm build\",\n \"dev:web\": \"vite --host 127.0.0.1\",\n \"setup\": \"tsx scripts/dev-cli.ts\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\"\n },\n \"engines\": {\n \"node\": \">=20\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.14.4\",\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"@pierre/diffs\": \"^1.2.1\",\n \"@tailwindcss/vite\": \"^4.1.7\",\n \"commander\": \"^14.0.0\",\n \"execa\": \"^9.5.3\",\n \"get-port\": \"^7.1.0\",\n \"hono\": \"^4.7.10\",\n \"lucide-react\": \"^1.16.0\",\n \"open\": \"^10.1.2\",\n \"react\": \"^19.1.0\",\n \"react-dom\": \"^19.1.0\",\n \"ulid\": \"^3.0.0\",\n \"zod\": \"^4.4.3\",\n \"zustand\": \"^5.0.5\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.0.6\",\n \"@types/node\": \"^24.0.1\",\n \"@types/react\": \"^19.1.6\",\n \"@types/react-dom\": \"^19.1.5\",\n \"@vitejs/plugin-react\": \"^4.5.2\",\n \"playwright\": \"^1.52.0\",\n \"tsup\": \"^8.5.0\",\n \"tsx\": \"^4.20.3\",\n \"typescript\": \"^5.8.3\",\n \"vite\": \"^6.3.5\",\n \"vitest\": \"^3.2.3\"\n },\n \"keywords\": [\n \"diff\",\n \"review\",\n \"coding-agents\",\n \"mcp\"\n ],\n \"author\": \"Raj Joshi\",\n \"license\": \"MIT\",\n \"homepage\": \"https://getgloss.dev\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/iamrajjoshi/gloss.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/iamrajjoshi/gloss/issues\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n","import type {\n Comment,\n DiffPayload,\n FeedbackBundle,\n OpenResult,\n ReviewEvent,\n ReviewMeta,\n ReviewRecord\n} from '../shared/types';\n\nexport class ServerClient {\n constructor(private readonly baseUrl: string) {}\n\n async health(): Promise<{ ok: boolean; version: string; activeReviews: number }> {\n return this.get('/api/health');\n }\n\n async createReview(diff: DiffPayload): Promise<{ meta: ReviewMeta; url: string }> {\n return this.post('/api/reviews', diff);\n }\n\n async getReview(reviewId: string): Promise<ReviewRecord> {\n return this.get(`/api/reviews/${reviewId}`);\n }\n\n async listReviews(): Promise<{ reviews: ReviewMeta[] }> {\n return this.get('/api/reviews');\n }\n\n async getFeedback(reviewId: string): Promise<FeedbackBundle> {\n return this.get(`/api/reviews/${reviewId}/feedback`);\n }\n\n async markResolved(reviewId: string, summary?: string): Promise<{ ok: true; path: string }> {\n return this.post(`/api/reviews/${reviewId}/resolved`, { summary });\n }\n\n async submitReview(reviewId: string, comments: Comment[]): Promise<OpenResult> {\n return this.post(`/api/reviews/${reviewId}/submit`, { comments });\n }\n\n async watchReview(reviewId: string, timeoutSeconds?: number): Promise<ReviewEvent> {\n const controller = new AbortController();\n const timeout =\n timeoutSeconds && timeoutSeconds > 0\n ? setTimeout(() => controller.abort(), timeoutSeconds * 1000)\n : null;\n\n try {\n const response = await fetch(`${this.baseUrl}/api/reviews/${reviewId}/events`, {\n signal: controller.signal\n });\n if (!response.ok || !response.body) {\n throw new Error(`watch failed: ${response.status} ${await response.text()}`);\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n throw new Error('watch stream ended before completion');\n }\n buffer += decoder.decode(value, { stream: true });\n const events = buffer.split('\\n\\n');\n buffer = events.pop() ?? '';\n for (const eventChunk of events) {\n const dataLine = eventChunk.split('\\n').find((line) => line.startsWith('data:'));\n if (!dataLine) {\n continue;\n }\n const event = JSON.parse(dataLine.slice(5).trim()) as ReviewEvent;\n if (event.type === 'review.completed' || event.type === 'review.cancelled') {\n return event;\n }\n }\n }\n } finally {\n if (timeout) {\n clearTimeout(timeout);\n }\n }\n }\n\n private async get<T>(path: string): Promise<T> {\n const response = await fetch(`${this.baseUrl}${path}`);\n return parseResponse<T>(response);\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const response = await fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body)\n });\n return parseResponse<T>(response);\n }\n}\n\nasync function parseResponse<T>(response: Response): Promise<T> {\n if (!response.ok) {\n throw new Error(`${response.status} ${response.statusText}: ${await response.text()}`);\n }\n return (await response.json()) as T;\n}\n"],"mappings":"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","names":["path"]}
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/index.ts","../../src/cli/lifecycle.ts","../../src/shared/paths.ts","../../package.json","../../src/cli/server-client.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod/v4';\nimport { ensureServer, serverUrl } from '../cli/lifecycle';\nimport { ServerClient } from '../cli/server-client';\nimport { packageVersion } from '../shared/paths';\n\nfunction textResult(value: unknown) {\n return {\n content: [\n {\n type: 'text' as const,\n text: typeof value === 'string' ? value : JSON.stringify(value, null, 2)\n }\n ]\n };\n}\n\nasync function client(): Promise<ServerClient> {\n const info = await ensureServer();\n return new ServerClient(serverUrl(info));\n}\n\nexport async function startMcpServer(): Promise<void> {\n const server = new McpServer({\n name: 'gloss',\n version: packageVersion\n });\n\n server.registerTool(\n 'list_pending_reviews',\n {\n title: 'List pending Gloss reviews',\n description: 'List pending local Gloss review sessions.'\n },\n async () => {\n const api = await client();\n const { reviews } = await api.listReviews();\n return textResult({ reviews: reviews.filter((review) => review.status === 'pending') });\n }\n );\n\n server.registerTool(\n 'get_review',\n {\n title: 'Get Gloss review',\n description: 'Fetch review metadata and diff payload.',\n inputSchema: { id: z.string() }\n },\n async ({ id }) => textResult(await (await client()).getReview(id))\n );\n\n server.registerTool(\n 'watch_review',\n {\n title: 'Watch Gloss review',\n description: 'Block until a review completes, then return feedback.',\n inputSchema: {\n id: z.string(),\n timeout: z.number().optional()\n }\n },\n async ({ id, timeout }) => {\n const api = await client();\n await api.watchReview(id, timeout);\n return textResult(await api.getFeedback(id));\n }\n );\n\n server.registerTool(\n 'get_review_feedback',\n {\n title: 'Get Gloss review feedback',\n description: 'Fetch completed review feedback.',\n inputSchema: { id: z.string() }\n },\n async ({ id }) => textResult(await (await client()).getFeedback(id))\n );\n\n server.registerTool(\n 'mark_review_resolved',\n {\n title: 'Mark Gloss review resolved',\n description: 'Write a resolved marker for a completed review.',\n inputSchema: {\n id: z.string(),\n summary: z.string().optional()\n }\n },\n async ({ id, summary }) => textResult(await (await client()).markResolved(id, summary))\n );\n\n await server.connect(new StdioServerTransport());\n}\n","import { spawn } from 'node:child_process';\nimport { existsSync, openSync } from 'node:fs';\nimport { readFile, rm, writeFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport getPort from 'get-port';\nimport {\n ensureDir,\n globalLogDir,\n globalServerFile,\n globalServerLogFile,\n globalStateDir,\n packageVersion\n} from '../shared/paths';\nimport type { ServerInfo } from '../shared/types';\nimport { ServerClient } from './server-client';\n\nexport async function readServerInfo(): Promise<ServerInfo | null> {\n try {\n return JSON.parse(await readFile(globalServerFile(), 'utf8')) as ServerInfo;\n } catch {\n return null;\n }\n}\n\nexport function serverUrl(info: Pick<ServerInfo, 'port'>): string {\n return `http://localhost:${info.port}`;\n}\n\nexport async function isServerResponsive(info: ServerInfo): Promise<boolean> {\n if (!isPidAlive(info.pid)) {\n return false;\n }\n try {\n const health = await new ServerClient(serverUrl(info)).health();\n return health.ok === true;\n } catch {\n return false;\n }\n}\n\nexport async function ensureServer(options: { port?: number } = {}): Promise<ServerInfo> {\n const existing = await readServerInfo();\n if (existing && (await isServerResponsive(existing))) {\n return existing;\n }\n return startServer(options);\n}\n\nexport async function startServer(options: { port?: number } = {}): Promise<ServerInfo> {\n const existing = await readServerInfo();\n if (existing && (await isServerResponsive(existing))) {\n return existing;\n }\n\n await ensureDir(globalStateDir());\n await ensureDir(globalLogDir());\n const port = options.port ?? (await getPort());\n const daemonPath = fileURLToPath(new URL('../server/daemon.js', import.meta.url));\n if (!existsSync(daemonPath)) {\n throw new Error(`Cannot find server daemon at ${daemonPath}. Run pnpm build first.`);\n }\n\n const logFd = openSync(globalServerLogFile(), 'a');\n const child = spawn(process.execPath, [daemonPath], {\n detached: true,\n env: {\n ...process.env,\n GLOSS_PORT: String(port),\n GLOSS_STATE_DIR: globalStateDir()\n },\n stdio: ['ignore', logFd, logFd]\n });\n child.unref();\n\n const info: ServerInfo = {\n pid: child.pid ?? -1,\n port,\n version: packageVersion,\n startedAt: new Date().toISOString(),\n stateDir: globalStateDir()\n };\n await writeFile(globalServerFile(), `${JSON.stringify(info, null, 2)}\\n`);\n\n const deadline = Date.now() + 8000;\n while (Date.now() < deadline) {\n if (await isServerResponsive(info)) {\n return info;\n }\n await new Promise((resolve) => setTimeout(resolve, 150));\n }\n\n throw new Error(`Server did not become responsive. See ${globalServerLogFile()}`);\n}\n\nexport async function stopServer(): Promise<{ stopped: boolean; info: ServerInfo | null }> {\n const info = await readServerInfo();\n if (!info) {\n return { stopped: false, info: null };\n }\n\n if (isPidAlive(info.pid)) {\n process.kill(info.pid, 'SIGTERM');\n }\n await rm(globalServerFile(), { force: true });\n return { stopped: true, info };\n}\n\nexport async function writeServerInfo(info: ServerInfo): Promise<void> {\n await ensureDir(globalStateDir());\n await writeFile(globalServerFile(), `${JSON.stringify(info, null, 2)}\\n`);\n}\n\nexport function isPidAlive(pid: number): boolean {\n if (pid <= 0) {\n return false;\n }\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n","import { mkdir } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport path from 'node:path';\nimport packageJson from '../../package.json';\n\nexport const packageVersion = packageJson.version;\n\nexport function expandHome(input: string): string {\n if (input === '~') {\n return homedir();\n }\n if (input.startsWith('~/')) {\n return path.join(homedir(), input.slice(2));\n }\n return input;\n}\n\nexport function globalStateDir(): string {\n return expandHome(process.env.GLOSS_STATE_DIR ?? '~/.gloss');\n}\n\nexport function globalServerFile(): string {\n return path.join(globalStateDir(), 'server.json');\n}\n\nexport function globalLogDir(): string {\n return path.join(globalStateDir(), 'logs');\n}\n\nexport function globalServerLogFile(): string {\n return path.join(globalLogDir(), 'server.log');\n}\n\nexport function repoGlossDir(cwd: string): string {\n return path.join(cwd, '.gloss');\n}\n\nexport function reviewsDir(cwd: string): string {\n return path.join(repoGlossDir(cwd), 'reviews');\n}\n\nexport function reviewDir(cwd: string, reviewId: string): string {\n return path.join(reviewsDir(cwd), reviewId);\n}\n\nexport async function ensureDir(dir: string): Promise<void> {\n await mkdir(dir, { recursive: true });\n}\n","{\n \"name\": \"getgloss\",\n \"version\": \"0.2.0\",\n \"description\": \"Local browser-based diff review for coding-agent loops.\",\n \"type\": \"module\",\n \"packageManager\": \"pnpm@10.33.2\",\n \"bin\": {\n \"getgloss\": \"./dist/cli/index.js\",\n \"gloss\": \"./dist/cli/index.js\"\n },\n \"files\": [\n \"dist\",\n \"skill\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"pnpm build:web && pnpm build:node\",\n \"build:web\": \"vite build\",\n \"build:node\": \"tsup\",\n \"check\": \"biome check .\",\n \"format\": \"biome format --write .\",\n \"prepack\": \"pnpm build\",\n \"dev:web\": \"vite --host 127.0.0.1\",\n \"setup\": \"tsx scripts/dev-cli.ts\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\"\n },\n \"engines\": {\n \"node\": \">=20\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.14.4\",\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"@pierre/diffs\": \"^1.2.1\",\n \"@tailwindcss/vite\": \"^4.1.7\",\n \"commander\": \"^14.0.0\",\n \"execa\": \"^9.5.3\",\n \"get-port\": \"^7.1.0\",\n \"hono\": \"^4.7.10\",\n \"lucide-react\": \"^1.16.0\",\n \"open\": \"^10.1.2\",\n \"react\": \"^19.1.0\",\n \"react-dom\": \"^19.1.0\",\n \"ulid\": \"^3.0.0\",\n \"zod\": \"^4.4.3\",\n \"zustand\": \"^5.0.5\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.0.6\",\n \"@types/node\": \"^24.0.1\",\n \"@types/react\": \"^19.1.6\",\n \"@types/react-dom\": \"^19.1.5\",\n \"@vitejs/plugin-react\": \"^4.5.2\",\n \"playwright\": \"^1.52.0\",\n \"tsup\": \"^8.5.0\",\n \"tsx\": \"^4.20.3\",\n \"typescript\": \"^5.8.3\",\n \"vite\": \"^6.3.5\",\n \"vitest\": \"^3.2.3\"\n },\n \"keywords\": [\n \"diff\",\n \"review\",\n \"coding-agents\",\n \"mcp\"\n ],\n \"author\": \"Raj Joshi\",\n \"license\": \"MIT\",\n \"homepage\": \"https://getgloss.dev\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/iamrajjoshi/gloss.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/iamrajjoshi/gloss/issues\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n","import type {\n Comment,\n DiffPayload,\n FeedbackBundle,\n OpenResult,\n ReviewEvent,\n ReviewMeta,\n ReviewRecord\n} from '../shared/types';\n\nexport class ServerClient {\n constructor(private readonly baseUrl: string) {}\n\n async health(): Promise<{ ok: boolean; version: string; activeReviews: number }> {\n return this.get('/api/health');\n }\n\n async createReview(diff: DiffPayload): Promise<{ meta: ReviewMeta; url: string }> {\n return this.post('/api/reviews', diff);\n }\n\n async getReview(reviewId: string): Promise<ReviewRecord> {\n return this.get(`/api/reviews/${reviewId}`);\n }\n\n async listReviews(): Promise<{ reviews: ReviewMeta[] }> {\n return this.get('/api/reviews');\n }\n\n async getFeedback(reviewId: string): Promise<FeedbackBundle> {\n return this.get(`/api/reviews/${reviewId}/feedback`);\n }\n\n async markResolved(reviewId: string, summary?: string): Promise<{ ok: true; path: string }> {\n return this.post(`/api/reviews/${reviewId}/resolved`, { summary });\n }\n\n async submitReview(reviewId: string, comments: Comment[]): Promise<OpenResult> {\n return this.post(`/api/reviews/${reviewId}/submit`, { comments });\n }\n\n async watchReview(reviewId: string, timeoutSeconds?: number): Promise<ReviewEvent> {\n const controller = new AbortController();\n const timeout =\n timeoutSeconds && timeoutSeconds > 0\n ? setTimeout(() => controller.abort(), timeoutSeconds * 1000)\n : null;\n\n try {\n const response = await fetch(`${this.baseUrl}/api/reviews/${reviewId}/events`, {\n signal: controller.signal\n });\n if (!response.ok || !response.body) {\n throw new Error(`watch failed: ${response.status} ${await response.text()}`);\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n throw new Error('watch stream ended before completion');\n }\n buffer += decoder.decode(value, { stream: true });\n const events = buffer.split('\\n\\n');\n buffer = events.pop() ?? '';\n for (const eventChunk of events) {\n const dataLine = eventChunk.split('\\n').find((line) => line.startsWith('data:'));\n if (!dataLine) {\n continue;\n }\n const event = JSON.parse(dataLine.slice(5).trim()) as ReviewEvent;\n if (event.type === 'review.completed' || event.type === 'review.cancelled') {\n return event;\n }\n }\n }\n } finally {\n if (timeout) {\n clearTimeout(timeout);\n }\n }\n }\n\n private async get<T>(path: string): Promise<T> {\n const response = await fetch(`${this.baseUrl}${path}`);\n return parseResponse<T>(response);\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const response = await fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body)\n });\n return parseResponse<T>(response);\n }\n}\n\nasync function parseResponse<T>(response: Response): Promise<T> {\n if (!response.ok) {\n throw new Error(`${response.status} ${response.statusText}: ${await response.text()}`);\n }\n return (await response.json()) as T;\n}\n"],"mappings":";AAAA,SAAS,iBAAiB;AAC1B,SAAS,4BAA4B;AACrC,SAAS,SAAS;;;ACFlB,SAAS,aAAa;AACtB,SAAS,YAAY,gBAAgB;AACrC,SAAS,UAAU,IAAI,iBAAiB;AACxC,SAAS,qBAAqB;AAC9B,OAAO,aAAa;;;ACJpB,SAAS,aAAa;AACtB,SAAS,eAAe;AACxB,OAAO,UAAU;;;ACFjB;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,aAAe;AAAA,EACf,MAAQ;AAAA,EACR,gBAAkB;AAAA,EAClB,KAAO;AAAA,IACL,UAAY;AAAA,IACZ,OAAS;AAAA,EACX;AAAA,EACA,OAAS;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,aAAa;AAAA,IACb,cAAc;AAAA,IACd,OAAS;AAAA,IACT,QAAU;AAAA,IACV,SAAW;AAAA,IACX,WAAW;AAAA,IACX,OAAS;AAAA,IACT,MAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA,SAAW;AAAA,IACT,MAAQ;AAAA,EACV;AAAA,EACA,cAAgB;AAAA,IACd,qBAAqB;AAAA,IACrB,6BAA6B;AAAA,IAC7B,iBAAiB;AAAA,IACjB,qBAAqB;AAAA,IACrB,WAAa;AAAA,IACb,OAAS;AAAA,IACT,YAAY;AAAA,IACZ,MAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,MAAQ;AAAA,IACR,OAAS;AAAA,IACT,aAAa;AAAA,IACb,MAAQ;AAAA,IACR,KAAO;AAAA,IACP,SAAW;AAAA,EACb;AAAA,EACA,iBAAmB;AAAA,IACjB,kBAAkB;AAAA,IAClB,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,oBAAoB;AAAA,IACpB,wBAAwB;AAAA,IACxB,YAAc;AAAA,IACd,MAAQ;AAAA,IACR,KAAO;AAAA,IACP,YAAc;AAAA,IACd,MAAQ;AAAA,IACR,QAAU;AAAA,EACZ;AAAA,EACA,UAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,QAAU;AAAA,EACV,SAAW;AAAA,EACX,UAAY;AAAA,EACZ,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,MAAQ;AAAA,IACN,KAAO;AAAA,EACT;AAAA,EACA,eAAiB;AAAA,IACf,QAAU;AAAA,EACZ;AACF;;;AD3EO,IAAM,iBAAiB,gBAAY;AAEnC,SAAS,WAAW,OAAuB;AAChD,MAAI,UAAU,KAAK;AACjB,WAAO,QAAQ;AAAA,EACjB;AACA,MAAI,MAAM,WAAW,IAAI,GAAG;AAC1B,WAAO,KAAK,KAAK,QAAQ,GAAG,MAAM,MAAM,CAAC,CAAC;AAAA,EAC5C;AACA,SAAO;AACT;AAEO,SAAS,iBAAyB;AACvC,SAAO,WAAW,QAAQ,IAAI,mBAAmB,UAAU;AAC7D;AAEO,SAAS,mBAA2B;AACzC,SAAO,KAAK,KAAK,eAAe,GAAG,aAAa;AAClD;AAEO,SAAS,eAAuB;AACrC,SAAO,KAAK,KAAK,eAAe,GAAG,MAAM;AAC3C;AAEO,SAAS,sBAA8B;AAC5C,SAAO,KAAK,KAAK,aAAa,GAAG,YAAY;AAC/C;AAcA,eAAsB,UAAU,KAA4B;AAC1D,QAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACtC;;;AErCO,IAAM,eAAN,MAAmB;AAAA,EACxB,YAA6B,SAAiB;AAAjB;AAAA,EAAkB;AAAA,EAAlB;AAAA,EAE7B,MAAM,SAA2E;AAC/E,WAAO,KAAK,IAAI,aAAa;AAAA,EAC/B;AAAA,EAEA,MAAM,aAAa,MAA+D;AAChF,WAAO,KAAK,KAAK,gBAAgB,IAAI;AAAA,EACvC;AAAA,EAEA,MAAM,UAAU,UAAyC;AACvD,WAAO,KAAK,IAAI,gBAAgB,QAAQ,EAAE;AAAA,EAC5C;AAAA,EAEA,MAAM,cAAkD;AACtD,WAAO,KAAK,IAAI,cAAc;AAAA,EAChC;AAAA,EAEA,MAAM,YAAY,UAA2C;AAC3D,WAAO,KAAK,IAAI,gBAAgB,QAAQ,WAAW;AAAA,EACrD;AAAA,EAEA,MAAM,aAAa,UAAkB,SAAuD;AAC1F,WAAO,KAAK,KAAK,gBAAgB,QAAQ,aAAa,EAAE,QAAQ,CAAC;AAAA,EACnE;AAAA,EAEA,MAAM,aAAa,UAAkB,UAA0C;AAC7E,WAAO,KAAK,KAAK,gBAAgB,QAAQ,WAAW,EAAE,SAAS,CAAC;AAAA,EAClE;AAAA,EAEA,MAAM,YAAY,UAAkB,gBAA+C;AACjF,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,UACJ,kBAAkB,iBAAiB,IAC/B,WAAW,MAAM,WAAW,MAAM,GAAG,iBAAiB,GAAI,IAC1D;AAEN,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,gBAAgB,QAAQ,WAAW;AAAA,QAC7E,QAAQ,WAAW;AAAA,MACrB,CAAC;AACD,UAAI,CAAC,SAAS,MAAM,CAAC,SAAS,MAAM;AAClC,cAAM,IAAI,MAAM,iBAAiB,SAAS,MAAM,IAAI,MAAM,SAAS,KAAK,CAAC,EAAE;AAAA,MAC7E;AAEA,YAAM,SAAS,SAAS,KAAK,UAAU;AACvC,YAAM,UAAU,IAAI,YAAY;AAChC,UAAI,SAAS;AAEb,aAAO,MAAM;AACX,cAAM,EAAE,OAAO,KAAK,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,MAAM;AACR,gBAAM,IAAI,MAAM,sCAAsC;AAAA,QACxD;AACA,kBAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAChD,cAAM,SAAS,OAAO,MAAM,MAAM;AAClC,iBAAS,OAAO,IAAI,KAAK;AACzB,mBAAW,cAAc,QAAQ;AAC/B,gBAAM,WAAW,WAAW,MAAM,IAAI,EAAE,KAAK,CAAC,SAAS,KAAK,WAAW,OAAO,CAAC;AAC/E,cAAI,CAAC,UAAU;AACb;AAAA,UACF;AACA,gBAAM,QAAQ,KAAK,MAAM,SAAS,MAAM,CAAC,EAAE,KAAK,CAAC;AACjD,cAAI,MAAM,SAAS,sBAAsB,MAAM,SAAS,oBAAoB;AAC1E,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAAA,IACF,UAAE;AACA,UAAI,SAAS;AACX,qBAAa,OAAO;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,IAAOA,OAA0B;AAC7C,UAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,GAAGA,KAAI,EAAE;AACrD,WAAO,cAAiB,QAAQ;AAAA,EAClC;AAAA,EAEA,MAAc,KAAQA,OAAc,MAA2B;AAC7D,UAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,GAAGA,KAAI,IAAI;AAAA,MACrD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AACD,WAAO,cAAiB,QAAQ;AAAA,EAClC;AACF;AAEA,eAAe,cAAiB,UAAgC;AAC9D,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,GAAG,SAAS,MAAM,IAAI,SAAS,UAAU,KAAK,MAAM,SAAS,KAAK,CAAC,EAAE;AAAA,EACvF;AACA,SAAQ,MAAM,SAAS,KAAK;AAC9B;;;AH1FA,eAAsB,iBAA6C;AACjE,MAAI;AACF,WAAO,KAAK,MAAM,MAAM,SAAS,iBAAiB,GAAG,MAAM,CAAC;AAAA,EAC9D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,UAAU,MAAwC;AAChE,SAAO,oBAAoB,KAAK,IAAI;AACtC;AAEA,eAAsB,mBAAmB,MAAoC;AAC3E,MAAI,CAAC,WAAW,KAAK,GAAG,GAAG;AACzB,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,SAAS,MAAM,IAAI,aAAa,UAAU,IAAI,CAAC,EAAE,OAAO;AAC9D,WAAO,OAAO,OAAO;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,aAAa,UAA6B,CAAC,GAAwB;AACvF,QAAM,WAAW,MAAM,eAAe;AACtC,MAAI,YAAa,MAAM,mBAAmB,QAAQ,GAAI;AACpD,WAAO;AAAA,EACT;AACA,SAAO,YAAY,OAAO;AAC5B;AAEA,eAAsB,YAAY,UAA6B,CAAC,GAAwB;AACtF,QAAM,WAAW,MAAM,eAAe;AACtC,MAAI,YAAa,MAAM,mBAAmB,QAAQ,GAAI;AACpD,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,eAAe,CAAC;AAChC,QAAM,UAAU,aAAa,CAAC;AAC9B,QAAM,OAAO,QAAQ,QAAS,MAAM,QAAQ;AAC5C,QAAM,aAAa,cAAc,IAAI,IAAI,uBAAuB,YAAY,GAAG,CAAC;AAChF,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,UAAM,IAAI,MAAM,gCAAgC,UAAU,yBAAyB;AAAA,EACrF;AAEA,QAAM,QAAQ,SAAS,oBAAoB,GAAG,GAAG;AACjD,QAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,UAAU,GAAG;AAAA,IAClD,UAAU;AAAA,IACV,KAAK;AAAA,MACH,GAAG,QAAQ;AAAA,MACX,YAAY,OAAO,IAAI;AAAA,MACvB,iBAAiB,eAAe;AAAA,IAClC;AAAA,IACA,OAAO,CAAC,UAAU,OAAO,KAAK;AAAA,EAChC,CAAC;AACD,QAAM,MAAM;AAEZ,QAAM,OAAmB;AAAA,IACvB,KAAK,MAAM,OAAO;AAAA,IAClB;AAAA,IACA,SAAS;AAAA,IACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,UAAU,eAAe;AAAA,EAC3B;AACA,QAAM,UAAU,iBAAiB,GAAG,GAAG,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,CAAI;AAExE,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,QAAI,MAAM,mBAAmB,IAAI,GAAG;AAClC,aAAO;AAAA,IACT;AACA,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAG,CAAC;AAAA,EACzD;AAEA,QAAM,IAAI,MAAM,yCAAyC,oBAAoB,CAAC,EAAE;AAClF;AAoBO,SAAS,WAAW,KAAsB;AAC/C,MAAI,OAAO,GAAG;AACZ,WAAO;AAAA,EACT;AACA,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ADnHA,SAAS,WAAW,OAAgB;AAClC,SAAO;AAAA,IACL,SAAS;AAAA,MACP;AAAA,QACE,MAAM;AAAA,QACN,MAAM,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,OAAO,MAAM,CAAC;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAe,SAAgC;AAC7C,QAAM,OAAO,MAAM,aAAa;AAChC,SAAO,IAAI,aAAa,UAAU,IAAI,CAAC;AACzC;AAEA,eAAsB,iBAAgC;AACpD,QAAM,SAAS,IAAI,UAAU;AAAA,IAC3B,MAAM;AAAA,IACN,SAAS;AAAA,EACX,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,YAAY;AACV,YAAM,MAAM,MAAM,OAAO;AACzB,YAAM,EAAE,QAAQ,IAAI,MAAM,IAAI,YAAY;AAC1C,aAAO,WAAW,EAAE,SAAS,QAAQ,OAAO,CAAC,WAAW,OAAO,WAAW,SAAS,EAAE,CAAC;AAAA,IACxF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE;AAAA,IAChC;AAAA,IACA,OAAO,EAAE,GAAG,MAAM,WAAW,OAAO,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;AAAA,EACnE;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,aAAa;AAAA,QACX,IAAI,EAAE,OAAO;AAAA,QACb,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,MAC/B;AAAA,IACF;AAAA,IACA,OAAO,EAAE,IAAI,QAAQ,MAAM;AACzB,YAAM,MAAM,MAAM,OAAO;AACzB,YAAM,IAAI,YAAY,IAAI,OAAO;AACjC,aAAO,WAAW,MAAM,IAAI,YAAY,EAAE,CAAC;AAAA,IAC7C;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE;AAAA,IAChC;AAAA,IACA,OAAO,EAAE,GAAG,MAAM,WAAW,OAAO,MAAM,OAAO,GAAG,YAAY,EAAE,CAAC;AAAA,EACrE;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,aAAa;AAAA,QACX,IAAI,EAAE,OAAO;AAAA,QACb,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,MAC/B;AAAA,IACF;AAAA,IACA,OAAO,EAAE,IAAI,QAAQ,MAAM,WAAW,OAAO,MAAM,OAAO,GAAG,aAAa,IAAI,OAAO,CAAC;AAAA,EACxF;AAEA,QAAM,OAAO,QAAQ,IAAI,qBAAqB,CAAC;AACjD;","names":["path"]}
|
package/dist/server/daemon.js
CHANGED
|
@@ -16,7 +16,7 @@ import path from "path";
|
|
|
16
16
|
// package.json
|
|
17
17
|
var package_default = {
|
|
18
18
|
name: "getgloss",
|
|
19
|
-
version: "0.
|
|
19
|
+
version: "0.2.0",
|
|
20
20
|
description: "Local browser-based diff review for coding-agent loops.",
|
|
21
21
|
type: "module",
|
|
22
22
|
packageManager: "pnpm@10.33.2",
|
|
@@ -183,6 +183,11 @@ function languageForPath(filePath) {
|
|
|
183
183
|
};
|
|
184
184
|
return ext ? map[ext] ?? ext : "";
|
|
185
185
|
}
|
|
186
|
+
function languageForSnippet(filePath, snippet) {
|
|
187
|
+
const lines = snippet.split("\n").filter((line) => line.length > 0);
|
|
188
|
+
const looksLikeUnifiedDiff = lines.length > 0 && lines.some((line) => line.startsWith("+") || line.startsWith("-")) && lines.every((line) => line.startsWith("+") || line.startsWith("-") || line.startsWith(" "));
|
|
189
|
+
return looksLikeUnifiedDiff ? "diff" : languageForPath(filePath);
|
|
190
|
+
}
|
|
186
191
|
function byFileThenLine(a, b) {
|
|
187
192
|
return a.filePath.localeCompare(b.filePath) || a.startLine - b.startLine || a.endLine - b.endLine || a.side.localeCompare(b.side);
|
|
188
193
|
}
|
|
@@ -205,7 +210,7 @@ function serializeFeedbackMarkdown(bundle) {
|
|
|
205
210
|
lines.push(heading, comment.body.trim(), "");
|
|
206
211
|
if (snippet) {
|
|
207
212
|
const fence = fenceFor(snippet);
|
|
208
|
-
lines.push(`${fence}${
|
|
213
|
+
lines.push(`${fence}${languageForSnippet(comment.filePath, snippet)}`, snippet, fence, "");
|
|
209
214
|
}
|
|
210
215
|
}
|
|
211
216
|
}
|
|
@@ -345,6 +350,8 @@ var mimeTypes = {
|
|
|
345
350
|
".js": "application/javascript; charset=utf-8",
|
|
346
351
|
".json": "application/json; charset=utf-8",
|
|
347
352
|
".map": "application/json; charset=utf-8",
|
|
353
|
+
".png": "image/png",
|
|
354
|
+
".sh": "text/x-shellscript; charset=utf-8",
|
|
348
355
|
".svg": "image/svg+xml"
|
|
349
356
|
};
|
|
350
357
|
function createApp(origin2) {
|
|
@@ -442,9 +449,15 @@ function createApp(origin2) {
|
|
|
442
449
|
const resolvedPath = await reviewStore.markResolved(c.req.param("id"), body.summary);
|
|
443
450
|
return c.json({ ok: true, path: resolvedPath });
|
|
444
451
|
});
|
|
452
|
+
app.get("/logo.svg", serveRootFile("logo.svg", mimeTypes[".svg"]));
|
|
453
|
+
app.get("/og.png", serveRootFile("og.png", mimeTypes[".png"]));
|
|
454
|
+
app.get("/install.sh", serveRootFile("install.sh", mimeTypes[".sh"]));
|
|
445
455
|
app.get("/setup.md", serveRootFile("setup.md", "text/markdown; charset=utf-8"));
|
|
446
456
|
app.get("/prompt.md", serveRootFile("prompt.md", "text/markdown; charset=utf-8"));
|
|
457
|
+
app.get("/skill/SKILL.md", serveRootFile("skill/SKILL.md", "text/markdown; charset=utf-8"));
|
|
447
458
|
app.get("/assets/*", serveAsset);
|
|
459
|
+
app.get("/setup", serveIndex);
|
|
460
|
+
app.get("/setup/", serveIndex);
|
|
448
461
|
app.get("/review/:id", serveIndex);
|
|
449
462
|
app.get("/", serveIndex);
|
|
450
463
|
return app;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/daemon.ts","../../src/cli/lifecycle.ts","../../src/shared/paths.ts","../../package.json","../../src/server/index.ts","../../src/server/store.ts","../../src/shared/markdown.ts"],"sourcesContent":["import { serve } from '@hono/node-server';\nimport { writeServerInfo } from '../cli/lifecycle';\nimport { globalStateDir, packageVersion } from '../shared/paths';\nimport { createApp } from './index';\n\nconst port = Number(process.env.GLOSS_PORT ?? '0');\n\nif (!port) {\n throw new Error('GLOSS_PORT is required');\n}\n\nconst origin = `http://localhost:${port}`;\nconst server = serve({\n fetch: createApp(origin).fetch,\n port\n});\n\nawait writeServerInfo({\n pid: process.pid,\n port,\n version: packageVersion,\n startedAt: new Date().toISOString(),\n stateDir: globalStateDir()\n});\n\nprocess.on('SIGTERM', () => {\n server.close(() => {\n process.exit(0);\n });\n});\n","import { spawn } from 'node:child_process';\nimport { existsSync, openSync } from 'node:fs';\nimport { readFile, rm, writeFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport getPort from 'get-port';\nimport {\n ensureDir,\n globalLogDir,\n globalServerFile,\n globalServerLogFile,\n globalStateDir,\n packageVersion\n} from '../shared/paths';\nimport type { ServerInfo } from '../shared/types';\nimport { ServerClient } from './server-client';\n\nexport async function readServerInfo(): Promise<ServerInfo | null> {\n try {\n return JSON.parse(await readFile(globalServerFile(), 'utf8')) as ServerInfo;\n } catch {\n return null;\n }\n}\n\nexport function serverUrl(info: Pick<ServerInfo, 'port'>): string {\n return `http://localhost:${info.port}`;\n}\n\nexport async function isServerResponsive(info: ServerInfo): Promise<boolean> {\n if (!isPidAlive(info.pid)) {\n return false;\n }\n try {\n const health = await new ServerClient(serverUrl(info)).health();\n return health.ok === true;\n } catch {\n return false;\n }\n}\n\nexport async function ensureServer(options: { port?: number } = {}): Promise<ServerInfo> {\n const existing = await readServerInfo();\n if (existing && (await isServerResponsive(existing))) {\n return existing;\n }\n return startServer(options);\n}\n\nexport async function startServer(options: { port?: number } = {}): Promise<ServerInfo> {\n const existing = await readServerInfo();\n if (existing && (await isServerResponsive(existing))) {\n return existing;\n }\n\n await ensureDir(globalStateDir());\n await ensureDir(globalLogDir());\n const port = options.port ?? (await getPort());\n const daemonPath = fileURLToPath(new URL('../server/daemon.js', import.meta.url));\n if (!existsSync(daemonPath)) {\n throw new Error(`Cannot find server daemon at ${daemonPath}. Run pnpm build first.`);\n }\n\n const logFd = openSync(globalServerLogFile(), 'a');\n const child = spawn(process.execPath, [daemonPath], {\n detached: true,\n env: {\n ...process.env,\n GLOSS_PORT: String(port),\n GLOSS_STATE_DIR: globalStateDir()\n },\n stdio: ['ignore', logFd, logFd]\n });\n child.unref();\n\n const info: ServerInfo = {\n pid: child.pid ?? -1,\n port,\n version: packageVersion,\n startedAt: new Date().toISOString(),\n stateDir: globalStateDir()\n };\n await writeFile(globalServerFile(), `${JSON.stringify(info, null, 2)}\\n`);\n\n const deadline = Date.now() + 8000;\n while (Date.now() < deadline) {\n if (await isServerResponsive(info)) {\n return info;\n }\n await new Promise((resolve) => setTimeout(resolve, 150));\n }\n\n throw new Error(`Server did not become responsive. See ${globalServerLogFile()}`);\n}\n\nexport async function stopServer(): Promise<{ stopped: boolean; info: ServerInfo | null }> {\n const info = await readServerInfo();\n if (!info) {\n return { stopped: false, info: null };\n }\n\n if (isPidAlive(info.pid)) {\n process.kill(info.pid, 'SIGTERM');\n }\n await rm(globalServerFile(), { force: true });\n return { stopped: true, info };\n}\n\nexport async function writeServerInfo(info: ServerInfo): Promise<void> {\n await ensureDir(globalStateDir());\n await writeFile(globalServerFile(), `${JSON.stringify(info, null, 2)}\\n`);\n}\n\nexport function isPidAlive(pid: number): boolean {\n if (pid <= 0) {\n return false;\n }\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n","import { mkdir } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport path from 'node:path';\nimport packageJson from '../../package.json';\n\nexport const packageVersion = packageJson.version;\n\nexport function expandHome(input: string): string {\n if (input === '~') {\n return homedir();\n }\n if (input.startsWith('~/')) {\n return path.join(homedir(), input.slice(2));\n }\n return input;\n}\n\nexport function globalStateDir(): string {\n return expandHome(process.env.GLOSS_STATE_DIR ?? '~/.gloss');\n}\n\nexport function globalServerFile(): string {\n return path.join(globalStateDir(), 'server.json');\n}\n\nexport function globalLogDir(): string {\n return path.join(globalStateDir(), 'logs');\n}\n\nexport function globalServerLogFile(): string {\n return path.join(globalLogDir(), 'server.log');\n}\n\nexport function repoGlossDir(cwd: string): string {\n return path.join(cwd, '.gloss');\n}\n\nexport function reviewsDir(cwd: string): string {\n return path.join(repoGlossDir(cwd), 'reviews');\n}\n\nexport function reviewDir(cwd: string, reviewId: string): string {\n return path.join(reviewsDir(cwd), reviewId);\n}\n\nexport async function ensureDir(dir: string): Promise<void> {\n await mkdir(dir, { recursive: true });\n}\n","{\n \"name\": \"getgloss\",\n \"version\": \"0.1.2\",\n \"description\": \"Local browser-based diff review for coding-agent loops.\",\n \"type\": \"module\",\n \"packageManager\": \"pnpm@10.33.2\",\n \"bin\": {\n \"getgloss\": \"./dist/cli/index.js\",\n \"gloss\": \"./dist/cli/index.js\"\n },\n \"files\": [\n \"dist\",\n \"skill\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"pnpm build:web && pnpm build:node\",\n \"build:web\": \"vite build\",\n \"build:node\": \"tsup\",\n \"check\": \"biome check .\",\n \"format\": \"biome format --write .\",\n \"prepack\": \"pnpm build\",\n \"dev:web\": \"vite --host 127.0.0.1\",\n \"setup\": \"tsx scripts/dev-cli.ts\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\"\n },\n \"engines\": {\n \"node\": \">=20\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.14.4\",\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"@pierre/diffs\": \"^1.2.1\",\n \"@tailwindcss/vite\": \"^4.1.7\",\n \"commander\": \"^14.0.0\",\n \"execa\": \"^9.5.3\",\n \"get-port\": \"^7.1.0\",\n \"hono\": \"^4.7.10\",\n \"lucide-react\": \"^1.16.0\",\n \"open\": \"^10.1.2\",\n \"react\": \"^19.1.0\",\n \"react-dom\": \"^19.1.0\",\n \"ulid\": \"^3.0.0\",\n \"zod\": \"^4.4.3\",\n \"zustand\": \"^5.0.5\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.0.6\",\n \"@types/node\": \"^24.0.1\",\n \"@types/react\": \"^19.1.6\",\n \"@types/react-dom\": \"^19.1.5\",\n \"@vitejs/plugin-react\": \"^4.5.2\",\n \"playwright\": \"^1.52.0\",\n \"tsup\": \"^8.5.0\",\n \"tsx\": \"^4.20.3\",\n \"typescript\": \"^5.8.3\",\n \"vite\": \"^6.3.5\",\n \"vitest\": \"^3.2.3\"\n },\n \"keywords\": [\n \"diff\",\n \"review\",\n \"coding-agents\",\n \"mcp\"\n ],\n \"author\": \"Raj Joshi\",\n \"license\": \"MIT\",\n \"homepage\": \"https://getgloss.dev\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/iamrajjoshi/gloss.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/iamrajjoshi/gloss/issues\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Context } from 'hono';\nimport { Hono } from 'hono';\nimport { packageVersion, reviewDir } from '../shared/paths';\nimport type { Comment, DiffPayload, ReviewEvent } from '../shared/types';\nimport { reviewStore } from './store';\n\nconst webRoot = fileURLToPath(new URL('../web', import.meta.url));\n\nconst mimeTypes: Record<string, string> = {\n '.css': 'text/css; charset=utf-8',\n '.html': 'text/html; charset=utf-8',\n '.js': 'application/javascript; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml'\n};\n\nexport function createApp(origin: string): Hono {\n const app = new Hono();\n\n app.get('/api/health', (c) =>\n c.json({\n ok: true,\n version: packageVersion,\n activeReviews: reviewStore.list().length\n })\n );\n\n app.get('/api/reviews', (c) => c.json({ reviews: reviewStore.list() }));\n\n app.post('/api/reviews', async (c) => {\n const diff = (await c.req.json()) as DiffPayload;\n const record = await reviewStore.create(diff);\n return c.json({ meta: record.meta, url: `${origin}/review/${record.meta.id}` }, 201);\n });\n\n app.get('/api/reviews/:id', async (c) => {\n const record = await reviewStore.get(c.req.param('id'));\n if (!record) {\n return c.json({ error: 'review not found' }, 404);\n }\n return c.json(record);\n });\n\n app.get('/api/reviews/:id/feedback', async (c) => {\n const feedback = await reviewStore.feedback(c.req.param('id'));\n if (!feedback) {\n return c.json({ error: 'feedback not found' }, 404);\n }\n return c.json(feedback);\n });\n\n app.get('/api/reviews/:id/events', async (c) => {\n const id = c.req.param('id');\n const record = await reviewStore.get(id);\n if (!record) {\n return c.json({ error: 'review not found' }, 404);\n }\n\n const encoder = new TextEncoder();\n let cleanup: (() => void) | null = null;\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n const send = (event: ReviewEvent) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\\n\\n`));\n if (event.type === 'review.completed' || event.type === 'review.cancelled') {\n cleanup?.();\n controller.close();\n }\n };\n cleanup = reviewStore.subscribe(id, send);\n send({ type: 'review.opened', reviewId: id });\n if (record.meta.status === 'completed' && record.feedback) {\n send({\n type: 'review.completed',\n reviewId: id,\n counts: {\n files: new Set(record.feedback.comments.map((comment) => comment.filePath)).size,\n comments: record.feedback.comments.length\n }\n });\n }\n },\n cancel() {\n cleanup?.();\n }\n });\n\n return new Response(stream, {\n headers: {\n 'cache-control': 'no-cache',\n connection: 'keep-alive',\n 'content-type': 'text/event-stream'\n }\n });\n });\n\n app.post('/api/reviews/:id/submit', async (c) => {\n const id = c.req.param('id');\n const body = (await c.req.json()) as { comments: Comment[] };\n const { record, feedbackPath, markdownPath } = await reviewStore.submit(\n id,\n body.comments ?? []\n );\n return c.json({\n reviewId: id,\n url: `${origin}/review/${id}`,\n files: record.diff.files.length,\n comments: body.comments?.length ?? 0,\n feedbackPath,\n markdownPath\n });\n });\n\n app.post('/api/reviews/:id/resolved', async (c) => {\n const body = (await c.req.json().catch(() => ({}))) as { summary?: string };\n const resolvedPath = await reviewStore.markResolved(c.req.param('id'), body.summary);\n return c.json({ ok: true, path: resolvedPath });\n });\n\n app.get('/setup.md', serveRootFile('setup.md', 'text/markdown; charset=utf-8'));\n app.get('/prompt.md', serveRootFile('prompt.md', 'text/markdown; charset=utf-8'));\n app.get('/assets/*', serveAsset);\n app.get('/review/:id', serveIndex);\n app.get('/', serveIndex);\n\n return app;\n}\n\nasync function serveAsset(c: Context) {\n const requestPath = new URL(c.req.url).pathname.replace(/^\\/assets\\//, '');\n const normalized = path.normalize(requestPath).replace(/^(\\.\\.(\\/|\\\\|$))+/, '');\n const assetPath = path.join(webRoot, 'assets', normalized);\n try {\n const body = await readFile(assetPath);\n return new Response(body, {\n headers: {\n 'content-type': mimeTypes[path.extname(assetPath)] ?? 'application/octet-stream'\n }\n });\n } catch {\n return new Response('Not found', { status: 404 });\n }\n}\n\nasync function serveIndex() {\n try {\n const body = await readFile(path.join(webRoot, 'index.html'));\n return new Response(body, {\n headers: { 'content-type': 'text/html; charset=utf-8' }\n });\n } catch {\n return new Response('Gloss web assets are missing. Run pnpm build.', { status: 500 });\n }\n}\n\nfunction serveRootFile(fileName: string, contentType: string) {\n return async () => {\n try {\n const body = await readFile(path.join(webRoot, fileName));\n return new Response(body, {\n headers: { 'content-type': contentType }\n });\n } catch {\n return new Response(`${fileName} is missing. Run pnpm build.`, { status: 404 });\n }\n };\n}\n\nexport function getReviewArtifactDir(cwd: string, reviewId: string): string {\n return reviewDir(cwd, reviewId);\n}\n","import { mkdir, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { ulid } from 'ulid';\nimport { serializeFeedbackMarkdown } from '../shared/markdown';\nimport { ensureDir, repoGlossDir, reviewDir } from '../shared/paths';\nimport type {\n Comment,\n DiffPayload,\n FeedbackBundle,\n ReviewEvent,\n ReviewMeta,\n ReviewRecord\n} from '../shared/types';\n\ntype Listener = (event: ReviewEvent) => void;\n\nexport class ReviewStore {\n private readonly reviews = new Map<string, ReviewRecord>();\n private readonly listeners = new Map<string, Set<Listener>>();\n\n async create(diff: DiffPayload): Promise<ReviewRecord> {\n const id = ulid();\n const createdAt = new Date().toISOString();\n const meta: ReviewMeta = {\n id,\n cwd: diff.cwd,\n base: diff.base,\n branch: diff.branch,\n status: 'pending',\n createdAt\n };\n const record: ReviewRecord = { meta, diff };\n this.reviews.set(id, record);\n await this.persistInitial(record);\n this.emit({ type: 'review.opened', reviewId: id });\n return record;\n }\n\n list(): ReviewMeta[] {\n return [...this.reviews.values()]\n .map((record) => record.meta)\n .sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n }\n\n async get(id: string): Promise<ReviewRecord | null> {\n return this.reviews.get(id) ?? (await this.loadKnownReview(id));\n }\n\n async submit(\n id: string,\n comments: Comment[]\n ): Promise<{ record: ReviewRecord; feedbackPath: string; markdownPath: string }> {\n const record = await this.get(id);\n if (!record) {\n throw new Error(`Review ${id} not found`);\n }\n const timestamp = new Date().toISOString();\n const feedback: FeedbackBundle = {\n version: 1,\n reviewId: id,\n timestamp,\n base: record.diff.base,\n branch: record.diff.branch,\n comments: [...comments].sort(\n (a, b) =>\n a.filePath.localeCompare(b.filePath) ||\n a.startLine - b.startLine ||\n a.endLine - b.endLine ||\n a.side.localeCompare(b.side)\n )\n };\n record.feedback = feedback;\n record.meta = { ...record.meta, status: 'completed', completedAt: timestamp };\n this.reviews.set(id, record);\n\n const dir = reviewDir(record.meta.cwd, id);\n const feedbackPath = path.join(dir, 'feedback.json');\n const markdownPath = path.join(dir, 'feedback.md');\n await ensureDir(dir);\n await Promise.all([\n writeFile(path.join(dir, 'meta.json'), `${JSON.stringify(record.meta, null, 2)}\\n`),\n writeFile(feedbackPath, `${JSON.stringify(feedback, null, 2)}\\n`),\n writeFile(markdownPath, serializeFeedbackMarkdown(feedback))\n ]);\n\n this.emit({\n type: 'review.completed',\n reviewId: id,\n counts: {\n files: new Set(feedback.comments.map((comment) => comment.filePath)).size,\n comments: feedback.comments.length\n }\n });\n return { record, feedbackPath, markdownPath };\n }\n\n async feedback(id: string): Promise<FeedbackBundle | null> {\n const record = await this.get(id);\n return record?.feedback ?? null;\n }\n\n async markResolved(id: string, summary?: string): Promise<string> {\n const record = await this.get(id);\n if (!record) {\n throw new Error(`Review ${id} not found`);\n }\n const resolvedPath = path.join(reviewDir(record.meta.cwd, id), 'resolved.json');\n await writeFile(\n resolvedPath,\n `${JSON.stringify({ reviewId: id, summary: summary ?? null, resolvedAt: new Date().toISOString() }, null, 2)}\\n`\n );\n return resolvedPath;\n }\n\n subscribe(reviewId: string, listener: Listener): () => void {\n const listeners = this.listeners.get(reviewId) ?? new Set<Listener>();\n listeners.add(listener);\n this.listeners.set(reviewId, listeners);\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) {\n this.listeners.delete(reviewId);\n }\n };\n }\n\n private emit(event: ReviewEvent): void {\n for (const listener of this.listeners.get(event.reviewId) ?? []) {\n listener(event);\n }\n }\n\n private async persistInitial(record: ReviewRecord): Promise<void> {\n await ensureDir(repoGlossDir(record.meta.cwd));\n await writeFile(path.join(repoGlossDir(record.meta.cwd), '.gitignore'), '*\\n!.gitignore\\n');\n const dir = reviewDir(record.meta.cwd, record.meta.id);\n await ensureDir(dir);\n await mkdir(path.join(dir, 'original'), { recursive: true });\n await Promise.all([\n writeFile(path.join(dir, 'meta.json'), `${JSON.stringify(record.meta, null, 2)}\\n`),\n writeFile(path.join(dir, 'diff.json'), `${JSON.stringify(record.diff, null, 2)}\\n`)\n ]);\n }\n\n private async loadKnownReview(id: string): Promise<ReviewRecord | null> {\n for (const meta of this.reviews.values()) {\n if (meta.meta.id === id) {\n return meta;\n }\n }\n return null;\n }\n}\n\nexport const reviewStore = new ReviewStore();\n\nexport async function removeReviewArtifacts(cwd: string, id: string): Promise<void> {\n await rm(reviewDir(cwd, id), { force: true, recursive: true });\n}\n","import type { Comment, FeedbackBundle } from './types';\n\nfunction formatLineRange(comment: Comment): string {\n const prefix = comment.side;\n if (comment.startLine === comment.endLine) {\n return `${prefix}${comment.startLine}`;\n }\n return `${prefix}${comment.startLine}-${prefix}${comment.endLine}`;\n}\n\nfunction fenceFor(snippet: string): string {\n let fence = '```';\n while (snippet.includes(fence)) {\n fence += '`';\n }\n return fence;\n}\n\nfunction languageForPath(filePath: string): string {\n const ext = filePath.split('.').pop()?.toLowerCase();\n const map: Record<string, string> = {\n cjs: 'js',\n css: 'css',\n go: 'go',\n html: 'html',\n js: 'js',\n json: 'json',\n jsx: 'jsx',\n md: 'markdown',\n mjs: 'js',\n py: 'python',\n rb: 'ruby',\n rs: 'rust',\n sh: 'bash',\n swift: 'swift',\n ts: 'ts',\n tsx: 'tsx',\n yaml: 'yaml',\n yml: 'yaml'\n };\n return ext ? (map[ext] ?? ext) : '';\n}\n\nfunction byFileThenLine(a: Comment, b: Comment): number {\n return (\n a.filePath.localeCompare(b.filePath) ||\n a.startLine - b.startLine ||\n a.endLine - b.endLine ||\n a.side.localeCompare(b.side)\n );\n}\n\nexport function serializeFeedbackMarkdown(bundle: FeedbackBundle): string {\n const comments = [...bundle.comments].sort(byFileThenLine);\n const files = [...new Set(comments.map((comment) => comment.filePath))];\n const lines: string[] = [\n `# Gloss feedback - ${bundle.timestamp}`,\n `Review: ${bundle.reviewId}`,\n `Base: ${bundle.base.ref} (${bundle.base.sha.slice(0, 7)}) Branch: ${bundle.branch ?? '(detached)'}`,\n `Files: ${files.length} Comments: ${comments.length}`,\n ''\n ];\n\n for (const filePath of files) {\n lines.push(`## ${filePath}`, '');\n for (const comment of comments.filter((item) => item.filePath === filePath)) {\n const snippet = comment.originalSnippet.trimEnd();\n const firstSnippetLine = snippet.split('\\n').find((line) => line.trim().length > 0);\n const heading =\n comment.startLine === comment.endLine && firstSnippetLine\n ? `### ${formatLineRange(comment)} - \\`${firstSnippetLine.trim().slice(0, 80)}\\``\n : `### ${formatLineRange(comment)}`;\n lines.push(heading, comment.body.trim(), '');\n if (snippet) {\n const fence = fenceFor(snippet);\n lines.push(`${fence}${languageForPath(comment.filePath)}`, snippet, fence, '');\n }\n }\n }\n\n return `${lines.join('\\n').trimEnd()}\\n`;\n}\n"],"mappings":"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","names":["readFile","path","fileURLToPath","mkdir","rm","writeFile","path","path","writeFile","mkdir","fileURLToPath","origin","path","readFile"]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/daemon.ts","../../src/cli/lifecycle.ts","../../src/shared/paths.ts","../../package.json","../../src/server/index.ts","../../src/server/store.ts","../../src/shared/markdown.ts"],"sourcesContent":["import { serve } from '@hono/node-server';\nimport { writeServerInfo } from '../cli/lifecycle';\nimport { globalStateDir, packageVersion } from '../shared/paths';\nimport { createApp } from './index';\n\nconst port = Number(process.env.GLOSS_PORT ?? '0');\n\nif (!port) {\n throw new Error('GLOSS_PORT is required');\n}\n\nconst origin = `http://localhost:${port}`;\nconst server = serve({\n fetch: createApp(origin).fetch,\n port\n});\n\nawait writeServerInfo({\n pid: process.pid,\n port,\n version: packageVersion,\n startedAt: new Date().toISOString(),\n stateDir: globalStateDir()\n});\n\nprocess.on('SIGTERM', () => {\n server.close(() => {\n process.exit(0);\n });\n});\n","import { spawn } from 'node:child_process';\nimport { existsSync, openSync } from 'node:fs';\nimport { readFile, rm, writeFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport getPort from 'get-port';\nimport {\n ensureDir,\n globalLogDir,\n globalServerFile,\n globalServerLogFile,\n globalStateDir,\n packageVersion\n} from '../shared/paths';\nimport type { ServerInfo } from '../shared/types';\nimport { ServerClient } from './server-client';\n\nexport async function readServerInfo(): Promise<ServerInfo | null> {\n try {\n return JSON.parse(await readFile(globalServerFile(), 'utf8')) as ServerInfo;\n } catch {\n return null;\n }\n}\n\nexport function serverUrl(info: Pick<ServerInfo, 'port'>): string {\n return `http://localhost:${info.port}`;\n}\n\nexport async function isServerResponsive(info: ServerInfo): Promise<boolean> {\n if (!isPidAlive(info.pid)) {\n return false;\n }\n try {\n const health = await new ServerClient(serverUrl(info)).health();\n return health.ok === true;\n } catch {\n return false;\n }\n}\n\nexport async function ensureServer(options: { port?: number } = {}): Promise<ServerInfo> {\n const existing = await readServerInfo();\n if (existing && (await isServerResponsive(existing))) {\n return existing;\n }\n return startServer(options);\n}\n\nexport async function startServer(options: { port?: number } = {}): Promise<ServerInfo> {\n const existing = await readServerInfo();\n if (existing && (await isServerResponsive(existing))) {\n return existing;\n }\n\n await ensureDir(globalStateDir());\n await ensureDir(globalLogDir());\n const port = options.port ?? (await getPort());\n const daemonPath = fileURLToPath(new URL('../server/daemon.js', import.meta.url));\n if (!existsSync(daemonPath)) {\n throw new Error(`Cannot find server daemon at ${daemonPath}. Run pnpm build first.`);\n }\n\n const logFd = openSync(globalServerLogFile(), 'a');\n const child = spawn(process.execPath, [daemonPath], {\n detached: true,\n env: {\n ...process.env,\n GLOSS_PORT: String(port),\n GLOSS_STATE_DIR: globalStateDir()\n },\n stdio: ['ignore', logFd, logFd]\n });\n child.unref();\n\n const info: ServerInfo = {\n pid: child.pid ?? -1,\n port,\n version: packageVersion,\n startedAt: new Date().toISOString(),\n stateDir: globalStateDir()\n };\n await writeFile(globalServerFile(), `${JSON.stringify(info, null, 2)}\\n`);\n\n const deadline = Date.now() + 8000;\n while (Date.now() < deadline) {\n if (await isServerResponsive(info)) {\n return info;\n }\n await new Promise((resolve) => setTimeout(resolve, 150));\n }\n\n throw new Error(`Server did not become responsive. See ${globalServerLogFile()}`);\n}\n\nexport async function stopServer(): Promise<{ stopped: boolean; info: ServerInfo | null }> {\n const info = await readServerInfo();\n if (!info) {\n return { stopped: false, info: null };\n }\n\n if (isPidAlive(info.pid)) {\n process.kill(info.pid, 'SIGTERM');\n }\n await rm(globalServerFile(), { force: true });\n return { stopped: true, info };\n}\n\nexport async function writeServerInfo(info: ServerInfo): Promise<void> {\n await ensureDir(globalStateDir());\n await writeFile(globalServerFile(), `${JSON.stringify(info, null, 2)}\\n`);\n}\n\nexport function isPidAlive(pid: number): boolean {\n if (pid <= 0) {\n return false;\n }\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n","import { mkdir } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport path from 'node:path';\nimport packageJson from '../../package.json';\n\nexport const packageVersion = packageJson.version;\n\nexport function expandHome(input: string): string {\n if (input === '~') {\n return homedir();\n }\n if (input.startsWith('~/')) {\n return path.join(homedir(), input.slice(2));\n }\n return input;\n}\n\nexport function globalStateDir(): string {\n return expandHome(process.env.GLOSS_STATE_DIR ?? '~/.gloss');\n}\n\nexport function globalServerFile(): string {\n return path.join(globalStateDir(), 'server.json');\n}\n\nexport function globalLogDir(): string {\n return path.join(globalStateDir(), 'logs');\n}\n\nexport function globalServerLogFile(): string {\n return path.join(globalLogDir(), 'server.log');\n}\n\nexport function repoGlossDir(cwd: string): string {\n return path.join(cwd, '.gloss');\n}\n\nexport function reviewsDir(cwd: string): string {\n return path.join(repoGlossDir(cwd), 'reviews');\n}\n\nexport function reviewDir(cwd: string, reviewId: string): string {\n return path.join(reviewsDir(cwd), reviewId);\n}\n\nexport async function ensureDir(dir: string): Promise<void> {\n await mkdir(dir, { recursive: true });\n}\n","{\n \"name\": \"getgloss\",\n \"version\": \"0.2.0\",\n \"description\": \"Local browser-based diff review for coding-agent loops.\",\n \"type\": \"module\",\n \"packageManager\": \"pnpm@10.33.2\",\n \"bin\": {\n \"getgloss\": \"./dist/cli/index.js\",\n \"gloss\": \"./dist/cli/index.js\"\n },\n \"files\": [\n \"dist\",\n \"skill\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"pnpm build:web && pnpm build:node\",\n \"build:web\": \"vite build\",\n \"build:node\": \"tsup\",\n \"check\": \"biome check .\",\n \"format\": \"biome format --write .\",\n \"prepack\": \"pnpm build\",\n \"dev:web\": \"vite --host 127.0.0.1\",\n \"setup\": \"tsx scripts/dev-cli.ts\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\"\n },\n \"engines\": {\n \"node\": \">=20\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.14.4\",\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"@pierre/diffs\": \"^1.2.1\",\n \"@tailwindcss/vite\": \"^4.1.7\",\n \"commander\": \"^14.0.0\",\n \"execa\": \"^9.5.3\",\n \"get-port\": \"^7.1.0\",\n \"hono\": \"^4.7.10\",\n \"lucide-react\": \"^1.16.0\",\n \"open\": \"^10.1.2\",\n \"react\": \"^19.1.0\",\n \"react-dom\": \"^19.1.0\",\n \"ulid\": \"^3.0.0\",\n \"zod\": \"^4.4.3\",\n \"zustand\": \"^5.0.5\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.0.6\",\n \"@types/node\": \"^24.0.1\",\n \"@types/react\": \"^19.1.6\",\n \"@types/react-dom\": \"^19.1.5\",\n \"@vitejs/plugin-react\": \"^4.5.2\",\n \"playwright\": \"^1.52.0\",\n \"tsup\": \"^8.5.0\",\n \"tsx\": \"^4.20.3\",\n \"typescript\": \"^5.8.3\",\n \"vite\": \"^6.3.5\",\n \"vitest\": \"^3.2.3\"\n },\n \"keywords\": [\n \"diff\",\n \"review\",\n \"coding-agents\",\n \"mcp\"\n ],\n \"author\": \"Raj Joshi\",\n \"license\": \"MIT\",\n \"homepage\": \"https://getgloss.dev\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/iamrajjoshi/gloss.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/iamrajjoshi/gloss/issues\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Context } from 'hono';\nimport { Hono } from 'hono';\nimport { packageVersion, reviewDir } from '../shared/paths';\nimport type { Comment, DiffPayload, ReviewEvent } from '../shared/types';\nimport { reviewStore } from './store';\n\nconst webRoot = fileURLToPath(new URL('../web', import.meta.url));\n\nconst mimeTypes: Record<string, string> = {\n '.css': 'text/css; charset=utf-8',\n '.html': 'text/html; charset=utf-8',\n '.js': 'application/javascript; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.png': 'image/png',\n '.sh': 'text/x-shellscript; charset=utf-8',\n '.svg': 'image/svg+xml'\n};\n\nexport function createApp(origin: string): Hono {\n const app = new Hono();\n\n app.get('/api/health', (c) =>\n c.json({\n ok: true,\n version: packageVersion,\n activeReviews: reviewStore.list().length\n })\n );\n\n app.get('/api/reviews', (c) => c.json({ reviews: reviewStore.list() }));\n\n app.post('/api/reviews', async (c) => {\n const diff = (await c.req.json()) as DiffPayload;\n const record = await reviewStore.create(diff);\n return c.json({ meta: record.meta, url: `${origin}/review/${record.meta.id}` }, 201);\n });\n\n app.get('/api/reviews/:id', async (c) => {\n const record = await reviewStore.get(c.req.param('id'));\n if (!record) {\n return c.json({ error: 'review not found' }, 404);\n }\n return c.json(record);\n });\n\n app.get('/api/reviews/:id/feedback', async (c) => {\n const feedback = await reviewStore.feedback(c.req.param('id'));\n if (!feedback) {\n return c.json({ error: 'feedback not found' }, 404);\n }\n return c.json(feedback);\n });\n\n app.get('/api/reviews/:id/events', async (c) => {\n const id = c.req.param('id');\n const record = await reviewStore.get(id);\n if (!record) {\n return c.json({ error: 'review not found' }, 404);\n }\n\n const encoder = new TextEncoder();\n let cleanup: (() => void) | null = null;\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n const send = (event: ReviewEvent) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\\n\\n`));\n if (event.type === 'review.completed' || event.type === 'review.cancelled') {\n cleanup?.();\n controller.close();\n }\n };\n cleanup = reviewStore.subscribe(id, send);\n send({ type: 'review.opened', reviewId: id });\n if (record.meta.status === 'completed' && record.feedback) {\n send({\n type: 'review.completed',\n reviewId: id,\n counts: {\n files: new Set(record.feedback.comments.map((comment) => comment.filePath)).size,\n comments: record.feedback.comments.length\n }\n });\n }\n },\n cancel() {\n cleanup?.();\n }\n });\n\n return new Response(stream, {\n headers: {\n 'cache-control': 'no-cache',\n connection: 'keep-alive',\n 'content-type': 'text/event-stream'\n }\n });\n });\n\n app.post('/api/reviews/:id/submit', async (c) => {\n const id = c.req.param('id');\n const body = (await c.req.json()) as { comments: Comment[] };\n const { record, feedbackPath, markdownPath } = await reviewStore.submit(\n id,\n body.comments ?? []\n );\n return c.json({\n reviewId: id,\n url: `${origin}/review/${id}`,\n files: record.diff.files.length,\n comments: body.comments?.length ?? 0,\n feedbackPath,\n markdownPath\n });\n });\n\n app.post('/api/reviews/:id/resolved', async (c) => {\n const body = (await c.req.json().catch(() => ({}))) as { summary?: string };\n const resolvedPath = await reviewStore.markResolved(c.req.param('id'), body.summary);\n return c.json({ ok: true, path: resolvedPath });\n });\n\n app.get('/logo.svg', serveRootFile('logo.svg', mimeTypes['.svg']));\n app.get('/og.png', serveRootFile('og.png', mimeTypes['.png']));\n app.get('/install.sh', serveRootFile('install.sh', mimeTypes['.sh']));\n app.get('/setup.md', serveRootFile('setup.md', 'text/markdown; charset=utf-8'));\n app.get('/prompt.md', serveRootFile('prompt.md', 'text/markdown; charset=utf-8'));\n app.get('/skill/SKILL.md', serveRootFile('skill/SKILL.md', 'text/markdown; charset=utf-8'));\n app.get('/assets/*', serveAsset);\n app.get('/setup', serveIndex);\n app.get('/setup/', serveIndex);\n app.get('/review/:id', serveIndex);\n app.get('/', serveIndex);\n\n return app;\n}\n\nasync function serveAsset(c: Context) {\n const requestPath = new URL(c.req.url).pathname.replace(/^\\/assets\\//, '');\n const normalized = path.normalize(requestPath).replace(/^(\\.\\.(\\/|\\\\|$))+/, '');\n const assetPath = path.join(webRoot, 'assets', normalized);\n try {\n const body = await readFile(assetPath);\n return new Response(body, {\n headers: {\n 'content-type': mimeTypes[path.extname(assetPath)] ?? 'application/octet-stream'\n }\n });\n } catch {\n return new Response('Not found', { status: 404 });\n }\n}\n\nasync function serveIndex() {\n try {\n const body = await readFile(path.join(webRoot, 'index.html'));\n return new Response(body, {\n headers: { 'content-type': 'text/html; charset=utf-8' }\n });\n } catch {\n return new Response('Gloss web assets are missing. Run pnpm build.', { status: 500 });\n }\n}\n\nfunction serveRootFile(fileName: string, contentType: string) {\n return async () => {\n try {\n const body = await readFile(path.join(webRoot, fileName));\n return new Response(body, {\n headers: { 'content-type': contentType }\n });\n } catch {\n return new Response(`${fileName} is missing. Run pnpm build.`, { status: 404 });\n }\n };\n}\n\nexport function getReviewArtifactDir(cwd: string, reviewId: string): string {\n return reviewDir(cwd, reviewId);\n}\n","import { mkdir, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { ulid } from 'ulid';\nimport { serializeFeedbackMarkdown } from '../shared/markdown';\nimport { ensureDir, repoGlossDir, reviewDir } from '../shared/paths';\nimport type {\n Comment,\n DiffPayload,\n FeedbackBundle,\n ReviewEvent,\n ReviewMeta,\n ReviewRecord\n} from '../shared/types';\n\ntype Listener = (event: ReviewEvent) => void;\n\nexport class ReviewStore {\n private readonly reviews = new Map<string, ReviewRecord>();\n private readonly listeners = new Map<string, Set<Listener>>();\n\n async create(diff: DiffPayload): Promise<ReviewRecord> {\n const id = ulid();\n const createdAt = new Date().toISOString();\n const meta: ReviewMeta = {\n id,\n cwd: diff.cwd,\n base: diff.base,\n branch: diff.branch,\n status: 'pending',\n createdAt\n };\n const record: ReviewRecord = { meta, diff };\n this.reviews.set(id, record);\n await this.persistInitial(record);\n this.emit({ type: 'review.opened', reviewId: id });\n return record;\n }\n\n list(): ReviewMeta[] {\n return [...this.reviews.values()]\n .map((record) => record.meta)\n .sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n }\n\n async get(id: string): Promise<ReviewRecord | null> {\n return this.reviews.get(id) ?? (await this.loadKnownReview(id));\n }\n\n async submit(\n id: string,\n comments: Comment[]\n ): Promise<{ record: ReviewRecord; feedbackPath: string; markdownPath: string }> {\n const record = await this.get(id);\n if (!record) {\n throw new Error(`Review ${id} not found`);\n }\n const timestamp = new Date().toISOString();\n const feedback: FeedbackBundle = {\n version: 1,\n reviewId: id,\n timestamp,\n base: record.diff.base,\n branch: record.diff.branch,\n comments: [...comments].sort(\n (a, b) =>\n a.filePath.localeCompare(b.filePath) ||\n a.startLine - b.startLine ||\n a.endLine - b.endLine ||\n a.side.localeCompare(b.side)\n )\n };\n record.feedback = feedback;\n record.meta = { ...record.meta, status: 'completed', completedAt: timestamp };\n this.reviews.set(id, record);\n\n const dir = reviewDir(record.meta.cwd, id);\n const feedbackPath = path.join(dir, 'feedback.json');\n const markdownPath = path.join(dir, 'feedback.md');\n await ensureDir(dir);\n await Promise.all([\n writeFile(path.join(dir, 'meta.json'), `${JSON.stringify(record.meta, null, 2)}\\n`),\n writeFile(feedbackPath, `${JSON.stringify(feedback, null, 2)}\\n`),\n writeFile(markdownPath, serializeFeedbackMarkdown(feedback))\n ]);\n\n this.emit({\n type: 'review.completed',\n reviewId: id,\n counts: {\n files: new Set(feedback.comments.map((comment) => comment.filePath)).size,\n comments: feedback.comments.length\n }\n });\n return { record, feedbackPath, markdownPath };\n }\n\n async feedback(id: string): Promise<FeedbackBundle | null> {\n const record = await this.get(id);\n return record?.feedback ?? null;\n }\n\n async markResolved(id: string, summary?: string): Promise<string> {\n const record = await this.get(id);\n if (!record) {\n throw new Error(`Review ${id} not found`);\n }\n const resolvedPath = path.join(reviewDir(record.meta.cwd, id), 'resolved.json');\n await writeFile(\n resolvedPath,\n `${JSON.stringify({ reviewId: id, summary: summary ?? null, resolvedAt: new Date().toISOString() }, null, 2)}\\n`\n );\n return resolvedPath;\n }\n\n subscribe(reviewId: string, listener: Listener): () => void {\n const listeners = this.listeners.get(reviewId) ?? new Set<Listener>();\n listeners.add(listener);\n this.listeners.set(reviewId, listeners);\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) {\n this.listeners.delete(reviewId);\n }\n };\n }\n\n private emit(event: ReviewEvent): void {\n for (const listener of this.listeners.get(event.reviewId) ?? []) {\n listener(event);\n }\n }\n\n private async persistInitial(record: ReviewRecord): Promise<void> {\n await ensureDir(repoGlossDir(record.meta.cwd));\n await writeFile(path.join(repoGlossDir(record.meta.cwd), '.gitignore'), '*\\n!.gitignore\\n');\n const dir = reviewDir(record.meta.cwd, record.meta.id);\n await ensureDir(dir);\n await mkdir(path.join(dir, 'original'), { recursive: true });\n await Promise.all([\n writeFile(path.join(dir, 'meta.json'), `${JSON.stringify(record.meta, null, 2)}\\n`),\n writeFile(path.join(dir, 'diff.json'), `${JSON.stringify(record.diff, null, 2)}\\n`)\n ]);\n }\n\n private async loadKnownReview(id: string): Promise<ReviewRecord | null> {\n for (const meta of this.reviews.values()) {\n if (meta.meta.id === id) {\n return meta;\n }\n }\n return null;\n }\n}\n\nexport const reviewStore = new ReviewStore();\n\nexport async function removeReviewArtifacts(cwd: string, id: string): Promise<void> {\n await rm(reviewDir(cwd, id), { force: true, recursive: true });\n}\n","import type { Comment, FeedbackBundle } from './types';\n\nfunction formatLineRange(comment: Comment): string {\n const prefix = comment.side;\n if (comment.startLine === comment.endLine) {\n return `${prefix}${comment.startLine}`;\n }\n return `${prefix}${comment.startLine}-${prefix}${comment.endLine}`;\n}\n\nfunction fenceFor(snippet: string): string {\n let fence = '```';\n while (snippet.includes(fence)) {\n fence += '`';\n }\n return fence;\n}\n\nfunction languageForPath(filePath: string): string {\n const ext = filePath.split('.').pop()?.toLowerCase();\n const map: Record<string, string> = {\n cjs: 'js',\n css: 'css',\n go: 'go',\n html: 'html',\n js: 'js',\n json: 'json',\n jsx: 'jsx',\n md: 'markdown',\n mjs: 'js',\n py: 'python',\n rb: 'ruby',\n rs: 'rust',\n sh: 'bash',\n swift: 'swift',\n ts: 'ts',\n tsx: 'tsx',\n yaml: 'yaml',\n yml: 'yaml'\n };\n return ext ? (map[ext] ?? ext) : '';\n}\n\nfunction languageForSnippet(filePath: string, snippet: string): string {\n const lines = snippet.split('\\n').filter((line) => line.length > 0);\n const looksLikeUnifiedDiff =\n lines.length > 0 &&\n lines.some((line) => line.startsWith('+') || line.startsWith('-')) &&\n lines.every((line) => line.startsWith('+') || line.startsWith('-') || line.startsWith(' '));\n return looksLikeUnifiedDiff ? 'diff' : languageForPath(filePath);\n}\n\nfunction byFileThenLine(a: Comment, b: Comment): number {\n return (\n a.filePath.localeCompare(b.filePath) ||\n a.startLine - b.startLine ||\n a.endLine - b.endLine ||\n a.side.localeCompare(b.side)\n );\n}\n\nexport function serializeFeedbackMarkdown(bundle: FeedbackBundle): string {\n const comments = [...bundle.comments].sort(byFileThenLine);\n const files = [...new Set(comments.map((comment) => comment.filePath))];\n const lines: string[] = [\n `# Gloss feedback - ${bundle.timestamp}`,\n `Review: ${bundle.reviewId}`,\n `Base: ${bundle.base.ref} (${bundle.base.sha.slice(0, 7)}) Branch: ${bundle.branch ?? '(detached)'}`,\n `Files: ${files.length} Comments: ${comments.length}`,\n ''\n ];\n\n for (const filePath of files) {\n lines.push(`## ${filePath}`, '');\n for (const comment of comments.filter((item) => item.filePath === filePath)) {\n const snippet = comment.originalSnippet.trimEnd();\n const firstSnippetLine = snippet.split('\\n').find((line) => line.trim().length > 0);\n const heading =\n comment.startLine === comment.endLine && firstSnippetLine\n ? `### ${formatLineRange(comment)} - \\`${firstSnippetLine.trim().slice(0, 80)}\\``\n : `### ${formatLineRange(comment)}`;\n lines.push(heading, comment.body.trim(), '');\n if (snippet) {\n const fence = fenceFor(snippet);\n lines.push(`${fence}${languageForSnippet(comment.filePath, snippet)}`, snippet, fence, '');\n }\n }\n }\n\n return `${lines.join('\\n').trimEnd()}\\n`;\n}\n"],"mappings":"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","names":["readFile","path","fileURLToPath","mkdir","rm","writeFile","path","path","writeFile","mkdir","fileURLToPath","origin","path","readFile"]}
|
package/dist/web/CNAME
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
getgloss.dev
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&display=swap";/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--font-weight-medium:500;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.block{display:block}.contents{display:contents}.hidden{display:none}.rounded{border-radius:.25rem}.border{border-style:var(--tw-border-style);border-width:1px}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}}:root{color-scheme:dark;--color-midnight-ink:#111;--color-ghost-ash:#f9f9f9;--color-deep-graphite:#2b2b2b;--color-muted-stone:#b7b3a2;--color-light-grayscale:#eee;--color-shadow-ink:#000;--color-subtle-gray:#d6d6d6;--color-midtone-gray:#6e6e6e;--color-silver-mist:#9c9c9c;--color-dark-frost:#222;--color-slate-border:#565656;--font-primary:"JetBrains Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;--font-mono:var(--font-primary);--text-caption:10px;--text-small:12px;--text-body:16px;--text-subheading:20px;--text-heading:24px;--text-display:80px;--leading-caption:1.73;--leading-body:1.5;--leading-subheading:1.3;--leading-heading:1.2;--leading-display:.9;--font-weight-regular:400;--font-weight-medium:500;--spacing-8:8px;--spacing-16:16px;--spacing-24:24px;--spacing-32:32px;--spacing-40:40px;--spacing-48:48px;--spacing-64:64px;--spacing-80:80px;--spacing-96:96px;--spacing-128:128px;--spacing-144:144px;--spacing-192:192px;--page-max-width:1200px;--section-gap:48px;--card-padding:32px;--element-gap:16px;--radius-nav:4px;--radius-cards:12px;--radius-buttons:4px;--radius-decorative:20px;--radius-panel:8px;--grid-line:#f9f9f90e;--surface-dark:var(--color-midnight-ink);--surface-inset:#191919;--surface-raised:var(--color-dark-frost);--surface-strong:var(--color-deep-graphite);--surface-soft:#f9f9f90b;--border-soft:#d6d6d62e;--border-strong:var(--color-slate-border);--text-primary:var(--color-ghost-ash);--text-secondary:var(--color-silver-mist);--text-muted:var(--color-muted-stone);--site-accent:#3fb950;--site-accent-hover:#56d364;--site-accent-muted:#2ea04326;--accent:var(--site-accent);--ide-bg:#0d1117;--ide-bg-muted:#151b23;--ide-fg:#f0f6fc;--ide-fg-muted:#9198a1;--ide-border:#3d444d;--ide-success:#3fb950;--ide-success-muted:#2ea04326;--ide-success-gutter:#3fb9504d;--ide-success-word:#2ea04366;--ide-danger:#f85149;--ide-danger-muted:#f851491a;--ide-danger-gutter:#f851494d;--ide-danger-word:#f8514966;--ide-warning:#d29922;--ide-warning-muted:#bb800926;--ide-warning-border:#bb800966;--ide-accent:#4493f8;--ide-accent-muted:#388bfd1a;--ide-accent-border:#388bfd66;font-family:var(--font-primary);background:var(--surface-dark);color:var(--text-primary);font-weight:var(--font-weight-regular)}*{box-sizing:border-box}html{background:var(--surface-dark);scroll-behavior:smooth}body{background:linear-gradient(var(--grid-line) 1px,transparent 1px),linear-gradient(90deg,var(--grid-line) 1px,transparent 1px),var(--surface-dark);min-width:320px;min-height:100vh;color:var(--text-primary);background-size:32px 32px;margin:0}button,textarea{font:inherit}button{color:inherit}a,button{-webkit-tap-highlight-color:transparent}a:focus-visible,button:focus-visible,textarea:focus-visible{outline:2px solid var(--accent);outline-offset:2px}code,pre{font-family:var(--font-mono)}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.brand-mark{border:1px solid var(--border-soft);border-radius:var(--radius-nav);background:var(--color-shadow-ink);width:28px;height:28px;display:block}.muted{color:var(--text-secondary);font-size:var(--text-small);line-height:var(--leading-body);margin:4px 0 0}.primary-button,.secondary-button,.icon-button,.marketing-copy,.hero-primary,.hero-secondary,.scene-popover button,.scene-submit button{border-radius:var(--radius-buttons)}.primary-button,.secondary-button,.icon-button{cursor:pointer;border:1px solid #0000;justify-content:center;align-items:center;display:inline-flex}.primary-button{gap:var(--spacing-8);min-height:36px;padding:0 var(--spacing-16);background:var(--accent);color:var(--color-midnight-ink);font-size:var(--text-small);font-weight:var(--font-weight-medium)}.primary-button:hover:not(:disabled){background:var(--site-accent-hover)}.primary-button:disabled{cursor:default;opacity:.58}.secondary-button{gap:var(--spacing-8);min-height:36px;padding:0 var(--spacing-16);border:1px solid var(--ide-border);color:var(--ide-fg);font-size:var(--text-small);font-weight:var(--font-weight-medium);background:0 0}.secondary-button:hover{border-color:var(--ide-warning-border);background:var(--ide-warning-muted)}.icon-button{width:28px;height:28px;color:var(--text-secondary);background:0 0}.icon-button:hover{border-color:var(--border-soft);background:var(--surface-strong);color:var(--text-primary)}.spin{animation:1s linear infinite spin}@keyframes spin{to{transform:rotate(360deg)}}.review-shell{width:min(1320px,100vw - 32px);padding:var(--spacing-16) 0 112px;margin:0 auto}.topbar{z-index:5;justify-content:space-between;align-items:center;gap:var(--spacing-16);padding:var(--spacing-16) 0;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border-bottom:1px solid var(--border-soft);background:#111111f0;display:flex;position:sticky;top:0}.topbar-main{align-items:flex-start;gap:var(--spacing-16);min-width:0;display:flex}.review-heading{min-width:0}.review-heading h1{color:var(--text-primary);font-size:var(--text-subheading);font-weight:var(--font-weight-medium);letter-spacing:0;margin:0}.brand-row{align-items:center;gap:10px;display:flex}.brand-row h1{color:var(--text-primary);font-size:var(--text-subheading);font-weight:var(--font-weight-medium);letter-spacing:0;margin:0}.product-name{color:var(--text-secondary);font-size:var(--text-small);font-weight:var(--font-weight-medium);margin:0 0 2px}.meta-row{flex-wrap:wrap;gap:6px;margin-top:7px;display:flex}.meta-row span{border:1px solid var(--border-soft);border-radius:var(--radius-nav);background:var(--surface-raised);max-width:min(46vw,420px);min-height:24px;color:var(--text-secondary);font-size:var(--text-small);padding:0 var(--spacing-8);text-overflow:ellipsis;white-space:nowrap;align-items:center;line-height:1;display:inline-flex;overflow:hidden}.branch-pill{align-items:center;gap:var(--spacing-8);border:1px solid var(--border-soft);border-radius:var(--radius-nav);background:var(--surface-raised);min-width:0;max-width:46vw;color:var(--text-primary);font-size:var(--text-small);padding:7px 10px;display:inline-flex}.branch-pill span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.diff-stack{gap:var(--spacing-16);padding-top:var(--spacing-16);display:grid}.file-card{border:1px solid var(--ide-border);border-radius:var(--radius-cards);background:var(--ide-bg);overflow:hidden}.file-header{width:100%;padding:11px var(--spacing-16);border:0;border-bottom:1px solid var(--ide-border);background:var(--ide-bg-muted);cursor:pointer;text-align:left;grid-template-columns:auto auto minmax(0,1fr) auto auto auto;align-items:center;gap:10px;display:grid}.file-header:hover{background:var(--ide-accent-muted)}.file-path,.rename-path{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.file-path{color:var(--ide-fg);font-family:var(--font-mono);font-size:13px}.rename-path{max-width:240px;color:var(--ide-fg-muted);font-size:var(--text-small)}.stat{font-family:var(--font-mono);font-size:var(--text-small);font-weight:var(--font-weight-medium)}.stat.add{color:var(--ide-success)}.stat.del{color:var(--ide-danger)}.diff-table{border:0;width:100%;margin:0;padding:6px 0;overflow-x:auto}.hunk+.hunk{border-top:1px solid var(--ide-border)}.hunk-header,.hidden-lines,.binary-note{background:var(--ide-accent-muted);width:100%;color:var(--ide-fg-muted);font-family:var(--font-mono);font-size:var(--text-small);text-align:left;border:0}.hunk-header{padding:var(--spacing-8) var(--spacing-16)}.hidden-lines{padding:7px var(--spacing-16);cursor:pointer;display:block}.hidden-lines:hover{color:var(--ide-accent)}.binary-note{padding:var(--spacing-16)}.diff-row{--range-background:var(--ide-accent)}@supports (color:color-mix(in lab,red,red)){.diff-row{--range-background:color-mix(in srgb, var(--ide-accent) 28%, var(--ide-bg-muted))}}.diff-row{--range-border:var(--ide-accent-border);--range-gutter-background:var(--ide-accent)}@supports (color:color-mix(in lab,red,red)){.diff-row{--range-gutter-background:color-mix(in srgb, var(--ide-accent) 40%, var(--ide-bg-muted))}}.diff-row{--range-rail:var(--ide-accent);width:100%;min-height:26px;color:var(--ide-fg);cursor:cell;text-align:left;background:0 0;border:0;grid-template-columns:58px 58px 24px minmax(680px,1fr);align-items:stretch;display:grid;position:relative}.diff-row:hover{background:var(--ide-accent-muted)}.diff-row.add{background:var(--ide-success-muted)}.diff-row.delete{background:var(--ide-danger-muted)}.diff-row.add:hover{background:var(--ide-success-gutter)}.diff-row.delete:hover{background:var(--ide-danger-gutter)}.diff-row.range-selected{background:var(--range-background);box-shadow:none}.diff-row.add.range-selected{--range-background:var(--ide-success)}@supports (color:color-mix(in lab,red,red)){.diff-row.add.range-selected{--range-background:color-mix(in srgb, var(--ide-success) 28%, color-mix(in srgb, var(--ide-accent) 24%, var(--ide-bg-muted)))}}.diff-row.add.range-selected{--range-border:var(--ide-success)}@supports (color:color-mix(in lab,red,red)){.diff-row.add.range-selected{--range-border:color-mix(in srgb, var(--ide-success) 70%, var(--ide-accent))}}.diff-row.add.range-selected{--range-gutter-background:var(--ide-accent)}@supports (color:color-mix(in lab,red,red)){.diff-row.add.range-selected{--range-gutter-background:color-mix(in srgb, var(--ide-accent) 40%, color-mix(in srgb, var(--ide-success) 18%, var(--ide-bg-muted)))}}.diff-row.add.range-selected{--range-rail:var(--ide-success)}.diff-row.delete.range-selected{--range-background:var(--ide-danger)}@supports (color:color-mix(in lab,red,red)){.diff-row.delete.range-selected{--range-background:color-mix(in srgb, var(--ide-danger) 30%, color-mix(in srgb, var(--ide-accent) 28%, var(--ide-bg-muted)))}}.diff-row.delete.range-selected{--range-border:var(--ide-danger)}@supports (color:color-mix(in lab,red,red)){.diff-row.delete.range-selected{--range-border:color-mix(in srgb, var(--ide-danger) 72%, var(--ide-accent))}}.diff-row.delete.range-selected{--range-gutter-background:var(--ide-accent)}@supports (color:color-mix(in lab,red,red)){.diff-row.delete.range-selected{--range-gutter-background:color-mix(in srgb, var(--ide-accent) 42%, color-mix(in srgb, var(--ide-danger) 18%, var(--ide-bg-muted)))}}.diff-row.delete.range-selected{--range-rail:var(--ide-danger)}.selection-rail{z-index:2;background:repeating-linear-gradient(to bottom,var(--range-rail) 0 2px,var(--range-rail) 2px 3px,transparent 3px 5px);width:6px;position:absolute;top:0;bottom:0;left:0}@supports (color:color-mix(in lab,red,red)){.selection-rail{background:repeating-linear-gradient(to bottom,var(--range-rail) 0 2px,color-mix(in srgb,var(--range-rail) 42%,transparent) 2px 3px,transparent 3px 5px)}}.selection-rail{pointer-events:none}.diff-row.range-start{box-shadow:inset 0 1px var(--range-border)}.diff-row.range-end{box-shadow:inset 0 -1px var(--range-border)}.diff-row.range-single{box-shadow:inset 0 1px var(--range-border),inset 0 -1px var(--range-border)}.diff-row.range-continues{box-shadow:none}.diff-row.range-single.range-continues{box-shadow:inset 0 1px var(--range-border)}.line-number{-webkit-user-select:none;user-select:none;border-right:1px solid var(--ide-border);color:var(--ide-fg-muted);font-family:var(--font-mono);font-size:var(--text-small);text-align:right;padding-right:10px;line-height:26px}.marker{-webkit-user-select:none;user-select:none;color:var(--ide-fg-muted);font-family:var(--font-mono);font-size:var(--text-small);text-align:center;line-height:26px}.diff-row.add .line-number{background:var(--ide-success-gutter)}.diff-row.delete .line-number{background:var(--ide-danger-gutter)}.diff-row.add .marker{color:var(--ide-success)}.diff-row.delete .marker{color:var(--ide-danger)}.diff-row.range-selected .line-number{background:var(--range-gutter-background);color:var(--ide-fg)}.diff-row.range-selected .marker{color:var(--range-rail)}.diff-row code{white-space:pre;font-family:var(--font-mono);font-size:12.5px;line-height:26px;overflow:visible}.inline-comment{gap:var(--spacing-8);padding:var(--spacing-8) 10px;border:1px solid var(--ide-warning-border);border-radius:var(--radius-nav);background:var(--ide-warning-muted);color:var(--ide-fg);align-items:flex-start;margin:4px 12px 8px 140px;font-size:13px;display:flex}.inline-comment svg{color:var(--ide-warning);flex:none}.draft-comment-shell{--draft-range-background:var(--ide-accent)}@supports (color:color-mix(in lab,red,red)){.draft-comment-shell{--draft-range-background:color-mix(in srgb, var(--ide-accent) 28%, var(--ide-bg-muted))}}.draft-comment-shell{--draft-range-border:var(--ide-accent-border);background:var(--draft-range-background);width:100%;box-shadow:inset 0 -1px var(--draft-range-border);padding:10px 12px 10px 140px}.draft-comment-card{border:1px solid var(--ide-border);border-radius:var(--radius-cards);width:min(1040px,100%);color:var(--ide-fg);background:#262626;gap:0;display:grid;overflow:hidden}.draft-comment-title{justify-content:space-between;align-items:center;gap:var(--spacing-16);min-height:48px;padding:0 var(--spacing-16);color:var(--ide-fg);font-size:14px;font-weight:var(--font-weight-medium);border-bottom:1px solid #f0f6fc0f;display:flex}.draft-comment-heading{align-items:center;gap:12px;min-width:0;display:inline-flex}.draft-comment-icon{width:28px;height:28px;color:var(--ide-fg);background:#151515;border:1px solid #f0f6fc21;border-radius:999px;flex:none;justify-content:center;align-items:center;display:inline-flex}.draft-comment-label{color:var(--ide-fg-muted);font-size:var(--text-small);font-weight:var(--font-weight-regular);text-align:right;white-space:nowrap;flex:0 auto}.draft-comment-card textarea{resize:vertical;width:100%;min-height:72px;color:var(--ide-fg);padding:14px var(--spacing-16);font-family:var(--font-mono);background:0 0;border:0;border-radius:0;outline:none;font-size:14px;line-height:1.45}.draft-comment-card textarea::placeholder{color:#f0f6fc57}.draft-comment-actions{justify-content:flex-end;align-items:center;gap:var(--spacing-8);padding:0 var(--spacing-16) 12px;display:flex}.draft-comment-actions .secondary-button{min-height:34px;color:var(--ide-fg-muted);background:0 0;border-color:#0000;padding:0 10px;font-size:14px}.draft-comment-actions .secondary-button:hover{color:var(--ide-fg);background:0 0;border-color:#0000}.draft-comment-actions .primary-button{min-height:34px;padding:0 var(--spacing-16);color:var(--ide-bg);background:#8b949e;border-color:#8b949e;font-size:14px}.draft-comment-actions .primary-button:hover:not(:disabled){background:#b1bac4;border-color:#b1bac4}.popover{z-index:20;width:min(360px,100vw - 24px);padding:var(--spacing-16);border:1px solid var(--ide-border);border-radius:var(--radius-panel);background:var(--ide-bg-muted);position:fixed}.popover-title{color:var(--ide-fg);font-size:13px;font-weight:var(--font-weight-medium);justify-content:space-between;align-items:center;display:flex}.popover-subtitle{color:var(--ide-fg-muted);font-size:var(--text-small);margin-top:2px}.popover textarea{resize:vertical;border:1px solid var(--ide-border);background:var(--ide-bg);width:100%;min-height:96px;color:var(--ide-fg);border-radius:0;outline:none;margin-top:10px;padding:9px}.popover textarea:focus{border-color:var(--ide-accent)}.popover-actions,.submit-actions{justify-content:flex-end;align-items:center;gap:10px;display:flex}.popover-actions{margin-top:10px}.submit-bar{right:var(--spacing-16);bottom:var(--spacing-16);left:var(--spacing-16);z-index:10;justify-content:space-between;align-items:center;gap:var(--spacing-16);border:1px solid var(--ide-border);border-radius:var(--radius-cards);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);background:#151b23f5;width:min(1320px,100vw - 32px);margin:0 auto;padding:12px;display:flex;position:fixed}.comment-list{gap:var(--spacing-8);flex:1;min-width:0;display:flex;overflow-x:auto}.comment-chip{align-items:center;gap:var(--spacing-8);border:1px solid var(--ide-warning-border);border-radius:var(--radius-nav);background:var(--ide-warning-muted);min-width:max-content;color:var(--ide-fg);font-family:var(--font-mono);font-size:var(--text-small);padding:5px 7px 5px 10px;display:inline-flex}.comment-chip .icon-button{color:var(--ide-warning)}.submit-message{max-width:36vw;color:var(--ide-fg-muted);text-overflow:ellipsis;white-space:nowrap;font-size:13px;overflow:hidden}.submit-message.error{color:var(--ide-danger)}.submit-message.done{color:var(--ide-success)}.empty-shell{min-height:100vh;padding:var(--spacing-24);place-items:center;display:grid}.empty-panel,.empty-diff{border:1px solid var(--border-soft);border-radius:var(--radius-cards);background:var(--surface-raised)}.empty-panel{width:min(420px,100%);padding:var(--spacing-24);text-align:center}.empty-panel h1{margin:0 0 var(--spacing-8);font-size:var(--text-heading);font-weight:var(--font-weight-medium)}.empty-panel p{color:var(--text-secondary)}.empty-diff{padding:var(--spacing-24);color:var(--text-secondary)}.setup-page{width:min(860px,100vw - 32px);padding:var(--spacing-32) 0 var(--spacing-80);margin:0 auto}.setup-nav{z-index:3;justify-content:space-between;align-items:center;gap:var(--spacing-16);margin-bottom:var(--spacing-64);padding:var(--spacing-16) 0;border-bottom:1px solid var(--border-soft);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);font-size:var(--text-small);background:#111111f0;display:flex;position:sticky;top:0}.setup-nav>a:not(.marketing-wordmark),.setup-note a{color:var(--accent);text-decoration:none}.setup-nav>a:not(.marketing-wordmark):hover,.setup-note a:hover{text-decoration:underline}.setup-header h1{max-width:760px;color:var(--text-primary);font-size:64px;font-weight:var(--font-weight-medium);line-height:var(--leading-display);letter-spacing:0;margin:0}.setup-header p{max-width:660px;margin:var(--spacing-16) 0 0;color:var(--text-secondary);font-size:var(--text-body);line-height:var(--leading-body)}.setup-section{margin-top:var(--section-gap);border-top:1px solid var(--border-soft);padding-top:var(--spacing-32)}.setup-section h2{margin:0 0 var(--spacing-16);color:var(--accent);font-size:var(--text-small);font-weight:var(--font-weight-medium);letter-spacing:0;text-transform:uppercase}.setup-section p,.setup-section li{color:var(--color-subtle-gray);font-size:15px;line-height:1.65}.setup-section ul{margin:12px 0 0;padding-left:22px}.setup-section pre{border:1px solid var(--border-soft);border-radius:var(--radius-panel);background:var(--surface-raised);color:var(--text-primary);padding:var(--spacing-16);margin:12px 0 0;font-size:13px;line-height:1.7;overflow-x:auto}.setup-section code{color:var(--text-primary)}.setup-note{margin-top:var(--section-gap);border:1px solid var(--border-strong);border-radius:var(--radius-cards);background:var(--surface-raised);padding:var(--spacing-16)}.setup-note p{color:var(--color-subtle-gray);margin:0;font-size:15px;line-height:1.65}.marketing-page{--marketing-max:var(--page-max-width);background:linear-gradient(var(--grid-line) 1px,transparent 1px),linear-gradient(90deg,var(--grid-line) 1px,transparent 1px),var(--surface-dark);background-size:32px 32px;min-height:100vh}.marketing-hero{border-bottom:1px solid var(--border-soft);min-height:100vh;padding-bottom:var(--spacing-64)}.marketing-nav{z-index:6;width:min(var(--marketing-max),calc(100vw - 40px));justify-content:flex-end;align-items:center;gap:var(--spacing-16);padding:var(--spacing-16) 0;border-bottom:1px solid var(--border-soft);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);font-size:var(--text-small);background:#111111f0;margin:0 auto;display:flex;position:sticky;top:0}.marketing-nav a{color:var(--text-primary);text-decoration:none}.marketing-nav a:hover{color:var(--accent);text-decoration:underline}.marketing-wordmark{color:var(--text-primary);font-weight:var(--font-weight-medium);align-items:center;gap:9px;margin-right:auto;text-decoration:none;display:inline-flex}.marketing-wordmark .brand-mark{width:26px;height:26px}.hero-copy{width:min(var(--marketing-max),calc(100vw - 40px));gap:var(--spacing-64);padding:var(--spacing-80) 0 var(--spacing-32);grid-template-columns:minmax(0,760px) minmax(260px,360px);align-items:end;margin:0 auto;display:grid}.hero-copy-main{min-width:0}.hero-kicker,.section-heading p{color:var(--accent);font-size:var(--text-small);font-weight:var(--font-weight-medium);letter-spacing:0;text-transform:uppercase;margin:0}.hero-copy h1{margin:var(--spacing-16) 0 0;color:var(--text-primary);font-size:var(--text-display);font-weight:var(--font-weight-medium);line-height:var(--leading-display);letter-spacing:0}.hero-subtitle{max-width:720px;margin:var(--spacing-16) 0 0;color:var(--text-primary);font-size:40px;font-weight:var(--font-weight-medium);line-height:1}.hero-body{max-width:640px;margin:var(--spacing-16) 0 0;color:var(--color-subtle-gray);font-size:var(--text-body);line-height:var(--leading-body)}.hero-actions{margin-top:var(--spacing-24);flex-wrap:wrap;gap:10px;display:flex}.hero-primary,.hero-secondary{justify-content:center;align-items:center;gap:var(--spacing-8);min-height:42px;padding:0 var(--spacing-16);font-size:var(--text-body);font-weight:var(--font-weight-medium);text-decoration:none;display:inline-flex}.hero-primary{border:1px solid var(--accent);background:var(--accent);color:var(--color-midnight-ink)}.hero-primary:hover{border-color:var(--site-accent-hover);background:var(--site-accent-hover);color:var(--color-midnight-ink);text-decoration:none}.hero-secondary{border:1px solid var(--accent);color:var(--accent);background:0 0}.hero-secondary:hover{background:var(--site-accent-muted);text-decoration:none}.hero-quickstart{border-left:2px solid var(--accent);padding:2px 0 2px var(--spacing-16);justify-items:start;gap:10px;display:grid}.hero-quickstart span{color:var(--text-secondary);font-size:var(--text-small);font-weight:var(--font-weight-medium)}.hero-quickstart code{max-width:100%;color:var(--text-primary);font-family:var(--font-mono);font-size:var(--text-small);white-space:nowrap;line-height:1.55;overflow-x:auto}.hero-scene{width:min(var(--marketing-max),calc(100vw - 40px));border:1px solid var(--ide-border);border-radius:var(--radius-cards);background:var(--ide-bg);margin:0 auto;overflow:hidden}.scene-topbar,.scene-shell,.scene-submit{min-width:0}.scene-topbar{justify-content:space-between;align-items:center;gap:var(--spacing-16);border-bottom:1px solid var(--ide-border);background:var(--ide-bg-muted);padding:10px 12px;display:flex}.scene-brand,.scene-meta,.terminal-header,.feedback-title{align-items:center;gap:var(--spacing-8);min-width:0;color:var(--ide-fg-muted);font-size:var(--text-small);display:flex}.scene-meta span,.feedback-title span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.scene-shell{background:var(--ide-bg);grid-template-rows:auto minmax(0,1fr) auto;grid-template-columns:minmax(0,1fr);display:grid}.scene-terminal{border-bottom:1px solid var(--ide-border);background:var(--ide-bg);grid-template-columns:auto minmax(0,1fr);align-items:center;gap:14px;padding:10px 12px;display:grid}.terminal-lines{min-width:0;color:var(--ide-fg-muted);font-family:var(--font-mono);font-size:var(--text-small);white-space:nowrap;gap:14px;display:flex;overflow:hidden}.terminal-lines p{margin:0}.terminal-lines span{color:var(--ide-success)}.scene-review{min-height:252px;position:relative;overflow:hidden}.scene-file-header{align-items:center;gap:var(--spacing-8);border:0;border-bottom:1px solid var(--ide-border);background:var(--ide-bg-muted);width:100%;color:var(--ide-fg);cursor:default;text-align:left;grid-template-columns:auto auto minmax(0,1fr) auto auto;padding:9px 12px;display:grid}.scene-file-header span{font-family:var(--font-mono);font-size:var(--text-small);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.scene-add{color:var(--ide-success);font-size:var(--text-small)}.scene-del{color:var(--ide-danger);font-size:var(--text-small)}.scene-hidden,.scene-hunk{background:var(--ide-accent-muted);width:100%;color:var(--ide-fg-muted);font-family:var(--font-mono);font-size:var(--text-small);text-align:left;border:0}.scene-hidden{padding:var(--spacing-8) 12px;cursor:pointer}.scene-hidden:hover{color:var(--ide-accent)}.scene-hunk{padding:var(--spacing-8) 12px}.scene-row{min-height:27px;color:var(--ide-fg);font-family:var(--font-mono);font-size:var(--text-small);grid-template-columns:48px 48px minmax(480px,1fr);align-items:center;display:grid}.scene-row span{border-right:1px solid var(--ide-border);color:var(--ide-fg-muted);text-align:right;padding-right:9px;line-height:27px}.scene-row.add span{background:var(--ide-success-gutter)}.scene-row.remove span{background:var(--ide-danger-gutter)}.scene-row code{white-space:pre;padding-left:12px}.scene-row.add{background:var(--ide-success-muted)}.scene-row.remove{background:var(--ide-danger-muted)}.scene-row.selected{outline:1px solid var(--ide-accent-border);outline-offset:-1px}.scene-row.dimmed{color:var(--ide-fg-muted)}.scene-comment{align-items:center;gap:var(--spacing-8);margin:var(--spacing-8) 0 0 112px;border:1px solid var(--ide-warning-border);border-radius:var(--radius-nav);background:var(--ide-warning-muted);color:var(--ide-fg);font-size:var(--text-small);padding:7px 9px;display:inline-flex}.scene-comment svg{color:var(--ide-warning)}.scene-popover{top:126px;right:var(--spacing-24);border:1px solid var(--ide-border);border-radius:var(--radius-panel);background:var(--ide-bg-muted);width:280px;padding:12px;position:absolute}.scene-popover strong,.scene-popover span{display:block}.scene-popover strong{color:var(--ide-fg);font-size:13px;font-weight:var(--font-weight-medium)}.scene-popover span{color:var(--ide-fg-muted);font-size:var(--text-small);margin-top:3px}.scene-popover p{border:1px solid var(--ide-border);border-radius:var(--radius-nav);background:var(--ide-bg);color:var(--ide-fg);margin:10px 0;padding:9px;font-size:13px;line-height:1.45}.scene-popover button,.scene-submit button{border:1px solid var(--accent);background:var(--accent);min-height:30px;color:var(--color-midnight-ink);font-size:var(--text-small);font-weight:var(--font-weight-medium);align-items:center;gap:7px;padding:0 10px;display:inline-flex}.scene-feedback{border-top:1px solid var(--ide-border);background:var(--ide-bg);grid-template-columns:minmax(0,.46fr) minmax(0,1fr);align-items:start;gap:14px;padding:10px 12px;display:grid}.scene-feedback pre{color:var(--ide-fg-muted);font-family:var(--font-mono);margin:0;font-size:11px;line-height:1.5;overflow:hidden}.scene-submit{border-top:1px solid var(--ide-border);background:var(--ide-bg-muted);color:var(--ide-fg-muted);justify-content:flex-end;align-items:center;gap:12px;padding:10px;font-size:13px;display:flex}.marketing-band{border-bottom:1px solid var(--border-soft);padding:var(--spacing-80) max(20px,calc((100vw - var(--marketing-max)) / 2))}.section-heading{gap:10px;max-width:760px;display:grid}.section-heading h2{max-width:720px;color:var(--text-primary);font-size:40px;font-weight:var(--font-weight-medium);letter-spacing:0;margin:0;line-height:1.06}.install-grid{margin-top:var(--spacing-32);border-top:1px solid var(--border-soft);gap:0;display:grid}.command-line{border-bottom:1px solid var(--border-soft);color:var(--color-subtle-gray);grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:10px;padding:13px 0;display:grid}.command-line>span{color:var(--accent);font-family:var(--font-mono)}.command-line code{color:var(--text-primary);font-family:var(--font-mono);white-space:nowrap;font-size:13px;overflow:auto}.marketing-copy{border:1px solid var(--accent);min-height:30px;color:var(--accent);font-size:var(--text-small);font-weight:var(--font-weight-medium);cursor:pointer;background:0 0;justify-content:center;align-items:center;gap:7px;padding:0 10px;display:inline-flex}.marketing-copy:hover{background:var(--site-accent-muted)}.agent-prompt{margin-top:var(--spacing-24);border-left:2px solid var(--accent);padding-left:var(--spacing-16);grid-template-columns:minmax(0,1fr) auto;gap:12px;display:grid}.agent-prompt div{align-items:center;gap:var(--spacing-8);color:var(--accent);font-size:13px;font-weight:var(--font-weight-medium);display:flex}.agent-prompt p{color:var(--text-primary);font-family:var(--font-mono);grid-column:1/-1;margin:0;font-size:13px;line-height:1.6}.workflow-grid{margin-top:var(--spacing-32);border-top:1px solid var(--border-soft);grid-template-columns:repeat(4,minmax(0,1fr));gap:0;display:grid}.workflow-step{border-right:1px solid var(--border-soft);min-height:150px;padding:var(--spacing-16) var(--spacing-16) var(--spacing-16) 0}.workflow-step:last-child{border-right:0}.workflow-step+.workflow-step{padding-left:var(--spacing-16)}.workflow-step span{color:var(--accent);font-family:var(--font-mono);font-size:var(--text-small)}.workflow-step h3{margin:var(--spacing-32) 0 0;color:var(--text-primary);font-size:var(--text-subheading);font-weight:var(--font-weight-medium);letter-spacing:0}.workflow-step p,.skill-copy p,.contract-copy p{color:var(--color-subtle-gray);font-size:14px;line-height:1.65}.skill-layout{gap:var(--spacing-24);margin-top:var(--spacing-32);border-top:1px solid var(--border-soft);padding-top:var(--spacing-24);grid-template-columns:minmax(260px,.56fr) minmax(0,1fr);align-items:start;display:grid}.skill-copy{padding-right:var(--spacing-16)}.skill-copy h2{color:var(--text-primary);font-size:var(--text-subheading);font-weight:var(--font-weight-medium);letter-spacing:0;margin:0}.skill-copy p{margin:12px 0 0}.skill-copy a{margin-top:var(--spacing-16);color:var(--accent);font-size:13px;font-weight:var(--font-weight-medium);text-decoration:none;display:inline-flex}.skill-copy a:hover{text-decoration:underline}.skill-commands{gap:0;display:grid}.contract-layout{gap:var(--spacing-24);margin-top:var(--spacing-32);grid-template-columns:minmax(0,1fr) minmax(280px,.7fr);align-items:start;display:grid}.contract-layout pre{border-left:2px solid var(--border-soft);color:var(--color-subtle-gray);font-family:var(--font-mono);padding:2px 0 2px var(--spacing-16);margin:0;font-size:13px;line-height:1.7;overflow:auto}.contract-copy{gap:12px;display:grid}.contract-copy p{border-left:2px solid var(--accent);margin:0;padding-left:12px}@media(max-width:980px){.marketing-nav,.hero-copy,.hero-scene{width:min(720px,100vw - 32px)}.hero-copy{gap:var(--spacing-32);padding:var(--spacing-64) 0 var(--spacing-32);grid-template-columns:1fr}.hero-copy h1{font-size:64px}.hero-subtitle,.section-heading h2{font-size:32px}.hero-quickstart{max-width:460px}.scene-terminal,.scene-feedback{grid-template-columns:1fr}.terminal-lines{gap:3px;display:grid}.scene-submit{justify-content:space-between}.workflow-grid,.skill-layout,.contract-layout{grid-template-columns:1fr}}@media(max-width:720px){.review-shell{width:calc(100vw - 18px)}.topbar{flex-direction:column;align-items:flex-start}.branch-pill{max-width:100%}.file-header{grid-template-columns:auto auto minmax(0,1fr) auto auto}.rename-path{display:none}.diff-row{grid-template-columns:46px 46px 22px minmax(520px,1fr)}.inline-comment{margin-left:108px}.draft-comment-shell{padding-left:114px}.draft-comment-title{min-height:auto;padding:12px var(--spacing-16);flex-direction:column;align-items:flex-start;gap:4px}.draft-comment-label{text-align:left;white-space:normal}.submit-bar{flex-direction:column;align-items:stretch}.submit-actions{justify-content:space-between}}@media(max-width:640px){.marketing-nav,.setup-nav{white-space:nowrap;justify-content:flex-start;gap:14px;overflow-x:auto}.marketing-nav{width:calc(100vw - 28px)}.marketing-wordmark{min-width:max-content}.hero-copy,.hero-scene{width:calc(100vw - 28px)}.hero-copy{padding-top:var(--spacing-48)}.hero-copy h1,.setup-header h1{font-size:48px}.hero-subtitle,.section-heading h2{font-size:var(--text-heading);line-height:var(--leading-heading)}.hero-body{font-size:15px}.hero-quickstart{max-width:none}.scene-meta,.scene-terminal,.scene-feedback,.scene-popover{display:none}.scene-review{min-height:230px}.scene-row{grid-template-columns:38px 38px minmax(360px,1fr)}.scene-comment{margin-left:88px}.command-line,.agent-prompt{grid-template-columns:1fr}.workflow-grid,.skill-layout{border-top:1px solid var(--border-soft)}.workflow-step,.workflow-step+.workflow-step{border-right:0;border-bottom:1px solid var(--border-soft);padding-left:0;padding-right:0}.workflow-step:last-child{border-bottom:0}.marketing-band{padding:var(--spacing-64) var(--spacing-16)}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}
|