pelican-cc 0.2.1 → 0.2.2
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/dist/server/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../package.json", "../../src/server/config.ts", "../../src/server/security.ts", "../../src/server/auth.ts", "../../src/server/demo-image.ts", "../../src/server/demo-visuals.ts", "../../src/server/demo.ts", "../../src/shared/tasks.ts", "../../src/server/archive.ts", "../../src/server/folders.ts", "../../src/server/live.ts", "../../src/server/normalize.ts", "../../src/server/history.ts", "../../src/server/transcripts.ts", "../../src/server/sessions.ts", "../../src/server/actions.ts", "../../src/server/feed.ts", "../../src/server/files.ts", "../../src/server/trust.ts", "../../src/server/models.ts", "../../src/server/git.ts", "../../src/server/project-files.ts", "../../src/server/push.ts", "../../src/server/shell.ts", "../../src/server/usage.ts", "../../src/server/runner.ts", "../../src/server/workspace.ts", "../../src/server/app.ts", "../../src/server/server.ts", "../../src/shared/qr.ts", "../../src/server/qr.ts", "../../src/server/cli.ts"],
|
|
4
|
-
"sourcesContent": ["{\n \"name\": \"pelican-cc\",\n \"version\": \"0.2.1\",\n \"description\": \"Your Claude Code sessions, from any browser: a phone-first web app that runs next to Claude Code.\",\n \"keywords\": [\n \"claude\",\n \"claude-code\",\n \"agent\",\n \"mobile\",\n \"web-ui\",\n \"ai-coding\"\n ],\n \"homepage\": \"https://github.com/Ki-Seki/pelican#readme\",\n \"bugs\": \"https://github.com/Ki-Seki/pelican/issues\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/Ki-Seki/pelican.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Shichao Song\",\n \"type\": \"module\",\n \"bin\": {\n \"pelican\": \"dist/server/index.js\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\",\n \"CHANGELOG.md\"\n ],\n \"engines\": {\n \"node\": \">=22.12\"\n },\n \"scripts\": {\n \"dev\": \"node scripts/dev.mjs\",\n \"demo\": \"node dist/server/index.js --demo\",\n \"build\": \"vite build && node scripts/build-server.mjs\",\n \"start\": \"node dist/server/index.js\",\n \"typecheck\": \"tsc --noEmit -p tsconfig.json\",\n \"test\": \"vitest run\",\n \"screenshots\": \"node scripts/screenshots.mjs\",\n \"prepublishOnly\": \"npm run typecheck && npm test && npm run build\"\n },\n \"dependencies\": {\n \"@anthropic-ai/claude-agent-sdk\": \"^0.3.318\",\n \"@hono/node-server\": \"^2.1.6\",\n \"hono\": \"^4.13.16\",\n \"qrcode-generator\": \"^1.4.4\",\n \"web-push\": \"^3.6.7\",\n \"ws\": \"^8.22.1\"\n },\n \"devDependencies\": {\n \"@tailwindcss/vite\": \"^4.3.4\",\n \"@tanstack/react-query\": \"^5.105.3\",\n \"@testing-library/react\": \"^16.3.0\",\n \"@types/node\": \"^24.0.0\",\n \"@types/react\": \"^19.1.0\",\n \"@types/react-dom\": \"^19.1.0\",\n \"@types/web-push\": \"^3.6.4\",\n \"@types/ws\": \"^8.18.1\",\n \"@vitejs/plugin-react\": \"^6.2.1\",\n \"diff\": \"^9.0.0\",\n \"esbuild\": \"^0.27.0\",\n \"jsdom\": \"^27.0.0\",\n \"lucide-react\": \"^1.68.0\",\n \"playwright-core\": \"^1.64.1\",\n \"react\": \"^19.3.1\",\n \"react-dom\": \"^19.3.1\",\n \"react-markdown\": \"^10.1.0\",\n \"rehype-highlight\": \"^7.0.2\",\n \"remark-breaks\": \"^4.0.0\",\n \"remark-gfm\": \"^4.0.1\",\n \"tailwindcss\": \"^4.3.4\",\n \"tsx\": \"^4.23.15\",\n \"typescript\": \"^7.0.2\",\n \"vite\": \"^8.3.5\",\n \"vitest\": \"^5.1.0\"\n }\n}\n", "import { existsSync } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst here = path.dirname(fileURLToPath(import.meta.url));\nconst home = os.homedir();\n\n/** Directory of the built web app: next to the bundled server, or `dist/web` when run from source. */\nfunction findWebDir(): string | undefined {\n for (const candidate of [path.join(here, '../web'), path.join(here, '../../dist/web')]) {\n if (existsSync(path.join(candidate, 'index.html'))) return candidate;\n }\n return undefined;\n}\n\nconst list = (value: string | undefined) => (value ?? '').split(',').map((s) => s.trim()).filter(Boolean);\n\n/** Settings Pelican could not use, said once at start (cli.ts). */\nexport const configWarnings: string[] = [];\n\n/**\n * Web origins (`https://host[:port]`), and nothing looser: no `*`, no `null`, no bare scheme,\n * no path. With `wildcardHost`, `https://*.example.com` is allowed too (for frame-ancestors).\n */\nfunction origins(name: string, value: string | undefined, wildcardHost = false): string[] {\n const kept: string[] = [];\n for (const entry of (value ?? '').split(/[\\s,]+/).filter(Boolean)) {\n const wildcard = wildcardHost && /^https?:\\/\\/\\*\\.[^*]+$/i.test(entry);\n let url: URL | undefined;\n try {\n url = /^https?:\\/\\//i.test(entry) ? new URL(wildcard ? entry.replace('*.', 'wildcard.') : entry) : undefined;\n } catch {\n url = undefined;\n }\n const origin = url && (wildcard ? url.origin.replace('://wildcard.', '://*.') : url.origin);\n // Host names of letters, digits, dots and dashes (or an IPv6 address): a URL may hold\n // characters such as `;` that would end a header's directive.\n if (url && origin && url.pathname === '/' && !url.search && !url.hash && !url.username && !url.password\n && (wildcard ? /^https?:\\/\\/\\*\\.[a-z0-9.-]+(:\\d+)?$/ : /^https?:\\/\\/([a-z0-9.-]+|\\[[0-9a-f:.]+\\])(:\\d+)?$/).test(origin)) {\n kept.push(origin);\n } else {\n configWarnings.push(`${name}: ignored \"${entry}\"; use an origin like https://pelican.example.com`);\n }\n }\n return kept;\n}\n\nexport type AuthMode = 'auto' | 'none';\n\n/**\n * Settings from the environment; the command line (`cli.ts`) overrides some of\n * them before the server starts.\n */\nexport const config = {\n host: process.env.PELICAN_HOST ?? '127.0.0.1',\n port: Number(process.env.PELICAN_PORT ?? 7860),\n /** Name shown in the app, for telling machines apart. */\n machine: process.env.PELICAN_MACHINE ?? (os.hostname().split('.')[0] || 'local'),\n home,\n claudeHome: process.env.CLAUDE_CONFIG_DIR ?? path.join(home, '.claude'),\n /** The password hash lives here. */\n configDir: process.env.PELICAN_CONFIG_DIR ?? path.join(home, '.config', 'pelican'),\n /** Uploads, push keys and subscriptions. */\n stateDir: process.env.PELICAN_STATE_DIR ?? path.join(home, '.local', 'state', 'pelican'),\n webDir: findWebDir(),\n /**\n * auto: sign in with the password when one is set (`pelican password`), else\n * with the access token in the link Pelican prints. none: no sign-in, for\n * when something in front already authenticates (a proxy, a gateway).\n */\n auth: (process.env.PELICAN_AUTH === 'none' ? 'none' : 'auto') as AuthMode,\n /** Origins, besides the page's own, allowed to send requests (for example a proxy's public URL). */\n allowedOrigins: origins('PELICAN_ALLOWED_ORIGINS', process.env.PELICAN_ALLOWED_ORIGINS),\n /** Host names the server answers to, besides this machine's own names and addresses (and *.ts.net). */\n allowedHosts: list(process.env.PELICAN_ALLOWED_HOSTS),\n /**\n * A proxy on this machine (Tailscale, Caddy, nginx) sets X-Forwarded-For truthfully: limit sign-in\n * attempts by the client it names. Off by default, since anyone can write that header.\n */\n trustedProxy: process.env.PELICAN_TRUSTED_PROXY === '1',\n /** Pages, besides Pelican's own origin, that may show Pelican in a frame (space or comma separated). */\n frameAncestors: origins('PELICAN_FRAME_ANCESTORS', process.env.PELICAN_FRAME_ANCESTORS, true),\n /** Where a tapped notification opens a session; `{id}` is replaced. Default: the page that subscribed. */\n sessionUrl: process.env.PELICAN_SESSION_URL || undefined,\n /** A Claude process with nothing to do is closed after this long; the next message resumes the session. */\n idleCloseMs: Number(process.env.PELICAN_IDLE_CLOSE_MS ?? 30 * 60 * 1000),\n /** A transcript written by someone else this recently counts as live. */\n liveWindowMs: 45_000,\n claudeExecutable: process.env.PELICAN_CLAUDE_PATH || undefined,\n /** Demo mode: made-up sessions, nothing runs. */\n demo: false,\n};\n\nexport type Config = typeof config;\n", "import os from 'node:os';\n\nimport { config } from './config.js';\n\nconst hostname = (host: string) => {\n const h = host.trim().toLowerCase();\n if (h.startsWith('[')) return h.slice(1, h.indexOf(']'));\n const colon = h.lastIndexOf(':');\n return colon > -1 && h.indexOf(':') === colon ? h.slice(0, colon) : h;\n};\n\nexport function isLoopback(host: string): boolean {\n const h = hostname(host);\n return h === 'localhost' || h.endsWith('.localhost') || h === '::1' || /^127(\\.\\d{1,3}){3}$/.test(h);\n}\n\n/** True for addresses that only this machine can reach. */\nexport function listensLocally(host: string): boolean {\n return isLoopback(host);\n}\n\nfunction knownHosts(): Set<string> {\n const names = new Set<string>();\n const machine = os.hostname().toLowerCase();\n names.add(machine);\n names.add(machine.split('.')[0]!);\n // This computer's own addresses: a phone on the network may open it by IP.\n for (const address of Object.values(os.networkInterfaces()).flat()) if (address) names.add(address.address.toLowerCase());\n if (config.host !== '0.0.0.0' && config.host !== '::') names.add(hostname(config.host));\n for (const host of config.allowedHosts) {\n const name = hostname(host);\n if (name) names.add(name);\n }\n for (const origin of config.allowedOrigins) {\n try {\n names.add(new URL(origin).hostname.toLowerCase().replace(/^\\[(.*)\\]$/, '$1'));\n } catch {\n // Not a URL; ignored.\n }\n }\n return names;\n}\n\n/**\n * The server answers only to names that point at it: this computer's names and\n * addresses, loopback, Tailscale names (*.ts.net, which only Tailscale can point\n * anywhere) and configured ones. Otherwise a web page could rebind its own\n * domain to 127.0.0.1 (DNS rebinding) and use Pelican through your browser.\n */\nexport function hostAllowed(host: string | undefined): boolean {\n if (!host) return false;\n const name = hostname(host);\n if (!name) return false;\n if (isLoopback(host)) return true;\n if (name.endsWith('.ts.net')) return true;\n return knownHosts().has(name);\n}\n\n/**\n * Requests that change something must come from this app's own page, or from\n * an origin allowed in the config, so a page on another site cannot drive Claude.\n */\nexport function originAllowed(origin: string | undefined, ...hosts: Array<string | undefined>): boolean {\n if (!origin) return true;\n let url: URL;\n try {\n url = new URL(origin);\n } catch {\n return false;\n }\n if (config.allowedOrigins.includes(url.origin)) return true;\n return hosts.flatMap((h) => (h ? h.split(',').map((s) => s.trim()) : [])).includes(url.host);\n}\n", "import { createHash, createHmac, randomBytes, scrypt, timingSafeEqual } from 'node:crypto';\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { config } from './config.js';\n\n/**\n * Who may use Pelican. Pelican runs commands as you, so nothing gets in\n * without proof, also on this computer, where every account and process can\n * reach 127.0.0.1:\n *\n * - token (the default): a random access token, kept in the config folder and\n * printed in the link Pelican shows at start. Opening that link, or pasting\n * the token, signs the browser in, the way Jupyter does.\n * - password: set on the computer with `pelican password` (never over the\n * network), stored as a scrypt hash. For reaching Pelican from a phone.\n * - none: `--no-auth`, for when something in front already authenticates\n * (a reverse proxy, a gateway), and for the demo, which holds nothing private.\n *\n * Signing in sets a cookie signed with a secret that belongs to the token or\n * the password and changes with it, so resetting either signs every browser out.\n */\nexport type AuthMode = 'none' | 'password' | 'token';\n\ntype Password = { version: 1; salt: string; hash: string; secret: string; N: number; r: number; p: number };\ntype Token = { version: 1; token: string; secret: string };\n\nconst passwordFile = () => path.join(config.configDir, 'auth.json');\nconst tokenFile = () => path.join(config.configDir, 'token.json');\nconst SESSION_DAYS = 30;\nconst PARAMS = { N: 16384, r: 8, p: 1 };\n\nlet password: Password | undefined;\nlet token: Token | undefined;\nlet loaded = false;\n\nfunction derive(text: string, salt: Buffer, params: { N: number; r: number; p: number }): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n scrypt(text.normalize('NFKC'), salt, 32, { ...params, maxmem: 64 * 1024 * 1024 }, (error, key) => (error ? reject(error) : resolve(key)));\n });\n}\n\nasync function writePrivate(file: string, value: unknown): Promise<void> {\n await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });\n await writeFile(file, `${JSON.stringify(value, null, 2)}\\n`, { mode: 0o600 });\n}\n\nconst readJson = async <T>(file: string): Promise<T | undefined> => {\n try {\n return JSON.parse(await readFile(file, 'utf8')) as T;\n } catch {\n return undefined;\n }\n};\n\n/** Reads the password and the access token, making the token on first use. */\nexport async function loadAuth(): Promise<void> {\n if (loaded) return;\n password = await readJson<Password>(passwordFile());\n if (config.auth !== 'none') {\n token = await readJson<Token>(tokenFile());\n if (!token?.token || !token.secret) await resetToken();\n }\n loaded = true;\n}\n\nexport function authMode(): AuthMode {\n if (config.auth === 'none') return 'none';\n return password ? 'password' : 'token';\n}\n\nexport function passwordSet(): boolean {\n return Boolean(password);\n}\n\n/** The access token, for the link Pelican prints; undefined when it is not in use. */\nexport function accessToken(): string | undefined {\n return authMode() === 'token' ? token?.token : undefined;\n}\n\n/** A new access token: links and browsers signed in with the old one stop working. */\nexport async function resetToken(): Promise<string> {\n token = { version: 1, token: randomBytes(24).toString('base64url'), secret: randomBytes(32).toString('base64') };\n await writePrivate(tokenFile(), token);\n return token.token;\n}\n\nexport async function setPassword(text: string): Promise<void> {\n const salt = randomBytes(16);\n const hash = await derive(text, salt, PARAMS);\n password = { version: 1, salt: salt.toString('base64'), hash: hash.toString('base64'), secret: randomBytes(32).toString('base64'), ...PARAMS };\n await writePrivate(passwordFile(), password);\n loaded = true;\n}\n\nexport async function clearPassword(): Promise<void> {\n await rm(passwordFile(), { force: true });\n password = undefined;\n}\n\nexport async function checkPassword(text: string): Promise<boolean> {\n if (!password) return false;\n const expected = Buffer.from(password.hash, 'base64');\n const actual = await derive(text, Buffer.from(password.salt, 'base64'), password);\n return actual.length === expected.length && timingSafeEqual(actual, expected);\n}\n\nconst same = (a: string, b: string) => {\n const x = createHash('sha256').update(a).digest();\n const y = createHash('sha256').update(b).digest();\n return timingSafeEqual(x, y);\n};\n\n/** Whether `given` is the access token (only while the token is how Pelican signs in). */\nexport function checkToken(given: string | undefined): boolean {\n const current = accessToken();\n return Boolean(current && given && same(given, current));\n}\n\n/** The secret that signs cookies in the current mode. */\nconst secret = () => (authMode() === 'password' ? password?.secret : authMode() === 'token' ? token?.secret : undefined);\n\n/** Cookie name: one per secret, so two Pelicans on one origin do not sign each other out. */\nexport function cookieName(): string {\n const value = secret();\n return `pelican_${value ? createHash('sha256').update(value).digest('hex').slice(0, 8) : 'none'}`;\n}\n\nfunction sign(expires: number): string {\n return createHmac('sha256', Buffer.from(secret() ?? '', 'base64')).update(String(expires)).digest('base64url');\n}\n\nexport function newSessionCookie(): { name: string; value: string; maxAge: number } {\n const maxAge = SESSION_DAYS * 24 * 3600;\n const expires = Date.now() + maxAge * 1000;\n return { name: cookieName(), value: `${expires}.${sign(expires)}`, maxAge };\n}\n\nfunction cookieValue(header: string | undefined, name: string): string | undefined {\n for (const part of (header ?? '').split(';')) {\n const [key, ...rest] = part.trim().split('=');\n if (key === name) return rest.join('=');\n }\n return undefined;\n}\n\n/**\n * Whether a request may use Pelican: always when nothing is required, else with a\n * valid session cookie, or (token mode) the token as `Authorization: Bearer <token>`.\n */\nexport function signedIn(cookieHeader: string | undefined, authorization?: string): boolean {\n const mode = authMode();\n if (mode === 'none') return true;\n if (mode === 'token' && authorization?.startsWith('Bearer ') && checkToken(authorization.slice(7).trim())) return true;\n if (!secret()) return false;\n const value = cookieValue(cookieHeader, cookieName());\n if (!value) return false;\n const [expires, mac] = value.split('.');\n const when = Number(expires);\n if (!mac || !Number.isFinite(when) || when < Date.now()) return false;\n const expected = Buffer.from(sign(when));\n const given = Buffer.from(mac);\n return expected.length === given.length && timingSafeEqual(expected, given);\n}\n\n// Failed sign-ins: at most 10 per client and 30 in all every 10 minutes, so neither\n// a rotating client key nor many clients together can guess for long.\nconst WINDOW_MS = 10 * 60_000;\nconst PER_CLIENT = 10;\nconst IN_ALL = 30;\nconst failures = new Map<string, number[]>();\nlet allFailures: number[] = [];\n\nconst recent = (times: number[], now: number) => times.filter((t) => now - t < WINDOW_MS);\n\n/** Whether this client may try to sign in now. */\nexport function attemptAllowed(client: string, now = Date.now()): boolean {\n allFailures = recent(allFailures, now);\n if (allFailures.length >= IN_ALL) return false;\n return recent(failures.get(client) ?? [], now).length < PER_CLIENT;\n}\n\n/** Counts a wrong password or token against the client and against everyone. */\nexport function noteFailure(client: string, now = Date.now()): void {\n allFailures.push(now);\n failures.set(client, [...recent(failures.get(client) ?? [], now), now]);\n // Forget clients whose failures have all expired, and never keep more than a few thousand.\n if (failures.size > 1000) {\n for (const [key, times] of failures) if (recent(times, now).length === 0) failures.delete(key);\n while (failures.size > 5000) failures.delete(failures.keys().next().value!);\n }\n}\n\n/**\n * Starts a sign-in attempt: counted as a failure at once, before the slow check, so many\n * requests sent together cannot all get past the limit. Returns undefined when the client\n * is over the limit, else a function that takes the attempt back once it succeeded.\n */\nexport function beginAttempt(client: string, now = Date.now()): (() => void) | undefined {\n if (!attemptAllowed(client, now)) return undefined;\n noteFailure(client, now);\n return () => {\n const drop = (times: number[] | undefined) => {\n const at = times?.lastIndexOf(now) ?? -1;\n if (times && at >= 0) times.splice(at, 1);\n };\n drop(allFailures);\n drop(failures.get(client));\n };\n}\n\n/** For tests: start counting afresh. */\nexport function resetAttempts(): void {\n failures.clear();\n allFailures = [];\n}\n\n// One-time sign-in codes, for the QR code that opens Pelican on a phone: the page never sees\n// the access token or the password, and a photo of the code is useless after two minutes.\nconst CODE_MS = 2 * 60_000;\nconst codes = new Map<string, number>();\n\n/** A new code that signs one browser in, once, within two minutes. */\nexport function issueCode(now = Date.now()): string {\n for (const [code, expires] of codes) if (expires <= now) codes.delete(code);\n while (codes.size >= 20) codes.delete(codes.keys().next().value!);\n const code = randomBytes(18).toString('base64url');\n codes.set(code, now + CODE_MS);\n return code;\n}\n\n/** Whether `given` is a live code; using it ends it. */\nexport function redeemCode(given: string | undefined, now = Date.now()): boolean {\n if (!given || authMode() === 'none') return false;\n for (const [code, expires] of codes) {\n if (same(code, given)) {\n codes.delete(code);\n return expires > now;\n }\n }\n return false;\n}\n", "// A made-up screenshot the demo's first prompt attaches: a settings page before its dark theme.\nexport const SETTINGS_SCREENSHOT = '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';\n", "// The demo session that draws: a chart as a Claude Desktop visual, and a page Claude writes.\n\nconst DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];\nconst SIGNUPS = [1420, 1588, 1710, 2214, 1966, 1702, 1880];\n\nexport const SIGNUPS_CSV = `day,signups\\n${DAYS.map((d, i) => `${d},${SIGNUPS[i]}`).join('\\n')}\\n`;\n\n/** A bar chart in the vocabulary of Claude Desktop's visuals (box, th, ts, c-*), sized to read on a phone. */\nexport function signupsChart(): string {\n const max = Math.max(...SIGNUPS);\n const bars = SIGNUPS.map((value, i) => {\n const h = Math.round((value / max) * 128);\n const x = 26 + i * 52;\n const y = 196 - h;\n const tone = value === max ? 'c-purple' : 'c-teal';\n return `<g class=\"${tone}\"><rect x=\"${x}\" y=\"${y}\" width=\"38\" height=\"${h}\" rx=\"6\"/></g>`\n + `<text class=\"ts\" x=\"${x + 19}\" y=\"${y - 8}\" text-anchor=\"middle\">${(value / 1000).toFixed(1)}k</text>`\n + `<text class=\"ts\" x=\"${x + 19}\" y=\"216\" text-anchor=\"middle\">${DAYS[i]}</text>`;\n }).join('');\n return `<svg width=\"100%\" viewBox=\"0 0 400 228\" role=\"img\"><title>Signups by day, last week</title>`\n + `<text class=\"th\" x=\"16\" y=\"24\">Signups by day \u00B7 last week</text>`\n + `<text class=\"ts\" x=\"16\" y=\"42\">12,480 in total, 18% more than the week before</text>`\n + `<line x1=\"16\" y1=\"196.5\" x2=\"384\" y2=\"196.5\" style=\"stroke:var(--line)\"/>`\n + `${bars}</svg>`;\n}\n\nexport const REPORT_HTML = `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Signups \u00B7 week 45</title>\n<style>\n :root { color-scheme: light dark; --ink: #17181a; --soft: #6b6e75; --line: #e6e6e3; --card: #fff; --bg: #f6f6f3; --a: #0e7490; --b: #7c6cf2; }\n @media (prefers-color-scheme: dark) { :root { --ink: #ececea; --soft: #a5a8ae; --line: #2a2d31; --card: #16181b; --bg: #0e0f11; --a: #3ba7bd; --b: #9d90f5; } }\n * { box-sizing: border-box; margin: 0; }\n body { background: var(--bg); color: var(--ink); font: 15px/1.55 -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; padding: 28px 20px 40px; }\n main { max-width: 640px; margin: 0 auto; }\n .eyebrow { color: var(--a); font-weight: 600; font-size: 13px; letter-spacing: .02em; }\n h1 { margin-top: 6px; font-size: 30px; line-height: 1.1; letter-spacing: -.03em; }\n .kpis { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 22px; }\n .kpi { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 14px; }\n .kpi b { display: block; font-size: 24px; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }\n .kpi span { color: var(--soft); font-size: 12.5px; }\n .up { color: #16a34a; }\n .chart { margin-top: 14px; background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 16px 14px 10px; }\n .bars { display: flex; align-items: flex-end; gap: 8px; height: 170px; }\n .bar { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 6px; height: 100%; }\n .bar i { display: block; width: 100%; border-radius: 8px 8px 4px 4px; background: color-mix(in srgb, var(--a) 75%, transparent); height: 0; transition: height .9s cubic-bezier(.2,.8,.2,1); }\n .bar.top i { background: var(--b); }\n .bar small { color: var(--soft); font-size: 11.5px; }\n p.note { margin-top: 16px; color: var(--soft); }\n</style>\n</head>\n<body>\n<main>\n <div class=\"eyebrow\">Growth \u00B7 week 45</div>\n <h1>Signups grew 18%, and Thursday led the week</h1>\n <div class=\"kpis\">\n <div class=\"kpi\"><b data-count=\"12480\">0</b><span>signups</span></div>\n <div class=\"kpi\"><b class=\"up\">+18%</b><span>on week 44</span></div>\n <div class=\"kpi\"><b>Thu</b><span>best day, 2,214</span></div>\n </div>\n <div class=\"chart\"><div class=\"bars\" id=\"bars\"></div></div>\n <p class=\"note\">Thursday's newsletter brought the peak; the weekend held above the weekday average.</p>\n</main>\n<script>\n const days = ${JSON.stringify(DAYS)};\n const values = ${JSON.stringify(SIGNUPS)};\n const max = Math.max(...values);\n const bars = document.getElementById('bars');\n values.forEach((v, i) => {\n const bar = document.createElement('div');\n bar.className = 'bar' + (v === max ? ' top' : '');\n bar.innerHTML = '<small>' + v.toLocaleString() + '</small><i></i><small>' + days[i] + '</small>';\n bars.append(bar);\n requestAnimationFrame(() => requestAnimationFrame(() => { bar.querySelector('i').style.height = (v / max * 120) + 'px'; }));\n });\n for (const el of document.querySelectorAll('[data-count]')) {\n const end = Number(el.dataset.count); const start = performance.now();\n const step = (t) => { const k = Math.min(1, (t - start) / 900); el.textContent = Math.round(end * (1 - Math.pow(1 - k, 3))).toLocaleString(); if (k < 1) requestAnimationFrame(step); };\n requestAnimationFrame(step);\n }\n</script>\n</body>\n</html>\n`;\n", "import { execFile } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport type { ModelOption, RunState } from '../shared/protocol.js';\nimport { config } from './config.js';\nimport { SETTINGS_SCREENSHOT } from './demo-image.js';\nimport { REPORT_HTML, SIGNUPS_CSV, signupsChart } from './demo-visuals.js';\n\n/**\n * `pelican --demo`: a made-up home with a few sessions, for trying Pelican\n * without Claude Code and for screenshots that show no real work. It is\n * written fresh on every start, with times relative to now. Nothing runs.\n */\n\nconst run = promisify(execFile);\nconst MIN = 60_000;\nconst HOUR = 60 * MIN;\n\ntype Block = Record<string, unknown>;\ntype Row = { role: 'user' | 'assistant'; content: string | Block[]; at: number };\n\nconst tool = (id: string, name: string, input: Record<string, unknown>): Block => ({ type: 'tool_use', id, name, input });\nconst result = (id: string, content: string, isError = false): Block => ({ type: 'tool_result', tool_use_id: id, content, ...(isError ? { is_error: true } : {}) });\nconst text = (value: string): Block => ({ type: 'text', text: value });\n\nfunction numbered(source: string): string {\n return source.split('\\n').map((line, i) => `${String(i + 1).padStart(6)}\\t${line}`).join('\\n');\n}\n\ntype DemoSession = { id: string; title: string; project: string; branch: string; rows: (cwd: string) => Row[]; scratch?: boolean };\n\nconst THEME_CSS = `:root {\n --bg: #ffffff;\n --fg: #111111;\n --muted: #6b6b6b;\n --accent: #2563eb;\n}\n\nbody {\n background: var(--bg);\n color: var(--fg);\n}`;\n\nconst SESSIONS: DemoSession[] = [\n {\n id: 'a3f1c2d4-1111-4a1b-8c3d-000000000001',\n title: 'Add a dark theme to the settings page',\n project: 'acme-web',\n branch: 'feat/dark-theme',\n rows: (cwd) => {\n const t = Date.now() - 4 * MIN;\n return [\n {\n role: 'user',\n at: t,\n content: [\n { type: 'image', source: { type: 'base64', media_type: 'image/png', data: SETTINGS_SCREENSHOT } },\n text('Add a dark theme to the settings page, with a toggle. Keep the colors in CSS variables.'),\n ],\n },\n { role: 'assistant', at: t + 4000, content: [text(\"I'll look at how the colors are set up first.\"), tool('t1', 'Read', { file_path: `${cwd}/src/styles/theme.css` })] },\n { role: 'user', at: t + 5000, content: [result('t1', numbered(THEME_CSS))] },\n {\n role: 'assistant',\n at: t + 9000,\n content: [\n text('The colors are already variables, so a dark theme is one more set of values plus a toggle that switches them.'),\n tool('t2', 'TodoWrite', {\n todos: [\n { content: 'Add dark color values', status: 'completed', activeForm: 'Adding dark color values' },\n { content: 'Add the toggle to Settings', status: 'in_progress', activeForm: 'Adding the toggle to Settings' },\n { content: 'Remember the choice', status: 'pending', activeForm: 'Remembering the choice' },\n { content: 'Run the tests', status: 'pending', activeForm: 'Running the tests' },\n ],\n }),\n ],\n },\n { role: 'user', at: t + 9500, content: [result('t2', 'Todos have been modified successfully.')] },\n {\n role: 'assistant',\n at: t + 14000,\n content: [tool('t3', 'Edit', {\n file_path: `${cwd}/src/styles/theme.css`,\n old_string: ' --accent: #2563eb;\\n}',\n new_string: \" --accent: #2563eb;\\n}\\n\\n:root[data-theme='dark'] {\\n --bg: #0b0b0c;\\n --fg: #ededed;\\n --muted: #a1a1a1;\\n --accent: #60a5fa;\\n}\",\n })],\n },\n { role: 'user', at: t + 15000, content: [result('t3', `The file ${cwd}/src/styles/theme.css has been updated successfully.`)] },\n { role: 'assistant', at: t + 19000, content: [text('Now the toggle itself, next to the other display settings:')] },\n ];\n },\n },\n {\n id: 'b7e2a9c1-2222-4b2c-9d4e-000000000002',\n title: 'Why is the nightly export slow?',\n project: 'data-pipeline',\n branch: 'main',\n rows: (cwd) => {\n const t = Date.now() - 26 * MIN;\n return [\n { role: 'user', at: t, content: 'The nightly export went from 4 minutes to 40. Can you find out why?' },\n { role: 'assistant', at: t + 5000, content: [tool('g1', 'Grep', { pattern: 'def export_rows', path: `${cwd}/pipeline` })] },\n { role: 'user', at: t + 6000, content: [result('g1', 'pipeline/export.py:41:def export_rows(batch):')] },\n { role: 'assistant', at: t + 9000, content: [tool('g2', 'Read', { file_path: `${cwd}/pipeline/export.py`, offset: 35, limit: 30 })] },\n { role: 'user', at: t + 10000, content: [result('g2', numbered('def export_rows(batch):\\n for row in batch:\\n owner = db.fetch_owner(row.owner_id)\\n write(row, owner)'))] },\n { role: 'assistant', at: t + 15000, content: [tool('g3', 'Bash', { command: 'python -m cProfile -s cumtime -m pipeline.export --sample 5000 | head -8', description: 'Profile the export on a 5,000-row sample' })] },\n {\n role: 'user',\n at: t + 31000,\n content: [result('g3', ' ncalls tottime cumtime filename:lineno(function)\\n 5000 0.412 61.902 db.py:88(fetch_owner)\\n 1 0.020 62.410 export.py:41(export_rows)\\n 5000 0.301 0.488 writer.py:12(write)')],\n },\n {\n role: 'assistant',\n at: t + 40000,\n content: [text([\n 'Found it: `export_rows` looks up each row\\'s owner one query at a time. Since the owners table grew past a million rows that lookup is no longer cached, so 5,000 rows cost 5,000 round trips.',\n '',\n '| | Before | After batching |',\n '|---|---|---|',\n '| Queries per 5,000 rows | 5,000 | 1 |',\n '| Time on the sample | 62.4 s | 0.9 s |',\n '',\n 'The fix fetches the owners for the whole batch at once:',\n '',\n '```python',\n 'owners = db.fetch_owners({row.owner_id for row in batch})',\n 'for row in batch:',\n ' write(row, owners[row.owner_id])',\n '```',\n '',\n 'Want me to apply it and add a test that counts the queries?',\n ].join('\\n'))],\n },\n ];\n },\n },\n {\n id: 'c9d3b8e2-3333-4c3d-ae5f-000000000003',\n title: 'Fix the flaky login test',\n project: 'acme-web',\n branch: 'fix/login-test',\n rows: (cwd) => {\n const t = Date.now() - 3 * HOUR;\n return [\n { role: 'user', at: t, content: 'The login test fails about one run in five on CI. Plan a fix first.' },\n {\n role: 'assistant',\n at: t + 30000,\n content: [tool('p1', 'ExitPlanMode', {\n plan: '## Fix the flaky login test\\n\\nThe test clicks **Sign in** before the form has loaded its CSRF token, so the request is sometimes rejected.\\n\\n1. Wait for the token field to be filled before submitting.\\n2. Replace the fixed 500 ms sleep with that wait.\\n3. Run the test 50 times in a row to confirm.',\n })],\n },\n { role: 'user', at: t + 90000, content: [result('p1', 'User has approved your plan. You can now start coding.')] },\n {\n role: 'assistant',\n at: t + 95000,\n content: [tool('p2', 'Edit', {\n file_path: `${cwd}/tests/login.spec.ts`,\n old_string: \" await page.waitForTimeout(500);\\n await page.click('text=Sign in');\",\n new_string: \" await expect(page.locator('input[name=csrf]')).not.toHaveValue('');\\n await page.click('text=Sign in');\",\n })],\n },\n { role: 'user', at: t + 96000, content: [result('p2', `The file ${cwd}/tests/login.spec.ts has been updated successfully.`)] },\n { role: 'assistant', at: t + 99000, content: [tool('p3', 'Bash', { command: 'npx playwright test tests/login.spec.ts --repeat-each=50', description: 'Run the login test 50 times' })] },\n { role: 'user', at: t + 160000, content: [result('p3', 'Running 50 tests using 4 workers\\n\\n 50 passed (58.2s)')] },\n { role: 'assistant', at: t + 165000, content: [text('All 50 runs pass now. The test waits for the CSRF token instead of sleeping, so it is also about 400 ms faster.')] },\n ];\n },\n },\n {\n id: 'd1e4c7f3-4444-4d4e-bf60-000000000004',\n title: 'Release notes for v2.4',\n project: 'docs-site',\n branch: 'main',\n rows: () => {\n const t = Date.now() - 26 * HOUR;\n return [\n { role: 'user', at: t, content: 'Write the release notes for v2.4 from the merged pull requests.' },\n {\n role: 'assistant',\n at: t + 50000,\n content: [text('## v2.4\\n\\n**Highlights**\\n\\n- A dark theme, which follows your system setting or can be chosen in Settings.\\n- Exports are about 60 times faster on large accounts.\\n\\n**Fixes**\\n\\n- The login page no longer fails when submitted right after it opens.\\n- Long project names no longer push the menu off screen on phones.')],\n },\n ];\n },\n },\n {\n id: 'f3a6e9b5-6666-4f60-9182-000000000006',\n title: \"Chart last week's signups\",\n project: 'data-pipeline',\n branch: 'main',\n rows: (cwd) => {\n const t = Date.now() - 50 * MIN;\n return [\n { role: 'user', at: t, content: \"Chart last week's signups by day, and make me a one-page summary I can share.\" },\n { role: 'assistant', at: t + 3000, content: [text(\"I'll read the numbers first.\"), tool('v1', 'Read', { file_path: `${cwd}/data/signups.csv` })] },\n { role: 'user', at: t + 4000, content: [result('v1', numbered(SIGNUPS_CSV.trimEnd()))] },\n { role: 'assistant', at: t + 9000, content: [tool('v2', 'mcp__visualize__show_widget', { title: 'signups_by_day', loading_messages: ['Stacking the bars'], widget_code: signupsChart() })] },\n { role: 'user', at: t + 10000, content: [result('v2', 'Shown to the user.')] },\n { role: 'assistant', at: t + 16000, content: [tool('v3', 'Write', { file_path: `${cwd}/reports/signups-week-45.html`, content: REPORT_HTML })] },\n { role: 'user', at: t + 17000, content: [result('v3', `File created successfully at: ${cwd}/reports/signups-week-45.html`)] },\n {\n role: 'assistant',\n at: t + 21000,\n content: [text('Signups grew **18%** to 12,480. Thursday was the best day (2,214), the day the newsletter went out, and the weekend stayed above the weekday average.\\n\\nThe one-pager is in `reports/signups-week-45.html`: the three numbers that matter and the chart. Open it from the step above.')],\n },\n ];\n },\n },\n {\n id: 'e2f5d8a4-5555-4e5f-8071-000000000005',\n title: 'Turn this CSV into JSON',\n project: 'scratch',\n branch: '',\n scratch: true,\n rows: (cwd) => {\n const t = Date.now() - 29 * HOUR;\n return [\n { role: 'user', at: t, content: 'Turn contacts.csv into a JSON array, one object per row.' },\n { role: 'assistant', at: t + 6000, content: [tool('s1', 'Bash', { command: \"python3 -c \\\"import csv, json; print(json.dumps(list(csv.DictReader(open('contacts.csv'))), indent=2))\\\" > contacts.json && wc -l contacts.json\", description: 'Convert the CSV to JSON' })] },\n { role: 'user', at: t + 8000, content: [result('s1', ` 1203 ${cwd}/contacts.json`)] },\n { role: 'assistant', at: t + 10000, content: [text('Done: `contacts.json` has 200 objects, with the CSV header as keys.')] },\n ];\n },\n },\n];\n\nconst SETTINGS_TSX = `import { FontSizePicker } from './FontSizePicker';\nimport { Section } from './Section';\n\nexport function Settings() {\n return (\n <Section title=\"Display\">\n <FontSizePicker />\n </Section>\n );\n}\n`;\n\nconst TOGGLE_TSX = `type Theme = 'light' | 'dark';\n\n/** Switches the page between the light and dark color values in theme.css. */\nexport function ThemeToggle({ value, onChange }: { value: Theme; onChange: (theme: Theme) => void }) {\n return (\n <label className=\"row\">\n Dark theme\n <input type=\"checkbox\" checked={value === 'dark'} onChange={(e) => onChange(e.target.checked ? 'dark' : 'light')} />\n </label>\n );\n}\n`;\n\nconst DARK_CSS = `${THEME_CSS.replace(' --accent: #2563eb;\\n}', \" --accent: #2563eb;\\n}\\n\\n:root[data-theme='dark'] {\\n --bg: #0b0b0c;\\n --fg: #ededed;\\n --muted: #a1a1a1;\\n --accent: #60a5fa;\\n}\")}\\n`;\n\nconst FILE_TEXT: Record<string, string> = {\n 'acme-web/src/styles/theme.css': `${THEME_CSS}\\n`,\n 'acme-web/src/settings/Settings.tsx': SETTINGS_TSX,\n 'acme-web/README.md': '# acme-web\\n\\nThe Acme web app.\\n',\n 'data-pipeline/README.md': '# data-pipeline\\n\\nNightly exports and the numbers behind them.\\n',\n 'data-pipeline/data/signups.csv': SIGNUPS_CSV,\n 'data-pipeline/reports/signups-week-45.html': REPORT_HTML,\n 'docs-site/README.md': '# docs-site\\n\\nThe public docs.\\n',\n};\n\n/**\n * The first project is a git repository with Claude's work so far left\n * uncommitted, so the Changes panel has something to show. Skipped without git.\n */\nasync function demoRepository(dir: string): Promise<void> {\n const env = { ...process.env, GIT_AUTHOR_NAME: 'Demo', GIT_AUTHOR_EMAIL: 'demo@example.com', GIT_COMMITTER_NAME: 'Demo', GIT_COMMITTER_EMAIL: 'demo@example.com' };\n // Your own git setup (hooks, templates, signing) has no say in the demo's repository.\n const git = (args: string[]) => run('git', ['-c', 'commit.gpgsign=false', '-c', 'core.hooksPath=/dev/null', '-c', 'core.fsmonitor=false', '-c', 'init.templateDir=', ...args], { cwd: dir, env, timeout: 10_000 });\n try {\n await git(['init', '-q', '-b', 'feat/dark-theme']);\n await rm(path.join(dir, 'src/settings/ThemeToggle.tsx'), { force: true });\n await git(['add', '-A']);\n await git(['commit', '-q', '--no-verify', '-m', 'Settings page']);\n await writeFile(path.join(dir, 'src/styles/theme.css'), DARK_CSS);\n await writeFile(path.join(dir, 'src/settings/ThemeToggle.tsx'), TOGGLE_TSX);\n } catch {\n // No git here; the panel shows the files only.\n }\n}\n\nconst DEMO_FILES: Record<string, string[]> = {\n 'acme-web': ['README.md', 'package.json', 'src/app.tsx', 'src/settings/Settings.tsx', 'src/settings/ThemeToggle.tsx', 'src/styles/theme.css', 'tests/login.spec.ts'],\n 'data-pipeline': ['README.md', 'data/signups.csv', 'pipeline/export.py', 'pipeline/db.py', 'pipeline/writer.py', 'reports/signups-week-45.html', 'tests/test_export.py'],\n 'docs-site': ['README.md', 'docs/releases.md'],\n};\n\n/** Writes the demo home and points the config (and the Claude Agent SDK) at it. */\nexport async function prepareDemo(): Promise<void> {\n const root = path.join(config.stateDir, 'demo');\n await rm(root, { recursive: true, force: true });\n const claudeHome = path.join(root, '.claude');\n for (const [project, files] of Object.entries(DEMO_FILES)) {\n for (const file of files) {\n const full = path.join(root, 'code', project, file);\n await mkdir(path.dirname(full), { recursive: true });\n await writeFile(full, FILE_TEXT[`${project}/${file}`] ?? '');\n }\n }\n await demoRepository(path.join(root, 'code', 'acme-web'));\n for (const session of SESSIONS) {\n const cwd = session.scratch ? path.join(root, 'scratch', '2026-11-04-7c2e') : path.join(root, 'code', session.project);\n await mkdir(cwd, { recursive: true });\n const dir = path.join(claudeHome, 'projects', cwd.replace(/[^a-zA-Z0-9]/g, '-'));\n await mkdir(dir, { recursive: true });\n let parent: string | null = null;\n const lines: string[] = [JSON.stringify({ type: 'custom-title', customTitle: session.title, sessionId: session.id })];\n for (const row of session.rows(cwd)) {\n const uuid = randomUUID();\n const message = row.role === 'assistant'\n ? { id: `msg_demo_${uuid.slice(0, 8)}`, type: 'message', role: 'assistant', model: 'claude-opus-5-5', content: row.content }\n : { role: 'user', content: row.content };\n lines.push(JSON.stringify({\n parentUuid: parent, isSidechain: false, userType: 'external', cwd, sessionId: session.id, version: '2.1.318',\n gitBranch: session.branch, type: row.role, message, uuid, timestamp: new Date(row.at).toISOString(),\n }));\n parent = uuid;\n }\n await writeFile(path.join(dir, `${session.id}.jsonl`), `${lines.join('\\n')}\\n`);\n }\n config.home = root;\n config.claudeHome = claudeHome;\n // Its own config and state too: the demo never reads or writes your password, token or push keys.\n config.configDir = path.join(root, 'config');\n config.stateDir = path.join(root, 'state');\n process.env.CLAUDE_CONFIG_DIR = claudeHome;\n process.env.PELICAN_SCRATCH_DIR = path.join(root, 'scratch');\n}\n\nexport const DEMO_MODELS: ModelOption[] = [\n { value: 'default', label: 'Default (recommended)', description: 'Opus \u00B7 best for everyday, complex tasks', efforts: ['low', 'medium', 'high', 'xhigh', 'max'], supportsFast: true, resolvedModel: 'claude-opus-5-5' },\n { value: 'opus', label: 'Opus', description: 'For complex work', efforts: ['low', 'medium', 'high', 'xhigh', 'max'], supportsFast: true, resolvedModel: 'claude-opus-5-5' },\n { value: 'sonnet', label: 'Sonnet', description: 'Efficient for everyday tasks', efforts: ['low', 'medium', 'high', 'xhigh', 'max'], supportsFast: false, resolvedModel: 'claude-sonnet-5-5' },\n { value: 'haiku', label: 'Haiku', description: 'Fastest for quick answers', efforts: [], supportsFast: false, resolvedModel: 'claude-haiku-5-5' },\n];\n\n/** The first demo session waits for an approval; another one is busy in Claude Desktop. */\nexport function demoRunState(sessionId: string): RunState | undefined {\n const first = SESSIONS[0]!;\n if (sessionId !== first.id) return undefined;\n const cwd = path.join(config.home, 'code', first.project);\n return {\n sessionId,\n cwd,\n busy: true,\n settings: { model: 'default', effort: 'high', permissionMode: 'default' },\n partial: [],\n permissions: [{\n id: 'demo-permission',\n sessionId,\n toolName: 'Edit',\n input: {\n file_path: `${cwd}/src/settings/Settings.tsx`,\n old_string: ' <FontSizePicker />\\n </Section>',\n new_string: ' <FontSizePicker />\\n <ThemeToggle value={theme} onChange={setTheme} />\\n </Section>',\n },\n canRemember: true,\n createdAt: Date.now() - 20_000,\n }],\n context: { percent: 31, tokens: 62_000, max: 200_000 },\n };\n}\n\nexport const DEMO_LIVE_SESSION = SESSIONS[1]!.id;\nexport const DEMO_WAITING_SESSION = SESSIONS[0]!.id;\n", "import type { ChatMessage } from './protocol';\n\nexport type Todo = { content: string; status: 'pending' | 'in_progress' | 'completed'; activeForm?: string };\n\nconst str = (value: unknown) => (typeof value === 'string' && value ? value : undefined);\nconst STATUSES = new Set(['pending', 'in_progress', 'completed']);\n\n/** The items of a TodoWrite call. */\nexport function todosOf(input: Record<string, unknown>): Todo[] {\n return Array.isArray(input.todos) ? (input.todos as Todo[]).filter((t) => t && typeof t.content === 'string') : [];\n}\n\n/**\n * Claude's task list as it stands after these messages. TodoWrite replaces\n * the whole list; TaskCreate adds one task and TaskUpdate changes one, by the\n * number Claude Code gave it (\"Task #3 created successfully\").\n */\nexport function currentTasks(messages: ChatMessage[]): Todo[] {\n const results = new Map<string, string>();\n for (const message of messages) {\n for (const block of message.blocks) {\n if (block.type !== 'tool_result') continue;\n const text = typeof block.content === 'string' ? block.content : block.content.map((c) => (c.type === 'text' ? c.text : '')).join('');\n results.set(block.tool_use_id, text);\n }\n }\n let tasks: Array<Todo & { id?: string }> = [];\n for (const message of messages) {\n if (message.role !== 'assistant' || message.parentToolUseId) continue;\n for (const block of message.blocks) {\n if (block.type !== 'tool_use') continue;\n const input = block.input;\n if (block.name === 'TodoWrite') {\n tasks = todosOf(input).map((t) => ({ ...t }));\n } else if (block.name === 'TaskCreate') {\n const id = results.get(block.id)?.match(/Task #(\\w+)/)?.[1] ?? String(tasks.length + 1);\n const activeForm = str(input.activeForm);\n tasks.push({ id, content: str(input.subject) ?? str(input.description) ?? 'Task', status: 'pending', ...(activeForm ? { activeForm } : {}) });\n } else if (block.name === 'TaskUpdate') {\n const task = tasks.find((t) => t.id !== undefined && t.id === String(input.taskId));\n if (!task) continue;\n const status = str(input.status);\n if (status === 'deleted') {\n tasks = tasks.filter((t) => t !== task);\n continue;\n }\n if (status && STATUSES.has(status)) task.status = status as Todo['status'];\n if (str(input.subject)) task.content = str(input.subject)!;\n if (str(input.activeForm)) task.activeForm = str(input.activeForm)!;\n }\n }\n }\n return tasks.map(({ id: _id, ...task }) => task);\n}\n", "import { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { config } from './config.js';\n\n/**\n * Sessions put away from the list. Only Pelican keeps this, in its state folder: the transcripts\n * are not touched, so every Claude Code app still has the session, and unarchiving is instant.\n */\nconst file = () => path.join(config.stateDir, 'archived.json');\nlet loaded: Promise<Set<string>> | undefined;\nlet writing: Promise<void> = Promise.resolve();\n\nasync function load(): Promise<Set<string>> {\n let text: string;\n try {\n text = await readFile(file(), 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return new Set();\n throw error;\n }\n try {\n const ids = (JSON.parse(text) as { ids?: unknown }).ids;\n return new Set(Array.isArray(ids) ? ids.filter((id): id is string => typeof id === 'string') : []);\n } catch {\n // Not ours to throw away: kept beside, and the list starts over.\n await rename(file(), `${file()}.bad-${Date.now()}`).catch(() => undefined);\n console.warn('[pelican] archived.json could not be read; it was set aside and the archive starts empty');\n return new Set();\n }\n}\n\nfunction archived(): Promise<Set<string>> {\n loaded ??= load().catch((error: Error) => {\n loaded = undefined;\n throw error;\n });\n return loaded;\n}\n\n/** The archived session ids, for the list. */\nexport async function archivedIds(): Promise<Set<string>> {\n return archived().catch(() => new Set<string>());\n}\n\n/** Archives or unarchives; writes go one at a time, each with the whole list as it is then. */\nexport async function setArchived(sessionId: string, on: boolean): Promise<void> {\n const ids = await archived();\n const before = ids.has(sessionId);\n if (on) ids.add(sessionId);\n else ids.delete(sessionId);\n const write = writing.then(async () => {\n await mkdir(path.dirname(file()), { recursive: true, mode: 0o700 });\n const temp = `${file()}.${randomUUID()}.tmp`;\n await writeFile(temp, `${JSON.stringify({ ids: [...ids] }, null, 2)}\\n`, { mode: 0o600 });\n await rename(temp, file());\n });\n writing = write.catch(() => undefined);\n try {\n await write;\n } catch (error) {\n if (before) ids.add(sessionId);\n else ids.delete(sessionId);\n throw error;\n }\n}\n", "import { randomBytes } from 'node:crypto';\nimport { realpathSync } from 'node:fs';\nimport { mkdir, stat } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { config } from './config.js';\n\nconst realpath = (p: string) => {\n try {\n return realpathSync(p);\n } catch {\n return p;\n }\n};\n\n/** Where \"New scratch session\" makes its folders. */\nexport const scratchRoot = process.env.PELICAN_SCRATCH_DIR ?? path.join(config.home, 'scratch');\n\n/**\n * Folders nobody chose as a project: the system's temp directories (Claude\n * Code's per-session scratchpads live there, and so do the folders tools\n * make inside them), Claude Desktop's scratch workspaces, and our own scratch\n * folders. Sessions in them are real conversations, but the folders are not\n * places to start new work in.\n */\nconst tempRoots = [...new Set(['/tmp', '/private/tmp', '/var/folders', '/private/var/folders', os.tmpdir(), realpath(os.tmpdir())])]\n .map((p) => p.replace(/\\/+$/, ''));\nconst scratchRoots = [path.join(config.home, 'Library/Application Support/Claude/scratch-workspaces'), scratchRoot]\n .map((p) => p.replace(/\\/+$/, ''));\n\nconst under = (dir: string, roots: string[]) => {\n const p = dir.replace(/\\/+$/, '');\n return roots.some((root) => p === root || p.startsWith(`${root}/`));\n};\n\n/**\n * temp: inside the system's temp directory. Mostly runs another session\n * started (Claude Code's per-session scratchpads live there); the OS cleans\n * these folders up. scratch: a session you started without a project, in\n * Claude Desktop or here; a real conversation in a throwaway folder.\n */\nexport function folderKind(dir: string): 'project' | 'scratch' | 'temp' {\n // The demo's made-up home can itself be in a temp folder (the scripts that record it put it\n // there): its folders are what they pretend to be.\n if (config.demo && under(dir, [config.home.replace(/\\/+$/, '')])) return under(dir, scratchRoots) ? 'scratch' : 'project';\n if (under(dir, tempRoots)) return 'temp';\n if (under(dir, scratchRoots)) return 'scratch';\n return 'project';\n}\n\nexport function isScratchFolder(dir: string): boolean {\n return folderKind(dir) !== 'project';\n}\n\nexport async function isDirectory(dir: string): Promise<boolean> {\n return stat(dir).then((s) => s.isDirectory(), () => false);\n}\n\n/** A new empty folder like `~/scratch/2026-11-05-a1b2`, for a session that belongs to no project. */\nexport async function createScratchFolder(): Promise<string> {\n const now = new Date();\n const day = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;\n const dir = path.join(scratchRoot, `${day}-${randomBytes(2).toString('hex')}`);\n await mkdir(dir, { recursive: true });\n return dir;\n}\n", "import { execFile } from 'node:child_process';\nimport { readdir, readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { config } from './config.js';\nimport { DEMO_LIVE_SESSION } from './demo.js';\n\n/**\n * A Claude Code process outside this app with a session open: busy, or idle and holding the\n * conversation as it last saw it. `updatedAt` is when it last worked on it.\n */\nexport type LiveProcess = { app?: string; busy: boolean; updatedAt?: number };\n\nconst run = promisify(execFile);\n\ntype Proc = { ppid: number; comm: string };\n\n/** Every process's parent and name, in one `ps` (none when ps is not there). */\nasync function processes(): Promise<Map<number, Proc> | undefined> {\n try {\n const { stdout } = await run('ps', ['-A', '-o', 'pid=,ppid=,comm='], { timeout: 3000, maxBuffer: 8 * 1024 * 1024 });\n const map = new Map<number, Proc>();\n for (const line of stdout.split('\\n')) {\n const match = /^\\s*(\\d+)\\s+(\\d+)\\s+(.*)$/.exec(line);\n if (match) map.set(Number(match[1]), { ppid: Number(match[2]), comm: match[3]!.trim() });\n }\n return map;\n } catch {\n return undefined;\n }\n}\n\n/** Started by this app, directly or through a wrapper. */\nfunction descends(pid: number, procs: Map<number, Proc>): boolean {\n for (let p = procs.get(pid)?.ppid, hops = 0; p && hops < 8; p = procs.get(p)?.ppid, hops += 1) {\n if (p === process.pid) return true;\n }\n return false;\n}\n\n/**\n * Claude Code's own clients, by the entrypoint they record. Any other program\n * that runs Claude Code is just \"elsewhere\": Pelican assumes nothing about it.\n */\nconst CLAUDE_CLIENTS: Record<string, string> = {\n 'claude-desktop': 'Claude Desktop',\n cli: 'the terminal',\n 'claude-vscode': 'VS Code',\n};\n\nlet cache: { at: number; byId: Map<string, LiveProcess> } | undefined;\n\nfunction alive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\n/**\n * Sessions other apps are running, from Claude Code's own registry: every\n * Claude Code process writes `~/.claude/sessions/<pid>.json` with its session\n * id, the app that started it and whether it is busy. This sees a turn that\n * is several minutes into a long command, which writes nothing to the\n * transcript meanwhile. Entries of exited processes are ignored.\n */\nexport async function liveProcesses(): Promise<Map<string, LiveProcess>> {\n if (config.demo) return new Map([[DEMO_LIVE_SESSION, { app: 'Claude Desktop', busy: true }]]);\n if (cache && Date.now() - cache.at < 1500) return cache.byId;\n const dir = path.join(config.claudeHome, 'sessions');\n const byId = new Map<string, LiveProcess>();\n let names: string[] = [];\n try {\n names = (await readdir(dir)).filter((n) => /^\\d+\\.json$/.test(n));\n } catch {\n // Older Claude Code, or none running.\n }\n type Entry = { pid?: number; sessionId?: string; status?: string; entrypoint?: string; updatedAt?: number; statusUpdatedAt?: number };\n const entries = (await Promise.all(names.map(async (name) => {\n try {\n const entry = JSON.parse(await readFile(path.join(dir, name), 'utf8')) as Entry;\n return entry.sessionId && typeof entry.pid === 'number' && entry.pid !== process.pid && alive(entry.pid) ? entry : undefined;\n } catch {\n return undefined; // Being rewritten; the next read gets it.\n }\n }))).filter((e): e is Entry & { pid: number; sessionId: string } => Boolean(e));\n // This app's own Claude processes descend from it; when that cannot be told, those of the SDK.\n // A pid now used by something that is not Claude Code is a registry entry left behind.\n const procs = await processes();\n for (const entry of entries) {\n const proc = procs?.get(entry.pid);\n if (procs && (!proc || !/claude|node|bun|disclaimer/i.test(proc.comm))) continue;\n const ours = procs ? descends(entry.pid, procs) : entry.entrypoint === 'sdk-ts';\n if (ours) continue;\n const busy = entry.status !== undefined && entry.status !== 'idle';\n const app = CLAUDE_CLIENTS[entry.entrypoint ?? ''];\n const known = byId.get(entry.sessionId);\n // When it last changed between working and idle: other writes to the registry are not work.\n const worked = typeof entry.statusUpdatedAt === 'number' ? entry.statusUpdatedAt : entry.updatedAt;\n const updatedAt = Math.max(known?.updatedAt ?? 0, typeof worked === 'number' ? worked : 0) || undefined;\n // A session open in two places: the busy one is what matters.\n const pick = !known || (busy && !known.busy) ? { ...(app ? { app } : {}), busy } : known;\n byId.set(entry.sessionId, { ...pick, ...(updatedAt ? { updatedAt } : {}) });\n }\n cache = { at: Date.now(), byId };\n return byId;\n}\n", "import type { Block, ChatMessage, ImageBlock, TextBlock } from '../shared/protocol.js';\n\nconst MAX_TEXT = 60_000;\nconst MAX_RESULT_TEXT = 24_000;\nconst MAX_IMAGE_BASE64 = 2_500_000;\n\ntype Raw = Record<string, unknown>;\n\nconst isRecord = (value: unknown): value is Raw => typeof value === 'object' && value !== null && !Array.isArray(value);\n\nfunction clip(text: string, max: number): TextBlock {\n return text.length > max\n ? { type: 'text', text: `${text.slice(0, max)}\\n\u2026`, truncated: true }\n : { type: 'text', text };\n}\n\nfunction image(raw: Raw): ImageBlock | undefined {\n const source = raw.source;\n if (!isRecord(source)) return undefined;\n if (source.type === 'base64' && typeof source.data === 'string') {\n const mediaType = typeof source.media_type === 'string' ? source.media_type : 'image/png';\n return source.data.length > MAX_IMAGE_BASE64\n ? { type: 'image', source: { type: 'base64', media_type: mediaType, data: '' }, omitted: true }\n : { type: 'image', source: { type: 'base64', media_type: mediaType, data: source.data } };\n }\n if (source.type === 'url' && typeof source.url === 'string') return { type: 'image', source: { type: 'url', url: source.url } };\n return undefined;\n}\n\nfunction resultContent(content: unknown): { content: string | Array<TextBlock | ImageBlock>; truncated: boolean } {\n if (typeof content === 'string') {\n const clipped = clip(content, MAX_RESULT_TEXT);\n return { content: clipped.text, truncated: Boolean(clipped.truncated) };\n }\n if (!Array.isArray(content)) return { content: '', truncated: false };\n let truncated = false;\n const parts: Array<TextBlock | ImageBlock> = [];\n for (const part of content) {\n if (!isRecord(part)) continue;\n if (part.type === 'text' && typeof part.text === 'string') {\n const clipped = clip(part.text, MAX_RESULT_TEXT);\n truncated ||= Boolean(clipped.truncated);\n parts.push(clipped);\n } else if (part.type === 'image') {\n const img = image(part);\n if (img) parts.push(img);\n }\n }\n return { content: parts, truncated };\n}\n\nfunction blocks(content: unknown): Block[] {\n if (typeof content === 'string') return content ? [clip(content, MAX_TEXT)] : [];\n if (!Array.isArray(content)) return [];\n const out: Block[] = [];\n for (const raw of content) {\n if (!isRecord(raw)) continue;\n switch (raw.type) {\n case 'text':\n if (typeof raw.text === 'string' && raw.text) out.push(clip(raw.text, MAX_TEXT));\n break;\n case 'thinking':\n if (typeof raw.thinking === 'string' && raw.thinking.trim()) out.push({ type: 'thinking', thinking: raw.thinking });\n break;\n case 'tool_use':\n case 'server_tool_use':\n if (typeof raw.id === 'string' && typeof raw.name === 'string') {\n out.push({ type: 'tool_use', id: raw.id, name: raw.name, input: isRecord(raw.input) ? raw.input : {} });\n }\n break;\n case 'tool_result': {\n if (typeof raw.tool_use_id !== 'string') break;\n const { content: resultBody, truncated } = resultContent(raw.content);\n out.push({\n type: 'tool_result',\n tool_use_id: raw.tool_use_id,\n content: resultBody,\n ...(raw.is_error === true ? { is_error: true } : {}),\n ...(truncated ? { truncated: true } : {}),\n });\n break;\n }\n case 'web_search_tool_result':\n if (typeof raw.tool_use_id === 'string') {\n const results = Array.isArray(raw.content) ? raw.content.filter(isRecord) : [];\n const text = results.map((r) => `${String(r.title ?? '')} ${String(r.url ?? '')}`.trim()).join('\\n');\n out.push({ type: 'tool_result', tool_use_id: raw.tool_use_id, content: text });\n }\n break;\n case 'image': {\n const img = image(raw);\n if (img) out.push(img);\n break;\n }\n case 'document':\n out.push({ type: 'text', text: '[document]' });\n break;\n default:\n break;\n }\n }\n return out;\n}\n\n/**\n * Converts a transcript message (`getSessionMessages`) or a streamed SDK message\n * into the shape the app renders. Returns undefined for messages with nothing\n * to show.\n */\nexport function toChatMessage(raw: Raw): ChatMessage | undefined {\n const type = raw.type;\n if (type !== 'user' && type !== 'assistant') return undefined;\n if (typeof raw.uuid !== 'string') return undefined;\n const message = isRecord(raw.message) ? raw.message : {};\n const out: ChatMessage = {\n uuid: raw.uuid,\n role: type,\n blocks: blocks(message.content),\n };\n if (out.blocks.length === 0) return undefined;\n const timestamp = typeof raw.timestamp === 'string' ? Date.parse(raw.timestamp) : NaN;\n if (!Number.isNaN(timestamp)) out.timestamp = timestamp;\n const parent = raw.parent_tool_use_id;\n if (typeof parent === 'string') out.parentToolUseId = parent;\n if (type === 'assistant') {\n if (typeof message.model === 'string') out.model = message.model;\n if (typeof message.id === 'string') out.messageId = message.id;\n }\n // Text Claude Code adds itself (an image's scale note, skill text) is meta in the transcript\n // and synthetic in the live stream; either way it is not something you typed.\n if (raw.is_meta === true || raw.isMeta === true || raw.isSynthetic === true) out.meta = true;\n if (raw.isCompactSummary === true) out.compactSummary = true;\n return out;\n}\n", "import { open, stat } from 'node:fs/promises';\n\nimport type { ChatMessage, RecordedSettings } from '../shared/protocol.js';\nimport { toChatMessage } from './normalize.js';\n\n/**\n * A session's whole conversation, read from its transcript, across compactions.\n *\n * Each row of a transcript points at the one before it (`parentUuid`), so the\n * conversation is the chain from the newest message back to the first. A\n * compaction starts a new chain: its boundary row has no parent and points\n * back only through `logicalParentUuid`. Claude Code's own reader stops there,\n * which would leave everything before the last compaction out of reach; here\n * the walk carries on through every boundary. When a boundary points at a row\n * that was never written, the walk carries on from the last message the\n * compaction kept, or else from the row just before the boundary.\n *\n * The messages a compaction keeps verbatim stay where they were, before the\n * boundary; Claude Code's reader shows them again after the summary.\n *\n * A transcript only grows while its session runs, so it is read incrementally:\n * each read parses only the bytes added since the last one.\n */\n\ntype Node = {\n uuid: string;\n parent: string | undefined;\n /** On a compaction boundary: the last row before the compaction, and the last message it kept verbatim. */\n logicalParent: string | undefined;\n preservedTail: string | undefined;\n /** Position among the rows that have a uuid, in file order. */\n index: number;\n /** What the app shows for this row; rows like attachments only link the chain. */\n message: ChatMessage | undefined;\n};\n\ntype Transcript = {\n /** The settings the session last ran with, in file order. */\n recorded: RecordedSettings;\n ino: number;\n /** Bytes parsed so far, and the start of a line not finished yet. */\n offset: number;\n carry: Buffer;\n nodes: Map<string, Node>;\n order: Node[];\n /** The newest user or assistant row: where the conversation ends. */\n leaf: Node | undefined;\n /** The last walk, reused when the conversation only grew. */\n chain: Node[];\n chainIndex: Map<string, number>;\n chainLeaf: string | undefined;\n messages: ChatMessage[];\n};\n\nconst CHUNK = 8 * 1024 * 1024;\nconst MAX_FILES = 16;\nconst transcripts = new Map<string, Transcript>();\nconst locks = new Map<string, Promise<unknown>>();\n\nconst str = (value: unknown) => (typeof value === 'string' && value ? value : undefined);\n\nfunction empty(ino: number): Transcript {\n return {\n recorded: {}, ino, offset: 0, carry: Buffer.alloc(0), nodes: new Map(), order: [], leaf: undefined,\n chain: [], chainIndex: new Map(), chainLeaf: undefined, messages: [],\n };\n}\n\nconst MODES = new Set(['default', 'acceptEdits', 'plan', 'auto', 'bypassPermissions']);\n\n/** Keeps the settings a row shows the session ran with: the latest row wins. */\nfunction note(recorded: RecordedSettings, row: Record<string, unknown>): void {\n if (row.type === 'assistant') {\n const model = str(row.requestedModel) ?? str((row.message as { model?: unknown } | undefined)?.model);\n if (model && !model.startsWith('<')) recorded.model = model;\n if (str(row.effort)) recorded.effort = str(row.effort);\n const speed = str((row.message as { usage?: { speed?: unknown } } | undefined)?.usage?.speed);\n if (speed) recorded.fast = speed === 'fast';\n recorded.ultracode ??= false;\n } else if (row.type === 'user' && str(row.permissionMode) && MODES.has(str(row.permissionMode)!)) {\n recorded.permissionMode = str(row.permissionMode) as RecordedSettings['permissionMode'];\n } else if (row.type === 'attachment') {\n const kind = str((row.attachment as { type?: unknown } | undefined)?.type) ?? '';\n // Claude Code notes ultracode turning on (and off) as attachments.\n if (kind === 'ultra_effort_enter') recorded.ultracode = true;\n else if (/^ultra_effort_(exit|leave|off)/.test(kind)) recorded.ultracode = false;\n }\n}\n\n/**\n * A message the user sent while Claude was working, which Claude read at its\n * next step: Claude Code records it as a `queued_command` attachment where it\n * was taken in, not as a user row.\n */\nfunction steeredMessage(row: Record<string, unknown>, uuid: string): ChatMessage | undefined {\n const attachment = row.attachment as { type?: string; prompt?: unknown; source_uuid?: unknown; commandMode?: unknown } | undefined;\n if (row.type !== 'attachment' || attachment?.type !== 'queued_command') return undefined;\n if (attachment.commandMode !== undefined && attachment.commandMode !== 'prompt') return undefined;\n const message = toChatMessage({ type: 'user', uuid, timestamp: row.timestamp, message: { role: 'user', content: attachment.prompt } });\n if (!message) return undefined;\n const source = str(attachment.source_uuid);\n return { ...message, steered: true, ...(source ? { sourceUuid: source } : {}) };\n}\n\nfunction addLine(t: Transcript, line: string): void {\n // Rows that take part in the conversation carry a uuid; skip the rest (titles, costs, requests) unparsed.\n if (line.length < 2 || line.charCodeAt(0) !== 123 || !line.includes('\"uuid\"')) return;\n let row: Record<string, unknown>;\n try {\n row = JSON.parse(line) as Record<string, unknown>;\n } catch {\n return;\n }\n const uuid = str(row.uuid);\n if (!uuid || row.isSidechain === true) return;\n note(t.recorded, row);\n const turn = row.type === 'user' || row.type === 'assistant';\n const message = turn ? toChatMessage(row) : steeredMessage(row, uuid);\n const existing = t.nodes.get(uuid);\n if (existing) {\n existing.message = message;\n return;\n }\n const metadata = row.compactMetadata as { preservedSegment?: { tailUuid?: unknown } } | undefined;\n const node: Node = {\n uuid,\n parent: str(row.parentUuid),\n logicalParent: str(row.logicalParentUuid),\n preservedTail: str(metadata?.preservedSegment?.tailUuid),\n index: t.order.length,\n message,\n };\n t.nodes.set(uuid, node);\n t.order.push(node);\n if (turn) t.leaf = node;\n}\n\nasync function readNew(file: string, t: Transcript, size: number): Promise<void> {\n const handle = await open(file, 'r');\n try {\n let position = t.offset;\n while (position < size) {\n const buffer = Buffer.allocUnsafe(Math.min(CHUNK, size - position));\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);\n if (bytesRead <= 0) break;\n position += bytesRead;\n const data = t.carry.length > 0 ? Buffer.concat([t.carry, buffer.subarray(0, bytesRead)]) : buffer.subarray(0, bytesRead);\n const lastBreak = data.lastIndexOf(0x0a);\n if (lastBreak < 0) {\n t.carry = Buffer.from(data);\n continue;\n }\n t.carry = Buffer.from(data.subarray(lastBreak + 1));\n for (const line of data.subarray(0, lastBreak).toString('utf8').split('\\n')) addLine(t, line);\n }\n t.offset = position;\n // A last row without its newline yet: use it if it is whole; it is read again with the rest later.\n if (t.carry.length > 0) addLine(t, t.carry.toString('utf8'));\n } finally {\n await handle.close();\n }\n}\n\n/**\n * The row a compaction boundary continues from. Its logical parent when that\n * was written; otherwise the last message the compaction kept, or the row\n * just before the boundary in the file.\n */\nfunction before(t: Transcript, boundary: Node): Node | undefined {\n return t.nodes.get(boundary.logicalParent!)\n ?? (boundary.preservedTail ? t.nodes.get(boundary.preservedTail) : undefined)\n ?? (boundary.index > 0 ? t.order[boundary.index - 1] : undefined);\n}\n\n/** The chain from `from` back to the start, oldest first; stops early where it meets `known`. */\nfunction walk(t: Transcript, from: Node, known?: Map<string, number>): { nodes: Node[]; joinedAt: number } {\n const out: Node[] = [];\n const seen = new Set<string>();\n let node: Node | undefined = from;\n while (node && !seen.has(node.uuid)) {\n const at = known?.get(node.uuid);\n if (at !== undefined) return { nodes: out.reverse(), joinedAt: at };\n seen.add(node.uuid);\n out.push(node);\n if (node.parent) node = t.nodes.get(node.parent);\n else if (node.logicalParent) node = before(t, node);\n else node = undefined;\n }\n return { nodes: out.reverse(), joinedAt: -1 };\n}\n\nfunction conversation(t: Transcript): ChatMessage[] {\n const leaf = t.leaf;\n if (!leaf) return [];\n if (t.chainLeaf === leaf.uuid) return t.messages;\n const { nodes, joinedAt } = walk(t, leaf, t.chainIndex);\n t.chain = joinedAt >= 0 ? t.chain.slice(0, joinedAt + 1).concat(nodes) : nodes;\n t.chainIndex = new Map(t.chain.map((n, i) => [n.uuid, i]));\n t.chainLeaf = leaf.uuid;\n t.messages = t.chain.flatMap((n) => (n.message ? [n.message] : []));\n return t.messages;\n}\n\nasync function read(file: string): Promise<ChatMessage[]> {\n let info;\n try {\n info = await stat(file);\n } catch {\n transcripts.delete(file);\n return [];\n }\n let t = transcripts.get(file);\n // Replaced or cut short: start over.\n if (!t || t.ino !== info.ino || info.size < t.offset) t = empty(info.ino);\n transcripts.delete(file);\n transcripts.set(file, t);\n while (transcripts.size > MAX_FILES) transcripts.delete(transcripts.keys().next().value!);\n if (info.size > t.offset) await readNew(file, t, info.size);\n return conversation(t);\n}\n\n/** What the session in a transcript file last ran with. */\nexport async function readRecordedSettings(file: string): Promise<RecordedSettings> {\n await readHistory(file);\n return { ...(transcripts.get(file)?.recorded ?? {}) };\n}\n\n/** The conversation in a transcript file, oldest message first. */\nexport function readHistory(file: string): Promise<ChatMessage[]> {\n // One read per file at a time: reads build on each other's state.\n const previous = locks.get(file) ?? Promise.resolve();\n const next = previous.catch(() => undefined).then(() => read(file));\n locks.set(file, next);\n void next.finally(() => {\n if (locks.get(file) === next) locks.delete(file);\n }).catch(() => undefined);\n return next;\n}\n", "import { createReadStream } from 'node:fs';\nimport { open, readdir, stat } from 'node:fs/promises';\nimport readline from 'node:readline';\nimport path from 'node:path';\n\nimport { config } from './config.js';\n\nconst projectsDir = () => path.join(config.claudeHome, 'projects');\n\nlet index = new Map<string, string>();\nlet indexBuiltAt = 0;\n\n/** Rebuilds the sessionId \u2192 transcript path index from `~/.claude/projects/<dir>/<id>.jsonl`. */\nasync function rebuildIndex(): Promise<void> {\n const next = new Map<string, string>();\n let dirs: string[] = [];\n try {\n dirs = await readdir(projectsDir());\n } catch {\n // No projects yet.\n }\n await Promise.all(dirs.map(async (dir) => {\n try {\n for (const name of await readdir(path.join(projectsDir(), dir))) {\n if (name.endsWith('.jsonl')) next.set(name.slice(0, -'.jsonl'.length), path.join(projectsDir(), dir, name));\n }\n } catch {\n // Not a directory, or removed meanwhile.\n }\n }));\n index = next;\n indexBuiltAt = Date.now();\n}\n\n/** Path of a session's transcript, or undefined when it does not exist (yet). */\nexport async function transcriptPath(sessionId: string): Promise<string | undefined> {\n if (!index.has(sessionId) && Date.now() - indexBuiltAt > 1000) await rebuildIndex();\n return index.get(sessionId);\n}\n\nexport async function refreshTranscriptIndex(): Promise<void> {\n await rebuildIndex();\n}\n\nconst CHUNK = 256 * 1024;\nconst MAX_SCAN = 16 * 1024 * 1024;\n\n/** Text blocks of an assistant row, as one line for a session-list preview. */\nfunction replyText(row: { message?: { content?: unknown } }): string | undefined {\n const content = row.message?.content;\n if (!Array.isArray(content)) return undefined;\n const text = content\n .filter((b): b is { type: string; text: string } => b?.type === 'text' && typeof b.text === 'string')\n .map((b) => b.text)\n .join(' ')\n .replace(/[`*_#>|]+/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n return text ? text.slice(0, 160) : undefined;\n}\n\n/**\n * The last user or assistant row, read backwards from the end: its time, the\n * folder and branch it ran in, and the text of Claude's latest reply.\n *\n * Claude Desktop appends metadata rows (titles, cost state) and queued\n * notifications to idle sessions, which moves the file's mtime although\n * nothing was said; only turns count as activity.\n */\nexport type LastTurn = {\n time: number; cwd?: string; gitBranch?: string; preview?: string;\n /** The row was copied from another session by a fork (it carries `forkedFrom`): nothing ran here then. */\n copied?: boolean;\n};\n\n/** After the last turn, how far back to look for a reply with text (tool calls in between). */\nconst PREVIEW_SCAN = 4 * 1024 * 1024;\n\nexport async function readLastTurn(file: string): Promise<LastTurn | undefined> {\n let handle: Awaited<ReturnType<typeof open>> | undefined;\n let found: LastTurn | undefined;\n let foundAt = 0;\n try {\n handle = await open(file, 'r');\n const { size } = await handle.stat();\n let end = size;\n let carry = Buffer.alloc(0);\n while (end > 0 && size - end < MAX_SCAN && !(found && foundAt - end > PREVIEW_SCAN)) {\n const start = Math.max(0, end - CHUNK);\n const chunk = Buffer.alloc(end - start);\n await handle.read(chunk, 0, chunk.length, start);\n let data = Buffer.concat([chunk, carry]);\n end = start;\n if (start > 0) {\n const firstBreak = data.indexOf(0x0a);\n if (firstBreak < 0) {\n carry = data;\n continue;\n }\n carry = data.subarray(0, firstBreak);\n data = data.subarray(firstBreak + 1);\n }\n const lines = data.toString('utf8').split('\\n');\n for (let i = lines.length - 1; i >= 0; i -= 1) {\n const line = lines[i]!;\n if (!line.includes('\"timestamp\"') || !(line.includes('\"type\":\"user\"') || line.includes('\"type\":\"assistant\"'))) continue;\n // Past the last turn, only rows with text matter: a reply, or the prompt that started the turn.\n if (found && !line.includes('\"type\":\"text\"') && !line.includes('\"content\":\"')) continue;\n try {\n const row = JSON.parse(line) as {\n type?: string; timestamp?: string; isSidechain?: boolean; cwd?: unknown; gitBranch?: unknown; message?: { content?: unknown }; forkedFrom?: unknown;\n };\n if ((row.type !== 'user' && row.type !== 'assistant') || row.isSidechain || typeof row.timestamp !== 'string') continue;\n if (!found) {\n const time = Date.parse(row.timestamp);\n if (Number.isNaN(time)) continue;\n found = {\n time,\n ...(typeof row.cwd === 'string' ? { cwd: row.cwd } : {}),\n ...(typeof row.gitBranch === 'string' ? { gitBranch: row.gitBranch } : {}),\n ...(row.forkedFrom ? { copied: true } : {}),\n };\n foundAt = end;\n }\n if (row.type === 'assistant') {\n const preview = replyText(row);\n if (preview) return { ...found, preview };\n } else if (found && row.type === 'user' && typeof (row.message as { content?: unknown })?.content === 'string') {\n // Reached the prompt before any reply text: the turn has said nothing yet.\n return found;\n }\n } catch {\n // A row can be half-written while Claude is streaming into the file.\n }\n }\n }\n } catch {\n // Missing or unreadable.\n } finally {\n await handle?.close();\n }\n return found;\n}\n\nconst lastTurnCache = new Map<string, { size: number; mtimeMs: number; value: LastTurn | undefined }>();\n\n/**\n * `readLastTurn`, cached by file size and mtime. The row also gives the\n * session's folder and branch, which the SDK misses when Desktop has put\n * metadata rows at the head of the file.\n */\nexport async function lastTurnTime(file: string): Promise<{ lastTurn?: LastTurn; mtimeMs: number; size: number } | undefined> {\n let info;\n try {\n info = await stat(file);\n } catch {\n return undefined;\n }\n const cached = lastTurnCache.get(file);\n if (cached && cached.size === info.size && cached.mtimeMs === info.mtimeMs) {\n return { lastTurn: cached.value, mtimeMs: info.mtimeMs, size: info.size };\n }\n const value = await readLastTurn(file);\n lastTurnCache.set(file, { size: info.size, mtimeMs: info.mtimeMs, value });\n return { lastTurn: value, mtimeMs: info.mtimeMs, size: info.size };\n}\n\nconst HEAD_SCAN = 64 * 1024 * 1024;\nconst headCache = new Map<string, string | null>();\n\n/**\n * The session id on the first prompt or reply of a transcript. It is the file's own id, except\n * in a copy of another session: Claude Desktop's side chats start with the whole conversation\n * they came from, rows and ids unchanged. A file only grows at its end, so an answer is kept;\n * a file that holds no turn yet is asked again next time.\n */\nexport async function firstTurnSession(file: string): Promise<string | undefined> {\n const cached = headCache.get(file);\n if (cached !== undefined) return cached ?? undefined;\n let found: string | undefined;\n let read = 0;\n try {\n const input = createReadStream(file);\n const lines = readline.createInterface({ input, crlfDelay: Infinity });\n try {\n for await (const line of lines) {\n read += line.length + 1;\n if (read > HEAD_SCAN) break;\n if (!line.includes('\"sessionId\"') || !(line.includes('\"type\":\"user\"') || line.includes('\"type\":\"assistant\"'))) continue;\n try {\n const row = JSON.parse(line) as { type?: string; sessionId?: unknown; isSidechain?: boolean };\n if ((row.type === 'user' || row.type === 'assistant') && !row.isSidechain && typeof row.sessionId === 'string') {\n found = row.sessionId;\n break;\n }\n } catch {\n // A row being written.\n }\n }\n } finally {\n lines.close();\n input.destroy();\n }\n } catch {\n return undefined; // Missing or unreadable.\n }\n if (found) {\n if (headCache.size > 10_000) headCache.clear();\n headCache.set(file, found);\n }\n return found;\n}\n\n/** An image of a prompt as the transcript holds it, in full (the message list leaves big ones out). */\nexport type RawImage = { type: 'base64'; mediaType: string; data: string } | { type: 'url' };\n\n/** One row of a transcript, as written: what it follows in the chain, and its content. */\nexport type RawEntry = { parentUuid: string | null; content: unknown };\n\n/** The transcript row with this uuid, read from the file itself. */\nexport async function rawEntry(file: string, uuid: string): Promise<RawEntry | undefined> {\n const needle = `\"uuid\":\"${uuid}\"`;\n const input = createReadStream(file);\n const lines = readline.createInterface({ input, crlfDelay: Infinity });\n try {\n for await (const line of lines) {\n if (!line.includes(needle)) continue;\n let row: { uuid?: unknown; parentUuid?: unknown; type?: unknown; message?: { content?: unknown }; attachment?: { type?: unknown; prompt?: unknown } };\n try {\n row = JSON.parse(line) as typeof row;\n } catch {\n continue;\n }\n if (row.uuid !== uuid) continue;\n // A message sent while Claude worked is an attachment row; its prompt is the content.\n const content = row.type === 'attachment' && row.attachment?.type === 'queued_command' ? row.attachment.prompt : row.message?.content;\n return { parentUuid: typeof row.parentUuid === 'string' ? row.parentUuid : null, content };\n }\n } catch {\n return undefined;\n } finally {\n lines.close();\n input.destroy();\n }\n return undefined;\n}\n\n/**\n * The images of one message, in the order and by the rules the message list uses (base64 and\n * url images; anything else is not an image there), read from the transcript row itself.\n */\nexport async function rawImages(file: string, uuid: string): Promise<RawImage[]> {\n const content = (await rawEntry(file, uuid))?.content;\n if (!Array.isArray(content)) return [];\n const images: RawImage[] = [];\n for (const block of content as Array<{ type?: unknown; source?: { type?: unknown; data?: unknown; media_type?: unknown; url?: unknown } }>) {\n if (block?.type !== 'image' || !block.source) continue;\n if (block.source.type === 'base64' && typeof block.source.data === 'string') {\n images.push({ type: 'base64', mediaType: typeof block.source.media_type === 'string' ? block.source.media_type : 'image/png', data: block.source.data });\n } else if (block.source.type === 'url' && typeof block.source.url === 'string') {\n images.push({ type: 'url' });\n }\n }\n return images;\n}\n", "import { stat } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { getSessionInfo, getSubagentMessages, listSessions, renameSession } from '@anthropic-ai/claude-agent-sdk';\nimport type { SDKSessionInfo } from '@anthropic-ai/claude-agent-sdk';\n\nimport type { ChatMessage, MessagesPage, RecordedSettings, SessionStatus, SessionSummary } from '../shared/protocol.js';\nimport { archivedIds } from './archive.js';\nimport { config } from './config.js';\nimport { folderKind } from './folders.js';\nimport { type LiveProcess, liveProcesses } from './live.js';\nimport { readHistory, readRecordedSettings } from './history.js';\nimport { toChatMessage } from './normalize.js';\nimport { firstTurnSession, lastTurnTime, transcriptPath } from './transcripts.js';\n\nexport type StatusSource = (sessionId: string) => SessionStatus | undefined;\n\nconst infoCache = new Map<string, SDKSessionInfo>();\n\nfunction titleOf(info: SDKSessionInfo): string {\n const title = info.customTitle || info.summary || info.firstPrompt || '';\n return title.replace(/\\s+/g, ' ').trim() || 'Untitled session';\n}\n\nasync function summarize(info: SDKSessionInfo, status: StatusSource, live: Map<string, LiveProcess>, archived: Set<string>): Promise<SessionSummary> {\n const file = await transcriptPath(info.sessionId);\n const times = file ? await lastTurnTime(file) : undefined;\n const lastTurn = times?.lastTurn;\n const lastActivity = lastTurn?.time ?? info.lastModified;\n const own = status(info.sessionId);\n let state: SessionStatus = own ?? 'idle';\n // Another app's Claude process says it is busy; failing that, a turn written moments ago\n // (not one a fork just copied over: a new branch is idle until someone sends to it).\n const other = live.get(info.sessionId);\n const elsewhere = own ? undefined : other;\n const recent = lastTurn && !lastTurn.copied && Date.now() - lastTurn.time < config.liveWindowMs;\n if (!own && (elsewhere?.busy || recent)) state = 'live';\n // Idle here but working in another app: that is what the session is doing.\n else if (own === 'idle' && other?.busy) state = 'live';\n const cwd = info.cwd || lastTurn?.cwd || '';\n if (!info.cwd && cwd) infoCache.set(info.sessionId, { ...info, cwd });\n const gitBranch = lastTurn?.gitBranch ?? info.gitBranch;\n const title = titleOf(info);\n // Claude Desktop's side chats: a copy of a conversation with a question or two of its own.\n const copiedFrom = file && title === 'Side chat' ? await firstTurnSession(file) : undefined;\n const sideChatOf = copiedFrom && copiedFrom !== info.sessionId ? copiedFrom : undefined;\n return {\n id: info.sessionId,\n title,\n cwd,\n project: cwd ? path.basename(cwd) : '',\n ...(gitBranch && gitBranch !== 'HEAD' ? { gitBranch } : {}),\n lastActivity,\n ...(info.createdAt ? { createdAt: info.createdAt } : {}),\n status: state,\n ...(info.fileSize ? { sizeBytes: info.fileSize } : {}),\n ...(cwd && folderKind(cwd) === 'scratch' ? { scratch: true } : {}),\n ...(cwd && folderKind(cwd) === 'temp' ? { temp: true } : {}),\n ...(state === 'live' && other?.busy && other.app ? { runningIn: other.app } : {}),\n ...(lastTurn?.preview ? { preview: lastTurn.preview } : {}),\n ...(sideChatOf ? { sideChatOf } : {}),\n ...(archived.has(info.sessionId) ? { archived: true } : {}),\n // Open, idle, in another app too: its Claude does not see what is sent here until it reopens.\n ...(other && !other.busy ? { openIn: other.app ?? 'another app' } : {}),\n };\n}\n\n/** Every session on this machine, newest activity first. */\nexport async function listSummaries(status: StatusSource): Promise<SessionSummary[]> {\n const infos = await listSessions({ includeProgrammatic: true });\n for (const info of infos) infoCache.set(info.sessionId, info);\n const [live, archived] = await Promise.all([liveProcesses(), archivedIds()]);\n const summaries = await Promise.all(infos.map((info) => summarize(info, status, live, archived)));\n return summaries.sort((a, b) => b.lastActivity - a.lastActivity);\n}\n\nexport async function sessionInfo(sessionId: string): Promise<SDKSessionInfo | undefined> {\n // Without a project dir, `getSessionInfo` leaves `cwd` out; the list has it.\n if (!infoCache.has(sessionId)) {\n for (const info of await listSessions({ includeProgrammatic: true }).catch(() => [])) infoCache.set(info.sessionId, info);\n }\n const cached = infoCache.get(sessionId);\n const info = await getSessionInfo(sessionId, cached?.cwd ? { dir: cached.cwd } : {}).catch(() => undefined);\n if (!info) return cached;\n let cwd = info.cwd || cached?.cwd;\n if (!cwd) {\n const file = await transcriptPath(sessionId);\n cwd = file ? (await lastTurnTime(file))?.lastTurn?.cwd : undefined;\n }\n const merged = { ...cached, ...info, ...(cwd ? { cwd } : {}) };\n infoCache.set(sessionId, merged);\n return merged;\n}\n\nexport async function sessionSummary(sessionId: string, status: StatusSource): Promise<SessionSummary | undefined> {\n const info = await sessionInfo(sessionId);\n return info ? summarize(info, status, await liveProcesses(), await archivedIds()) : undefined;\n}\n\nexport async function renameSessionTitle(sessionId: string, title: string): Promise<void> {\n const dir = infoCache.get(sessionId)?.cwd;\n await renameSession(sessionId, title, dir ? { dir } : {});\n infoCache.delete(sessionId);\n}\n\n/** What the session last ran with, from its transcript; empty for a session not written yet. */\nexport async function recordedSettings(sessionId: string): Promise<RecordedSettings> {\n const file = await transcriptPath(sessionId);\n return file ? readRecordedSettings(file) : {};\n}\n\n/** The whole conversation, oldest first, including what came before each compaction. */\nexport async function loadMessages(sessionId: string): Promise<ChatMessage[]> {\n const file = await transcriptPath(sessionId);\n return file ? readHistory(file) : [];\n}\n\n/**\n * Files Claude handed to the user in a session (SendUserFile, Artifact): the\n * only files outside the session's folder the app may show, and only these paths.\n */\nexport async function sentFiles(sessionId: string): Promise<Set<string>> {\n const paths = new Set<string>();\n for (const message of await loadMessages(sessionId)) {\n if (message.role !== 'assistant') continue;\n for (const block of message.blocks) {\n if (block.type !== 'tool_use') continue;\n if (block.name === 'SendUserFile' && Array.isArray(block.input.files)) {\n for (const file of block.input.files) if (typeof file === 'string') paths.add(file);\n } else if (block.name === 'Artifact' && typeof block.input.file_path === 'string') {\n paths.add(block.input.file_path);\n }\n }\n }\n return paths;\n}\n\n/** What a subagent did inside an Agent call: its own transcript, next to the session's. */\nexport async function loadSubagent(sessionId: string, agentId: string): Promise<ChatMessage[]> {\n if (!/^[\\w-]{1,80}$/.test(agentId)) return [];\n const dir = infoCache.get(sessionId)?.cwd;\n const raw = await getSubagentMessages(sessionId, agentId, dir ? { dir } : {}).catch(() => []);\n return raw\n .map((message) => toChatMessage(message as unknown as Record<string, unknown>))\n .filter((message): message is ChatMessage => Boolean(message));\n}\n\n/** A page of messages ending before index `before` (default: the end). */\nexport async function loadPage(sessionId: string, before: number | undefined, limit: number): Promise<MessagesPage> {\n const all = await loadMessages(sessionId);\n const end = Math.min(before ?? all.length, all.length);\n const offset = Math.max(0, end - limit);\n return { messages: all.slice(offset, end), total: all.length, offset };\n}\n", "import { deleteSession, forkSession } from '@anthropic-ai/claude-agent-sdk';\n\nimport type { ChatMessage } from '../shared/protocol.js';\nimport { config } from './config.js';\nimport { loadMessages, sessionInfo } from './sessions.js';\nimport { refreshTranscriptIndex } from './transcripts.js';\n\n/**\n * A copy of the session to carry on separately. With `upTo`, the copy ends at\n * that message, which is how \"edit and resend\" works: the conversation\n * branches before the prompt you change, and the original stays as it was.\n */\nexport async function fork(sessionId: string, upTo?: string, title?: string): Promise<string> {\n if (config.demo) throw new Error('This is the demo: nothing changes.');\n const info = await sessionInfo(sessionId);\n const result = await forkSession(sessionId, {\n ...(info?.cwd ? { dir: info.cwd } : {}),\n ...(upTo ? { upToMessageId: upTo } : {}),\n ...(title ? { title } : {}),\n });\n await refreshTranscriptIndex();\n return result.sessionId;\n}\n\n/**\n * Where a branch that rewrites the message `uuid` starts: the message just\n * before it, or null when `uuid` is the first one (the branch is then a new\n * session in the same folder). Undefined when the message is not there.\n */\nexport async function messageBefore(sessionId: string, uuid: string): Promise<string | null | undefined> {\n const messages = await loadMessages(sessionId);\n const at = messages.findIndex((m) => m.uuid === uuid);\n if (at < 0) return undefined;\n return at === 0 ? null : messages[at - 1]!.uuid;\n}\n\nexport async function remove(sessionId: string): Promise<void> {\n if (config.demo) throw new Error('This is the demo: nothing changes.');\n const info = await sessionInfo(sessionId);\n await deleteSession(sessionId, info?.cwd ? { dir: info.cwd } : {});\n await refreshTranscriptIndex();\n}\n\nconst toolLine = (name: string, input: Record<string, unknown>) => {\n const target = ['file_path', 'command', 'pattern', 'url', 'query', 'description'].map((k) => input[k]).find((v) => typeof v === 'string');\n return `- ${name}${target ? `: \\`${String(target).split('\\n')[0]}\\`` : ''}`;\n};\n\n/** The conversation as Markdown: prompts and replies in full, tool calls as one line each. */\nexport async function exportMarkdown(sessionId: string): Promise<{ title: string; markdown: string }> {\n const info = await sessionInfo(sessionId);\n const title = info?.customTitle || info?.summary || 'Claude Code session';\n const messages: ChatMessage[] = await loadMessages(sessionId);\n const out: string[] = [`# ${title}`, ''];\n if (info?.cwd) out.push(`Folder: \\`${info.cwd}\\``, '');\n let speaker: string | undefined;\n for (const message of messages) {\n if (message.meta || message.parentToolUseId) continue;\n const parts: string[] = [];\n const tools: string[] = [];\n for (const block of message.blocks) {\n if (block.type === 'text' && block.text.trim()) parts.push(block.text.trim());\n else if (block.type === 'tool_use') tools.push(toolLine(block.name, block.input));\n }\n if (parts.length === 0 && tools.length === 0) continue;\n const who = message.role === 'user' ? 'You' : 'Claude';\n if (who !== speaker) {\n out.push(`## ${who}`, '');\n speaker = who;\n }\n if (tools.length > 0) out.push(...tools, '');\n if (parts.length > 0) out.push(parts.join('\\n\\n'), '');\n }\n return { title, markdown: `${out.join('\\n').trim()}\\n` };\n}\n", "import { stat } from 'node:fs/promises';\n\nimport type { ChatMessage, ServerEvent } from '../shared/protocol.js';\nimport { loadMessages } from './sessions.js';\nimport { transcriptPath } from './transcripts.js';\n\nexport type Listener = (event: ServerEvent) => void;\n\nconst POLL_MS = 1000;\n\n/**\n * Delivers one session's messages to the clients viewing it.\n *\n * Messages come from two places: the stream of a turn this app runs, and the\n * transcript file, which other clients (Desktop, the terminal) also write.\n * Both carry the same ids, so each message is delivered once. The file is\n * polled while anyone is watching; a stat per second is cheap and works the\n * same on macOS and Linux, unlike file watchers across atomic rewrites.\n */\nclass Feed {\n readonly listeners = new Set<Listener>();\n private delivered = new Set<string>();\n /** Recently published messages, replayed to a client that subscribes late (a new session's first prompt). */\n private recent: ChatMessage[] = [];\n /** Ids last read from the file; one that disappears means the conversation was rewound. */\n private fromFile = new Set<string>();\n private primed?: Promise<void>;\n private size = -1;\n private mtimeMs = -1;\n private timer?: NodeJS.Timeout;\n private checking = false;\n\n constructor(readonly sessionId: string) {}\n\n private async prime(): Promise<void> {\n this.primed ??= (async () => {\n const file = await transcriptPath(this.sessionId);\n if (file) {\n const info = await stat(file).catch(() => undefined);\n if (info) {\n this.size = info.size;\n this.mtimeMs = info.mtimeMs;\n }\n }\n for (const message of await loadMessages(this.sessionId)) {\n this.delivered.add(message.uuid);\n this.fromFile.add(message.uuid);\n }\n })();\n return this.primed;\n }\n\n broadcast(event: ServerEvent): void {\n for (const listener of this.listeners) {\n try {\n listener(event);\n } catch {\n // A closed socket; its unsubscribe follows.\n }\n }\n }\n\n /** Publishes still on their way (priming reads the transcript first). */\n publishing = 0;\n\n async publish(message: ChatMessage): Promise<void> {\n this.publishing += 1;\n try {\n await this.prime();\n if (this.delivered.has(message.uuid)) return;\n this.delivered.add(message.uuid);\n this.recent.push(message);\n if (this.recent.length > 40) this.recent.shift();\n this.broadcast({ type: 'message', sessionId: this.sessionId, message });\n } finally {\n this.publishing -= 1;\n }\n }\n\n get hasRecent(): boolean {\n return this.recent.length > 0;\n }\n\n replayRecent(listener: Listener): void {\n for (const message of this.recent) listener({ type: 'message', sessionId: this.sessionId, message });\n }\n\n watch(): void {\n if (this.timer) return;\n void this.prime();\n this.timer = setInterval(() => {\n void this.check().then((changed) => {\n if (changed) sessionsChanged();\n });\n }, POLL_MS);\n }\n\n unwatch(): void {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n\n get watching(): boolean {\n return Boolean(this.timer);\n }\n\n async check(): Promise<boolean> {\n if (this.checking) return false;\n this.checking = true;\n try {\n await this.prime();\n const file = await transcriptPath(this.sessionId);\n if (!file) return false;\n const info = await stat(file).catch(() => undefined);\n if (!info || (info.size === this.size && info.mtimeMs === this.mtimeMs)) return false;\n this.size = info.size;\n this.mtimeMs = info.mtimeMs;\n const messages = await loadMessages(this.sessionId);\n const present = new Set(messages.map((m) => m.uuid));\n for (const uuid of this.fromFile) {\n if (!present.has(uuid)) {\n this.fromFile = present;\n this.delivered = new Set(present);\n this.broadcast({ type: 'reload', sessionId: this.sessionId });\n return true;\n }\n }\n this.fromFile = present;\n for (const message of messages) {\n if (this.delivered.has(message.uuid)) continue;\n this.delivered.add(message.uuid);\n this.broadcast({ type: 'message', sessionId: this.sessionId, message });\n }\n return true;\n } finally {\n this.checking = false;\n }\n }\n}\n\nconst feeds = new Map<string, Feed>();\nconst everyone = new Set<Listener>();\nlet changedTimer: NodeJS.Timeout | undefined;\n\nexport function feedFor(sessionId: string): Feed {\n let feed = feeds.get(sessionId);\n if (!feed) {\n feed = new Feed(sessionId);\n feeds.set(sessionId, feed);\n }\n return feed;\n}\n\n/** Tells every connected client the session list changed, at most every 2 s. */\nexport function sessionsChanged(): void {\n if (changedTimer) return;\n changedTimer = setTimeout(() => {\n changedTimer = undefined;\n for (const listener of everyone) {\n try {\n listener({ type: 'sessions_changed' });\n } catch {\n // Closed socket.\n }\n }\n }, 2000);\n}\n\nexport function broadcastAll(event: ServerEvent): void {\n for (const listener of everyone) {\n try {\n listener(event);\n } catch {\n // Closed socket.\n }\n }\n}\n\nexport function connect(listener: Listener): () => void {\n everyone.add(listener);\n return () => everyone.delete(listener);\n}\n\nexport function subscribe(sessionId: string, listener: Listener): () => void {\n const feed = feedFor(sessionId);\n feed.listeners.add(listener);\n feed.replayRecent(listener);\n feed.watch();\n return () => {\n feed.listeners.delete(listener);\n if (feed.listeners.size > 0) return;\n feed.unwatch();\n // Nobody watches and no run has published into it: forget it, so feeds do not pile up\n // for every session ever opened. A run's feed stays, for a client that comes back to it.\n if (!feed.hasRecent && feed.publishing === 0 && feeds.get(sessionId) === feed) feeds.delete(sessionId);\n };\n}\n", "import { randomUUID } from 'node:crypto';\nimport { mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport type { Attachment } from '../shared/protocol.js';\nimport { config } from './config.js';\n\nconst uploadsDir = () => path.join(config.stateDir, 'uploads');\nexport const MAX_UPLOAD_BYTES = 25 * 1024 * 1024;\n\nconst safeName = (name: string) => name.replace(/[/\\\\\\0]/g, '_').replace(/^\\.+/, '_').slice(0, 120) || 'file';\n\n/** Saves an uploaded file where Claude can read it, and returns its handle. */\nexport async function saveUpload(file: File): Promise<Attachment> {\n if (file.size > MAX_UPLOAD_BYTES) throw new Error(`File is larger than ${MAX_UPLOAD_BYTES / 1024 / 1024} MB`);\n const id = randomUUID();\n const dir = path.join(uploadsDir(), id);\n await mkdir(dir, { recursive: true, mode: 0o700 });\n const name = safeName(file.name);\n const filePath = path.join(dir, name);\n await writeFile(filePath, Buffer.from(await file.arrayBuffer()), { mode: 0o600 });\n return { id, name, mime: file.type || 'application/octet-stream', size: file.size, path: filePath };\n}\n\n/** Looks an upload up by id; the path is rebuilt here, never taken from the client. */\nexport async function findUpload(id: string): Promise<Attachment | undefined> {\n if (!/^[0-9a-f-]{36}$/.test(id)) return undefined;\n const dir = path.join(uploadsDir(), id);\n try {\n const [name] = await readdir(dir);\n if (!name) return undefined;\n const filePath = path.join(dir, name);\n const info = await stat(filePath);\n return { id, name, mime: mimeOf(name), size: info.size, path: filePath };\n } catch {\n return undefined;\n }\n}\n\nexport async function readUpload(id: string): Promise<{ attachment: Attachment; data: Buffer } | undefined> {\n const attachment = await findUpload(id);\n if (!attachment) return undefined;\n return { attachment, data: await readFile(attachment.path) };\n}\n\nconst MIME: Record<string, string> = {\n png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp',\n pdf: 'application/pdf', txt: 'text/plain', md: 'text/markdown', json: 'application/json', csv: 'text/csv',\n};\nconst mimeOf = (name: string) => MIME[name.split('.').pop()?.toLowerCase() ?? ''] ?? 'application/octet-stream';\n\nexport type DirListing = { path: string; parent?: string; dirs: Array<{ name: string; path: string }> };\n\n/** Subfolders of a folder, for choosing where a new session runs. */\nexport async function listDirs(dir: string | undefined, showHidden: boolean): Promise<DirListing> {\n const target = path.resolve(dir || config.home);\n const entries = await readdir(target, { withFileTypes: true });\n const dirs = entries\n .filter((e) => (e.isDirectory() || e.isSymbolicLink()) && (showHidden || !e.name.startsWith('.')))\n .map((e) => ({ name: e.name, path: path.join(target, e.name) }))\n .sort((a, b) => a.name.localeCompare(b.name));\n const parent = path.dirname(target);\n return { path: target, ...(parent !== target ? { parent } : {}), dirs };\n}\n\n/** Removes an upload (made for a message that was never sent). */\nexport async function removeUpload(id: string): Promise<void> {\n if (!/^[0-9a-f-]{36}$/.test(id)) return;\n await rm(path.join(uploadsDir(), id), { recursive: true, force: true });\n}\n", "import { realpath } from 'node:fs/promises';\nimport os from 'node:os';\n\nimport { listSessions } from '@anthropic-ai/claude-agent-sdk';\n\nimport { lastTurnTime, transcriptPath } from './transcripts.js';\n\n/** A yes holds for a while; a no is asked again soon (the folder's first session may be starting). */\nconst YES_MS = 30_000;\nconst NO_MS = 3_000;\nconst cache = new Map<string, { at: number; value: boolean }>();\n\n/**\n * Whether Claude Code has run in this very folder, so what the folder brings (its project\n * settings, hooks and MCP servers, its git config with filters and hooks) is something the user\n * already let run. A folder Pelican is only shown (a fresh clone, a download, an address another\n * site opened) runs nothing of its own: probes get the user's settings only, and git does not run.\n *\n * Claude Code files sessions by the folder's path with punctuation turned into dashes, so `a.b`\n * and `a-b` share a list, and the list may fill in a session's folder from that shared name. A\n * session counts only by the folder its own transcript records, and only if it is this one.\n */\nexport async function claudeRanIn(folder: string): Promise<boolean> {\n const dir = await realpath(folder).catch(() => folder);\n if (dir === os.homedir()) return true;\n const cached = cache.get(dir);\n if (cached && Date.now() - cached.at < (cached.value ? YES_MS : NO_MS)) return cached.value;\n let value = false;\n const sessions = await listSessions({ dir, limit: 50, includeWorktrees: false }).catch(() => []);\n for (const session of sessions) {\n const file = await transcriptPath(session.sessionId);\n const recorded = file ? (await lastTurnTime(file))?.lastTurn?.cwd : undefined;\n if (!recorded) continue;\n if (recorded === dir || (await realpath(recorded).catch(() => '')) === dir) {\n value = true;\n break;\n }\n }\n if (cache.size > 2000) cache.clear();\n cache.set(dir, { at: Date.now(), value });\n return value;\n}\n", "import { realpath } from 'node:fs/promises';\nimport os from 'node:os';\n\nimport { query } from '@anthropic-ai/claude-agent-sdk';\nimport type { McpServerStatus, ModelInfo, SDKControlGetUsageResponse, SDKUserMessage, SlashCommand } from '@anthropic-ai/claude-agent-sdk';\n\nimport type { Account, Agent, Command, McpServer, ModelOption, PlanUsage } from '../shared/protocol.js';\nimport { config } from './config.js';\nimport { DEMO_MODELS } from './demo.js';\nimport { claudeRanIn } from './trust.js';\n\nconst TTL_MS = 10 * 60 * 1000;\nconst PROBE_TIMEOUT_MS = 30_000;\nconst MAX_PROBES = 2;\n\n/** A prompt stream that never yields: the process starts, answers control requests, and sends no turn. */\nconst idlePrompt: AsyncIterable<SDKUserMessage> = {\n [Symbol.asyncIterator]: () => ({ next: () => new Promise<never>(() => {}) }),\n};\n\nlet running = 0;\nconst waiting: (() => void)[] = [];\n\n/** At most MAX_PROBES Claude processes at a time, however many requests ask. */\nasync function slot<T>(work: () => Promise<T>): Promise<T> {\n if (running >= MAX_PROBES) await new Promise<void>((resolve) => waiting.push(resolve));\n running += 1;\n try {\n return await work();\n } finally {\n running -= 1;\n waiting.shift()?.();\n }\n}\n\nconst inflight = new Map<string, Promise<unknown>>();\n\n/**\n * Starts Claude Code without a conversation in `cwd`, asks it something, and\n * stops it. Costs no tokens and writes no transcript. One probe per folder and\n * question at a time; it gives up after PROBE_TIMEOUT_MS.\n */\nasync function probe<T>(key: string, cwd: string, ask: (q: ReturnType<typeof query>) => Promise<T>): Promise<T> {\n const dir = await realpath(cwd);\n const id = `${key}:${dir}`;\n const pending = inflight.get(id);\n if (pending) return pending as Promise<T>;\n const work = slot(async () => {\n const q = query({\n prompt: idlePrompt,\n options: {\n cwd: dir,\n persistSession: false,\n settingSources: (await claudeRanIn(dir)) ? ['user', 'project', 'local'] : ['user'],\n ...(config.claudeExecutable ? { pathToClaudeCodeExecutable: config.claudeExecutable } : {}),\n },\n });\n let timer: NodeJS.Timeout | undefined;\n try {\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('Claude Code did not answer in time.')), PROBE_TIMEOUT_MS);\n });\n return await Promise.race([ask(q), timeout]);\n } finally {\n clearTimeout(timer);\n q.close();\n }\n }).finally(() => inflight.delete(id));\n inflight.set(id, work);\n return work;\n}\n\nfunction toOption(info: ModelInfo): ModelOption {\n return {\n value: info.value,\n label: info.displayName || info.value,\n description: info.description ?? '',\n efforts: info.supportedEffortLevels ?? [],\n supportsFast: Boolean(info.supportsFastMode),\n ...(info.supportsAutoMode !== undefined ? { supportsAuto: info.supportsAutoMode } : {}),\n ...(info.resolvedModel ? { resolvedModel: info.resolvedModel } : {}),\n };\n}\n\nexport type ModelList = { models: ModelOption[]; signedIn: boolean | null; account?: Account };\n\nlet cache: { at: number; value: ModelList } | undefined;\n\n/**\n * The models this account can use, with their effort levels and fast-mode\n * support, as Claude Code reports them, and whether Claude Code is signed in.\n */\nexport async function listModels(): Promise<ModelList> {\n if (config.demo) return { models: DEMO_MODELS, signedIn: true, account: { email: 'you@example.com', plan: 'max' } };\n if (cache && Date.now() - cache.at < TTL_MS) return cache.value;\n const asking = probe('models', os.homedir(), async (q) => {\n const models = (await q.supportedModels()).map(toOption);\n const account = await q.accountInfo().catch(() => undefined);\n const signedIn = account ? Boolean(account.email || account.tokenSource || account.apiKeySource || account.apiProvider) : null;\n const who: Account | undefined = account?.email || account?.organization || account?.subscriptionType\n ? {\n ...(account.email ? { email: account.email } : {}),\n ...(account.organization ? { organization: account.organization } : {}),\n ...(account.subscriptionType ? { plan: account.subscriptionType } : {}),\n }\n : undefined;\n return { models, signedIn, ...(who ? { account: who } : {}) };\n })\n .then((value) => {\n cache = { at: Date.now(), value };\n return value;\n });\n try {\n return await asking;\n } catch (error) {\n if (cache) return cache.value;\n throw error;\n }\n}\n\nconst DEMO_COMMANDS: Command[] = [\n { name: 'compact', description: 'Summarize the conversation so far to free up context', argumentHint: '' },\n { name: 'review', description: 'Review the changes on this branch', argumentHint: '' },\n { name: 'init', description: 'Write a CLAUDE.md for this project', argumentHint: '' },\n];\nconst DEMO_AGENTS: Agent[] = [{ name: 'code-reviewer', description: 'Reviews a change for bugs and style' }];\n\nexport type Completions = { commands: Command[]; agents: Agent[] };\nconst completionCache = new Map<string, { at: number; value: Completions }>();\n\n/** Slash commands (built-in, the project's and the user's, skills) and subagents available in a folder. */\nexport async function listCompletions(cwd: string): Promise<Completions> {\n if (config.demo) return { commands: DEMO_COMMANDS, agents: DEMO_AGENTS };\n const dir = await realpath(cwd);\n const cached = completionCache.get(dir);\n if (cached && Date.now() - cached.at < TTL_MS) return cached.value;\n const value = await probe('completions', dir, async (q) => {\n const [commands, agents] = await Promise.all([q.supportedCommands(), q.supportedAgents().catch(() => [])]);\n return {\n commands: commands.map((c: SlashCommand) => ({ name: c.name, description: c.description ?? '', argumentHint: c.argumentHint ?? '' })),\n agents: agents.map((a) => ({ name: a.name, description: a.description ?? '' })),\n };\n });\n completionCache.set(dir, { at: Date.now(), value });\n return value;\n}\n\nexport function toMcpServers(list: McpServerStatus[]): McpServer[] {\n return list.map((server) => ({\n name: server.name,\n status: server.status,\n ...(server.error ? { error: server.error } : {}),\n tools: server.tools?.length ?? 0,\n ...(server.scope ? { scope: server.scope } : {}),\n }));\n}\n\nconst mcpCache = new Map<string, { at: number; servers: McpServer[] }>();\n\n/** The MCP servers a session in this folder gets, and whether they connect, when no Claude runs there. */\nexport async function probeMcpServers(cwd: string): Promise<McpServer[]> {\n if (config.demo) return [{ name: 'github', status: 'connected', tools: 26, scope: 'user' }, { name: 'linear', status: 'needs-auth', tools: 0, scope: 'project' }];\n const dir = await realpath(cwd);\n const cached = mcpCache.get(dir);\n if (cached && Date.now() - cached.at < 60_000) return cached.servers;\n const servers = toMcpServers(await probe('mcp', dir, (q) => q.mcpServerStatus()));\n mcpCache.set(dir, { at: Date.now(), servers });\n return servers;\n}\n\nconst DEMO_PLAN: PlanUsage = {\n windows: [\n { type: 'five_hour', utilization: 0.42, resetsAt: Date.now() + 2.4 * 3600_000 },\n { type: 'seven_day', utilization: 0.18, resetsAt: Date.now() + 3.5 * 86400_000 },\n ],\n};\n\ntype Window = { utilization: number | null; resets_at: string | null } | null | undefined;\nlet planCache: { at: number; value: PlanUsage | null } | undefined;\n\n/**\n * The plan's usage, as Claude Code's `/usage` reads it from claude.ai: the windows of a Pro or\n * Max plan, and the monthly cap on extra usage that Team and Enterprise plans have instead.\n * Null where plan limits do not apply (an API key, Bedrock, Vertex) or the answer is not there.\n * The SDK marks this call experimental, so anything unexpected is no answer, not an error.\n */\nexport async function planUsage(): Promise<PlanUsage | null> {\n if (config.demo) return DEMO_PLAN;\n if (planCache && Date.now() - planCache.at < 60_000) return planCache.value;\n const value = await probe('usage', os.homedir(), async (q) => {\n const ask = (q as unknown as { usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET?: (o: { skipBehaviors: boolean }) => Promise<SDKControlGetUsageResponse> })\n .usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET;\n if (typeof ask !== 'function') return null;\n const answer = await ask.call(q, { skipBehaviors: true });\n const limits = answer?.rate_limits;\n if (!answer?.rate_limits_available || !limits) return null;\n const windows: PlanUsage['windows'] = [];\n const add = (type: string, window: Window, label?: string) => {\n if (!window || typeof window.utilization !== 'number') return;\n const resetsAt = window.resets_at ? Date.parse(window.resets_at) : NaN;\n windows.push({ type, ...(label ? { label } : {}), utilization: window.utilization / 100, ...(Number.isFinite(resetsAt) ? { resetsAt } : {}) });\n };\n add('five_hour', limits.five_hour);\n add('seven_day', limits.seven_day);\n add('seven_day_opus', limits.seven_day_opus);\n add('seven_day_sonnet', limits.seven_day_sonnet);\n for (const scoped of limits.model_scoped ?? []) add(`model:${scoped.display_name}`, scoped, `Weekly ${scoped.display_name} limit`);\n const extra = limits.extra_usage;\n return {\n windows,\n ...(extra ? {\n extra: {\n enabled: Boolean(extra.is_enabled),\n ...(typeof extra.used_credits === 'number' ? { usedCents: extra.used_credits } : {}),\n ...(typeof extra.monthly_limit === 'number' ? { limitCents: extra.monthly_limit } : {}),\n ...(typeof extra.utilization === 'number' ? { utilization: extra.utilization / 100 } : {}),\n ...(extra.currency ? { currency: extra.currency } : {}),\n },\n } : {}),\n };\n }).catch(() => null);\n planCache = { at: Date.now(), value };\n return value;\n}\n", "import { execFile } from 'node:child_process';\nimport { realpathSync } from 'node:fs';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { config } from './config.js';\n\nconst run = promisify(execFile);\n\n/**\n * Git runs only in folders Claude Code has worked in (see trust.ts), where the repository's\n * config is the user's own. Even there Pelican only reads, so it starts nothing git would start\n * on the side: no hooks, no file-system monitor, no external diff or text converter. It takes\n * no locks the user's own git would trip over, never asks for credentials, reads pathspecs\n * literally (`:/` is a name, not \"the top of the repository\"), and leaves submodules' work\n * trees alone.\n */\nconst SAFE = ['-c', 'core.fsmonitor=false', '-c', 'core.hooksPath=/dev/null', '-c', 'safe.bareRepository=explicit', '-c', 'core.quotepath=off'];\n\nfunction environment(): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env, GIT_OPTIONAL_LOCKS: '0', GIT_TERMINAL_PROMPT: '0', GIT_LITERAL_PATHSPECS: '1' };\n // The folder decides the repository, not whatever the server was started with.\n for (const name of Object.keys(env)) {\n if (/^GIT_(DIR|WORK_TREE|INDEX_FILE|COMMON_DIR|OBJECT_DIRECTORY|ALTERNATE_OBJECT_DIRECTORIES|NAMESPACE|CONFIG_PARAMETERS|CONFIG_COUNT|CONFIG_KEY_\\d+|CONFIG_VALUE_\\d+|CEILING_DIRECTORIES)$/.test(name)) delete env[name];\n }\n if (config.demo) {\n // The demo's folders may sit inside a repository of yours (a home folder kept in git):\n // git must not look above the demo for one.\n try {\n env.GIT_CEILING_DIRECTORIES = path.dirname(realpathSync(config.home));\n } catch {\n env.GIT_CEILING_DIRECTORIES = path.dirname(config.home);\n }\n }\n return env;\n}\n\n/** Runs a read-only git command in `cwd`; rejects on a non-zero exit, with stdout on the error. */\nexport async function git(cwd: string, args: string[], timeout = 15_000): Promise<string> {\n const command = args[0]!;\n const extra = command === 'diff' ? ['--no-ext-diff', '--no-textconv', '--ignore-submodules=dirty']\n : command === 'status' ? ['--ignore-submodules=dirty']\n : [];\n const { stdout } = await run('git', [...SAFE, command, ...extra, ...args.slice(1)], { cwd, env: environment(), maxBuffer: 32 * 1024 * 1024, timeout });\n return stdout;\n}\n", "import { readdir } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { config } from './config.js';\nimport { git } from './git.js';\nimport { claudeRanIn } from './trust.js';\n\nconst cache = new Map<string, { at: number; files: string[] }>();\nconst TTL_MS = 30_000;\nconst SKIP = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.venv', 'venv', '__pycache__', 'target', '.cache']);\nconst MAX_FILES = 20_000;\n\n/** Files of a folder as `git ls-files` sees them (ignored ones left out), or by walking it. */\nasync function projectFiles(cwd: string): Promise<string[]> {\n const cached = cache.get(cwd);\n if (cached && Date.now() - cached.at < TTL_MS) return cached.files;\n let files: string[];\n try {\n // Git only where Claude Code has run; anywhere else the folder is walked.\n if (!config.demo && !(await claudeRanIn(cwd))) throw new Error('not trusted');\n const stdout = await git(cwd, ['ls-files', '--cached', '--others', '--exclude-standard'], 5000);\n files = stdout.split('\\n').filter(Boolean).slice(0, MAX_FILES);\n } catch {\n files = [];\n const walk = async (dir: string, depth: number): Promise<void> => {\n if (depth > 8 || files.length >= MAX_FILES) return;\n let entries;\n try {\n entries = await readdir(path.join(cwd, dir), { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (files.length >= MAX_FILES) return;\n if (entry.name.startsWith('.') || SKIP.has(entry.name)) continue;\n const rel = dir ? `${dir}/${entry.name}` : entry.name;\n if (entry.isDirectory()) await walk(rel, depth + 1);\n else files.push(rel);\n }\n };\n await walk('', 0);\n }\n cache.set(cwd, { at: Date.now(), files });\n return files;\n}\n\n/** How well `query` matches `file`: higher is better, -1 for no match. */\nfunction score(file: string, query: string): number {\n const f = file.toLowerCase();\n const q = query.toLowerCase();\n const base = f.slice(f.lastIndexOf('/') + 1);\n if (base === q) return 1000;\n if (base.startsWith(q)) return 800 - base.length;\n if (base.includes(q)) return 600 - base.length;\n if (f.includes(q)) return 400 - f.length / 10;\n // Every character in order: \"stset\" finds \"src/settings/Settings.tsx\".\n let at = 0;\n for (const char of q) {\n at = f.indexOf(char, at);\n if (at === -1) return -1;\n at += 1;\n }\n return 100 - f.length / 10;\n}\n\n/** Files and folders matching a query, for `@` mentions in the composer. */\nexport async function searchFiles(cwd: string, query: string, limit = 30): Promise<string[]> {\n const files = await projectFiles(cwd);\n const dirs = new Set<string>();\n for (const file of files) {\n let cut = file.lastIndexOf('/');\n while (cut > 0) {\n dirs.add(`${file.slice(0, cut)}/`);\n cut = file.lastIndexOf('/', cut - 1);\n }\n }\n const candidates = [...files, ...dirs];\n if (!query) return files.slice().sort((a, b) => a.split('/').length - b.split('/').length || a.localeCompare(b)).slice(0, limit);\n return candidates\n .map((file) => ({ file, score: score(file, query) }))\n .filter((m) => m.score >= 0)\n .sort((a, b) => b.score - a.score || a.file.localeCompare(b.file))\n .slice(0, limit)\n .map((m) => m.file);\n}\n", "import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport webpush from 'web-push';\n\nimport { config } from './config.js';\n\ntype Subscription = webpush.PushSubscription & { openUrl?: string };\n\nconst keysFile = () => path.join(config.stateDir, 'vapid.json');\nconst subsFile = () => path.join(config.stateDir, 'push-subscriptions.json');\n\nlet keys: { publicKey: string; privateKey: string } | undefined;\nlet subscriptions: Subscription[] = [];\n\nasync function save(file: string, value: unknown): Promise<void> {\n await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });\n await writeFile(file, JSON.stringify(value, null, 2), { mode: 0o600 });\n}\n\n/** Loads this machine's VAPID keys (created on first start) and the saved subscriptions. */\nexport async function initPush(): Promise<void> {\n try {\n keys = JSON.parse(await readFile(keysFile(), 'utf8'));\n } catch {\n keys = webpush.generateVAPIDKeys();\n await save(keysFile(), keys);\n }\n webpush.setVapidDetails('https://github.com/Ki-Seki/pelican', keys!.publicKey, keys!.privateKey);\n try {\n subscriptions = JSON.parse(await readFile(subsFile(), 'utf8'));\n } catch {\n subscriptions = [];\n }\n}\n\nexport function pushKey(): string | undefined {\n return keys?.publicKey;\n}\n\nexport async function addSubscription(subscription: Subscription): Promise<void> {\n subscriptions = subscriptions.filter((s) => s.endpoint !== subscription.endpoint);\n subscriptions.push(subscription);\n await save(subsFile(), subscriptions);\n}\n\nexport async function removeSubscription(endpoint: string): Promise<void> {\n subscriptions = subscriptions.filter((s) => s.endpoint !== endpoint);\n await save(subsFile(), subscriptions);\n}\n\nexport type PushPayload = { title: string; body: string; sessionId: string; tag: string };\n\n/** Sends a notification to every subscribed device; drops subscriptions the push service rejects. */\nexport async function notify(payload: PushPayload): Promise<void> {\n if (!keys || subscriptions.length === 0) return;\n const gone: string[] = [];\n await Promise.all(subscriptions.map(async (subscription) => {\n // PELICAN_SESSION_URL wins (for a page that embeds Pelican); otherwise the page that subscribed.\n const id = encodeURIComponent(payload.sessionId);\n const url = config.sessionUrl ? config.sessionUrl.replaceAll('{id}', id)\n : subscription.openUrl ? `${subscription.openUrl}${id}` : undefined;\n const body = JSON.stringify({ ...payload, url });\n try {\n await webpush.sendNotification(subscription, body, { TTL: 3600, urgency: 'high' });\n } catch (error) {\n const status = (error as { statusCode?: number }).statusCode;\n if (status === 404 || status === 410) gone.push(subscription.endpoint);\n else console.warn('[pelican] push failed', status ?? (error as Error).message);\n }\n }));\n if (gone.length > 0) {\n subscriptions = subscriptions.filter((s) => !gone.includes(s.endpoint));\n await save(subsFile(), subscriptions);\n }\n}\n", "import { spawn } from 'node:child_process';\n\nimport type { ShellResult } from '../shared/protocol.js';\n\nconst MAX_OUTPUT = 200_000;\nconst TIMEOUT_MS = 2 * 60_000;\n\n/**\n * Runs one `!` command in a session's folder through the user's login shell,\n * like Claude Code's bash mode: no terminal, output captured up to a limit,\n * stopped after two minutes.\n */\nexport function runCommand(cwd: string, command: string): Promise<ShellResult> {\n const shell = process.env.SHELL || '/bin/bash';\n const started = Date.now();\n return new Promise((resolve) => {\n let stdout = '';\n let stderr = '';\n let truncated = false;\n const child = spawn(shell, ['-lc', command], { cwd, env: { ...process.env, PAGER: 'cat', GIT_PAGER: 'cat', TERM: 'dumb' }, stdio: ['ignore', 'pipe', 'pipe'] });\n const take = (current: string, chunk: Buffer) => {\n if (current.length >= MAX_OUTPUT) {\n truncated = true;\n return current;\n }\n const next = current + chunk.toString('utf8');\n if (next.length > MAX_OUTPUT) truncated = true;\n return next.slice(0, MAX_OUTPUT);\n };\n child.stdout.on('data', (chunk: Buffer) => { stdout = take(stdout, chunk); });\n child.stderr.on('data', (chunk: Buffer) => { stderr = take(stderr, chunk); });\n const timer = setTimeout(() => child.kill('SIGKILL'), TIMEOUT_MS);\n const finish = (code: number | null, error?: Error) => {\n clearTimeout(timer);\n resolve({\n command,\n stdout,\n stderr: error ? `${stderr}${error.message}` : stderr,\n code,\n durationMs: Date.now() - started,\n ...(truncated ? { truncated: true } : {}),\n });\n };\n child.on('error', (error) => finish(null, error));\n child.on('close', (code, signal) => finish(signal ? null : code));\n });\n}\n\nconst escape = (text: string) => text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n\n/** The command and its output the way Claude Code's bash mode writes them into the conversation. */\nexport function bashModeText(result: ShellResult): string {\n const note = result.code === null ? '\\n(stopped after two minutes)' : result.code !== 0 ? `\\n(exit code ${result.code})` : '';\n return `<bash-input>${escape(result.command)}</bash-input><bash-stdout>${escape(result.stdout)}</bash-stdout><bash-stderr>${escape(result.stderr + note)}</bash-stderr>`;\n}\n", "import type { SDKRateLimitInfo } from '@anthropic-ai/claude-agent-sdk';\n\nimport type { UsageLimit } from '../shared/protocol.js';\n\n/** The account's usage limits as Claude Code last reported them, one per kind of limit. */\nconst limits = new Map<string, UsageLimit>();\n\nexport function recordLimit(info: SDKRateLimitInfo): void {\n const type = info.rateLimitType ?? 'unknown';\n limits.set(type, {\n type,\n status: info.status,\n ...(typeof info.utilization === 'number' ? { utilization: info.utilization } : {}),\n ...(typeof info.resetsAt === 'number' ? { resetsAt: info.resetsAt < 1e12 ? info.resetsAt * 1000 : info.resetsAt } : {}),\n at: Date.now(),\n });\n}\n\nexport function usageLimits(): UsageLimit[] {\n const now = Date.now();\n // A limit that has reset since it was reported says nothing any more.\n return [...limits.values()].filter((limit) => !limit.resetsAt || limit.resetsAt > now);\n}\n", "import { randomUUID } from 'node:crypto';\nimport { readFile, stat } from 'node:fs/promises';\n\nimport { query } from '@anthropic-ai/claude-agent-sdk';\nimport type {\n CanUseTool,\n EffortLevel,\n Options,\n PermissionResult,\n PermissionUpdate,\n Query,\n SDKMessage,\n SDKUserMessage,\n} from '@anthropic-ai/claude-agent-sdk';\n\nimport type {\n Attachment,\n BackgroundTask,\n ChatMessage,\n McpServer,\n PartialBlock,\n PermissionDecision,\n PermissionRequest,\n RewindResult,\n RunSettings,\n RunState,\n SendMode,\n SessionStatus,\n ShellResult,\n} from '../shared/protocol.js';\nimport { config } from './config.js';\nimport { DEMO_WAITING_SESSION, demoRunState } from './demo.js';\nimport { toMcpServers } from './models.js';\nimport { broadcastAll, feedFor, sessionsChanged } from './feed.js';\nimport { liveProcesses } from './live.js';\nimport { toChatMessage } from './normalize.js';\nimport { notify } from './push.js';\nimport { sessionInfo } from './sessions.js';\nimport { bashModeText } from './shell.js';\nimport { lastTurnTime, refreshTranscriptIndex, transcriptPath } from './transcripts.js';\nimport { recordLimit } from './usage.js';\n\n/** Messages the user sends, handed to the Claude process as they come. */\nclass InputQueue implements AsyncIterable<SDKUserMessage> {\n private items: SDKUserMessage[] = [];\n private waiters: Array<(result: IteratorResult<SDKUserMessage>) => void> = [];\n private done = false;\n\n push(item: SDKUserMessage): void {\n const waiter = this.waiters.shift();\n if (waiter) waiter({ value: item, done: false });\n else this.items.push(item);\n }\n\n close(): void {\n this.done = true;\n for (const waiter of this.waiters.splice(0)) waiter({ value: undefined, done: true });\n }\n\n [Symbol.asyncIterator](): AsyncIterator<SDKUserMessage> {\n return {\n next: () => {\n const item = this.items.shift();\n if (item) return Promise.resolve({ value: item, done: false });\n if (this.done) return Promise.resolve({ value: undefined, done: true });\n return new Promise((resolve) => this.waiters.push(resolve));\n },\n };\n }\n}\n\ntype Pending = {\n request: PermissionRequest;\n input: Record<string, unknown>;\n suggestions?: PermissionUpdate[];\n resolve: (result: PermissionResult) => void;\n};\n\nexport type SendInput = {\n text: string;\n attachments: Attachment[];\n settings: RunSettings;\n /** Id for the user message, so the client's optimistic copy matches the transcript row. */\n uuid?: string;\n /** While a turn runs: join it at Claude's next step, or wait for it to end. */\n mode?: SendMode;\n};\n\nconst sdkModel = (model: string | undefined) => (model && model !== 'default' ? model : undefined);\n\n/** One Claude Code process driving one session. It stays alive between turns until idle. */\n/** Claude Code's error, or what it means for someone who edited their last prompt. */\nfunction readableError(text: string): string {\n if (text.includes('Resume rejected by --resume-drops-turn')) {\n return 'The conversation changed after that prompt (another app added to it), so the prompt was not replaced. Reload, then edit it again or send a new message.';\n }\n return text;\n}\n\nexport class SessionRun {\n busy = false;\n closed = false;\n error?: string;\n lastActive = Date.now();\n private partial: PartialBlock[] = [];\n private completedBlocks = 0;\n private permissions = new Map<string, Pending>();\n private input = new InputQueue();\n private q?: Query;\n private emitTimer?: NodeJS.Timeout;\n private title = '';\n /** Set by Stop: the turn then ends with an error result that is not a failure. */\n private interrupted = false;\n private lastTurn?: RunState['lastTurn'];\n private context?: RunState['context'];\n private activity?: RunState['activity'];\n private tasks: BackgroundTask[] = [];\n\n get hasBackgroundTasks(): boolean {\n return this.tasks.length > 0;\n }\n /** Messages waiting for the turn to end, published when it does. */\n private queued: ChatMessage[] = [];\n private started!: { resolve: () => void; reject: (error: Error) => void };\n /** Settles once Claude has started (its first message), or fails when it ends before that. */\n readonly ready = new Promise<void>((resolve, reject) => {\n this.started = { resolve, reject };\n });\n // Most callers never wait for it: a failure here must not count as unhandled.\n private readonly readyHandled = this.ready.catch(() => undefined);\n private suggestion?: string;\n\n constructor(\n readonly sessionId: string,\n readonly cwd: string,\n private settings: RunSettings,\n private readonly resume: boolean,\n /** Resume only up to this entry, dropping the turn after it (an edited last prompt). */\n private readonly resumeAt?: { entry: string; dropsTurn: string },\n ) {}\n\n start(): void {\n const settings = this.settings;\n const options: Options = {\n cwd: this.cwd,\n ...(this.resume ? { resume: this.sessionId } : { sessionId: this.sessionId }),\n // Claude Code checks that everything past the entry belongs to the dropped turn, so a\n // message another app added meanwhile is never thrown away unseen.\n ...(this.resume && this.resumeAt ? { resumeSessionAt: this.resumeAt.entry, resumeDropsTurn: this.resumeAt.dropsTurn } : {}),\n ...(sdkModel(settings.model) ? { model: sdkModel(settings.model) } : {}),\n ...(settings.effort ? { effort: settings.effort as EffortLevel } : {}),\n permissionMode: settings.permissionMode ?? 'default',\n allowDangerouslySkipPermissions: true,\n ...(settings.fast || settings.ultracode ? {\n settings: {\n ...(settings.fast ? { fastMode: true } : {}),\n // Ultracode needs workflows; both are session settings, like the CLI's --settings.\n ...(settings.ultracode ? { ultracode: true, enableWorkflows: true } : {}),\n },\n } : {}),\n includePartialMessages: true,\n // Lets \"Rewind code\" put files back the way they were before a prompt.\n enableFileCheckpointing: true,\n promptSuggestions: true,\n settingSources: ['user', 'project', 'local'],\n systemPrompt: { type: 'preset', preset: 'claude_code' },\n tools: { type: 'preset', preset: 'claude_code' },\n canUseTool: this.canUseTool,\n ...(config.claudeExecutable ? { pathToClaudeCodeExecutable: config.claudeExecutable } : {}),\n stderr: (data) => {\n if (process.env.PELICAN_DEBUG) process.stderr.write(`[claude ${this.sessionId.slice(0, 8)}] ${data}`);\n },\n };\n this.q = query({ prompt: this.input, options });\n void this.pump();\n void sessionInfo(this.sessionId).then((info) => {\n if (info) this.title = info.customTitle || info.summary || '';\n });\n }\n\n state(): RunState {\n return {\n sessionId: this.sessionId,\n cwd: this.cwd,\n busy: this.busy,\n settings: this.settings,\n permissions: [...this.permissions.values()].map((p) => p.request),\n partial: this.partial.slice(this.completedBlocks).filter(Boolean),\n ...(this.error ? { error: this.error } : {}),\n ...(this.lastTurn ? { lastTurn: this.lastTurn } : {}),\n ...(this.context ? { context: this.context } : {}),\n ...(this.activity ? { activity: this.activity } : {}),\n ...(this.tasks.length > 0 ? { tasks: this.tasks } : {}),\n ...(this.queued.length > 0 ? { queued: this.queued.map((m) => m.uuid) } : {}),\n ...(this.suggestion && !this.busy ? { suggestion: this.suggestion } : {}),\n };\n }\n\n status(): SessionStatus {\n if (this.permissions.size > 0) return 'waiting';\n return this.busy ? 'running' : 'idle';\n }\n\n /** Sends the run state now, or within 80 ms when called for streamed text. */\n private emit(soon = false): void {\n if (soon) {\n if (this.emitTimer) return;\n this.emitTimer = setTimeout(() => {\n this.emitTimer = undefined;\n this.emitNow();\n }, 80);\n return;\n }\n clearTimeout(this.emitTimer);\n this.emitTimer = undefined;\n this.emitNow();\n }\n\n private lastStatus?: SessionStatus;\n\n private emitNow(): void {\n const state = this.state();\n feedFor(this.sessionId).broadcast({ type: 'run', state });\n // Session lists only care about the status; streamed text stays with the session's viewers.\n const status = this.status();\n if (status !== this.lastStatus) {\n this.lastStatus = status;\n broadcastAll({ type: 'status', sessionId: this.sessionId, status });\n }\n }\n\n private setBusy(busy: boolean): void {\n if (this.busy === busy) return;\n this.busy = busy;\n this.lastActive = Date.now();\n if (!busy) {\n this.partial = [];\n this.completedBlocks = 0;\n }\n this.emit();\n sessionsChanged();\n }\n\n async send(input: SendInput): Promise<void> {\n await this.applySettings(input.settings);\n const uuid = input.uuid ?? randomUUID();\n const content: Array<Record<string, unknown>> = [];\n const fileLines: string[] = [];\n for (const attachment of input.attachments) {\n if (attachment.mime.startsWith('image/') && attachment.size <= 5 * 1024 * 1024) {\n const data = await readFile(attachment.path).catch(() => undefined);\n if (data) {\n content.push({ type: 'image', source: { type: 'base64', media_type: attachment.mime, data: data.toString('base64') } });\n continue;\n }\n }\n fileLines.push(`- ${attachment.path} (${attachment.name})`);\n }\n let text = input.text;\n if (fileLines.length > 0) text = `${text}${text ? '\\n\\n' : ''}Attached files:\\n${fileLines.join('\\n')}`;\n if (text) content.push({ type: 'text', text });\n // A running turn takes a new message at Claude's next step, unless it should wait for the turn to end.\n const queue = this.busy && input.mode === 'queue';\n const message = {\n type: 'user',\n uuid,\n session_id: this.sessionId,\n parent_tool_use_id: null,\n origin: { kind: 'human' },\n ...(this.busy ? { priority: queue ? 'later' : 'next' } : {}),\n message: { role: 'user', content },\n } as unknown as SDKUserMessage;\n this.error = undefined;\n this.suggestion = undefined;\n const chat = toChatMessage({ type: 'user', uuid, message: { role: 'user', content } });\n if (queue) {\n if (chat) this.queued.push({ ...chat, timestamp: Date.now() });\n this.emit();\n } else {\n this.setBusy(true);\n if (chat) await feedFor(this.sessionId).publish({ ...chat, timestamp: Date.now() });\n }\n this.input.push(message);\n }\n\n /**\n * Adds a `!` command and its output to the conversation without starting a\n * turn, the way Claude Code's bash mode does: Claude reads it with the next message.\n */\n async appendShell(result: ShellResult): Promise<void> {\n const uuid = randomUUID();\n const text = bashModeText(result);\n const message = {\n type: 'user',\n uuid,\n session_id: this.sessionId,\n parent_tool_use_id: null,\n origin: { kind: 'human' },\n shouldQuery: false,\n message: { role: 'user', content: text },\n } as unknown as SDKUserMessage;\n const chat = toChatMessage({ type: 'user', uuid, message: { role: 'user', content: text } });\n if (chat) await feedFor(this.sessionId).publish({ ...chat, timestamp: Date.now() });\n this.input.push(message);\n }\n\n /** Puts files back the way they were before the prompt `uuid`; `dryRun` only says what would change. */\n async rewind(uuid: string, dryRun: boolean): Promise<RewindResult> {\n if (!this.q) throw new Error('Claude is not running in this session');\n const result = await this.q.rewindFiles(uuid, { dryRun });\n return {\n canRewind: result.canRewind,\n ...(result.error ? { error: result.error } : {}),\n ...(result.filesChanged ? { files: result.filesChanged } : {}),\n ...(typeof result.insertions === 'number' ? { insertions: result.insertions } : {}),\n ...(typeof result.deletions === 'number' ? { deletions: result.deletions } : {}),\n };\n }\n\n async mcpServers(): Promise<McpServer[]> {\n if (!this.q) return [];\n return toMcpServers(await this.q.mcpServerStatus());\n }\n\n async setMcpServer(name: string, change: { enabled?: boolean; reconnect?: boolean }): Promise<void> {\n if (!this.q) throw new Error('Claude is not running in this session');\n if (change.reconnect) await this.q.reconnectMcpServer(name);\n if (change.enabled !== undefined) await this.q.toggleMcpServer(name, change.enabled);\n }\n\n async stopTask(taskId: string): Promise<void> {\n await this.q?.stopTask(taskId);\n }\n\n async applySettings(next: RunSettings): Promise<void> {\n const q = this.q;\n if (!q) return;\n const prev = this.settings;\n try {\n if ((next.model ?? 'default') !== (prev.model ?? 'default')) await q.setModel(sdkModel(next.model));\n // A new effort level sent without the ultracode key turns ultracode off, so both go together.\n if ((next.effort ?? '') !== (prev.effort ?? '') || Boolean(next.ultracode) !== Boolean(prev.ultracode)) {\n await q.applyFlagSettings({\n effortLevel: (next.effort as EffortLevel) ?? null,\n ultracode: Boolean(next.ultracode),\n ...(next.ultracode ? { enableWorkflows: true } : {}),\n });\n }\n if (Boolean(next.fast) !== Boolean(prev.fast)) await q.applyFlagSettings({ fastMode: Boolean(next.fast) });\n if (next.permissionMode && next.permissionMode !== (prev.permissionMode ?? 'default')) await q.setPermissionMode(next.permissionMode);\n } catch (error) {\n console.warn('[pelican] could not apply settings', (error as Error).message);\n }\n this.settings = { ...prev, ...next };\n }\n\n async setPermissionMode(mode: RunSettings['permissionMode']): Promise<void> {\n await this.applySettings({ ...this.settings, permissionMode: mode });\n this.emit();\n }\n\n /** Settings changed in the composer, applied to Claude now: from its next step, also mid-turn. */\n async updateSettings(next: RunSettings): Promise<void> {\n await this.applySettings({ ...this.settings, ...next });\n this.emit();\n }\n\n private canUseTool: CanUseTool = (toolName, input, { signal, suggestions, blockedPath, decisionReason }) => {\n const id = randomUUID();\n const request: PermissionRequest = {\n id,\n sessionId: this.sessionId,\n toolName,\n input,\n canRemember: Boolean(suggestions && suggestions.length > 0),\n ...(decisionReason ? { reason: decisionReason } : {}),\n ...(blockedPath ? { blockedPath } : {}),\n createdAt: Date.now(),\n };\n return new Promise<PermissionResult>((resolve) => {\n this.permissions.set(id, { request, input, ...(suggestions ? { suggestions } : {}), resolve });\n signal.addEventListener('abort', () => {\n if (this.permissions.delete(id)) {\n resolve({ behavior: 'deny', message: 'Cancelled.' });\n this.emit();\n sessionsChanged();\n }\n });\n this.emit();\n sessionsChanged();\n const what = toolName === 'AskUserQuestion' ? 'has a question for you'\n : toolName === 'ExitPlanMode' ? 'has a plan for you to review'\n : `wants to use ${toolName}`;\n void notify({ title: this.title || 'Claude Code', body: `Claude ${what}`, sessionId: this.sessionId, tag: `${this.sessionId}:ask` });\n });\n };\n\n decide(id: string, decision: PermissionDecision): boolean {\n const pending = this.permissions.get(id);\n if (!pending) return false;\n this.permissions.delete(id);\n if (decision.behavior === 'allow') {\n pending.resolve({\n behavior: 'allow',\n updatedInput: decision.updatedInput ?? pending.input,\n ...(decision.remember && pending.suggestions ? { updatedPermissions: pending.suggestions } : {}),\n });\n if (decision.permissionMode) void this.setPermissionMode(decision.permissionMode);\n } else {\n pending.resolve({ behavior: 'deny', message: decision.message?.trim() || 'The user declined this.' });\n }\n this.emit();\n sessionsChanged();\n return true;\n }\n\n async interrupt(): Promise<void> {\n this.interrupted = true;\n for (const [id] of this.permissions) this.decide(id, { behavior: 'deny', message: 'Interrupted by the user.' });\n try {\n await this.q?.interrupt();\n } catch (error) {\n console.warn('[pelican] interrupt failed', (error as Error).message);\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n for (const [id] of this.permissions) this.decide(id, { behavior: 'deny', message: 'Session closed.' });\n this.input.close();\n try {\n this.q?.close();\n } catch {\n // Already exited.\n }\n }\n\n /** How full the context window is now; shown in the session header. */\n private async readContext(): Promise<void> {\n try {\n const usage = await this.q?.getContextUsage();\n if (!usage) return;\n this.context = { percent: Math.round(usage.percentage), tokens: usage.totalTokens, max: usage.maxTokens };\n this.emit();\n } catch {\n // Older Claude Code; the header just shows no meter.\n }\n }\n\n private onStream(event: Record<string, unknown>): void {\n const index = typeof event.index === 'number' ? event.index : -1;\n switch (event.type) {\n case 'message_start':\n this.partial = [];\n this.completedBlocks = 0;\n break;\n case 'content_block_start': {\n const block = (event.content_block ?? {}) as Record<string, unknown>;\n if (block.type === 'text') this.partial[index] = { type: 'text', text: '' };\n else if (block.type === 'thinking') this.partial[index] = { type: 'thinking', thinking: '' };\n else if (block.type === 'tool_use' || block.type === 'server_tool_use') {\n this.partial[index] = { type: 'tool_use', name: String(block.name ?? ''), inputJson: '' };\n }\n break;\n }\n case 'content_block_delta': {\n const delta = (event.delta ?? {}) as Record<string, unknown>;\n const block = this.partial[index];\n if (!block) break;\n if (block.type === 'text' && delta.type === 'text_delta') block.text += String(delta.text ?? '');\n else if (block.type === 'thinking' && delta.type === 'thinking_delta') block.thinking += String(delta.thinking ?? '');\n else if (block.type === 'tool_use' && delta.type === 'input_json_delta') block.inputJson += String(delta.partial_json ?? '');\n break;\n }\n default:\n return;\n }\n this.setBusy(true);\n this.emit(true);\n }\n\n private async pump(): Promise<void> {\n const q = this.q!;\n try {\n for await (const message of q as AsyncIterable<SDKMessage>) {\n this.lastActive = Date.now();\n this.started.resolve();\n const raw = message as unknown as Record<string, unknown>;\n switch (message.type) {\n case 'stream_event':\n if (!message.parent_tool_use_id) this.onStream(message.event as unknown as Record<string, unknown>);\n break;\n case 'assistant':\n case 'user': {\n if (raw.parent_tool_use_id) break;\n if (message.type === 'assistant') this.completedBlocks += 1;\n if (this.activity?.kind === 'retrying') this.activity = undefined;\n const chat = toChatMessage(raw);\n if (chat) await feedFor(this.sessionId).publish({ ...chat, timestamp: chat.timestamp ?? Date.now() } satisfies ChatMessage);\n this.emit(true);\n break;\n }\n case 'system':\n if (message.subtype === 'session_state_changed') {\n const state = (raw as { state?: string }).state;\n if (state === 'running' || state === 'requires_action') this.setBusy(true);\n else if (state === 'idle') this.setBusy(false);\n } else if (message.subtype === 'init') {\n void refreshTranscriptIndex().then(sessionsChanged);\n } else if (message.subtype === 'status') {\n this.activity = message.status === 'compacting' ? { kind: 'compacting' } : undefined;\n this.emit();\n } else if (message.subtype === 'api_retry') {\n this.activity = { kind: 'retrying', attempt: message.attempt, max: message.max_retries, delayMs: message.retry_delay_ms };\n this.emit();\n } else if (message.subtype === 'background_tasks_changed') {\n this.tasks = message.tasks\n .filter((t) => !t.ambient)\n .map((t) => ({ id: t.task_id, description: t.description, type: t.subagent_type ?? t.task_type }));\n this.emit();\n }\n break;\n case 'rate_limit_event':\n recordLimit(message.rate_limit_info);\n break;\n case 'prompt_suggestion':\n this.suggestion = message.suggestion;\n this.emit();\n break;\n case 'result': {\n const stopped = this.interrupted;\n this.interrupted = false;\n const failed = !stopped && (message.subtype !== 'success' || message.is_error);\n this.error = failed\n ? readableError('errors' in message && message.errors.length > 0 ? message.errors.join('\\n') : ('result' in message ? String(message.result) : message.subtype))\n : undefined;\n this.lastTurn = {\n durationMs: message.duration_ms,\n ...(typeof message.total_cost_usd === 'number' ? { costUsd: message.total_cost_usd } : {}),\n ...(typeof message.usage?.output_tokens === 'number' ? { outputTokens: message.usage.output_tokens } : {}),\n };\n this.activity = undefined;\n // Messages that waited for this turn go in now: the next turn answers them.\n for (const waiting of this.queued.splice(0)) await feedFor(this.sessionId).publish(waiting);\n this.setBusy(false);\n this.emit();\n void this.readContext();\n if (!stopped) {\n void notify({\n title: this.title || 'Claude Code',\n body: failed ? `Stopped: ${this.error ?? 'error'}`.slice(0, 180) : 'Finished',\n sessionId: this.sessionId,\n tag: `${this.sessionId}:done`,\n });\n }\n break;\n }\n default:\n break;\n }\n }\n } catch (error) {\n this.error = readableError((error as Error).message);\n console.warn(`[pelican] session ${this.sessionId.slice(0, 8)} ended with an error:`, this.error);\n } finally {\n this.closed = true;\n this.started.reject(new Error(this.error ?? 'Claude Code stopped before it started'));\n for (const [, pending] of this.permissions) pending.resolve({ behavior: 'deny', message: 'Session closed.' });\n this.permissions.clear();\n this.setBusy(false);\n this.emit();\n // A newer run may have taken this session's place already (see sendMessage).\n if (runs.get(this.sessionId) === this) runs.delete(this.sessionId);\n sessionsChanged();\n }\n }\n}\n\nconst runs = new Map<string, SessionRun>();\n\nexport function getRun(sessionId: string): SessionRun | undefined {\n const run = runs.get(sessionId);\n return run && !run.closed ? run : undefined;\n}\n\n/** Status of a session this app runs; undefined when it has no Claude process here. */\nexport function runStatus(sessionId: string): SessionStatus | undefined {\n if (config.demo) return sessionId === DEMO_WAITING_SESSION ? 'waiting' : undefined;\n return getRun(sessionId)?.status();\n}\n\nexport function runState(sessionId: string): RunState | undefined {\n if (config.demo) return demoRunState(sessionId);\n return getRun(sessionId)?.state();\n}\n\nasync function isDirectory(dir: string): Promise<boolean> {\n return stat(dir).then((s) => s.isDirectory(), () => false);\n}\n\n/**\n * Sends a message, starting (or resuming) the session's Claude process when\n * none is running. Without a session id a new session starts in `cwd`.\n */\n/**\n * Whether another app (Claude Desktop, the terminal) has worked on the session since this\n * run's Claude last did. Its process then holds a conversation this one has not seen, and a\n * message sent to it would continue an old version, beside the other: it is started afresh.\n */\nasync function staleRun(run: SessionRun): Promise<boolean> {\n if (run.busy || run.hasBackgroundTasks) return false;\n const other = (await liveProcesses()).get(run.sessionId);\n if (other?.updatedAt && other.updatedAt > run.lastActive) return true;\n // An app that has closed since leaves no process to ask: a turn in the transcript newer than\n // anything this run did is the sign.\n const file = await transcriptPath(run.sessionId);\n const last = file ? (await lastTurnTime(file))?.lastTurn : undefined;\n return Boolean(last && !last.copied && last.time > run.lastActive + 2000);\n}\n\n/** The session's run, unless it holds an older version of the conversation: then it is closed. */\nasync function currentRun(sessionId: string): Promise<SessionRun | undefined> {\n const run = getRun(sessionId);\n if (run && (await staleRun(run))) {\n run.close();\n if (runs.get(sessionId) === run) runs.delete(sessionId);\n return getRun(sessionId);\n }\n return run;\n}\n\nexport async function sendMessage(target: { sessionId?: string; cwd?: string }, input: SendInput): Promise<string> {\n if (config.demo) throw new Error('This is the demo: nothing runs. Start Pelican without --demo to talk to Claude.');\n if (target.sessionId) {\n const existing = await currentRun(target.sessionId);\n if (existing) {\n await existing.send(input);\n return existing.sessionId;\n }\n const info = await sessionInfo(target.sessionId);\n const cwd = info?.cwd ?? target.cwd;\n if (!cwd || !(await isDirectory(cwd))) throw new Error(`The session's folder is missing: ${cwd ?? '(unknown)'}`);\n // Another request may have started one while this one looked the folder up.\n const started = getRun(target.sessionId);\n if (started) {\n await started.send(input);\n return started.sessionId;\n }\n const run = new SessionRun(target.sessionId, cwd, input.settings, true);\n runs.set(run.sessionId, run);\n run.start();\n await run.send(input);\n return run.sessionId;\n }\n const cwd = target.cwd;\n if (!cwd || !(await isDirectory(cwd))) throw new Error(`Not a folder: ${cwd ?? '(none)'}`);\n const run = new SessionRun(randomUUID(), cwd, input.settings, false);\n runs.set(run.sessionId, run);\n run.start();\n await run.send(input);\n return run.sessionId;\n}\n\n/**\n * Replaces the session's last turn: Claude resumes it up to `entry` (the row just before the\n * prompt being replaced) and gets `input` instead, in the same session, as rewinding does in\n * Claude Code. The old turn stays in the transcript, off the conversation's line.\n */\nexport async function replaceLastTurn(sessionId: string, cwd: string, entry: string, prompt: string, input: SendInput): Promise<string> {\n if (config.demo) throw new Error('This is the demo: nothing runs.');\n const existing = getRun(sessionId);\n if (existing?.busy) throw new Error('Stop Claude before editing a prompt.');\n if (existing) {\n existing.close();\n runs.delete(sessionId);\n }\n const run = new SessionRun(sessionId, cwd, input.settings, true, { entry, dropsTurn: prompt });\n runs.set(sessionId, run);\n run.start();\n await run.send(input);\n return sessionId;\n}\n\n/**\n * The session's Claude process, started (resumed, with no turn) when there is\n * none: for what only a running process can do, like rewinding files.\n */\nexport async function ensureRun(sessionId: string, settings: RunSettings = {}): Promise<SessionRun> {\n if (config.demo) throw new Error('This is the demo: nothing runs.');\n const existing = await currentRun(sessionId);\n if (existing) return existing;\n const info = await sessionInfo(sessionId);\n const cwd = info?.cwd;\n if (!cwd || !(await isDirectory(cwd))) throw new Error(`The session's folder is missing: ${cwd ?? '(unknown)'}`);\n const started = getRun(sessionId);\n if (started) return started;\n const run = new SessionRun(sessionId, cwd, settings, true);\n runs.set(sessionId, run);\n run.start();\n return run;\n}\n\n/** Closes Claude processes that have been idle for a while; the next message resumes them. */\nsetInterval(() => {\n for (const run of runs.values()) {\n if (!run.busy && run.status() === 'idle' && Date.now() - run.lastActive > config.idleCloseMs) run.close();\n }\n}, 60_000).unref();\n\nexport function closeAll(): void {\n for (const run of runs.values()) run.close();\n}\n", "import { open, readdir, realpath, stat } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport type { FileContent, GitFile, GitStatus, TreeEntry } from '../shared/protocol.js';\nimport { git } from './git.js';\n\nconst MAX_FILE = 512 * 1024;\nconst MAX_DIFF = 400_000;\n\n\nconst OUTSIDE = 'That path is outside the session\u2019s folder';\nconst within = (root: string, full: string) => full === root || full.startsWith(`${root}${path.sep}`);\n\n/**\n * A path inside the session's folder, or an error: the panels never reach outside it. No `..`\n * at all: resolving it by the text would cancel a linked folder before the link is checked\n * (`link/../x`), and the panels never ask for one.\n */\nexport function inside(cwd: string, relative: string): string {\n if ((relative ?? '').split(/[\\\\/]/).includes('..')) throw new Error(OUTSIDE);\n const root = path.resolve(cwd);\n const full = path.resolve(root, relative || '.');\n if (!within(root, full)) throw new Error(OUTSIDE);\n return full;\n}\n\n/** The same, for a path that exists, with links followed: a link may not lead out of the folder either. */\nasync function insideReal(cwd: string, relative: string): Promise<string> {\n const full = inside(cwd, relative);\n const [root, real] = await Promise.all([realpath(cwd), realpath(full)]);\n if (!within(root, real)) throw new Error(OUTSIDE);\n return real;\n}\n\nconst KIND: Record<string, GitFile['status']> = { M: 'modified', T: 'modified', A: 'added', D: 'deleted', R: 'renamed', C: 'added', U: 'conflicted' };\n\n/**\n * The repository the folder is in, when its work tree is the folder or a folder above it. A\n * repository whose config points its work tree somewhere else (core.worktree) would show files\n * from outside: Pelican treats that as no repository.\n */\nasync function repositoryOf(cwd: string): Promise<string | undefined> {\n try {\n const top = (await git(cwd, ['rev-parse', '--show-toplevel'], 5000)).trim();\n if (!top) return undefined;\n const [root, folder] = await Promise.all([realpath(top), realpath(cwd)]);\n return within(root, folder) ? root : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** What changed in the session's folder since the last commit, with line counts. */\nexport async function gitStatus(cwd: string): Promise<GitStatus> {\n if (!(await repositoryOf(cwd))) return { repo: false };\n let raw: string;\n let prefix = '';\n try {\n // A session in a folder inside a repository sees that folder's changes, by its own paths.\n prefix = (await git(cwd, ['rev-parse', '--show-prefix'], 5000)).trim();\n raw = await git(cwd, ['status', '--porcelain=v2', '--branch', '-z', '--untracked-files=all', '--', '.']);\n } catch {\n return { repo: false };\n }\n const local = (file: string | undefined) => (file && prefix && file.startsWith(prefix) ? file.slice(prefix.length) : file);\n const out: { repo: true; branch?: string; ahead?: number; behind?: number; files: GitFile[] } = { repo: true, files: [] };\n const entries = raw.split('\\0');\n for (let i = 0; i < entries.length; i += 1) {\n const entry = entries[i]!;\n if (entry.startsWith('# branch.head ')) {\n const head = entry.slice(14);\n if (head !== '(detached)') out.branch = head;\n } else if (entry.startsWith('# branch.ab ')) {\n const [ahead, behind] = entry.slice(12).split(' ');\n out.ahead = Number(ahead?.slice(1)) || 0;\n out.behind = Number(behind?.slice(1)) || 0;\n } else if (entry.startsWith('1 ') || entry.startsWith('2 ') || entry.startsWith('u ')) {\n const parts = entry.split(' ');\n const xy = parts[1] ?? '..';\n const code = xy[0] !== '.' ? xy[0]! : xy[1]!;\n const file = entry.startsWith('1 ') ? parts.slice(8).join(' ') : entry.startsWith('2 ') ? parts.slice(9).join(' ') : parts.slice(10).join(' ');\n const status = entry.startsWith('u ') ? 'conflicted' : KIND[code] ?? 'modified';\n const item: GitFile = { path: local(file)!, status };\n if (entry.startsWith('2 ')) {\n const from = local(entries[i + 1]);\n if (from) item.from = from;\n i += 1;\n }\n out.files.push(item);\n } else if (entry.startsWith('? ')) {\n out.files.push({ path: local(entry.slice(2))!, status: 'untracked' });\n }\n }\n // Line counts against the last commit (or the empty tree before the first one).\n try {\n const base = await git(cwd, ['rev-parse', '--verify', '-q', 'HEAD']).then(() => 'HEAD', () => '4b825dc642cb6eb9a060e54bf8d69288fbee4904');\n const numstat = await git(cwd, ['diff', '--numstat', '--relative', '-z', base]);\n const counts = new Map<string, { added?: number; removed?: number }>();\n const fields = numstat.split('\\0');\n for (let i = 0; i < fields.length; i += 1) {\n const field = fields[i]!;\n if (!field) continue;\n const [added, removed, name] = field.split('\\t');\n // A rename lists its old and new paths in the next two fields.\n const file = name === '' ? fields[(i += 2)] : name;\n if (!file) continue;\n counts.set(file, { ...(added !== '-' ? { added: Number(added) } : {}), ...(removed !== '-' ? { removed: Number(removed) } : {}) });\n }\n for (const file of out.files) Object.assign(file, counts.get(file.path) ?? {});\n } catch {\n // Counts are a nicety.\n }\n // A new file is all added lines; git does not count those for files it does not track yet.\n await Promise.all(out.files.filter((f) => f.status === 'untracked').slice(0, 200).map(async (file) => {\n try {\n const content = await readProjectFile(cwd, file.path);\n if (content.text !== undefined) file.added = content.text.split('\\n').length - (content.text.endsWith('\\n') ? 1 : 0);\n } catch {\n // Unreadable; no count.\n }\n }));\n return out;\n}\n\n/** The diff of one file against the last commit; an untracked file shows as all added. */\nexport async function gitDiff(cwd: string, file: string): Promise<string> {\n if (!(await repositoryOf(cwd))) return '';\n // The file itself may be gone (a deleted file has a diff), but the folder it is in may not lead\n // out of the session's folder through a link.\n const full = inside(cwd, file);\n const [root, parent] = await Promise.all([realpath(cwd), realpath(path.dirname(full)).catch(() => undefined)]);\n if (parent && !within(root, parent)) throw new Error(OUTSIDE);\n let diff = '';\n try {\n diff = await git(cwd, ['diff', 'HEAD', '--', file]);\n } catch {\n diff = '';\n }\n if (!diff) {\n // Untracked (or no commits yet): git diff --no-index exits with 1 when the files differ.\n // `./`: a file named `-` would otherwise be read from stdin.\n diff = await git(cwd, ['diff', '--no-index', '--', '/dev/null', `./${file}`]).catch((error: { stdout?: string }) => error.stdout ?? '');\n }\n return diff.length > MAX_DIFF ? `${diff.slice(0, MAX_DIFF)}\\n\u2026 (diff cut short)` : diff;\n}\n\nconst HIDDEN = new Set(['.git', 'node_modules', '.DS_Store']);\n\n/** One folder of the session's tree: folders first, then files. */\nexport async function listTree(cwd: string, relative: string): Promise<TreeEntry[]> {\n const dir = await insideReal(cwd, relative);\n const names = await readdir(dir, { withFileTypes: true });\n const entries: TreeEntry[] = [];\n for (const entry of names) {\n if (HIDDEN.has(entry.name)) continue;\n const rel = path.join(relative, entry.name);\n if (entry.isDirectory()) entries.push({ name: entry.name, path: rel, dir: true });\n else if (entry.isFile() || entry.isSymbolicLink()) {\n const size = await stat(path.join(dir, entry.name)).then((s) => (s.isDirectory() ? -1 : s.size), () => undefined);\n if (size === -1) entries.push({ name: entry.name, path: rel, dir: true });\n else entries.push({ name: entry.name, path: rel, dir: false, ...(size !== undefined ? { size } : {}) });\n }\n }\n return entries.sort((a, b) => Number(b.dir) - Number(a.dir) || a.name.localeCompare(b.name, undefined, { numeric: true }));\n}\n\nconst IMAGE: Record<string, string> = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp', svg: 'image/svg+xml' };\n\n/** A file to read on the phone: text up to half a megabyte, images as data, other binaries named only. */\nexport async function readProjectFile(cwd: string, relative: string): Promise<FileContent> {\n const full = await insideReal(cwd, relative);\n const info = await stat(full);\n const ext = path.extname(full).slice(1).toLowerCase();\n const handle = await open(full, 'r');\n try {\n if (IMAGE[ext] && info.size <= 4 * 1024 * 1024) {\n const data = Buffer.alloc(info.size);\n await handle.read(data, 0, info.size, 0);\n return { path: relative, size: info.size, image: `data:${IMAGE[ext]};base64,${data.toString('base64')}` };\n }\n const length = Math.min(info.size, MAX_FILE);\n const data = Buffer.alloc(length);\n await handle.read(data, 0, length, 0);\n if (data.subarray(0, 8000).includes(0)) return { path: relative, size: info.size, binary: true };\n return { path: relative, size: info.size, text: data.toString('utf8'), ...(info.size > MAX_FILE ? { truncated: true } : {}) };\n } finally {\n await handle.close();\n }\n}\n", "import { readFile, realpath, stat } from 'node:fs/promises';\nimport { isIP } from 'node:net';\nimport path from 'node:path';\n\nimport { getConnInfo } from '@hono/node-server/conninfo';\nimport { type Context, Hono } from 'hono';\nimport { bodyLimit } from 'hono/body-limit';\n\nimport pkg from '../../package.json' with { type: 'json' };\nimport type { Attachment, ChatMessage, PermissionDecision, PermissionMode, RunSettings, SendMode, ServerInfo } from '../shared/protocol.js';\nimport { currentTasks } from '../shared/tasks.js';\nimport { exportMarkdown, fork, messageBefore, remove } from './actions.js';\nimport { setArchived } from './archive.js';\nimport { authMode, beginAttempt, checkPassword, checkToken, cookieName, issueCode, newSessionCookie, redeemCode, signedIn } from './auth.js';\nimport { config } from './config.js';\nimport { sessionsChanged } from './feed.js';\nimport { findUpload, listDirs, MAX_UPLOAD_BYTES, readUpload, removeUpload, saveUpload } from './files.js';\nimport { createScratchFolder, isDirectory } from './folders.js';\nimport { listCompletions, listModels, planUsage, probeMcpServers } from './models.js';\nimport { searchFiles } from './project-files.js';\nimport { addSubscription, pushKey, removeSubscription } from './push.js';\nimport { ensureRun, getRun, replaceLastTurn, runState, runStatus, sendMessage } from './runner.js';\nimport { runCommand } from './shell.js';\nimport { usageLimits } from './usage.js';\nimport { gitDiff, gitStatus, listTree, readProjectFile } from './workspace.js';\nimport { hostAllowed, isLoopback, originAllowed } from './security.js';\nimport { listSummaries, loadMessages, loadPage, loadSubagent, recordedSettings, renameSessionTitle, sentFiles, sessionInfo, sessionSummary } from './sessions.js';\nimport { rawEntry, rawImages, transcriptPath } from './transcripts.js';\nimport { claudeRanIn } from './trust.js';\n\nexport const app = new Hono();\n\n/**\n * Who is asking, for limiting sign-in attempts: the address of the connection.\n * Behind a proxy on this machine that says so (PELICAN_TRUSTED_PROXY=1), the\n * client the proxy added last to X-Forwarded-For, when that is an IP address.\n * Anyone can write that header, so it is never believed otherwise.\n */\nfunction clientAddress(c: Context): string {\n let remote = 'unknown';\n try {\n remote = getConnInfo(c).remote.address ?? 'unknown';\n } catch {\n // Not served by Node (tests): one bucket for all.\n }\n const address = remote.replace(/^::ffff:/, '');\n if (config.trustedProxy && isLoopback(address)) {\n const forwarded = c.req.header('x-forwarded-for')?.split(',').at(-1)?.trim();\n if (forwarded && isIP(forwarded)) return forwarded;\n }\n return address;\n}\n\nconst secure = (c: { req: { header: (name: string) => string | undefined; url: string } }) =>\n c.req.header('x-forwarded-proto') === 'https' || c.req.url.startsWith('https:');\n\nconst cookieHeader = (c: Context, name: string, value: string, maxAge: number) =>\n `${name}=${value}; Path=/; Max-Age=${maxAge}; HttpOnly; SameSite=Strict${secure(c) ? '; Secure' : ''}`;\n\n/** Answers only to host names that point at this server (DNS rebinding). */\napp.use('*', async (c, next) => {\n if (!hostAllowed(c.req.header('host'))) {\n return c.text('This host name is not allowed. Add it to PELICAN_ALLOWED_HOSTS.', 403);\n }\n await next();\n});\n\n/** What the page loads without fetch, so it cannot mark: an uploaded image, a download. */\nconst unmarked = (c: Context) => c.req.method === 'GET'\n && (/^\\/api\\/uploads\\/[^/]+$/.test(c.req.path) || /^\\/api\\/sessions\\/[^/]+\\/export$/.test(c.req.path));\n\n/** The Origin header as an origin (`null`, garbage and missing are none). */\nfunction originOf(header: string | undefined): string | undefined {\n if (!header || header === 'null') return undefined;\n try {\n return new URL(header).origin;\n } catch {\n return undefined;\n }\n}\n\napp.use('/api/*', async (c, next) => {\n const origin = c.req.header('origin');\n const parsed = originOf(origin);\n const allowedOrigin = Boolean(parsed && config.allowedOrigins.includes(parsed));\n // Another site's page can make a browser send requests here, reads included, and some reads\n // run programs (git, Claude Code). Browsers say where a request comes from: only Pelican's own\n // page, or a page the config allows, gets in.\n const site = c.req.header('sec-fetch-site');\n if (site && site !== 'same-origin' && site !== 'none' && !allowedOrigin) {\n return c.json({ error: 'Request from another site' }, 403);\n }\n // Pelican's page also marks every request. Another site cannot add a header without asking\n // first (CORS), and Pelican never says yes, so this holds in browsers that send no Sec-Fetch-Site.\n if (!unmarked(c) && c.req.header('x-pelican') !== '1') {\n return c.json({ error: 'Requests must come from Pelican (send the header X-Pelican: 1)' }, 403);\n }\n if (c.req.method !== 'GET' && c.req.method !== 'HEAD'\n && !originAllowed(origin, c.req.header('host'), c.req.header('x-forwarded-host'))) {\n return c.json({ error: 'Request from another site' }, 403);\n }\n if (!c.req.path.startsWith('/api/auth') && !signedIn(c.req.header('cookie'), c.req.header('authorization'))) {\n return c.json({ error: 'Sign in to continue', signIn: true }, 401);\n }\n if (config.demo && c.req.method !== 'GET' && c.req.method !== 'HEAD') {\n return c.json({ error: 'This is the demo: nothing runs and nothing changes. Start Pelican without --demo to talk to Claude.' }, 403);\n }\n await next();\n});\n\n// Bodies are small, except an upload; nothing is buffered past these sizes. Only signed-in\n// requests get this far, apart from signing in itself, whose limit is 4 KB.\nconst tooLarge = (c: Context) => c.json({ error: 'Request too large' }, 413);\nconst limits = {\n auth: bodyLimit({ maxSize: 4 * 1024, onError: tooLarge }),\n upload: bodyLimit({ maxSize: MAX_UPLOAD_BYTES + 64 * 1024, onError: tooLarge }),\n json: bodyLimit({ maxSize: 1024 * 1024, onError: tooLarge }),\n};\napp.use('/api/*', (c, next) => (c.req.path.startsWith('/api/auth') ? limits.auth : c.req.path === '/api/uploads' ? limits.upload : limits.json)(c, next));\n\napp.onError((error, c) => {\n console.warn('[pelican]', c.req.method, c.req.path, error.message);\n return c.json({ error: error.message }, 500);\n});\n\napp.get('/api/auth', (c) => c.json({\n required: authMode() !== 'none',\n mode: authMode(),\n signedIn: signedIn(c.req.header('cookie'), c.req.header('authorization')),\n}));\n\napp.post('/api/auth/login', async (c) => {\n const succeeded = beginAttempt(clientAddress(c));\n if (!succeeded) return c.json({ error: 'Too many attempts. Try again in a few minutes.' }, 429);\n const body = await c.req.json<{ password?: unknown; token?: unknown }>().catch(() => ({} as { password?: unknown; token?: unknown }));\n const mode = authMode();\n const ok = mode === 'password' ? typeof body.password === 'string' && (await checkPassword(body.password))\n : mode === 'token' ? typeof body.token === 'string' && checkToken(body.token.trim())\n : false;\n if (!ok) return c.json({ error: mode === 'token' ? 'That is not the access token' : 'Wrong password', mode }, 401);\n succeeded();\n const cookie = newSessionCookie();\n c.header('Set-Cookie', cookieHeader(c, cookie.name, cookie.value, cookie.maxAge));\n return c.json({ ok: true });\n});\n\napp.post('/api/auth/logout', (c) => {\n c.header('Set-Cookie', cookieHeader(c, cookieName(), '', 0));\n return c.json({ ok: true });\n});\n\n/** A one-time code for the QR code that signs a phone in: good once, for two minutes. */\napp.post('/api/access/code', (c) => {\n if (authMode() === 'none') return c.json({ code: null });\n return c.json({ code: issueCode() });\n});\n\napp.get('/api/info', (c) => {\n const info: ServerInfo = { machine: config.machine, home: config.home, version: pkg.version, auth: authMode() !== 'none', demo: config.demo };\n const key = pushKey();\n if (key) info.pushKey = key;\n return c.json(info);\n});\n\napp.get('/api/sessions', async (c) => c.json({ sessions: await listSummaries(runStatus) }));\n\napp.get('/api/sessions/:id', async (c) => {\n const id = c.req.param('id');\n const session = await sessionSummary(id, runStatus);\n const run = runState(id);\n if (!session && !run) return c.json({ error: 'No such session' }, 404);\n // The task list needs the whole conversation; the page may only have loaded its end.\n const tasks = currentTasks(await loadMessages(id));\n return c.json({ session: session ?? null, run: run ?? null, tasks, settings: await recordedSettings(id) });\n});\n\napp.get('/api/sessions/:id/messages', async (c) => {\n const before = c.req.query('before');\n const limit = Math.min(Number(c.req.query('limit') ?? 120) || 120, 500);\n return c.json(await loadPage(c.req.param('id'), before === undefined ? undefined : Number(before), limit));\n});\n\ntype SendBody = { text?: string; attachments?: string[]; settings?: RunSettings; uuid?: string; cwd?: string; scratch?: boolean; mode?: SendMode };\n\nconst PERMISSION_MODES: PermissionMode[] = ['default', 'acceptEdits', 'plan', 'auto', 'bypassPermissions'];\nconst permissionMode = (value: unknown): PermissionMode | undefined =>\n (PERMISSION_MODES as unknown[]).includes(value) ? (value as PermissionMode) : undefined;\n\n/** The settings a request may carry, and only those, each of its own type. */\nfunction cleanSettings(raw: unknown): RunSettings {\n const value = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>;\n const text = (v: unknown, max: number) => (typeof v === 'string' && v.length > 0 && v.length <= max ? v : undefined);\n const out: RunSettings = {};\n const model = text(value.model, 200);\n const effort = text(value.effort, 20);\n const mode = permissionMode(value.permissionMode);\n if (model) out.model = model;\n if (effort) out.effort = effort;\n if (typeof value.fast === 'boolean') out.fast = value.fast;\n if (typeof value.ultracode === 'boolean') out.ultracode = value.ultracode;\n if (mode) out.permissionMode = mode;\n return out;\n}\n\nconst sendMode = (value: unknown): SendMode | undefined => (value === 'steer' || value === 'queue' ? value : undefined);\nconst uuidOf = (value: unknown) => (typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value) ? value : undefined);\n\n/** A message body of the right types, or why not: a wrong one is the client's error (400), not ours. */\nfunction sendBody(raw: unknown): SendBody | string {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return 'Not a message';\n const body = raw as Record<string, unknown>;\n if (body.text !== undefined && typeof body.text !== 'string') return 'The text must be a string';\n if (body.attachments !== undefined && (!Array.isArray(body.attachments) || body.attachments.length > 50 || !body.attachments.every((a) => typeof a === 'string'))) return 'Attachments must be upload ids';\n if (body.settings !== undefined && (typeof body.settings !== 'object' || body.settings === null)) return 'Settings must be an object';\n if (body.cwd !== undefined && typeof body.cwd !== 'string') return 'The folder must be a path';\n if (body.scratch !== undefined && typeof body.scratch !== 'boolean') return 'scratch must be true or false';\n return body as SendBody;\n}\n\nasync function attachmentsFrom(ids: string[] | undefined): Promise<Attachment[]> {\n const found = await Promise.all((ids ?? []).map((id) => findUpload(id)));\n return found.filter((a): a is Attachment => Boolean(a));\n}\n\napp.post('/api/sessions', async (c) => {\n const body = sendBody(await c.req.json().catch(() => undefined));\n if (typeof body === 'string') return c.json({ error: body }, 400);\n // A scratch session gets its folder only when its first message is sent, so backing out leaves nothing behind.\n const cwd = body.scratch ? await createScratchFolder() : await checkedFolder(body.cwd);\n if (!cwd) return c.json({ error: 'No such folder' }, 400);\n const uuid = uuidOf(body.uuid);\n const sessionId = await sendMessage({ cwd }, {\n text: typeof body.text === 'string' ? body.text : '',\n attachments: await attachmentsFrom(body.attachments),\n settings: cleanSettings(body.settings),\n ...(uuid ? { uuid } : {}),\n });\n return c.json({ sessionId });\n});\n\napp.post('/api/sessions/:id/messages', async (c) => {\n const body = sendBody(await c.req.json().catch(() => undefined));\n if (typeof body === 'string') return c.json({ error: body }, 400);\n const uuid = uuidOf(body.uuid);\n const mode = sendMode(body.mode);\n const sessionId = await sendMessage({ sessionId: c.req.param('id') }, {\n text: typeof body.text === 'string' ? body.text : '',\n attachments: await attachmentsFrom(body.attachments),\n settings: cleanSettings(body.settings),\n ...(uuid ? { uuid } : {}),\n ...(mode ? { mode } : {}),\n });\n return c.json({ sessionId });\n});\n\n/** Edits made, and being made, by the id the editor gave them: a retry gets the same result, never a second one. */\ntype Edited = { sessionId: string; inPlace: boolean };\nconst edits = new Map<string, { at: number } & Edited>();\nconst editing = new Map<string, Promise<Edited>>();\n\n/** A prompt the user wrote (text or images), not a tool's result or injected context. */\nconst isPrompt = (m: ChatMessage) => m.role === 'user' && !m.meta && m.blocks.some((b) => b.type === 'text' || b.type === 'image');\n\n/** The images of a prompt, saved as uploads again so the edited prompt carries the ones kept. */\nasync function imagesOf(sessionId: string, uuid: string, keep: unknown): Promise<Attachment[]> {\n const file = await transcriptPath(sessionId);\n if (!file) return [];\n const images = await rawImages(file, uuid);\n const wanted = Array.isArray(keep) ? new Set(keep.filter((i): i is number => Number.isInteger(i))) : new Set(images.map((_, i) => i));\n const kept: Attachment[] = [];\n for (const [i, image] of images.entries()) {\n if (!wanted.has(i) || image.type !== 'base64' || !image.data) continue;\n const ext = image.mediaType.split('/')[1]?.replace('jpeg', 'jpg') ?? 'png';\n kept.push(await saveUpload(new File([Buffer.from(image.data, 'base64')], `image-${i + 1}.${ext}`, { type: image.mediaType })));\n }\n return kept;\n}\n\n/**\n * Edit a prompt and send it again, as one request.\n * - The last prompt is replaced in place, as rewinding does in Claude Code: the session goes on\n * from just before it with the new text (the old turn stays in the transcript, off the line).\n * - An earlier one branches: a copy of the conversation up to just before it gets the new text\n * (the first prompt: a new session in the same folder), and this session stays as it was.\n * A branch is new work: it never starts in Bypass.\n * If the send fails, a branch and the copied images are removed, so nothing is left behind.\n */\ntype EditFailure = { error: string; status: 404 | 409 };\n\n/** Waits until the Claude of a session it just started is running; a start that fails throws. */\nasync function claudeStarted(sessionId: string): Promise<void> {\n const run = getRun(sessionId);\n if (!run) return;\n let timer: NodeJS.Timeout | undefined;\n try {\n await Promise.race([run.ready, new Promise<void>((resolve) => {\n timer = setTimeout(resolve, 45_000);\n })]);\n } finally {\n clearTimeout(timer);\n }\n}\n\nasync function makeEdit(id: string, before: string, body: { text: string; settings?: unknown; keepImages?: unknown }, key: string): Promise<Edited | EditFailure> {\n const info = await sessionInfo(id);\n if (!info?.cwd || !(await isDirectory(info.cwd))) return { error: 'The session\u2019s folder is missing', status: 404 };\n if (getRun(id)?.busy) return { error: 'Stop Claude before editing an earlier prompt.', status: 409 };\n const messages = await loadMessages(id);\n const at = messages.findIndex((m) => m.uuid === before);\n if (at < 0) return { error: 'That message is not in this session', status: 404 };\n const previous = at === 0 ? null : messages[at - 1]!.uuid;\n const last = !messages.slice(at + 1).some(isPrompt);\n const file = await transcriptPath(id);\n // In place only for the last prompt, after another one (the first has nothing to go on from),\n // when no background task would be cut off by restarting Claude.\n const entry = last && previous !== null && file && !getRun(id)?.hasBackgroundTasks ? (await rawEntry(file, before))?.parentUuid : undefined;\n const settings = cleanSettings(body.settings);\n if (!entry && settings.permissionMode === 'bypassPermissions') settings.permissionMode = 'default';\n const attachments = await imagesOf(id, before, body.keepImages);\n const message = { text: body.text, attachments, settings, uuid: key };\n let branch: string | undefined;\n let started: string | undefined;\n try {\n if (entry) {\n started = await replaceLastTurn(id, info.cwd, entry, before, message);\n await claudeStarted(started);\n return { sessionId: started, inPlace: true };\n }\n if (previous !== null) branch = await fork(id, previous);\n started = await sendMessage(branch ? { sessionId: branch } : { cwd: info.cwd }, message);\n // Only a Claude that started makes the branch real; one that could not start leaves nothing.\n await claudeStarted(started);\n return { sessionId: started, inPlace: false };\n } catch (error) {\n if (branch) await remove(branch).catch(() => undefined);\n else if (started && !entry) await remove(started).catch(() => undefined);\n await Promise.all(attachments.map((a) => removeUpload(a.id)));\n throw error;\n }\n}\n\n/**\n * Edit a prompt and send it again, as one request.\n * - The last prompt is replaced in place, as rewinding does in Claude Code: the session goes on\n * from just before it with the new text (the old turn stays in the transcript, off the line).\n * - An earlier one branches: a copy of the conversation up to just before it gets the new text\n * (the first prompt: a new session in the same folder), and this session stays as it was.\n * A branch is new work: it never starts in Bypass.\n * If Claude cannot start, a branch and the copied images are removed, so nothing is left behind.\n * Requests with the same `uuid` share one edit, also while it is still being made.\n */\napp.post('/api/sessions/:id/edit', async (c) => {\n const id = c.req.param('id');\n const body = await c.req.json<{ before?: unknown; text?: unknown; settings?: unknown; uuid?: unknown; keepImages?: unknown }>()\n .catch(() => ({}) as Record<string, unknown>);\n const key = uuidOf(body.uuid);\n const before = uuidOf(body.before);\n if (!key || !before || typeof body.text !== 'string' || !body.text.trim()) return c.json({ error: 'Nothing to send' }, 400);\n for (const [k, v] of edits) if (Date.now() - v.at > 10 * 60_000) edits.delete(k);\n const done = edits.get(key);\n if (done) return c.json({ sessionId: done.sessionId, inPlace: done.inPlace });\n // Registered before anything is awaited, so a second request with this id waits for this one.\n let work = editing.get(key) as Promise<Edited | EditFailure> | undefined;\n if (!work) {\n const made = makeEdit(id, before, { text: body.text, settings: body.settings, keepImages: body.keepImages }, key);\n work = made;\n editing.set(key, made as Promise<Edited>);\n void made.then((result) => {\n if (!('error' in result)) edits.set(key, { at: Date.now(), ...result });\n }, () => undefined).finally(() => editing.delete(key));\n }\n const result = await work;\n if ('error' in result) return c.json({ error: result.error }, result.status);\n return c.json(result);\n});\n\napp.post('/api/sessions/:id/interrupt', async (c) => {\n await getRun(c.req.param('id'))?.interrupt();\n return c.json({ ok: true });\n});\n\napp.post('/api/sessions/:id/permissions/:requestId', async (c) => {\n const decision = await c.req.json<PermissionDecision>().catch(() => undefined);\n const raw = (decision ?? {}) as Record<string, unknown>;\n const valid = (raw.behavior === 'allow' || raw.behavior === 'deny')\n && (raw.message === undefined || typeof raw.message === 'string')\n && (raw.remember === undefined || typeof raw.remember === 'boolean')\n && (raw.updatedInput === undefined || (typeof raw.updatedInput === 'object' && raw.updatedInput !== null && !Array.isArray(raw.updatedInput)))\n && (raw.permissionMode === undefined || Boolean(permissionMode(raw.permissionMode)));\n if (!decision || !valid) return c.json({ error: 'Not a decision' }, 400);\n const ok = getRun(c.req.param('id'))?.decide(c.req.param('requestId'), decision) ?? false;\n return ok ? c.json({ ok }) : c.json({ error: 'That request is no longer pending' }, 409);\n});\n\n/** Model, effort, speed, ultracode or mode changed while Claude runs: it switches at its next step. */\napp.post('/api/sessions/:id/settings', async (c) => {\n const body = await c.req.json<{ settings?: unknown }>().catch(() => ({}) as { settings?: unknown });\n if (!body.settings || typeof body.settings !== 'object') return c.json({ error: 'No settings' }, 400);\n await getRun(c.req.param('id'))?.updateSettings(cleanSettings(body.settings));\n return c.json({ ok: true });\n});\n\napp.post('/api/sessions/:id/mode', async (c) => {\n const body = await c.req.json<{ permissionMode?: unknown }>();\n const mode = permissionMode(body.permissionMode);\n if (!mode) return c.json({ error: 'No such mode' }, 400);\n await getRun(c.req.param('id'))?.setPermissionMode(mode);\n return c.json({ ok: true });\n});\n\napp.patch('/api/sessions/:id', async (c) => {\n const { title } = await c.req.json<{ title?: string }>();\n if (!title?.trim()) return c.json({ error: 'Title is empty' }, 400);\n await renameSessionTitle(c.req.param('id'), title.trim());\n return c.json({ ok: true });\n});\n\napp.post('/api/sessions/:id/fork', async (c) => {\n const id = c.req.param('id');\n const body = await c.req.json<{ upTo?: string; before?: string; title?: string }>().catch(() => ({} as { upTo?: string; before?: string; title?: string }));\n let upTo = body.upTo;\n if (body.before) {\n const previous = await messageBefore(id, body.before);\n if (previous === undefined) return c.json({ error: 'That message is not in this session' }, 404);\n // Nothing comes before the first prompt: the caller starts a new session instead.\n if (previous === null) return c.json({ sessionId: null });\n upTo = previous;\n }\n return c.json({ sessionId: await fork(id, upTo, body.title) });\n});\n\n/** Put a session away from the list, or bring it back; nothing else changes. */\napp.post('/api/sessions/:id/archive', async (c) => {\n const id = uuidOf(c.req.param('id'));\n const body = await c.req.json<{ archived?: unknown }>().catch(() => ({}) as { archived?: unknown });\n if (!id || typeof body.archived !== 'boolean') return c.json({ error: 'Say archived: true or false' }, 400);\n await setArchived(id, body.archived);\n sessionsChanged();\n return c.json({ ok: true });\n});\n\napp.delete('/api/sessions/:id', async (c) => {\n const id = c.req.param('id');\n if (getRun(id)?.busy) return c.json({ error: 'Stop Claude in this session before deleting it.' }, 409);\n getRun(id)?.close();\n await remove(id);\n return c.json({ ok: true });\n});\n\napp.get('/api/sessions/:id/export', async (c) => {\n const { title, markdown } = await exportMarkdown(c.req.param('id'));\n const name = `${title.replace(/[^\\p{L}\\p{N} _-]+/gu, '').trim().slice(0, 60) || 'session'}.md`;\n return c.body(markdown, 200, {\n 'Content-Type': 'text/markdown; charset=utf-8',\n 'Content-Disposition': `attachment; filename*=UTF-8''${encodeURIComponent(name)}`,\n 'X-Content-Type-Options': 'nosniff',\n });\n});\n\napp.get('/api/models', async (c) => c.json(await listModels()));\n\napp.get('/api/commands', async (c) => {\n const cwd = await folderOf(c);\n if (!cwd) return c.json({ commands: [], agents: [] });\n return c.json(await listCompletions(cwd));\n});\n\n/** What `/usage` shows in Claude Code: asked of claude.ai when Settings opens, kept a minute. */\napp.get('/api/usage/plan', async (c) => c.json({ plan: await planUsage() }));\n\napp.get('/api/usage', (c) => c.json({\n limits: config.demo\n ? [\n { type: 'five_hour', status: 'allowed', utilization: 0.42, resetsAt: Date.now() + 2.4 * 3600_000, at: Date.now() },\n { type: 'seven_day', status: 'allowed', utilization: 0.18, resetsAt: Date.now() + 3.5 * 86400_000, at: Date.now() },\n ]\n : usageLimits(),\n}));\n\n/** The folder a workspace request is about: the session's own, which must exist. */\n/** Whether a path is inside the demo's own folder (the demo never shows anything else). */\nasync function inDemo(dir: string): Promise<boolean> {\n try {\n const [real, root] = await Promise.all([realpath(dir), realpath(config.home)]);\n return real === root || real.startsWith(`${root}${path.sep}`);\n } catch {\n return false;\n }\n}\n\n/** A folder named in a request: an existing absolute path, and in the demo only the demo's own. */\nasync function checkedFolder(cwd: string | undefined): Promise<string | undefined> {\n if (!cwd || !path.isAbsolute(cwd) || !(await isDirectory(cwd))) return undefined;\n if (config.demo && !(await inDemo(cwd))) return undefined;\n return cwd;\n}\n\nconst folderOf = (c: Context) => checkedFolder(c.req.query('cwd'));\n\n/** Whether a folder is in a git repository, by its `.git` and its parents' (this runs nothing). */\nasync function insideRepository(cwd: string): Promise<boolean> {\n for (let dir = path.resolve(cwd); ; dir = path.dirname(dir)) {\n if (await stat(path.join(dir, '.git')).then(() => true, () => false)) return true;\n if (path.dirname(dir) === dir) return false;\n }\n}\n\n/** Git runs only where Claude Code has run: a folder you are only shown runs nothing of its own. */\nconst gitAllowed = (cwd: string) => (config.demo ? Promise.resolve(true) : claudeRanIn(cwd));\n\napp.get('/api/git', async (c) => {\n const cwd = await folderOf(c);\n if (!cwd) return c.json({ error: 'No such folder' }, 404);\n if (!(await gitAllowed(cwd))) return c.json(await insideRepository(cwd) ? { repo: false, untrusted: true } : { repo: false });\n return c.json(await gitStatus(cwd));\n});\n\napp.get('/api/git/diff', async (c) => {\n const cwd = await folderOf(c);\n const file = c.req.query('path');\n if (!cwd || !file) return c.json({ error: 'No such file' }, 404);\n if (!(await gitAllowed(cwd))) return c.json({ diff: '' });\n return c.json({ diff: await gitDiff(cwd, file) });\n});\n\napp.get('/api/tree', async (c) => {\n const cwd = await folderOf(c);\n if (!cwd) return c.json({ error: 'No such folder' }, 404);\n return c.json({ entries: await listTree(cwd, c.req.query('path') ?? '') });\n});\n\napp.get('/api/file', async (c) => {\n const cwd = await folderOf(c);\n const file = c.req.query('path');\n if (!cwd || !file) return c.json({ error: 'No such file' }, 404);\n return c.json(await readProjectFile(cwd, file));\n});\n\napp.post('/api/sessions/:id/shell', async (c) => {\n const id = c.req.param('id');\n const { command } = await c.req.json<{ command?: string }>().catch(() => ({ command: undefined }));\n if (!command?.trim()) return c.json({ error: 'No command' }, 400);\n const info = await sessionInfo(id);\n if (!info?.cwd || !(await isDirectory(info.cwd))) return c.json({ error: 'The session\u2019s folder is missing' }, 404);\n const result = await runCommand(info.cwd, command.trim());\n // Into the conversation without a turn: Claude reads it with the next message.\n await (await ensureRun(id)).appendShell(result);\n return c.json(result);\n});\n\napp.post('/api/sessions/:id/rewind', async (c) => {\n const { uuid, dryRun } = await c.req.json<{ uuid?: string; dryRun?: boolean }>().catch(() => ({ uuid: undefined, dryRun: undefined }));\n if (!uuid) return c.json({ error: 'Which prompt?' }, 400);\n const run = await ensureRun(c.req.param('id'));\n if (run.busy) return c.json({ error: 'Stop Claude before rewinding files.' }, 409);\n return c.json(await run.rewind(uuid, dryRun !== false));\n});\n\napp.get('/api/sessions/:id/mcp', async (c) => {\n const id = c.req.param('id');\n const run = getRun(id);\n if (run) return c.json({ servers: await run.mcpServers(), live: true });\n const info = await sessionInfo(id);\n if (!info?.cwd || !(await isDirectory(info.cwd))) return c.json({ servers: [], live: false });\n return c.json({ servers: await probeMcpServers(info.cwd), live: false });\n});\n\napp.post('/api/sessions/:id/mcp/:name', async (c) => {\n const run = getRun(c.req.param('id'));\n if (!run) return c.json({ error: 'Claude is not running in this session' }, 409);\n const change = await c.req.json<{ enabled?: boolean; reconnect?: boolean }>().catch(() => ({}));\n await run.setMcpServer(c.req.param('name'), change);\n return c.json({ servers: await run.mcpServers() });\n});\n\napp.post('/api/sessions/:id/tasks/:task/stop', async (c) => {\n await getRun(c.req.param('id'))?.stopTask(c.req.param('task'));\n return c.json({ ok: true });\n});\n\napp.get('/api/sessions/:id/sent', async (c) => {\n const file = c.req.query('path') ?? '';\n if (!file || !(await sentFiles(c.req.param('id'))).has(file)) return c.json({ error: 'Not a file Claude sent in this session' }, 404);\n try {\n return c.json(await readProjectFile(path.dirname(file), path.basename(file)));\n } catch {\n return c.json({ error: 'The file is gone' }, 404);\n }\n});\n\napp.get('/api/sessions/:id/agents/:agent', async (c) => {\n return c.json({ messages: await loadSubagent(c.req.param('id'), c.req.param('agent')) });\n});\n\napp.get('/api/files', async (c) => {\n const cwd = await folderOf(c);\n if (!cwd) return c.json({ files: [] });\n return c.json({ files: await searchFiles(cwd, c.req.query('q') ?? '') });\n});\n\n/**\n * Folders to start a new session in: those of recent sessions, without temp\n * and scratch folders and without folders that are gone. Two folders with the\n * same name are told apart by their parent.\n */\napp.get('/api/projects', async (c) => {\n const seen = new Map<string, { path: string; name: string; lastActivity: number; sessions: number }>();\n for (const s of await listSummaries(runStatus)) {\n if (!s.cwd || s.scratch || s.temp) continue;\n const entry = seen.get(s.cwd);\n if (entry) entry.sessions += 1;\n else seen.set(s.cwd, { path: s.cwd, name: s.project, lastActivity: s.lastActivity, sessions: 1 });\n }\n const existing = (await Promise.all([...seen.values()].map(async (p) => ((await isDirectory(p.path)) ? p : undefined))))\n .filter((p): p is NonNullable<typeof p> => Boolean(p));\n const counts = new Map<string, number>();\n for (const p of existing) counts.set(p.name, (counts.get(p.name) ?? 0) + 1);\n for (const p of existing) {\n if ((counts.get(p.name) ?? 0) > 1) p.name = `${path.basename(path.dirname(p.path))}/${p.name}`;\n }\n return c.json({ projects: existing });\n});\n\napp.get('/api/dirs', async (c) => {\n const asked = c.req.query('path');\n if (config.demo && asked && !(await inDemo(asked))) return c.json({ error: 'Not part of the demo' }, 404);\n try {\n return c.json(await listDirs(asked, c.req.query('hidden') === '1'));\n } catch (error) {\n return c.json({ error: (error as Error).message }, 400);\n }\n});\n\napp.post('/api/uploads', async (c) => {\n const body = await c.req.parseBody();\n const file = body.file;\n if (!(file instanceof File)) return c.json({ error: 'No file' }, 400);\n return c.json({ attachment: await saveUpload(file) });\n});\n\napp.get('/api/uploads/:id', async (c) => {\n const found = await readUpload(c.req.param('id'));\n if (!found) return c.notFound();\n // Shown in the page only as an image; anything else downloads. Either way it runs nothing:\n // no sniffing, no scripts, no frames.\n const image = found.attachment.mime.startsWith('image/') && found.attachment.mime !== 'image/svg+xml';\n return c.body(new Uint8Array(found.data), 200, {\n 'Content-Type': found.attachment.mime,\n 'Cache-Control': 'private, max-age=86400',\n 'X-Content-Type-Options': 'nosniff',\n 'Content-Security-Policy': \"sandbox; default-src 'none'; img-src 'self' data:; frame-ancestors 'self'\",\n 'X-Frame-Options': 'SAMEORIGIN',\n ...(image ? {} : { 'Content-Disposition': `attachment; filename*=UTF-8''${encodeURIComponent(found.attachment.name)}` }),\n });\n});\n\napp.get('/api/push/key', (c) => c.json({ key: pushKey() ?? null }));\n\n/** A push subscription as browsers make them: an https endpoint, a P-256 key, a 16-byte secret. */\nfunction validSubscription(value: unknown): value is { endpoint: string; keys: { p256dh: string; auth: string } } {\n const { endpoint, keys } = (value ?? {}) as { endpoint?: unknown; keys?: { p256dh?: unknown; auth?: unknown } };\n const bytes = (text: unknown) => (typeof text === 'string' && /^[\\w-]+=*$/.test(text) ? Buffer.from(text, 'base64url').length : -1);\n return typeof endpoint === 'string' && endpoint.length <= 2048 && endpoint.startsWith('https://')\n && bytes(keys?.p256dh) === 65 && bytes(keys?.auth) === 16;\n}\n\nconst pageUrl = (value: unknown) => typeof value === 'string' && value.length <= 2048 && /^https?:\\/\\//.test(value);\n\napp.post('/api/push/subscribe', async (c) => {\n const body = await c.req.json<{ subscription?: unknown; openUrl?: unknown }>().catch(() => ({}) as { subscription?: unknown; openUrl?: unknown });\n if (!validSubscription(body.subscription)) return c.json({ error: 'Not a push subscription' }, 400);\n const { endpoint, keys } = body.subscription;\n await addSubscription({ endpoint, keys: { p256dh: keys.p256dh, auth: keys.auth }, ...(pageUrl(body.openUrl) ? { openUrl: body.openUrl as string } : {}) });\n return c.json({ ok: true });\n});\n\napp.post('/api/push/unsubscribe', async (c) => {\n const { endpoint } = await c.req.json<{ endpoint?: unknown }>().catch(() => ({ endpoint: undefined }));\n if (typeof endpoint !== 'string') return c.json({ error: 'No endpoint' }, 400);\n await removeSubscription(endpoint);\n return c.json({ ok: true });\n});\n\napp.all('/api/*', (c) => c.json({ error: 'Not found' }, 404));\n\nconst TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8',\n '.svg': 'image/svg+xml', '.png': 'image/png', '.ico': 'image/x-icon', '.webmanifest': 'application/manifest+json',\n '.json': 'application/json', '.woff2': 'font/woff2', '.txt': 'text/plain; charset=utf-8',\n};\n\n/**\n * - Only Pelican's own origin, and pages the config names, may show it in a frame: any other page\n * could lay its own buttons over Pelican's and have you approve something (clickjacking).\n * - Images and media come only from Pelican: a picture in Claude's reply, or in a page it wrote,\n * could otherwise carry what is in the conversation to another server just by loading. The\n * preview frames inherit this, on top of their own policy (Preview.tsx).\n */\nconst POLICY: Record<string, string> = {\n 'Content-Security-Policy': [\n `frame-ancestors 'self'${config.frameAncestors.map((o) => ` ${o}`).join('')}`,\n \"frame-src 'self' blob: data:\",\n \"img-src 'self' data: blob:\",\n \"media-src 'self' data: blob:\",\n \"object-src 'none'\",\n \"base-uri 'self'\",\n ].join('; '),\n ...(config.frameAncestors.length === 0 ? { 'X-Frame-Options': 'SAMEORIGIN' } : {}),\n};\n\n/** The web app. Every URL in it is relative, so it works under any path a proxy mounts it at. */\napp.get('/*', async (c) => {\n if (!config.webDir) return c.text('The web app is not built. Run `npm run build`, or use `npm run dev`.', 503);\n const root = config.webDir;\n let pathname: string;\n try {\n pathname = decodeURIComponent(new URL(c.req.url).pathname);\n } catch {\n return c.text('Bad path', 400);\n }\n // Opening the link Pelican printed (\u2026/?token=\u2026), or the QR code from Settings (\u2026/?code=\u2026),\n // signs this browser in, then drops the secret from the address.\n const token = c.req.query('token');\n const code = c.req.query('code');\n if (token !== undefined || code !== undefined) {\n const url = new URL(c.req.url);\n url.searchParams.delete('token');\n url.searchParams.delete('code');\n // Relative to where the page is, so it works under a proxy's path prefix, and can never\n // name another site (`//other.example/` would).\n const location = `./${url.pathname.split('/').pop() ?? ''}${url.search}`;\n const ok = token !== undefined ? checkToken(token) : redeemCode(code);\n if (!ok) return c.body(null, 302, { Location: location, 'Cache-Control': 'no-store' });\n const cookie = newSessionCookie();\n return c.body(null, 302, { Location: location, 'Set-Cookie': cookieHeader(c, cookie.name, cookie.value, cookie.maxAge), 'Cache-Control': 'no-store' });\n }\n const requested = path.normalize(pathname).replace(/^(\\.\\.[/\\\\])+/, '');\n let file = path.join(root, requested);\n if (!file.startsWith(root) || requested === '/' || !path.extname(file)) file = path.join(root, 'index.html');\n try {\n const data = await readFile(file);\n const ext = path.extname(file);\n const hashed = file.includes(`${path.sep}assets${path.sep}`);\n return c.body(new Uint8Array(data), 200, {\n 'Content-Type': TYPES[ext] ?? 'application/octet-stream',\n 'Cache-Control': hashed ? 'public, max-age=31536000, immutable' : 'no-cache',\n 'X-Content-Type-Options': 'nosniff',\n 'Referrer-Policy': 'same-origin',\n ...POLICY,\n });\n } catch {\n return c.notFound();\n }\n});\n", "import type { IncomingMessage } from 'node:http';\n\nimport { serve } from '@hono/node-server';\nimport { WebSocketServer, type WebSocket } from 'ws';\n\nimport type { ClientEvent, ServerEvent } from '../shared/protocol.js';\nimport { app } from './app.js';\nimport { loadAuth, signedIn } from './auth.js';\nimport { config } from './config.js';\nimport { connect, subscribe } from './feed.js';\nimport { initPush } from './push.js';\nimport { closeAll, runState } from './runner.js';\nimport { hostAllowed, originAllowed } from './security.js';\n\nconst SESSION_ID = /^[\\w-]{1,100}$/;\n/** A browser follows a handful of sessions at once; more from one socket is not a browser. */\nconst MAX_SUBSCRIPTIONS = 64;\n\n/** A client event, or undefined for anything else. */\nfunction clientEvent(text: string): ClientEvent | undefined {\n let value: unknown;\n try {\n value = JSON.parse(text);\n } catch {\n return undefined;\n }\n if (!value || typeof value !== 'object') return undefined;\n const { type, sessionId } = value as Record<string, unknown>;\n if ((type !== 'subscribe' && type !== 'unsubscribe') || typeof sessionId !== 'string' || !SESSION_ID.test(sessionId)) return undefined;\n return { type, sessionId };\n}\n\n/** Starts the HTTP server and the WebSocket that streams sessions. Resolves once it listens. */\nexport async function startServer(): Promise<{ url: string }> {\n await loadAuth();\n await initPush();\n\n const url = await new Promise<string>((resolve, reject) => {\n const server = serve({ fetch: app.fetch, hostname: config.host, port: config.port }, (info) => {\n const host = info.family === 'IPv6' ? `[${info.address}]` : info.address;\n resolve(`http://${host}:${info.port}/`);\n });\n server.on('error', reject);\n\n // Client events are a few hundred bytes; nothing bigger is read.\n const wss = new WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });\n\n server.on('upgrade', (req: IncomingMessage, socket, head) => {\n const pathname = new URL(req.url ?? '/', 'http://x').pathname;\n const forwarded = req.headers['x-forwarded-host'];\n // The same checks as every request: a known Host, a page of Pelican's own, a signed-in browser.\n const ok = pathname.endsWith('/api/ws')\n && hostAllowed(req.headers.host)\n && originAllowed(req.headers.origin, req.headers.host, Array.isArray(forwarded) ? forwarded[0] : forwarded)\n && signedIn(req.headers.cookie, req.headers.authorization);\n if (!ok) {\n socket.destroy();\n return;\n }\n wss.handleUpgrade(req, socket, head, (ws) => wss.emit('connection', ws, req));\n });\n\n wss.on('connection', (ws: WebSocket) => {\n const send = (event: ServerEvent) => {\n if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(event));\n };\n const disconnect = connect(send);\n const subscriptions = new Map<string, () => void>();\n // Proxies close idle sockets (Cloudflare after 100 s); a ping every 25 s keeps it open.\n const ping = setInterval(() => ws.ping(), 25_000);\n // A frame too large or malformed closes this socket; unheard, the error would stop the server.\n ws.on('error', () => ws.terminate());\n\n ws.on('message', (data) => {\n const event = clientEvent(String(data));\n if (!event) return;\n if (event.type === 'subscribe' && !subscriptions.has(event.sessionId)) {\n if (subscriptions.size >= MAX_SUBSCRIPTIONS) return;\n subscriptions.set(event.sessionId, subscribe(event.sessionId, send));\n const state = runState(event.sessionId);\n if (state) send({ type: 'run', state });\n } else if (event.type === 'unsubscribe') {\n subscriptions.get(event.sessionId)?.();\n subscriptions.delete(event.sessionId);\n }\n });\n\n ws.on('close', () => {\n clearInterval(ping);\n disconnect();\n for (const unsubscribe of subscriptions.values()) unsubscribe();\n });\n });\n\n const shutdown = () => {\n closeAll();\n server.close();\n setTimeout(() => process.exit(0), 500).unref();\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n });\n return { url };\n}\n", "import qrcode from 'qrcode-generator';\n\nconst QUIET = 2;\n\n/** The dark and light modules of a QR code for `text`, with a quiet border. */\nexport function qrModules(text: string): boolean[][] {\n const qr = qrcode(0, 'M');\n qr.addData(text);\n qr.make();\n const size = qr.getModuleCount();\n const rows: boolean[][] = [];\n for (let y = -QUIET; y < size + QUIET; y += 1) {\n const row: boolean[] = [];\n for (let x = -QUIET; x < size + QUIET; x += 1) row.push(x >= 0 && y >= 0 && x < size && y < size && qr.isDark(y, x));\n rows.push(row);\n }\n return rows;\n}\n\n/** The same code as an SVG, black on white whatever the theme. */\nexport function qrSvg(text: string): string {\n const rows = qrModules(text);\n const size = rows.length;\n let path = '';\n rows.forEach((row, y) => row.forEach((dark, x) => {\n if (dark) path += `M${x} ${y}h1v1h-1z`;\n }));\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ${size} ${size}\" shape-rendering=\"crispEdges\"><rect width=\"${size}\" height=\"${size}\" fill=\"#fff\"/><path d=\"${path}\" fill=\"#000\"/></svg>`;\n}\n", "import { qrModules } from '../shared/qr.js';\n\n/** A QR code for a terminal: two rows per line, drawn in black and white whatever the theme. */\nexport function qrTerminal(text: string): string {\n const rows = qrModules(text);\n const lines: string[] = [];\n for (let y = 0; y < rows.length; y += 2) {\n let line = '';\n for (let x = 0; x < rows[y]!.length; x += 1) {\n const top = rows[y]![x] ? 16 : 231;\n const bottom = rows[y + 1]?.[x] ? 16 : 231;\n line += `\\x1b[38;5;${top};48;5;${bottom}m\u2580`;\n }\n lines.push(`${line}\\x1b[0m`);\n }\n return lines.join('\\n');\n}\n", "import { spawn } from 'node:child_process';\nimport { mkdir, rm, writeFile } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { parseArgs } from 'node:util';\n\nimport pkg from '../../package.json' with { type: 'json' };\nimport { config } from './config.js';\nimport { listensLocally } from './security.js';\n\nconst DOCS = 'https://ki-seki.github.io/pelican/docs.html';\n\nconst HELP = `Pelican ${pkg.version}: your Claude Code sessions, from any browser.\n\nUsage\n pelican [options] Start the server\n pelican token Print the link that signs a browser in\n pelican token --reset Make a new access token; old links stop working\n pelican password Sign in with a password instead of the token\n pelican password --off Remove the password; the token signs in again\n\nOptions\n -p, --port <port> Port to listen on (default 7860, or PELICAN_PORT)\n -H, --host <address> Address to listen on (default 127.0.0.1, or PELICAN_HOST)\n --name <name> Name shown for this machine (default: its host name)\n --no-auth Ask no one to sign in. Only when something in front\n of Pelican already authenticates.\n --demo Show made-up sessions; nothing runs\n --open Open the browser\n -v, --version Print the version\n -h, --help Show this help\n\nPelican runs Claude Code, and so commands, as you. It listens on 127.0.0.1, and\nevery browser signs in, with the access token in the link Pelican prints or\nwith your password. To use it from your phone, see ${DOCS}#phone\n`;\n\n/** Reads a line from the terminal without echoing it. */\nfunction askHidden(prompt: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const input = process.stdin;\n if (!input.isTTY) {\n reject(new Error('Run `pelican password` in a terminal, or pipe the password in with --stdin.'));\n return;\n }\n process.stdout.write(prompt);\n input.setRawMode(true);\n input.resume();\n input.setEncoding('utf8');\n let value = '';\n const onData = (chunk: string) => {\n for (const char of chunk) {\n if (char === '\\r' || char === '\\n') {\n input.setRawMode(false);\n input.pause();\n input.off('data', onData);\n process.stdout.write('\\n');\n resolve(value);\n return;\n }\n if (char === '\\u0003') {\n process.stdout.write('\\n');\n process.exit(130);\n }\n if (char === '\\u007f' || char === '\\b') value = value.slice(0, -1);\n else value += char;\n }\n };\n input.on('data', onData);\n });\n}\n\nasync function readStdin(): Promise<string> {\n let data = '';\n for await (const chunk of process.stdin) data += chunk;\n return data.replace(/\\r?\\n$/, '');\n}\n\nasync function passwordCommand(off: boolean, stdin: boolean): Promise<void> {\n const { clearPassword, loadAuth, setPassword } = await import('./auth.js');\n await loadAuth();\n if (off) {\n await clearPassword();\n console.log('Password removed: browsers sign in with the access token again (`pelican token`). Restart Pelican to apply.');\n return;\n }\n const password = stdin ? await readStdin() : await askHidden('New password: ');\n if (password.length < 8) throw new Error('Use at least 8 characters.');\n if (!stdin && (await askHidden('Repeat it: ')) !== password) throw new Error('The two did not match.');\n await setPassword(password);\n console.log(`Password saved in ${config.configDir}. Restart Pelican to apply; signed-in browsers are signed out.`);\n}\n\n/** The link with the access token, for this computer's own address. */\nconst tokenLink = (base: string, token: string) => `${base}?token=${token}`;\n\n/** Pelican's address on this computer: localhost, unless it listens on one other address only. */\nfunction localAddress(): string {\n const h = config.host;\n const host = ['0.0.0.0', '::', '127.0.0.1', 'localhost', '::1'].includes(h) ? 'localhost' : h.includes(':') ? `[${h}]` : h;\n return `http://${host}:${config.port}/`;\n}\n\nasync function tokenCommand(reset: boolean): Promise<void> {\n const { accessToken, authMode, loadAuth, resetToken } = await import('./auth.js');\n await loadAuth();\n if (authMode() === 'none') {\n console.log('Sign-in is off (--no-auth or PELICAN_AUTH=none), so there is no access token.');\n return;\n }\n if (reset) {\n await resetToken();\n console.log('New access token made. Restart Pelican to apply; browsers signed in with the old one are signed out.');\n }\n if (authMode() === 'password') {\n console.log('A password is set, so browsers sign in with it. `pelican password --off` goes back to the token.');\n return;\n }\n const token = accessToken();\n if (token) console.log(tokenLink(localAddress(), token));\n}\n\n/**\n * Opens Pelican in the browser. The link with the token is never put on a command line, where\n * other accounts could read it: on macOS a page only you can read redirects to it (and is\n * removed); elsewhere, where a browser may not open such a page, Pelican opens to its sign-in.\n */\nasync function openPelican(address: string, token: string | undefined): Promise<void> {\n if (!token || process.platform !== 'darwin') {\n openBrowser(address);\n return;\n }\n const file = path.join(config.configDir, 'open.html');\n try {\n await mkdir(config.configDir, { recursive: true, mode: 0o700 });\n await rm(file, { force: true });\n const link = tokenLink(address, token);\n await writeFile(file, `<!doctype html><meta http-equiv=\"refresh\" content=\"0;url=${link}\"><a href=\"${link}\">Open Pelican</a>\\n`, { mode: 0o600 });\n openBrowser(pathToFileURL(file).href);\n setTimeout(() => void rm(file, { force: true }), 30_000).unref();\n } catch {\n openBrowser(address);\n }\n}\n\nfunction openBrowser(url: string): void {\n const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';\n try {\n spawn(opener, [url], { stdio: 'ignore', detached: true, shell: process.platform === 'win32' }).on('error', () => {}).unref();\n } catch {\n // Opening a browser is a convenience; the address is printed above.\n }\n}\n\nasync function main(): Promise<void> {\n const { values, positionals } = parseArgs({\n allowPositionals: true,\n options: {\n port: { type: 'string', short: 'p' },\n host: { type: 'string', short: 'H' },\n name: { type: 'string' },\n 'no-auth': { type: 'boolean' },\n demo: { type: 'boolean' },\n open: { type: 'boolean' },\n off: { type: 'boolean' },\n reset: { type: 'boolean' },\n stdin: { type: 'boolean' },\n version: { type: 'boolean', short: 'v' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n if (values.help) return void process.stdout.write(HELP);\n if (values.version) return void console.log(pkg.version);\n\n if (values.port) config.port = Number(values.port);\n if (values.host) config.host = values.host;\n if (values.name) config.machine = values.name;\n if (values['no-auth']) config.auth = 'none';\n if (!Number.isInteger(config.port) || config.port <= 0 || config.port > 65535) throw new Error(`Not a port: ${values.port}`);\n\n if (positionals[0] === 'password') return passwordCommand(Boolean(values.off), Boolean(values.stdin));\n if (positionals[0] === 'token') return tokenCommand(Boolean(values.reset));\n if (positionals.length > 0) throw new Error(`Unknown command: ${positionals.join(' ')}. See pelican --help.`);\n\n // --no-auth, or PELICAN_AUTH=none, is an explicit choice; the demo asks no one to sign in on its own.\n const noAuth = config.auth === 'none';\n if (values.demo) {\n config.demo = true;\n const { prepareDemo } = await import('./demo.js');\n await prepareDemo();\n if (!values.name) config.machine = 'demo';\n }\n\n const local = listensLocally(config.host);\n if (config.demo) {\n // The demo holds nothing private, but it signs no one in: beyond this computer only when asked to.\n if (!local && !noAuth) throw new Error('The demo signs no one in, so it listens on 127.0.0.1 only. Pass --no-auth to show it on your network.');\n config.auth = 'none';\n }\n const { accessToken, authMode, loadAuth } = await import('./auth.js');\n await loadAuth();\n\n const { configWarnings } = await import('./config.js');\n for (const warning of configWarnings) console.warn(`pelican: ${warning}`);\n\n const { startServer } = await import('./server.js');\n const { url } = await startServer();\n const shown = url.replace('://127.0.0.1:', '://localhost:');\n const token = accessToken();\n const mode = authMode();\n // The link with the token is a standing key: shown on a terminal, never written to a log\n // (a service's output file, the journal). `pelican token` prints it on demand.\n const interactive = Boolean(process.stdout.isTTY);\n const open = token && interactive ? tokenLink(shown, token) : shown;\n const protection = config.demo ? 'demo: made-up sessions, nothing runs'\n : mode === 'password' ? 'browsers sign in with your password'\n : mode === 'token' ? (interactive ? 'the link signs a browser in (`pelican token` prints it again)' : 'browsers sign in with the link `pelican token` prints')\n : local ? 'no sign-in (--no-auth)' : 'no sign-in (--no-auth): anyone who reaches this address can run commands as you';\n console.log(`\\n Pelican ${pkg.version} \u00B7 ${config.machine}\\n ${open}\\n ${protection}\\n`);\n if (!config.webDir) console.log(' The web app is not built: run `npm run build` (or use `npm run dev`).\\n');\n // Listening beyond this computer: the address a phone on the same network can open, as a QR code.\n if (!local) {\n const { qrTerminal } = await import('./qr.js');\n const lan = Object.values(os.networkInterfaces()).flat().find((a) => a && a.family === 'IPv4' && !a.internal);\n const base = config.host === '0.0.0.0' || config.host === '::' ? (lan ? `http://${lan.address}:${config.port}/` : undefined) : url;\n const phone = base && token && interactive ? tokenLink(base, token) : base;\n if (phone) console.log(` On your phone: ${phone}\\n${interactive ? `\\n${qrTerminal(phone).replace(/^/gm, ' ')}\\n` : ''}`);\n } else if (!config.demo) {\n console.log(` From your phone: ${DOCS}#phone\\n`);\n }\n if (values.open) await openPelican(shown, token);\n}\n\nmain().catch((error: Error) => {\n console.error(`pelican: ${error.message}`);\n process.exit(1);\n});\n"],
|
|
4
|
+
"sourcesContent": ["{\n \"name\": \"pelican-cc\",\n \"version\": \"0.2.2\",\n \"description\": \"Your Claude Code sessions, from any browser: a phone-first web app that runs next to Claude Code.\",\n \"keywords\": [\n \"claude\",\n \"claude-code\",\n \"agent\",\n \"mobile\",\n \"web-ui\",\n \"ai-coding\"\n ],\n \"homepage\": \"https://github.com/Ki-Seki/pelican#readme\",\n \"bugs\": \"https://github.com/Ki-Seki/pelican/issues\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/Ki-Seki/pelican.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Shichao Song\",\n \"type\": \"module\",\n \"bin\": {\n \"pelican\": \"dist/server/index.js\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\",\n \"CHANGELOG.md\"\n ],\n \"engines\": {\n \"node\": \">=22.12\"\n },\n \"scripts\": {\n \"dev\": \"node scripts/dev.mjs\",\n \"demo\": \"node dist/server/index.js --demo\",\n \"build\": \"vite build && node scripts/build-server.mjs\",\n \"start\": \"node dist/server/index.js\",\n \"typecheck\": \"tsc --noEmit -p tsconfig.json\",\n \"test\": \"vitest run\",\n \"screenshots\": \"node scripts/screenshots.mjs\",\n \"prepublishOnly\": \"npm run typecheck && npm test && npm run build\"\n },\n \"dependencies\": {\n \"@anthropic-ai/claude-agent-sdk\": \"^0.3.318\",\n \"@hono/node-server\": \"^2.1.6\",\n \"hono\": \"^4.13.16\",\n \"qrcode-generator\": \"^1.4.4\",\n \"web-push\": \"^3.6.7\",\n \"ws\": \"^8.22.1\"\n },\n \"devDependencies\": {\n \"@tailwindcss/vite\": \"^4.3.4\",\n \"@tanstack/react-query\": \"^5.105.3\",\n \"@testing-library/react\": \"^16.3.0\",\n \"@types/node\": \"^24.0.0\",\n \"@types/react\": \"^19.1.0\",\n \"@types/react-dom\": \"^19.1.0\",\n \"@types/web-push\": \"^3.6.4\",\n \"@types/ws\": \"^8.18.1\",\n \"@vitejs/plugin-react\": \"^6.2.1\",\n \"diff\": \"^9.0.0\",\n \"esbuild\": \"^0.27.0\",\n \"jsdom\": \"^27.0.0\",\n \"lucide-react\": \"^1.68.0\",\n \"playwright-core\": \"^1.64.1\",\n \"react\": \"^19.3.1\",\n \"react-dom\": \"^19.3.1\",\n \"react-markdown\": \"^10.1.0\",\n \"rehype-highlight\": \"^7.0.2\",\n \"remark-breaks\": \"^4.0.0\",\n \"remark-gfm\": \"^4.0.1\",\n \"tailwindcss\": \"^4.3.4\",\n \"tsx\": \"^4.23.15\",\n \"typescript\": \"^7.0.2\",\n \"vite\": \"^8.3.5\",\n \"vitest\": \"^5.1.0\"\n }\n}\n", "import { existsSync } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst here = path.dirname(fileURLToPath(import.meta.url));\nconst home = os.homedir();\n\n/** Directory of the built web app: next to the bundled server, or `dist/web` when run from source. */\nfunction findWebDir(): string | undefined {\n for (const candidate of [path.join(here, '../web'), path.join(here, '../../dist/web')]) {\n if (existsSync(path.join(candidate, 'index.html'))) return candidate;\n }\n return undefined;\n}\n\nconst list = (value: string | undefined) => (value ?? '').split(',').map((s) => s.trim()).filter(Boolean);\n\n/** Settings Pelican could not use, said once at start (cli.ts). */\nexport const configWarnings: string[] = [];\n\n/**\n * Web origins (`https://host[:port]`), and nothing looser: no `*`, no `null`, no bare scheme,\n * no path. With `wildcardHost`, `https://*.example.com` is allowed too (for frame-ancestors).\n */\nfunction origins(name: string, value: string | undefined, wildcardHost = false): string[] {\n const kept: string[] = [];\n for (const entry of (value ?? '').split(/[\\s,]+/).filter(Boolean)) {\n const wildcard = wildcardHost && /^https?:\\/\\/\\*\\.[^*]+$/i.test(entry);\n let url: URL | undefined;\n try {\n url = /^https?:\\/\\//i.test(entry) ? new URL(wildcard ? entry.replace('*.', 'wildcard.') : entry) : undefined;\n } catch {\n url = undefined;\n }\n const origin = url && (wildcard ? url.origin.replace('://wildcard.', '://*.') : url.origin);\n // Host names of letters, digits, dots and dashes (or an IPv6 address): a URL may hold\n // characters such as `;` that would end a header's directive.\n if (url && origin && url.pathname === '/' && !url.search && !url.hash && !url.username && !url.password\n && (wildcard ? /^https?:\\/\\/\\*\\.[a-z0-9.-]+(:\\d+)?$/ : /^https?:\\/\\/([a-z0-9.-]+|\\[[0-9a-f:.]+\\])(:\\d+)?$/).test(origin)) {\n kept.push(origin);\n } else {\n configWarnings.push(`${name}: ignored \"${entry}\"; use an origin like https://pelican.example.com`);\n }\n }\n return kept;\n}\n\nexport type AuthMode = 'auto' | 'none';\n\n/**\n * Settings from the environment; the command line (`cli.ts`) overrides some of\n * them before the server starts.\n */\nexport const config = {\n host: process.env.PELICAN_HOST ?? '127.0.0.1',\n port: Number(process.env.PELICAN_PORT ?? 7860),\n /** Name shown in the app, for telling machines apart. */\n machine: process.env.PELICAN_MACHINE ?? (os.hostname().split('.')[0] || 'local'),\n home,\n claudeHome: process.env.CLAUDE_CONFIG_DIR ?? path.join(home, '.claude'),\n /** The password hash lives here. */\n configDir: process.env.PELICAN_CONFIG_DIR ?? path.join(home, '.config', 'pelican'),\n /** Uploads, push keys and subscriptions. */\n stateDir: process.env.PELICAN_STATE_DIR ?? path.join(home, '.local', 'state', 'pelican'),\n webDir: findWebDir(),\n /**\n * auto: sign in with the password when one is set (`pelican password`), else\n * with the access token in the link Pelican prints. none: no sign-in, for\n * when something in front already authenticates (a proxy, a gateway).\n */\n auth: (process.env.PELICAN_AUTH === 'none' ? 'none' : 'auto') as AuthMode,\n /** Origins, besides the page's own, allowed to send requests (for example a proxy's public URL). */\n allowedOrigins: origins('PELICAN_ALLOWED_ORIGINS', process.env.PELICAN_ALLOWED_ORIGINS),\n /** Host names the server answers to, besides this machine's own names and addresses (and *.ts.net). */\n allowedHosts: list(process.env.PELICAN_ALLOWED_HOSTS),\n /**\n * A proxy on this machine (Tailscale, Caddy, nginx) sets X-Forwarded-For truthfully: limit sign-in\n * attempts by the client it names. Off by default, since anyone can write that header.\n */\n trustedProxy: process.env.PELICAN_TRUSTED_PROXY === '1',\n /** Pages, besides Pelican's own origin, that may show Pelican in a frame (space or comma separated). */\n frameAncestors: origins('PELICAN_FRAME_ANCESTORS', process.env.PELICAN_FRAME_ANCESTORS, true),\n /** Where a tapped notification opens a session; `{id}` is replaced. Default: the page that subscribed. */\n sessionUrl: process.env.PELICAN_SESSION_URL || undefined,\n /** A Claude process with nothing to do is closed after this long; the next message resumes the session. */\n idleCloseMs: Number(process.env.PELICAN_IDLE_CLOSE_MS ?? 30 * 60 * 1000),\n /** A transcript written by someone else this recently counts as live. */\n liveWindowMs: 45_000,\n claudeExecutable: process.env.PELICAN_CLAUDE_PATH || undefined,\n /** Demo mode: made-up sessions, nothing runs. */\n demo: false,\n};\n\nexport type Config = typeof config;\n", "import os from 'node:os';\n\nimport { config } from './config.js';\n\nconst hostname = (host: string) => {\n const h = host.trim().toLowerCase();\n if (h.startsWith('[')) return h.slice(1, h.indexOf(']'));\n const colon = h.lastIndexOf(':');\n return colon > -1 && h.indexOf(':') === colon ? h.slice(0, colon) : h;\n};\n\nexport function isLoopback(host: string): boolean {\n const h = hostname(host);\n return h === 'localhost' || h.endsWith('.localhost') || h === '::1' || /^127(\\.\\d{1,3}){3}$/.test(h);\n}\n\n/** True for addresses that only this machine can reach. */\nexport function listensLocally(host: string): boolean {\n return isLoopback(host);\n}\n\nfunction knownHosts(): Set<string> {\n const names = new Set<string>();\n const machine = os.hostname().toLowerCase();\n names.add(machine);\n names.add(machine.split('.')[0]!);\n // This computer's own addresses: a phone on the network may open it by IP.\n for (const address of Object.values(os.networkInterfaces()).flat()) if (address) names.add(address.address.toLowerCase());\n if (config.host !== '0.0.0.0' && config.host !== '::') names.add(hostname(config.host));\n for (const host of config.allowedHosts) {\n const name = hostname(host);\n if (name) names.add(name);\n }\n for (const origin of config.allowedOrigins) {\n try {\n names.add(new URL(origin).hostname.toLowerCase().replace(/^\\[(.*)\\]$/, '$1'));\n } catch {\n // Not a URL; ignored.\n }\n }\n return names;\n}\n\n/**\n * The server answers only to names that point at it: this computer's names and\n * addresses, loopback, Tailscale names (*.ts.net, which only Tailscale can point\n * anywhere) and configured ones. Otherwise a web page could rebind its own\n * domain to 127.0.0.1 (DNS rebinding) and use Pelican through your browser.\n */\nexport function hostAllowed(host: string | undefined): boolean {\n if (!host) return false;\n const name = hostname(host);\n if (!name) return false;\n if (isLoopback(host)) return true;\n if (name.endsWith('.ts.net')) return true;\n return knownHosts().has(name);\n}\n\n/**\n * Requests that change something must come from this app's own page, or from\n * an origin allowed in the config, so a page on another site cannot drive Claude.\n */\nexport function originAllowed(origin: string | undefined, ...hosts: Array<string | undefined>): boolean {\n if (!origin) return true;\n let url: URL;\n try {\n url = new URL(origin);\n } catch {\n return false;\n }\n if (config.allowedOrigins.includes(url.origin)) return true;\n return hosts.flatMap((h) => (h ? h.split(',').map((s) => s.trim()) : [])).includes(url.host);\n}\n", "import { createHash, createHmac, randomBytes, scrypt, timingSafeEqual } from 'node:crypto';\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { config } from './config.js';\n\n/**\n * Who may use Pelican. Pelican runs commands as you, so nothing gets in\n * without proof, also on this computer, where every account and process can\n * reach 127.0.0.1:\n *\n * - token (the default): a random access token, kept in the config folder and\n * printed in the link Pelican shows at start. Opening that link, or pasting\n * the token, signs the browser in, the way Jupyter does.\n * - password: set on the computer with `pelican password` (never over the\n * network), stored as a scrypt hash. For reaching Pelican from a phone.\n * - none: `--no-auth`, for when something in front already authenticates\n * (a reverse proxy, a gateway), and for the demo, which holds nothing private.\n *\n * Signing in sets a cookie signed with a secret that belongs to the token or\n * the password and changes with it, so resetting either signs every browser out.\n */\nexport type AuthMode = 'none' | 'password' | 'token';\n\ntype Password = { version: 1; salt: string; hash: string; secret: string; N: number; r: number; p: number };\ntype Token = { version: 1; token: string; secret: string };\n\nconst passwordFile = () => path.join(config.configDir, 'auth.json');\nconst tokenFile = () => path.join(config.configDir, 'token.json');\nconst SESSION_DAYS = 30;\nconst PARAMS = { N: 16384, r: 8, p: 1 };\n\nlet password: Password | undefined;\nlet token: Token | undefined;\nlet loaded = false;\n\nfunction derive(text: string, salt: Buffer, params: { N: number; r: number; p: number }): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n scrypt(text.normalize('NFKC'), salt, 32, { ...params, maxmem: 64 * 1024 * 1024 }, (error, key) => (error ? reject(error) : resolve(key)));\n });\n}\n\nasync function writePrivate(file: string, value: unknown): Promise<void> {\n await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });\n await writeFile(file, `${JSON.stringify(value, null, 2)}\\n`, { mode: 0o600 });\n}\n\nconst readJson = async <T>(file: string): Promise<T | undefined> => {\n try {\n return JSON.parse(await readFile(file, 'utf8')) as T;\n } catch {\n return undefined;\n }\n};\n\n/** Reads the password and the access token, making the token on first use. */\nexport async function loadAuth(): Promise<void> {\n if (loaded) return;\n password = await readJson<Password>(passwordFile());\n if (config.auth !== 'none') {\n token = await readJson<Token>(tokenFile());\n if (!token?.token || !token.secret) await resetToken();\n }\n loaded = true;\n}\n\nexport function authMode(): AuthMode {\n if (config.auth === 'none') return 'none';\n return password ? 'password' : 'token';\n}\n\nexport function passwordSet(): boolean {\n return Boolean(password);\n}\n\n/** The access token, for the link Pelican prints; undefined when it is not in use. */\nexport function accessToken(): string | undefined {\n return authMode() === 'token' ? token?.token : undefined;\n}\n\n/** A new access token: links and browsers signed in with the old one stop working. */\nexport async function resetToken(): Promise<string> {\n token = { version: 1, token: randomBytes(24).toString('base64url'), secret: randomBytes(32).toString('base64') };\n await writePrivate(tokenFile(), token);\n return token.token;\n}\n\nexport async function setPassword(text: string): Promise<void> {\n const salt = randomBytes(16);\n const hash = await derive(text, salt, PARAMS);\n password = { version: 1, salt: salt.toString('base64'), hash: hash.toString('base64'), secret: randomBytes(32).toString('base64'), ...PARAMS };\n await writePrivate(passwordFile(), password);\n loaded = true;\n}\n\nexport async function clearPassword(): Promise<void> {\n await rm(passwordFile(), { force: true });\n password = undefined;\n}\n\nexport async function checkPassword(text: string): Promise<boolean> {\n if (!password) return false;\n const expected = Buffer.from(password.hash, 'base64');\n const actual = await derive(text, Buffer.from(password.salt, 'base64'), password);\n return actual.length === expected.length && timingSafeEqual(actual, expected);\n}\n\nconst same = (a: string, b: string) => {\n const x = createHash('sha256').update(a).digest();\n const y = createHash('sha256').update(b).digest();\n return timingSafeEqual(x, y);\n};\n\n/** Whether `given` is the access token (only while the token is how Pelican signs in). */\nexport function checkToken(given: string | undefined): boolean {\n const current = accessToken();\n return Boolean(current && given && same(given, current));\n}\n\n/** The secret that signs cookies in the current mode. */\nconst secret = () => (authMode() === 'password' ? password?.secret : authMode() === 'token' ? token?.secret : undefined);\n\n/** Cookie name: one per secret, so two Pelicans on one origin do not sign each other out. */\nexport function cookieName(): string {\n const value = secret();\n return `pelican_${value ? createHash('sha256').update(value).digest('hex').slice(0, 8) : 'none'}`;\n}\n\nfunction sign(expires: number): string {\n return createHmac('sha256', Buffer.from(secret() ?? '', 'base64')).update(String(expires)).digest('base64url');\n}\n\nexport function newSessionCookie(): { name: string; value: string; maxAge: number } {\n const maxAge = SESSION_DAYS * 24 * 3600;\n const expires = Date.now() + maxAge * 1000;\n return { name: cookieName(), value: `${expires}.${sign(expires)}`, maxAge };\n}\n\nfunction cookieValue(header: string | undefined, name: string): string | undefined {\n for (const part of (header ?? '').split(';')) {\n const [key, ...rest] = part.trim().split('=');\n if (key === name) return rest.join('=');\n }\n return undefined;\n}\n\n/**\n * Whether a request may use Pelican: always when nothing is required, else with a\n * valid session cookie, or (token mode) the token as `Authorization: Bearer <token>`.\n */\nexport function signedIn(cookieHeader: string | undefined, authorization?: string): boolean {\n const mode = authMode();\n if (mode === 'none') return true;\n if (mode === 'token' && authorization?.startsWith('Bearer ') && checkToken(authorization.slice(7).trim())) return true;\n if (!secret()) return false;\n const value = cookieValue(cookieHeader, cookieName());\n if (!value) return false;\n const [expires, mac] = value.split('.');\n const when = Number(expires);\n if (!mac || !Number.isFinite(when) || when < Date.now()) return false;\n const expected = Buffer.from(sign(when));\n const given = Buffer.from(mac);\n return expected.length === given.length && timingSafeEqual(expected, given);\n}\n\n// Failed sign-ins: at most 10 per client and 30 in all every 10 minutes, so neither\n// a rotating client key nor many clients together can guess for long.\nconst WINDOW_MS = 10 * 60_000;\nconst PER_CLIENT = 10;\nconst IN_ALL = 30;\nconst failures = new Map<string, number[]>();\nlet allFailures: number[] = [];\n\nconst recent = (times: number[], now: number) => times.filter((t) => now - t < WINDOW_MS);\n\n/** Whether this client may try to sign in now. */\nexport function attemptAllowed(client: string, now = Date.now()): boolean {\n allFailures = recent(allFailures, now);\n if (allFailures.length >= IN_ALL) return false;\n return recent(failures.get(client) ?? [], now).length < PER_CLIENT;\n}\n\n/** Counts a wrong password or token against the client and against everyone. */\nexport function noteFailure(client: string, now = Date.now()): void {\n allFailures.push(now);\n failures.set(client, [...recent(failures.get(client) ?? [], now), now]);\n // Forget clients whose failures have all expired, and never keep more than a few thousand.\n if (failures.size > 1000) {\n for (const [key, times] of failures) if (recent(times, now).length === 0) failures.delete(key);\n while (failures.size > 5000) failures.delete(failures.keys().next().value!);\n }\n}\n\n/**\n * Starts a sign-in attempt: counted as a failure at once, before the slow check, so many\n * requests sent together cannot all get past the limit. Returns undefined when the client\n * is over the limit, else a function that takes the attempt back once it succeeded.\n */\nexport function beginAttempt(client: string, now = Date.now()): (() => void) | undefined {\n if (!attemptAllowed(client, now)) return undefined;\n noteFailure(client, now);\n return () => {\n const drop = (times: number[] | undefined) => {\n const at = times?.lastIndexOf(now) ?? -1;\n if (times && at >= 0) times.splice(at, 1);\n };\n drop(allFailures);\n drop(failures.get(client));\n };\n}\n\n/** For tests: start counting afresh. */\nexport function resetAttempts(): void {\n failures.clear();\n allFailures = [];\n}\n\n// One-time sign-in codes, for the QR code that opens Pelican on a phone: the page never sees\n// the access token or the password, and a photo of the code is useless after two minutes.\nconst CODE_MS = 2 * 60_000;\nconst codes = new Map<string, number>();\n\n/** A new code that signs one browser in, once, within two minutes. */\nexport function issueCode(now = Date.now()): string {\n for (const [code, expires] of codes) if (expires <= now) codes.delete(code);\n while (codes.size >= 20) codes.delete(codes.keys().next().value!);\n const code = randomBytes(18).toString('base64url');\n codes.set(code, now + CODE_MS);\n return code;\n}\n\n/** Whether `given` is a live code; using it ends it. */\nexport function redeemCode(given: string | undefined, now = Date.now()): boolean {\n if (!given || authMode() === 'none') return false;\n for (const [code, expires] of codes) {\n if (same(code, given)) {\n codes.delete(code);\n return expires > now;\n }\n }\n return false;\n}\n", "// A made-up screenshot the demo's first prompt attaches: a settings page before its dark theme.\nexport const SETTINGS_SCREENSHOT = 'iVBORw0KGgoAAAANSUhEUgAAAtAAAAHCCAIAAABJw2nGAAAQAElEQVR4nOzdB3gUVRuG4ZPeIIkECDUQEnrvHQSpFkBFRBEpoojwIxaKomBDQSxYEESKSrPRERGQLr2HGloKJYQW0kjP/+0OjmsCMcjObkie+1r3mp2ZnZmsIefd75yZcY6PT1AAAABGclQAAAAGI3AAAADDETgAAIDhCBwAAMBwBA4AAGA4AgcAADAcgQMAABiOwAEAAAxH4AAAAIYjcAAAAMMROAAAgOEIHAAAwHAEDgAAYDgCBwAAMByBAwAAGI7AAQAADEfgAAAAhiNwAAAAwxE4AACA4QgcAADAcAQOAABgOAIHAAAwHIEDAAAYjsABAAAMR+AAAACGI3AAAADDETgAAIDhCBwAAMBwBA4AAGA4AgcAADAcgQMAABiOwAEAAAxH4AAAAIYjcAAAAMMROAAAgOEIHAAAwHAEDgAAYDhnlTdcu3bt3LnzZ8+euXr1qr+/f0BAQKlSpVxdXRUAALj72T9w7Nmz5/33x+/bty/7oqFD/9enz9M+Pj7KHmbNmjVu3AfadJ06dX755ScFAAD+E3sGjszMzNdff+Pnn3++1Qqff/7FjBkzv/pqcvPmzdQd+PDDiampadp0//59S5YsqS/aunXb2rXrtOmKFYN79HhMX5ScnKJPx8fHKwAA8F/ZM3DMmTM3h7ShSUhIeOGFwYsWLaxQIVD9JxkZGdOmfaO/fOCBzpaBY//+/VLJ0KZbtmxpGTgAAIC12G3Q6Pnz599++x3LOX5+fo891n3gwOcaN25sOV8yx9ChQ5XNOTr+/eE4OzspAADwX9mtwrF//wHLl998M61Nm3v1lxcuXOjTp9+JEye0l0ePHrt06VLRokWVDQ0Y8Mwzz/RXAADgjtktcBw6dEiflpKGZdoQ/v7+7733bs+eT+hzDhwIadu2jeU6oaGha9euO3z4yJEjh318fOvWrVu7dq0GDeqXKFFCWyElJUVWyMzMsHzXli1bo6KiZB03N/fw8LCjR4/qiy5ejF65cqUy9614eXmFhIRs375DWyTVl0cffUSbll6YvXtvDHGtWrVKo0aN1q/fsGzZsrCwMJlToUJQw4YNHnnkYRcXF5XNuXPn1q1bf+DAAfnxpWenWrVqTz3Vy9nZ+eeff9FWcHV17du3j+VboqMvzps3TyLXyZMnYmPjypYtW6pUqdatW91/f2cPDw8FAMDdwCE+PkHZw7hx78+a9a02LY3oihXLszSfaWlpEyZM1F926NBeGnJtOj09fcaMmR9+OPGmWx47dkzv3k8pc5mkefOWN11HtlasWLG5c+fddOmyZUslSUyd+vVHH32szQkODl65coU2/fnnX8hDm+7V60k5zh9/zHoCi/xEs2d/V6ZMGcuZO3fuGjDgWekhspwpyWbkyBFjxozV55w4EapPz507d+zYt292jKY3Tp/+jf6ZAACQl9mtwlGlShV9OjIyslevp5577tmmTZvqJ8HK9/7Ro1+76XtHjBi1ZMmSW2357bffcXR06NWrlzLe4sVLsgQIjfxEw4ePmDNntpPTjcEff/65pU+fvtnXlLdbpg1LUgu5VdrQ3ijxZeHCBUFBFRQAAHmb3QKH9CZYvpQekyFDTCNDa9Wq2axZs/r169etW8fX1zf7G3fs2JElbdSvX+/atVh9wIcyFTne7ty5s3SayKL09AzLi3xIrcLHx7tixYqSbGRpWFj45cuXtUVSM6hSpbJ5wlPljp42pI8mLi7OMnxIPePYsWPaj5mZmXmrekwORo36O2/J9qVHSXphdu3atWHDRn3vCxcuHD78VQUAQN5mt8AhfRbSH5G9U0OShzy0aQkEkhseffSRwoULa3Ok5R4z5i19ZWmG58+fK/0XMv3bb7/9738v6ovkpRQ5fvzxh4yMjEqV/q6mjB//fp06dbTp/v37Wfab1KtXb9asGeo2mXtPvi9TprR09LzzznvSCaIvCg8P1wLH+vUbLMesBAaWf++9d2vVqnX+fNTYsWO3bt2WfbNnzpzVk5D48svP9cMeOXLUggULtek//lhL4AAA5H32vJfKm2++0alTxxxW2L17z3vvjWvRotXGjTe+01+8eNGykiGJQUsbQqKJVEf0RT/99Iuyia++mixpQyak92To0CGWiyIjz2gTq1atzvKWxo0be3h4VKgQOH36N35+ftk3GxUV9c+3TN2zZ49kGpkeNOj5t98eqz3kE1AAAOR59rzwl7Oz85dffrF///4ffvhp7dq1ll/oLUnHQf/+A1at+l2a54iISMtFiYmJlgM2ixf3V+pGdUQqCteuXbPBZdG1XhiNRAfpl9E7VrR8ICIiIvR1WrRoLh06+ks3N7dnnumfvcOlbNl/DDhda6bMZ9A0btxIulcqVaqkAAC4S9j/Xiq1zaTjIzT0+G6paezZu3nz5uzhY9y4cTNmTI+M/EfgmDTpsxy2HB8fb3TgkG4OBwcHyzlFihTJPozUsipTsWLWoHDTi6j6+/u3bt1KH66h22Qm3UDBwcFvvTWmSZMmCgCAPC+v3J7e0dFRSgW9ej358ccTt23bsnz5Mu3UVt2uXbszMzMvXbqU+21K/UMZTB9ckjPL/OTrmzUDubu73/RdEyd+KP1E6hYkxDz11NPZz8gFACAPsluF4+2339ErAa1bt37ggfv1RVIzkPAxduwY6Z6YOvVrbaasHBUVpY2W0L3yyss57EKKDSpvCAwsf/p0mDZ99OixLEvPn4+66bvk+L/44rNDh55btWr10qXLslR3NKNHv3HffW1tfA1WAABulx0vbb5fPxtl7969loFDV7JkCcuXkj/KlStnOeeJJ57IXjDIgypWrKgHjh07dly/ft3yKmfa5U2zuHjxYlyc6Ra1Hh6evXr1eumlYefPn9+xY+fGjZuynBUcEnIwy3VaAQDIa+zWpVK7dm19WhrjRYsWZ1lBuiEszzQJDCzv7e0dEBBguc6cOXMsX06ZMvWJJ57UHs8997y6mZSUFHULOSy6Q5ZXOZOf6803x2Zk3Ljg+vz5P2QfqCEmTJjYoUNH7fH006aLnZcsWbJr1y7S5bRo0ULLNWNjrykAAPI2u1U4mjZtOnv233Fh+PARv/22skmTxqVKlYqJiYmIiJg7d57l6Mt7771XngsVKiTf5tetW6/NnDTpM6kWSJ+CMl2PfLnlBm81+kHeW6JEiXvuuSf78IuDBw8ePnzYx8endOnSyqqeeKKnfjV0Zbo+6eLVq1dLR1J4eLjl9TksVa9eVVbTpk+cOPHNN9OfeqqX1EXkM9m1a5flmpUrV1EAAORtdgscHTq079nz8R9++FGfo5/5mV1gYPlhw25c1Ou9997t0KGTnkW+/nqaPLK/ZdiwG3e0d3R0lIShX9ZCWm55yN6/+mqyMpcN9LfINrt06ab+upeKsp5ixYq9+OLQzz773HJfK1asyOEtrVvfO27cB/rLCRM+lIflObca+dGCg4MUAAB5m50v/NWyZct/Xc3Pz0/CgbS12kt/f/8PP5yQw/qy5jffTAsK+rsZlp6IW60sZQZlE88/P/BWN7vv2rVr9pkVKgR+8MH7WWZmP+F2ypSvnJ3tf24zAAA5s2fgcHNzmzVrxnfffatftDsLiQ6jRo1cv36t5ZWyRMeOHTZuXH///TcZZ9qiRfNVq1ZmGUT59NO9b3VJU19fn4kTP7zptT71+64p0zXKnP51fvY5UlzRp11cXF57bZQkITlsKUtoM+vXrzdjhqncoq+m5yrx2GPdZ8/+/lYfTrt27ZYuXVyzZg0FAECeZ7fb02dx/vz5c+fkce7s2bOFChUqXbp0mTLyKJPlnvVZXLp06dSpUydOnJSmXUKJVDVyOGklOTk5Ojo6Li7O1dXVx8dHujn0RZmZmTExMdHRF2XC3d1N9i75QBnp+vXr7u7u2kXDpk+fMX78jZpNcHDwypX/6GqRQzp16rR8PPIRXblytXjx4lLjCQgom+Xe9wAA5GV5JXDkb8uWLV+/foP+8tVXX9bHjkjyeOyxHvrFOaSP6T/cQA4AgDyO7n9bkEqG5cUzpI4zdOjQokX9pHTx7bffWl4KrH37dgoAgHyHCoctxMRca9363uxDPrPw8/NbtWqlDW44BwCAjeWVe6nkb76+PnPnzrYcEJpd48aNly9fStoAAORLVDhsJzo6esGChXPnztMvCqLMF9KoVq1aw4YN+vfvZ3n+CwAA+QmBww5SUlIuXryYlJRUqlSpnE/DAQAgfyBwAAAAwzGGAwAAGI7AAQAADEfgAAAAhiNwAAAAwxE4AACA4QgcAADAcAQOAABgOAIHAAAwHIEDAAAYjsABAAAMR+AAAACGI3AAAADDETgAAIDhCBwAAMBwBA4AAGA4AgcAADAcgQMAABiOwAEAAAxH4AAAAIZzVnlVenpGWlpaukmGyMzMVAAA4J8cHBwcHR2dnOTh5OzsLBMqT8qLgSMlJTUlJUXChkzLR+fi4uLs7CifpwIAAP+UmSnfz+UhTWeqMrebriYuKo9xiI9PUHlGampqUlKK1DQkY3h6eri7u8sHpwAAwL+RL+pJSUmJiddTU9Ok2uHu7irf2FWekYcCR2JikhQ2JGoULlzIw8NdAQCA23f9elJcXLzEDil0eHrmlfY0TwSOjIzMxMREiWYSNby9CykAAHBnYmPjJXZIR4Gnp6ejo4OyN/sHDkkbCQmJGRnp99zjS2EDAABrkVLH1asxjo5OXl72zxz2H4kptQ1JG35+RUgbAABYkTSs0rxKIytNrbI3OweOxMQk6UmR2oabm6sCAABWJc2rNLLS1EqDq+zKnoEjNdV0+itDRAEAMI40stLUSoMrza6yH3sGjqQk0zkpjBIFAMBQ0tRKgyvNrrIfuwUOSVrp6abTUhQAADCYNLjS7GoXB7MLOwYOU3mDzhQAAGxAGlxpdqXxVXZin8Bhvk9KuqenhwIAADYhza75HmUZyh7sEzjS0tLk2d2d8gYAADaiNbtaE2x79rl5m+QrZ2cn7pMCAIDNaC2vNMHKHuzWpeLsnOduZAcAQP4mjW/B6lLJyJDAwe3mAQCwKWl8pQlW9mCfLpXMzEwHBwIHAAA2JY2vNMHKHuwTOJTpZ1YAAKCAoMwAAAAMR+AAAACGI3AAAADDETgAAIDhCBwAAMBwBA4AAGA4AgcAADAcgQMAABiOwAEAAAxH4AAAAIYjcAAAAMMROAAAgOEIHAAAwHB2u1usFWVmZm7cuFEm6tat6+3trQAAMDt+/NT169dzWKFs2dL33OOrrCQsLCI2Ni4goIyvr4+8PHDgkDxXr17Fyckp5zdKQ7Zr1155btCgrqNj/qwF5IfAkZ6e3rNnL5lYsmRho0aNFAAAZvPn/xITcy2HFR555KEWLZooK1myZEV4eGSPHt2aNGmYkZHx7bfzZObYsSN9fP7ly7BEjfnzFyhTOqnq5eWp8qP8EDgAAMiBu7t74cKFbrrI27uwMpiDg4IicAAA8r3Ondu1bNlU2Zb0jEyc+I6ULpydCbrR7wAAEABJREFUaWpNCtynEBkZuWXLVjc3t3r16gYEBFguCgkJcXBwqFGjxrVr1/bvPxAeHh4UFFSzZo3Chf+Rf6VKdurUqaNHj5UuXVqWym9SVFSUPBctWlSWRkVdiI6+ULx48RIlSuhv0Wb6+5fw9y9uuZ3Dhw/v3r1HtiMHU6RIkSyHmvOONPKrfPp0mGwnOTm5Vq2awcHBDmRpALhN8if03LkoT08P+St95crVU6fC4uMTypUrW7Zs6SxxIS0tLTz8zMWLl2RpyZL+8jI2Nk4qKPLe7JuNjr4kf6VlNf0vc0TEGelziYuL9/cvFhBQplixojc9mNDQk9JwlC5dKjAwwMPDQ+ULBShwrF277vXXR4eHR+hzpOV+443XH3+8hzL/DnXo0Fkm5s6d3atXb8t1lixZVKFCoPbyypUrgwf/b/36DdrLQoUKjRgxfMyYseXKBWzbtkXmzJo16/PPvxwy5IXRo1/XNzJz5swvvpg8cuTwYcNe1OZ89tkXX345OT4+Xl+nQoUKU6dOrlmzZi53JCQYvfTSK7/9tlLfiCydPfu7ihUrKgBArp0+HT5t2ndlypQOCiq/YcOf+nwJBC+//IJ8R9VeSlaYOnVmcnKK9lJyRsOGdTdt2lqzZrV+/Xpl2aZ8aZw48XP11xiO9PT0b775TpKE5Tpt27Z64IEOll8UZe9r1qzXX0rcefXV/xUvXlTd/QpK4LhwIVqLEQ88cH/nzp0uX74s+WPDho3Dhr3crFlTCbH6mrKahIx+/fqkpKTMmDHr0qVLw4ePWLDgZ2UenfrYY48fPnxEmv8uXR4qUuSeH374SUKAuk1r1vwxfvwEmRg8+IUaNapLGWPBgkXy3KdP/z17duZyR6mpaV26PBwaGipHO2BAf0dHp99//13qJfff/9Dy5UsrV66kAAC348yZs/K45x7funVrSQ0jJOSwPK9Ysfrhhx9Upu+BVz/7bKpMeHl5Vq1aWdqIAwcOSdrI5cZlO5I2JEA0bdqwRAl/yS47duxeu3ZjUFBg1ap//8XW0oYkGF9fnz//3C5fhr/7bt7w4UPV3a+gBI59+/bJc/369aZPn6bNefrp3g0bNpE8cejQIcvAUadO7fnz5/r6ms6Satq0Sc+evaQLRmpikkAXL16ihYDVq38vX76crPDCC4M6d37AsmqSG5s3m+KzVDuk5qHN6dixY7t2Hc6fPy9JyM/PLzc7mjdvnqQNKWn8/vtvPj6m86+ef/65vn37S5D69tvvPvhgnAIAmC1ZsmLFilXZ51eqFJylMlGrVvXevR/XzmL9+efFW7fujIw8qy1auHC5PEsOGDXqJVdXF2Wui3zxxTSVOxJflOmvfdv77mutTO1LQwku69Zt2rlzj2XgkG+YEi/c3FxlunLlitOnf3/hwkWtDVJ3uYJy4a9KlSpNnfrVhAnj9TkpKanFixeTiaSkZMs1X3ttlJY2RMOGDbUJiQLyLDlAnocMGayFAHHPPfKb8aq6TQ899KAcTJ8+T+tz9C466brL5Y6+/362PEs/i5Y2hIuLy4ABz8jEL78skFKeAgCYyZ9E6QfJ/oiNjcuy5mOPddOvmVGliikHREVFK/OAucOHj8rE448/rKUNERhYrlq1Krk7BFPpWp6laCGVEm1Ohw5tX311iHSpWK720EOdtLRh3n6AdvD6W+5qBaXCERhYXh5SIfjssy9OnDghE0eOHLEcQqELCqqgT3t6ekqZQVZLTExUpgvIHFemyPmP3oqaNWuo2ySFFnns3Llr5syZYWHh8ggJCbFc4V93JL+4UgKRiZUrTd0o+vyEhAR5lgOOiYnJPgoVAAqmTp3ua9KkQfb58j3N8qW09JbXwNAu3iVdJ/KsX8yjVKkSlm+pUKGcFkT+VYsWTZYtWynbee+9j4oW9atSpWL16lUqVgzKcpmvEiX+Prcgy3fRu11BCRxSohgxYtSaNX/oc6R6ER19IXtvSJZzUixdvnxFnrOc2yKhRP2buLh/JJtt27aPHv2GlhiUeUBop04dJTrkfkeXLl3WJpYtW37TPWr/QgAAytxy5+Y61DmcD3L1aowyn+mapY3Qx5P+qzZtWvr4eK9bt/ns2XPyN3zzZnlsc3d3Hziwb7lyf3frS4ug8qmCEjhef/0NSRslS5YcPfq1xo0blSpVSn5v+vTpd1vDL+T3TIoHWa6SGxp6/F/fKDUVZa7IKVMPTlLv3n1kOy1aNH/uuWel1CGlCFlUqlTZ3O9IOvm0iWnTpjZtepNr5Pn5+SkAgJVo1wfLMLOsSZw5cy73G6lXr7Y8YmNjjx07sW9fyJEjodIifP31t++//6YqAArEGA5pzrX6weefT3r00UfKlCkjvy7yS7NlS25HF2u0Po49e/ZYztyxY4flSy3tXrx4SZ+Tlpa2b99+/aWED60r57vvZrVv307r+NASSe53JGXAcuVM9Y8DBw4UtXD16tXRo9+cOPFjrsYBAFakf80LD4/UZ0rjcvz4ydy8XULG5MnTp083jb2TWkvDhvWefbaPPJT5W2hCQqIqAPJV4DAPAsoqNTVV/VWkOnnyxm+G/JZ88smkm47hyEG7du2UKbV8GRYWrs3ZvPnPzz77wnKdqlWryvPGjZvk10ubM2HCh5Y7cnC48ZnrG5Gl77zz3u3uqHv37vK8YMGimJgYbY781o4Z89bSpcu0HxkAoJEvfikpqTd95HKIvXxN1To+fv55sT6iYvHiX3M5nNPLy+vkydOHDx+1DChxcTeGrHp4uKsCIF91qfTo0TP7TOlG2bNnZ4cO7RYuXDxq1OsHDoRI+Fi/fkNoaKg2IHT27Dk+Pj4tW7b4t82bzqT9/vvZ8sb27TtKh0hERIQ+DkNXu3YtZR4y0qFDp4YNG+7Zs/fUqVOWK1SsGCyHJCs8/vgTPXo8dvny5T/+WHvp0iXtYD788KMhQ17IzY4GDx40b9582U7btu0ffribs7PT8uUrZF+yneHDX1EAgL8sW7ZSHjdd1KxZo+7du6pceOKJR8ePnxQVFf3mm++XLVs6MvKs5BiVO05OTnXq1JRulClTZkp5o1gxv/DwM0eOHFOm+5zX0oruKr8rKKfFvvPO240bN1amy1fMnzbtm3Pnzk2cOOHHH+dLN4R0rKxbt15f86adEdpMFxfnZcuW9O/fT5lPD5EQ0Lp1q9mzv1OmnpQb+bRUqVJz55qKZuHhEb/8skASwOOP9xgz5g1lDsjy7OrqOmPGN5I5JGR89dWUH3/8ycvLc+nSxW+9NUaWysvjx0/kZkceHh6//fZr27ZtJHPIdqQcIvt64IH7V678VTauAAC5oP15t3zWOTo6qL/+dIvixYuNGPFi6dKlJGecPh0uEaFatSrt27dRpj/sLtm3lmUXjz3WtVKlIJnYuXPPihWrDx06IluoXr1qjx4PZ99ptoPMD421Q3x8grK5mJhYb+9Ct7p3n0G0e5ecOXM2ODioQoUK2u+Q+ZL1oeXKlcvNAGad9MhIXChSpIiEVuk9kVqFBIIffpinryC/jmFhYVeuXClfPlC72kcW169fP3jwkPSG1KxZs0QJf22mVDsuXIiWw5NQkssdyQpnz549cuSo/HRBQRWCg4MVAMBI6enpCQkJWquxYMHSP//cfu+9Lbp06Zyb9164cFH+zl+7Fuvj4126dEk/P1tfvyA2Nj4uLt7X9zaaPGspQPdSkYRRw8xyppubm377kn8lVYR58+Z16tRJqhHFit2IEQsXLlKmmlhdyzWdnZ1zbvulPtGwYdaTwv3MbmtHEpzLmCkAgJEmTZoSHX1JgkWTJg20tJGUlCy9JDJRvnxALjfi719MHqpA4p65t6Fq1SrSUfL119Okme/Qob2UMX79dYV0ghQqVKh790eU9dhsRwCAXPL3Lx4RcWbhwmUXL16SzpSrV6+uXbsxISHR27twtWqVFf5NAepSuXPSfzF27NvffDPdcqaEgJ9//rFOndrKemy2IwBALklX+KRJUy2veqBMf5m9hg//Xw5XjMxr7NilQuC4bbGxsdu379iyZauk2tq1a0sCMOgi4jbbEQAgly5fvnLkSOipU2HFixcLDAwoX76cfuuTuwKBAwAAGM6OgaOgnBYLAADsiMABAAAMR+AAAACGI3AAAADDETgAAIDhCBwAAMBwBA4AAGA4AgcAADAcgQMAABiOwAEAAAxH4AAAAIYjcAAAAMMROAAAgOEIHAAAwHAEDgAAYDgCBwAAMJyzusslJSUrAADyO3d3N3U3u+sDh0hOJnMAAPIzN7e7O22ofBA4JPHd7aEPAIB8jzEcAADAcAQOAABgOAIHAAAwHIEDAAAYjsABAAAMR+AAAACGI3AAAADDETgAAIDhCBwAAMBwBA4AAGA4AgcAADAcgQMAABiOwAEAAAxH4AAAAIYjcAAAAMMROAAAgOEIHAAAwHDOCgAAs/T09LQ0eUrPzMxU1uPg4ODk5OTsLE9O6j/5/fdVy5Yt37FjR2TkGWVVXl5e1apVbdu2TY8ej5UqVUrdPvms0syfWkZGxp1/bA4OytHR0fxxOcvnpvIRh/j4BGVzMTGx3t6FChcupAAAeYA0likpKdJoKiM5OTm6urpKg5r7t4SEhIwZ89a2bduV8UaOHD5s2Iu39ZbU1NSUlFRlDFdXF6GsKjY2Pi4u3tfXW9kcXSoAUNDJt/Pr15OMThvmHWWYd5Sey/WlsNGhQ2fbpA0xYcLEQYMG53795ORk49KGkI3LLlR+QeAAgAJNahtJSTZt1WR3stN/XU1qG3379le2tXjxkhEjRuVmTQkb0pGiDCa7kB2pfIHAAQAFmvSkKJvLzU7HjHlb2cPs2XNWrPgt53UkB6SlpSmbSDMxPNnYAIEDAAou0wBR43tSbrbfjJw7VqQzZdu2bcpOpk79OucV0tIM7Emx++4MQuAAgILLjl+dc971smXLlf3s3LkrNDT0VkulP8jGKc18BowdcqF1ETgAoODK/fhNG+96x44dyq727t13q0V2afvzQeDIJ9fh2L17T3x83K2Wenp6NmzYUOUZGzZskOdmzZq7uHAdFAD29B+ut5GUlLR69ZrTp0+fOXPGx8e3XLmAli1blCtXTt2mnHed++ttPPFEz6JF/ZTpT+umAwcOZF+hUqVKHTu2l4mrV2PmzJmbu62q8+fP32rRbX1o165dCwkJkYkWLVqoO2DdK6PYRT5p8F57bbT2f/SmKlSo8OefG5WVHD9+PCrqQnBwUMmSJdXtS0tL69mzl0wcOLC3WLFiCgDuHtu2bXv11RFRUVFZ5nfr1m3cuHfd3NyUzcl+PTw8ZOKRRx5p0+a+7Ct89NEE7Ttnampq7gNHDj0+t9X0S9eMdq7NsWNH/vN1z253p3lTvvqGLUG7RImbhICyZcso65ky5ev58394//33+vXrq+5APruEHIB8T770Dxw4KCEhITCw/JNPPlm8ePELFy78+eefGzZsXLx4sTI17R8q+6lcuZIkj+vXr1vOdJQvzxgAABAASURBVHR0rFu3rkIekK8Cx8CBz91hCLABZ2fniIgwSatWv34cABhq9uw5kjb8/PxWrPhV/wvWv3+/adO++fDDiZI5hg0bWqaMNb/g3Rb5Fte3b58pU6ZazuzWrYv81VXIAwrc/wYJv/v3Hzhy5EhGRmZwcAWps3l6eupLT506HR8fV7FiRSl8HTx4SFaTCF+zZs0SJfyVqRyXduTI4cuXL8t0RESkdBZK16C7u/tNd3T48JFDhw7JyuXLl69evVrZsmX1RRER4fIcGBgo0fvcuXOXLl266RaqVq2mD/KQSuAxk1CpWDZo0EA7HgCwJa3nWhJGlu9LvXs/JYFDmXoNjtkrcKSkpLi6uj75ZM8sgaNfv376UgW7KliBQ0LAs88OPHXqlD6nZMmSX375ebNmTbWXI0eO2rz5z8mTv/j4408tV5s1a0anTh2joy907Hi/Nmfq1K/lsXr1yho1amTZS1pa2rBhLy9YsNBy5ptvjh406HkJ4LK0RYvW6q8xHF99NWXGjFk3Pdrt27cEBAQo07/h0P79B1geT7t2902e/KW3d2EFALaiXepKviZlmS8dGfPmzbly5WrlypWVnWzfvqNlyxZBQUFeXl5ShtFmype6OnVq60sV7KoABY6oqAv33WcaqCxVje7dH0lPT1++/NctW7Y++uhjW7ZsDgwsr685ePD/5Pm5554tUqTIwoWLQkND+/V75tSp497e3iNGvCrvkuDStm2bBg3q+/vfpNIwZcrXkjaKFi3av39fKWNIpWTy5K/efXdcvXr1mjRpnGXlOnXqdO/+qOWcX35ZoE0UKmS6uV14eMS997aVifr163Xt2kXKIT//vGDNmj8eeqjr77+vuFV9BQCsrn79+jt37po7d57Ubrt27dqwYQNfX19tUaNGjZRdHTx4sFGjhlIDHjDgmc8++1ybKX9dpT9FitPbtm0ncNhdvgoc778//osvJmeZWblypfnzTcOSP/10kjKdjNpUXmq1taee6tW3b/+1a9eNGTN29uzv9LdIS79s2ZIqVUxRvXfvXtWr15KJkydPSjHjpZeGRUaekcAhNYZbjRdZvXqNPA8f/srTT/dWph7Erj4+Pu+//8GyZcuzBw7592AZOKTaoQWOWbOmS9xRpgHY78tzly4PSd1F64mU/bZv30likOzooYceVABgE88/P3DTps3SWbxy5e/ykDnBwcFNmza59957W7RofienYNw52bscm/xlfvzxHnrg6NPnaWW6itdOhTwgX134Kz4+/nw2586ZzqXOyMj4/vvZytzA6z150g2phQYpGFheU0VqG1raENLqS2lBJk6cOJnLw0hNNd0jYM6cefpp3AMG9N+0af3gwYNyfuOGDRveeONNmRg1amSnTp2U6azxq9rl9qRHRh/3VKJECemnlIlFixYrALAV+TL2ww/zxo17r2XLltqcEydOzJ4955lnBrRu3WbJkiXKrqSHWpmGx5WX73jKHEG0/pQZM2Yq5AH5qsLx6qsvd+/ePctM7bzw6Oho7aXU3CyX1qhRXZuIiooqVaqUNh0cHGS5TunSpXfv3pOQkKhy56mnntq3b39ISEi9etJ707BVq5atW7esW7duziOlpYIyYMBAZTqVvNvQoUO0mWFh4dqEdNNkWdn8fEoBgA15eHhICUEe169fP3LkyJ49e9etW799+3b5E/rKK8OLFi3WvHkzZSdbt25LTEz09PQcOPDZDz/8SA7S0dExNTV1xYrfqlSpomBv+SpwSDWiXLmAmy46e/bGKKcKFSpYztfGSSjz2Sv6zMKF72gwZq9eTxQrVvS7776XzpqdZh9//Ikc2IwZ06tXr3bTt1y7dq1Xr95SoZE8/tFHE/VLdJw9e1abmDnzJgNLk5OTFADYRHJyclxcnLOzi6+vqX4gyaOe2YABzxw/frxz5wdk5oIFC+wYOMTGjRulPCyVbAkcffqYOrW3b7fzJdKhKyiDRrXxEOLMmTOWZ6gePHhIm9DLG1bRoUN7eVy6dGnHjp2rVq3+8cefwsMjpOq4bduW7CunpaU9//wLskLRokVnzZqpXTJPI8FFm5A3enl5ZnkjV/IAYDPy3alv3/5eXl67d+/MUq+tWLHi00/3lm7rHK74bBtffDFZAof8kff3L66dQvj119MU8oaCcvM2/dTwLVu2Ws7fs2ePPEtLb9nM3wkJGQMHDpLSorbZ++/vPGnSJ4sWmcaBSqSQSkb2t7zzznvr15vurjJ79ndZLrBRrlx5bUK+QBS1IDlm9Og3Fy5cpADAJoKDg+U5ISFh79692Zdqwya0M/ntSLp4pFQsE/Pnz5P+lJSUlDVr/lDIGwpK4HBxce7S5SFl+i38Ue89uXLlyty582Vi0KCBud+U1t8h773pUh8f36VLl82bN3/nzl36TO1aYcqiB0cnB/DNN9OVaZTGZG18kyUJ6dotAH744UftDHhlvrrwyJGvyV7usOsHAHKvRIkS2mj6ESNGyVcgy0Vbt27Tun3r1Kmj7O2PP9Yq04UTTYM2tm7dqvIMST/J2djxbr22V4Cuw/Haa6Okkd6+fXunTg9I4UH+3//6669SdShZsqR2/mouaeNMP/rok2PHQseOfbN06dKWSyXZ9O/fT/7tdenS7YUXBpUtW0Z6bZYsWarM56o4OTnpuUHExsa9+upwbVrqHPKw3JSURlq1ajl+/Pv33df+119XdOv2yL333hsTc/XHH3+WCC9BpFu3rgoAbGXSpE87dbo/MjKyc+cHGjZsUKVKlaSk5GPHjh44YOpJqV69unYaqn1Jr0rXrl206SlT8lB/Ss2atbPPfPzxHuPGvacKhnwVOHK+HVr58uXWrFn13HPPh5ppM1u0aD516ld64UE7j1wKcZZv1F46Ot7Y+H33tZVODek6WbZs+dChQ7IEDmU6qXWEdJ0sWLBQO0dL06vXk6+//lqWzWZk/J1ts98KOSnJNCa0WrWqv/326/PPD9q9e488tEVDhrwwZMhgbhAAwJakV2XlyhUffDB+w4aNUsS1rOP269dXvmJ5e3srm9Pu266XCg4dOhQbGytHIvWDDRs2aDO1Cx/Y5Q7vDg6OOS4tQHfxdIiPT1A2FxMjvw2FChcupGxOCgynT58ODTVdNrRy5crFi/+XG8SnpqadO3fOzc3V39//Vr8uYWHh4eFhFy9eKlasqPwrzZ5LbvewT548JWVMLy+vatWqSVeLAoA7lvsT/i2dPXtW6hxnz57z8vKUrpZKlSpZ3pQq97KPhdeVLGm3m8BpRo4cPmzYizddlJqampKSqmzL1dXFKicKxMbGx8XF+/raIRoWuK/IUhWoaKbugPSb3Or8W50UVOShrEQOu3LlSvJQAGA98pXpP3zvL22m7kzOX+6lPzoy8oyyH+ltv9Uiu5Ql8kEtpKAMGgUAZGfH65HnvGu735ylbt1bDoDN0u1uG3bZqXUROACg4HJ2tlvgyHnX9r1RVMOGDaSf6FZLpe13crJp6ym7I3AAAO5iTiZ2aAhkpzlXODp27JD9bpc28/zz/3KtBGdnm1530ca7MwiBAwAKNP1+lnltp++885ayh969n7r//s45ryPlGZudJ+hsYs878VoLgQMACjSp1bu7uykbkt3lpoOgZs2a335r6xu9duvW9cMPx+dmTTc3VxvkANmF7EjlCwQOACjopHfDw8PdBn0rsgvzjnLbTkvHyqpVvzVp0kTZxMiRw6dMmZz79d0kC7ga2NkhG9dueJ4/FLjrcAAAbiU9PT0tTZ7SrXuNLAcHBwkZ8mX9P58U8/vvq5YtW75jxw6rnytrvrhR1bZt2/To8dh/u4unfFZp5k8tIyPjzj82BwdtUKqpy8aIU2HteB0OAgcAAAWFHQMHXSoAAMBwBA4AAGA4AgcAADAcgQMAABiOwAEAAAxH4AAAAIYjcAAAAMMROAAAgOEIHAAAwHAEDgAAYDgCBwAAMByBAwAAGI7AAQAADEfgAAAAhiNwAAAAwxE4AACA4QgcAADAcM7qLpeUlCwUAAD5l5ubm7u7m7qb3fWBQ5n/NygAAPIvBwd1t7vrA8fdnvgAACgIGMMBAAAMR+AAAACGI3AAAADDETgAAIDhCBwAAMBwBA4AAGA4AgcAADAcgQMAABiOwAEAAAxH4AAAAIYjcAAAAMMROAAAgOEIHAAAwHAEDgAAYDgCBwAAMByBAwAAGM5ZAQCAW4u4mB56Jv3clfS4xMyMTGUVjg6qsKdDqSJOlco4BRRzUgUAgQMAgJs4FJ62dHvyH3tTJHAoI0nguK+ua5fGbtXL5edG2SE+PkHZXExMrLd3ocKFCykAAPIYiRpTfk1ctSdF2VaHeq6DHvA0NHbExsbHxcX7+norm6PCAQDA375clvj5kkRlAOlDqRXoXL64k28h0wDKmPiMsOj0A6fTpKdGW0EijjyGdvUc8pCnyncIHAAAmKRnqBenxhpR2HigkdvDzdxa1XC96dKNB1MWbUn+dUey9lLiztHItM+e93bKX+d10KUCAIApbfT/9NrWI6nKqlpUd3npYa+a5f/9631IWNqnixI2H7pxAE2rusx8ycfqmcOOXSqcFgsAgKm2YfW0MfghTwkNuUkbQlaTlQf/1ZkiByOHpPIRAgcAoKD7cpn1h4iO7un1YtfbHoohb5E3atNySHJgKr8gcAAACrRD4WlWHyUqhYo+7TzUfyJv1OsccmByeCpfcHr99dHK5pKSkt3cXOWhrCEzM3Pjxo3h4eGxsbH+/v7ZV9i4cVNYWJgscnFxUf9JeHjE/v370tLSihQpos2Jirrw+++r1qz5w8+vSOHC3ps3b4qMPFOuXIAyRvYDAABYxTvz4k+et+aVNlpUd/mgb2F1B5pUcdl7MjXiYoZMX43PuL+hm7KS5OQU4e5utQ3mXn44SyU9Pb1nz17a9JYtmwMDy1sulTjy+ONPyMSmTeuDg4NVLiQnJ+/YsVPSSZMmjbU5ixcvHj/+wwED+r/77jvy8syZM23atIuPj5fpoKAKEji0Azh//oyykp07dyUlJdWvX8/T0zP7AQAArELqB1bvTHnpYS91x2Qjmw/FKHPHihxkPrgmWH7rUnnttdfVHZPqRY8ePR9++FGLeQ6m/xxufFx//rlF0kahQoVGjHi1Vq2aDg4Oytr69x8gx3D6dNhNDwAAYBVLtycrq3qgkVsuR4nmTDYim9KmrX6QdpHfGrANGzYuXrxEWduQIS9ERJweO/ZN7eXp06fluWfPHi+9NKxs2bLFixeTpRERYcowWQ4AAGAVf+y1cnnj4WZW663QN2X1g7SLfHXhr4YNG+7cuXP48JFt2tzr4+OT88pSPDh48GB4eETJkiVq166l97acPXv2yJEj2vSBAwe8vX3Kly8XFxd3+fJl2WaRIkUkbVy+fEWWpqSknjx5skSJEu7u7hEREY6OToGB5fXtJyQk7N695+jRo1KWkG6XVq1aOjv/49OWjhtZ4dixY9WrV6tZs6a1OG6tAAAQAElEQVSHx43hRdeuXQsPD5f+FJmWpfIsK+gHULRoUX0LMmf//gOhoaFSa6lcubL0vzg6/p0gQ0JCpPRSo0YN2aCsJtsMCgqqWbNG4cL/6FmMioqS7hvpJJIfpFKlSrIvBQAFQ8TFdOveJ6Wwp8Otru71H8imZINxiZnacd7t93jLV4HjzTdHP/nkU9LZ8f774ydM+OBWq6WlpX3yyaRPP51kObN//36vvz7Ky8tr0qTP58yZq83s2PH+zp07zZw5/bvvvteGULzyysstWrTWln7//Wx5TJ8+rUGDBtpMfQyHNPD9+j1z/vx5ffsVKlT44Ye5Ug5R5qgxbNjLWSoxrVu3mjr1K19f37Vr173wwhBt5uDB/9M2qx+APoZj+fJfX3rpFW0ciaZFi+YffzwxICBA+xk7dOgsE3Pnzu7Vq7e+juSVJUsWVagQqL385psZY8aMtTyMRx7pNmHCeEkwCgDyu9AzVr4rW61AK7eqssE/zZcCk0O92wNHvupSKVLknk8++UiZo4B8a7/Vah98MEFLG0OHDvnyy88HD35BpmfOnDV27NvKFDI6PPfcs9qaI0a8+vDD3SzfK3UImSmlFGVu4GW6UqWKWbYvOaNTp/vlWeoWkoGefrq3tN+nTp0aNGiwtsLEiR9L2pCZ0kUyefIXAwc+J9PSGTRunCkkValSRTarNfny3jfeuMmolNWr1zz77EBJG927P/r555NGjRpRrlzA5s1/PvHEUykp/6i8SdqQkDF8+Csvvvg/2ealS5eGDx+hLZJqkJY25ACmTJksByNrLly4ePr0GQoACoBzV6wcOMoXt3Im0Ddo9UO1vfx2L5WHHnqwXbv71qz549VXR6xZ83v282ClD+Wrr6bIxLx5c6TnRZspE92795g7d16vXk/K2ytWrDht2jcy/6WXhmV5u5ubm8yUEoW01i1atJBWXGZeuBBtuc5HH30iz9WqVV2yZKHWUdK3b5+2bdtJB4r0XJQpU2bdunUyc/r0r1u3NtVFHnnkYYkLr7/+xo4dO+Vl1apV5DFz5reSJyRwZO/jkOrFW2+Z6hwSmF57bZQ28/HHe7Rv30lizaxZ30qA0FeuU6f2/PlzpXAi002bNunZs9eWLVszMzOlt2Xjxk0ys0uXh956a4y2cv369bt06fbzzwuGDXtRAUB+p981zVq0u7IZsUGrH6rt5cOzHj74YJw8h4aGaqEhi5UrVypzz8K997bWZzZv3kyqEcr8pV/dmYyMjHnz5suEtNn6sAwJEC+8MEh6Z7TBH8OHvyq9J82bN9ffpdUzkpOTcrOLgwcPSbCQiR49HtNnlihRomfPHsp0YvBWy5UlkWhpQ5nHuGgTln09S5cu27t3nzbdoEH9TZvWf//9LAUAgFXlw7vFSgnhnXfels6C9957/8EHHwwIKGu5VDvRtHXrVlnOZa1Xr25ISMjJk6fUnYmOvlHtqFKlsuV86VvRpzt16piamrZq1eqtW7dGRESeOHFSCxC5FB4ersyZqUKFCpbza9Soof4aZ6oLCvp7HU9PT0k2UjhJTDRdU086j7RizP33PyibatNGCi6tJXtpV/4AgHyvsKeVL2oQE5+hrErfoNUP1fby53Ud+vXro1Ussl9HNTIyUp5r166dZb6Pj+nWeQkJd3rv3LNnz2kT5cuXv9U6s2fPqVevwTPPPDt9+kyJHbGxsW3btlG3sYuzylyNyJKZvL1NP4JWRNFlOSfFkgSUNWtWPfmk6apoknhmzJj19NN9a9eut3DhIgUABUCpIlYechEWbeWRFvoGrX6otpc/A4ezs/Mnn0yUibVr10mXgeWiYsWKyfPRo0ezvGXXrt3yrJ3icSfuuecebSIiIsJy/tWrV8+dO5ecnCx1lBEjRl26dKlPn6eXLl104sSxkJB9I0cOV7nm5+enzCfCZJm/f/9+ea5cuZLKterVq3388cRTp47//POPQ4a8IFUTqX8MHvw/OVQFAPldpTJWbsUPnLbyfU/0DVr9UG0v3165Ur6+SwsqE2+8McZyfmBgeXmWuoLlTOng0IY+6OeL/mdly5bRJrZv36HPvH79eqtWberXbySZQDt9pmHDhuPHvy/PXl6mK+CGhBxUuVa+fDllHodhcR1SE23LlSrlNnCMHv3mwIGDZDseHh4tWjQfPfr1zZs3aKNJDh8+ogAgvwso5mTdc03jEjM3HrTaRbpkU9pYUasfp13k50tlv/zySyVLlpRaguXM9u3by7PMXLPmD33mnDlzlHnkZqdOHWXC0fFGV8V/6GFxcXHRxnL+8MNPkjO0md999712GHXr1tX6QU6fPq2fv3rq1Okvvvgy+3aUuS6SfRe1atXSLv81d+48fea+ffulnKNM58E+qXJHulGk/LNgwd8dKPFmMlG8eDEFAAXAfXWtdp0uzaItVrsMub4pqx+kXeTDQaM6+eL+6acf6fd101SrVvXpp3t///3s3r37PP54j8DAwEOHDi1btlyZBnyM0r7fS0zRBld2797j4Ye76ZflyKXhw1/56aefd+7c2a5dxzZtWq9bt0EbEzpu3LsSI5o1a6bMiefBB7u2a3efdO789ttKbb/h4RHjx0948cWhcuR16tSW2sPQocNatWo5adInWX6ud999e9CgwZMnfyUH36RJ48jIM0uWLJVFnTt3ql+/Xi6PU6LJ+vUbxo17/9ixY/Xq1T179tzy5cu1j6hqVa43CqBA6NLYbdaq68p6ft2R3L+Dx53fTiUkLE02pU3LQaq7X76qcDg6Zq04tW7dunv3G/dg04dYvvfeu9p1Jn788Sdp4LW08eWXn/fr11dbwdnZuV+/PspcNti2bbv6665p+va1Cf064npFRFOmTBnpm5CuDW0kpjxLnnjrrTHa9itXrvTppx8r86XHP/10kqSN1q1bbdq0QdKPzPzssy+0ssoTT/RU5n4TOcjsB9CtW9dp06bKZiUxjB//oZQ6JB4NHPjc119PzfIJ3PTGctrMBx98QLuMxy+/LHj99Tckvkjiady48cyZM1xc8nMSBQBd9XLOHepZuX7w6aI7Pf/AciNyePngVrHCIT7eCp/L7YqJifX2LlS4sD2vnx0TE3PkyFGpNAQFBQUHB7m6Zv2Fk+6MmJhr/v7F//NpolFRUcePn/Dz8wsMLK9fk0Nz+fLlQ4cOS8Nfu3Ztb+8bJ5KEhYWnp6dVqFBBCwSJiYkXLkT7+vroA1GzkC6b0NDjYWFhEnEqVaqYwwkpOZBP4MSJk2fPnpW3BwQEZDmbFwDyvUPhaQ+/G6OsavBDni92/e+XGPhsSeLkZYna9KI3fa0YOGJj4+Pi4n19vZXNFdzAAQCA5stliZ8vSVRWNbqnV592Hrf/PvXdmuvjfrjRNA/t6jnkIWteG8mOgSM/DxoFACA3pFG3eseKhIbPbj/EyFv0tCGHZN20YV8EDgAA1GfPezet6qKsSrpF+n96LSQsVxfnkNVkZb0nRQ5GDknlI3SpAABgkp6hXpwau2qP1S6koXugkdvDzdxa1bh5EWXjwZRFW5L1c1KUubYhacPJgJoAYzgAAMgTjBjPoSns6VAr0Ll8cSftHrAx8Rlh0ekHTqdluROs1cdtWCJwAACQVxwKT5vya6IRpY6cSWFj0AOehp4ES+AAACBvkdixdHvyH3tTIi5a+ZZsWQQUc7qvrmuXxm42uN4GgQMAgDxKAkfomfRzV9Kl7yMjU1mFo4Oph6VUEadKZWx6nxQ7Bg4uKAkAQE7yx73T7I7TYgEAgOEIHAAAwHAEDgAAYDgCBwAAMByBAwAAGI7AAQAADEfgAAAAhiNwAAAAwxE4AACA4ex2pdFMK10dFgAA5H32CRwODg6ZmRkKAADYkDS+0gQre7BP4HB0dExLI3AAAGBT0vhKE6zswT6Bw8lJAkeqAgAANiSNrzTByh7ss1cnJ6e0tHR5KAAAYBNayytNsLIHO9VVnE2VlaSkJAUAAGxCa3a1Jtj27FXhcHR2dkpMvK4AAIBNSLMrjW/B6lIRrq6uqalp169T5AAAwHDS4EqzK42vshM7Bg4XSVlxcfEKAAAYTBpcaXal8VV2Ys8rjbq7m4ocsbFkDgAADCRNrTS40uwq+7Fn4HBxcZHajmQuOlYAADCINLLS1EqDK82ush8730vF09Pd2dnp6tWY5OQUBQAArEqaV2lkpamVBlfZlf1v3ubp6eno6HT58hXqHAAAWJE0rNK8SiMrTa2yN/sHDkdHBy8vTycnpytXYhjPAQCAVUiTKg2rNK/SyEpTq+zNIT4+QeUNiYlJKSkpLi7OhQsX8vCwc+UHAIC7lDZoQzsJ1u49Kbo8FDhEampqUlJKenq6xA5PTw93d9MIDwUAAP5NWlp6UlJSYuJ1iRpS2HB3t/Mo0SzyVuDQpKSkSqlDu9OKBA5nZxdnZ0cHB0cH+xeEAADIQzIzTXecT0uTR6rebrqa5KGoocmLgUOTni4fX1q6SYbIlA8VAAD8k4N8I3d0dHKSh3xFd7bXlcv/lX3u4JIb5s/OnpcoAQAA1pJHcxAAAMhPCBwAAMBwBA4AAGA4AgcAADAcgQMAABiOwAEAAAxH4AAAAIYjcAAAAMMROAAAgOEIHAAAwHAEDgAAYDgCBwAAMByBAwAAGI7AAQAADEfgAAAAhiNwAAAAwxE4AACA4QgcAADAcAQOAABgOAIHAAAwHIHD+lJT09LT0xUAAPiLs4JVJSenLF++2sencLt2rZTBEhIST5+OvHz5SlpaWuHChYKCyvv53XOrlS9fvhoZefbKlRhnZ2dfX+8qVYJdXV0VAAA2QeC4W0l02LBha0ZGhvYyJiY2MvJc9eqVJUlkX/nUqYi9e0P0lxcvXj5xIqxt2xaSPBQAAMYjcNyttm/fI2kjOLh8zZpVpQfn2LGT8jh06Fi5cmU8PNyzrLxv30EHB4dGjeqUKVMqMfH6tm27r169tnv3/vvua6kAADCePQOHfO2WIv/VqzGOjo733ONbtmyp8uXL6ksjIs4ePHgsKSlJpgsV8qpfv5bWX7BmzUY3NzdpaI8cOX7tWpy3d6GqVSv6+vrs2LE3JuaabMrfv1ijRnWlfdW2Exsbt2dPiKwp0/KFXmoARYsWuenxrFv3p3SItG3bXOtrkHdFR18KDAyoXDlIXl6/niQVBTmGhg3r5Gaz0vafPh2RkpLq5uZasWKFSpUqqNv0xx+bUlPTWrRoJD++Nic+PmHz5h2enh4yU3KDdI7Url1d5stPXaNGlZMnw6Vv5cKFi5YfozLXQjIzM+XIJW3IS3l7vXq1ZOOyBQUAgE3YbdDo4cOhUuS/dOmKi4uLtJfStO/efeDkyTBt6dGjJ3bu3Hf9+nVpreVlXFy8NPZaAxkbGy89Alu27JLv6NKOSlfCtm17Vq1af/nyVaUcpIU+c+a8bErbjrS+a9ZskkVSA3B0dJDdbdy4TboebnpIsrWEhMTz56O1lxERZ+RlWFik9vLs2Sh56eTklJvNVN+Z+AAACQRJREFUShCRHyE9PUOyUVJSckjIkf37D6vbJB+L7FFimT5HIoXMkc9Ekoc8lyhR/KbvyjLHyckxKKi8xDJ9joQ8eS5S5JYDPgAAsC67VTi0hrx166ZaYUCaZykJSJstTaNUBSSOyMxmzRqULOmfkZG5ceNWad2PHz9du3Y1ZU4G8n29adMG0rjKN3VpgzMyVKtWTYoV85NYIP0F0dGXtb3s2LFPVpbqglQgZGXZqWQRqUxINSX7IZUuXUJCTFRUtPRKSIsucUGZB2bKAUiqOH/+grwMCCidy81KmUGqMsqcVOSQJEtVqRKs5SdLskdZJEWUwoULZVkUHBwoZZszZ87VqlVVmyMFIXmuVCnI27vwgw+2t1xZPhwpb0hdp3jxolm24+PjXadOde1nOXbshIQhqXnImlrlBgAAG7BbhUNaa2kF9W4IbdiBNJnKXFqQ5lyaSUkbpkN0dKhTp4Y0sXoviTJnEWm8XVyctfZVsoKkDW1CVtM6YqQOkZKSIjWJmjWrat/7JQS4u7vLXqTFleKElEz0R0ZGRtmypjAh5Qp5PnfOFC9kZTmSixcvKfNZHrJlOeCcN6sdniytV6+mNi2HJMcp29G2nIUUcsLDz+zdezD7ojJlSsr2pStHK+1ImUd6fOSnvuceH8vVZL9bt+46cMBUQalWrZK7u5u6BekGOn06UjtIV1cXeSgAAGzCbhUO+fouPSObNm2XVlBKGvrZFsrcaSLPls2qr693+/Z/n2UqDb9+SqfWauqjHCxduxarzOWQlSvX6TMlK8izNOHS/EtPhz6/ceN60sDLZqUHRJpwqXPIzJo1q0ggkNqGl5enBBQtHuW8WVlTOx7LeFS0qJ9sRBJD9oMsV6700aMntcJJFrKFkiWLS4Hk9OkIKaVo/U2BgQGW60iHy4EDh6QYI9Gkbt0aWUZvZOHvX6xt2xbR0RfDw8/Kwaxdu7lbt84KAADj2S1wrF69UaKGTMhXdskTPj6F5cu3tkirT3h6etzqvdpACkvZBy4o0zDPZG3CMs1onRrOzk6enu6WZ3No8/39i0q3zoULl6SnQ5aWLl1SAoe89PAwHYw26DLnzf41I/OfB2w6PIsE8rfKlYPloW6hYsUKEjgiIs5K4IiMPK/MQU3f++bNOyS0aQdWr14NF5ebVyykLpKamiqfp3lwro88AgPLLV++WmKKlG1yuHQHAADWYp/AIV/3JW3IN/iOHe/VSgLS8umBQytXSFOqj3OMj0/Yv/+wdJrc1rke3t6mURHS0Mpe9JmyKalhSKMrqUULEJak0iCB49Qp0+ke0hUiQUEOLyEh8dy5KFlatmzJf91sWprpGqMJCf84AeTSpavmNxZWt0nSgJRwpHCi9eNIMtNHgezYsVc+IlnavHmjIkV8c9jItm27pZxTu3Y1PazIuyROyWblRyNwAABswD5jOKR5VuaOEi1tKNOYx1P6Uqn8K/PJnBkZN+oE+/cfkj4OrfKRe1oPiOwrNTVNmyMxQiorGzZsvdWlx2XXEoOio02DNrQRJDJHek/kYKSF1vpxcrNZmaMNMlWmgk2yHLl5/Iefun0BAWWUKTTsURblDdmRVD5k4t57m900bSQnJ1+7Fqd19Pj6mjqnLE+ikf4UbVwIaQMAYBv2qXD4+ZnabOm22Lv3oDTkZ86c1wZGSGFAgkWJEsWlyCEt+qpV66XkIHNkTWmwg4LK385OVOHChUqUKBYVdfH339dVqhQkuSE09KT0REiSuNVVvWUvEoNkdzIt71XmIZ9S8FB/5Y9/3az0X2irbd26u0yZklIIOXnS9PZy5cq4uPyXT7tixcATJ26cgaIP9dAikczZsmVXlvXr1KkuIWnXrgPyuQUGBtSrV7NKlaCTJ8MkM61du1k+WykmaR0xkjb0wAcAgKHsU+GQL+XSjkp7KW35oUPHpHulQYPa0tJLs7pv3yFZoU2bZtJzIQX/I0eOS/Mv3R9NmtT7D61j48b1pPWVEBAScuTgwaMpKally5aSmTm8RRKGMp0146GNFJF+HG34p+W4zn/drPSeSLeFFBWOHTspP1SpUiX0k1Zul0QWrY9JAo0+EPXCBVPgkKwjsSzL4/p1Ux3Icsiqm5tb06b1pXtIPkn5PCWsyBuLFy/avHlDBQCATThovRt2Ic2e9DtIg6rV/JXphiDXpEigDxeVzgj5Xi4vfXz+cU7s7dLOSdFOas1+2W/jNiufrfRrSGTJCyegpqdnSBlJsp15lK6PFT8HAAD+lT0DBwAAKCDsduEvAABQcBA4AACA4QgcAADAcAQOAABgOAIHAAAwHIEDAAAYjsABAAAMR+AAAACGI3AAAADDETgAAIDhCBwAAMBwBA4AAGA4AgcAADAcgQMAABiOwAEAAAxH4AAAAIYjcAAAAMMROAAAgOEIHAAAwHAEDgAAYDgCBwAAMByBAwAAGI7AAQAADEfgAAAAhiNwAAAAwxE4AACA4QgcAADAcAQOAABgOAIHAAAwHIEDAAAYjsABAAAMR+AAAACGI3AAAADDETgAAIDhCBwAAMBwBA4AAGA4AgcAADAcgQMAABiOwAEAAAxH4AAAAIYjcAAAAMMROAAAgOEIHAAAwHAEDgAAYDgCBwAAMByBAwAAGI7AAQAADEfgAAAAhiNwAAAAwxE4AACA4QgcAADAcAQOAABgOAIHAAAwHIEDAAAYjsABAAAMR+AAAACGI3AAAADDETgAAIDhCBwAAMBwBA4AAGA4AgcAADAcgQMAABiOwAEAAAxH4AAAAIYjcAAAAMMROAAAgOEIHAAAwHAEDgAAYDgCBwAAMByBAwAAGI7AAQAADEfgAAAAhiNwAAAAwxE4AACA4QgcAADAcAQOAABgOAIHAAAwHIEDAAAYjsABAAAMR+AAAACGI3AAAADDETgAAIDhCBwAAMBwBA4AAGA4AgcAADAcgQMAABiOwAEAAAxH4AAAAIYjcAAAAMMROAAAgOEIHAAAwHAEDgAAYDgCBwAAMByBAwAAGI7AAQAADEfgAAAAhiNwAAAAwxE4AACA4QgcAADAcAQOAABgOAIHAAAwHIEDAAAYjsABAAAMR+AAAACG+z8AAAD//1aW6CAAAAAGSURBVAMAjFgjFaA2jm8AAAAASUVORK5CYII=';\n", "// The demo session that draws: a chart as a Claude Desktop visual, and a page Claude writes.\n\nconst DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];\nconst SIGNUPS = [1420, 1588, 1710, 2214, 1966, 1702, 1880];\n\nexport const SIGNUPS_CSV = `day,signups\\n${DAYS.map((d, i) => `${d},${SIGNUPS[i]}`).join('\\n')}\\n`;\n\n/** A bar chart in the vocabulary of Claude Desktop's visuals (box, th, ts, c-*), sized to read on a phone. */\nexport function signupsChart(): string {\n const max = Math.max(...SIGNUPS);\n const bars = SIGNUPS.map((value, i) => {\n const h = Math.round((value / max) * 128);\n const x = 26 + i * 52;\n const y = 196 - h;\n const tone = value === max ? 'c-purple' : 'c-teal';\n return `<g class=\"${tone}\"><rect x=\"${x}\" y=\"${y}\" width=\"38\" height=\"${h}\" rx=\"6\"/></g>`\n + `<text class=\"ts\" x=\"${x + 19}\" y=\"${y - 8}\" text-anchor=\"middle\">${(value / 1000).toFixed(1)}k</text>`\n + `<text class=\"ts\" x=\"${x + 19}\" y=\"216\" text-anchor=\"middle\">${DAYS[i]}</text>`;\n }).join('');\n return `<svg width=\"100%\" viewBox=\"0 0 400 228\" role=\"img\"><title>Signups by day, last week</title>`\n + `<text class=\"th\" x=\"16\" y=\"24\">Signups by day \u00B7 last week</text>`\n + `<text class=\"ts\" x=\"16\" y=\"42\">12,480 in total, 18% more than the week before</text>`\n + `<line x1=\"16\" y1=\"196.5\" x2=\"384\" y2=\"196.5\" style=\"stroke:var(--line)\"/>`\n + `${bars}</svg>`;\n}\n\nexport const REPORT_HTML = `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Signups \u00B7 week 45</title>\n<style>\n :root { color-scheme: light dark; --ink: #17181a; --soft: #6b6e75; --line: #e6e6e3; --card: #fff; --bg: #f6f6f3; --a: #0e7490; --b: #7c6cf2; }\n @media (prefers-color-scheme: dark) { :root { --ink: #ececea; --soft: #a5a8ae; --line: #2a2d31; --card: #16181b; --bg: #0e0f11; --a: #3ba7bd; --b: #9d90f5; } }\n * { box-sizing: border-box; margin: 0; }\n body { background: var(--bg); color: var(--ink); font: 15px/1.55 -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; padding: 28px 20px 40px; }\n main { max-width: 640px; margin: 0 auto; }\n .eyebrow { color: var(--a); font-weight: 600; font-size: 13px; letter-spacing: .02em; }\n h1 { margin-top: 6px; font-size: 30px; line-height: 1.1; letter-spacing: -.03em; }\n .kpis { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 22px; }\n .kpi { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 14px; }\n .kpi b { display: block; font-size: 24px; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }\n .kpi span { color: var(--soft); font-size: 12.5px; }\n .up { color: #16a34a; }\n .chart { margin-top: 14px; background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 16px 14px 10px; }\n .bars { display: flex; align-items: flex-end; gap: 8px; height: 170px; }\n .bar { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 6px; height: 100%; }\n .bar i { display: block; width: 100%; border-radius: 8px 8px 4px 4px; background: color-mix(in srgb, var(--a) 75%, transparent); height: 0; transition: height .9s cubic-bezier(.2,.8,.2,1); }\n .bar.top i { background: var(--b); }\n .bar small { color: var(--soft); font-size: 11.5px; }\n p.note { margin-top: 16px; color: var(--soft); }\n</style>\n</head>\n<body>\n<main>\n <div class=\"eyebrow\">Growth \u00B7 week 45</div>\n <h1>Signups grew 18%, and Thursday led the week</h1>\n <div class=\"kpis\">\n <div class=\"kpi\"><b data-count=\"12480\">0</b><span>signups</span></div>\n <div class=\"kpi\"><b class=\"up\">+18%</b><span>on week 44</span></div>\n <div class=\"kpi\"><b>Thu</b><span>best day, 2,214</span></div>\n </div>\n <div class=\"chart\"><div class=\"bars\" id=\"bars\"></div></div>\n <p class=\"note\">Thursday's newsletter brought the peak; the weekend held above the weekday average.</p>\n</main>\n<script>\n const days = ${JSON.stringify(DAYS)};\n const values = ${JSON.stringify(SIGNUPS)};\n const max = Math.max(...values);\n const bars = document.getElementById('bars');\n values.forEach((v, i) => {\n const bar = document.createElement('div');\n bar.className = 'bar' + (v === max ? ' top' : '');\n bar.innerHTML = '<small>' + v.toLocaleString() + '</small><i></i><small>' + days[i] + '</small>';\n bars.append(bar);\n requestAnimationFrame(() => requestAnimationFrame(() => { bar.querySelector('i').style.height = (v / max * 120) + 'px'; }));\n });\n for (const el of document.querySelectorAll('[data-count]')) {\n const end = Number(el.dataset.count); const start = performance.now();\n const step = (t) => { const k = Math.min(1, (t - start) / 900); el.textContent = Math.round(end * (1 - Math.pow(1 - k, 3))).toLocaleString(); if (k < 1) requestAnimationFrame(step); };\n requestAnimationFrame(step);\n }\n</script>\n</body>\n</html>\n`;\n", "import { execFile } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport type { ModelOption, RunState } from '../shared/protocol.js';\nimport { config } from './config.js';\nimport { SETTINGS_SCREENSHOT } from './demo-image.js';\nimport { REPORT_HTML, SIGNUPS_CSV, signupsChart } from './demo-visuals.js';\n\n/**\n * `pelican --demo`: a made-up home with a few sessions, for trying Pelican\n * without Claude Code and for screenshots that show no real work. It is\n * written fresh on every start, with times relative to now. Nothing runs.\n */\n\nconst run = promisify(execFile);\nconst MIN = 60_000;\nconst HOUR = 60 * MIN;\n\ntype Block = Record<string, unknown>;\ntype Row = { role: 'user' | 'assistant'; content: string | Block[]; at: number };\n\nconst tool = (id: string, name: string, input: Record<string, unknown>): Block => ({ type: 'tool_use', id, name, input });\nconst result = (id: string, content: string, isError = false): Block => ({ type: 'tool_result', tool_use_id: id, content, ...(isError ? { is_error: true } : {}) });\nconst text = (value: string): Block => ({ type: 'text', text: value });\n\nfunction numbered(source: string): string {\n return source.split('\\n').map((line, i) => `${String(i + 1).padStart(6)}\\t${line}`).join('\\n');\n}\n\ntype DemoSession = { id: string; title: string; project: string; branch: string; rows: (cwd: string) => Row[]; scratch?: boolean };\n\nconst THEME_CSS = `:root {\n --bg: #ffffff;\n --fg: #111111;\n --muted: #6b6b6b;\n --accent: #2563eb;\n}\n\nbody {\n background: var(--bg);\n color: var(--fg);\n}`;\n\nconst SESSIONS: DemoSession[] = [\n {\n id: 'a3f1c2d4-1111-4a1b-8c3d-000000000001',\n title: 'Add a dark theme to the settings page',\n project: 'acme-web',\n branch: 'feat/dark-theme',\n rows: (cwd) => {\n const t = Date.now() - 4 * MIN;\n return [\n {\n role: 'user',\n at: t,\n content: [\n { type: 'image', source: { type: 'base64', media_type: 'image/png', data: SETTINGS_SCREENSHOT } },\n text('Add a dark theme to the settings page, with a toggle. Keep the colors in CSS variables.'),\n ],\n },\n { role: 'assistant', at: t + 4000, content: [text(\"I'll look at how the colors are set up first.\"), tool('t1', 'Read', { file_path: `${cwd}/src/styles/theme.css` })] },\n { role: 'user', at: t + 5000, content: [result('t1', numbered(THEME_CSS))] },\n {\n role: 'assistant',\n at: t + 9000,\n content: [\n text('The colors are already variables, so a dark theme is one more set of values plus a toggle that switches them.'),\n tool('t2', 'TodoWrite', {\n todos: [\n { content: 'Add dark color values', status: 'completed', activeForm: 'Adding dark color values' },\n { content: 'Add the toggle to Settings', status: 'in_progress', activeForm: 'Adding the toggle to Settings' },\n { content: 'Remember the choice', status: 'pending', activeForm: 'Remembering the choice' },\n { content: 'Run the tests', status: 'pending', activeForm: 'Running the tests' },\n ],\n }),\n ],\n },\n { role: 'user', at: t + 9500, content: [result('t2', 'Todos have been modified successfully.')] },\n {\n role: 'assistant',\n at: t + 14000,\n content: [tool('t3', 'Edit', {\n file_path: `${cwd}/src/styles/theme.css`,\n old_string: ' --accent: #2563eb;\\n}',\n new_string: \" --accent: #2563eb;\\n}\\n\\n:root[data-theme='dark'] {\\n --bg: #0b0b0c;\\n --fg: #ededed;\\n --muted: #a1a1a1;\\n --accent: #60a5fa;\\n}\",\n })],\n },\n { role: 'user', at: t + 15000, content: [result('t3', `The file ${cwd}/src/styles/theme.css has been updated successfully.`)] },\n { role: 'assistant', at: t + 19000, content: [text('Now the toggle itself, next to the other display settings:')] },\n ];\n },\n },\n {\n id: 'b7e2a9c1-2222-4b2c-9d4e-000000000002',\n title: 'Why is the nightly export slow?',\n project: 'data-pipeline',\n branch: 'main',\n rows: (cwd) => {\n const t = Date.now() - 26 * MIN;\n return [\n { role: 'user', at: t, content: 'The nightly export went from 4 minutes to 40. Can you find out why?' },\n { role: 'assistant', at: t + 5000, content: [tool('g1', 'Grep', { pattern: 'def export_rows', path: `${cwd}/pipeline` })] },\n { role: 'user', at: t + 6000, content: [result('g1', 'pipeline/export.py:41:def export_rows(batch):')] },\n { role: 'assistant', at: t + 9000, content: [tool('g2', 'Read', { file_path: `${cwd}/pipeline/export.py`, offset: 35, limit: 30 })] },\n { role: 'user', at: t + 10000, content: [result('g2', numbered('def export_rows(batch):\\n for row in batch:\\n owner = db.fetch_owner(row.owner_id)\\n write(row, owner)'))] },\n { role: 'assistant', at: t + 15000, content: [tool('g3', 'Bash', { command: 'python -m cProfile -s cumtime -m pipeline.export --sample 5000 | head -8', description: 'Profile the export on a 5,000-row sample' })] },\n {\n role: 'user',\n at: t + 31000,\n content: [result('g3', ' ncalls tottime cumtime filename:lineno(function)\\n 5000 0.412 61.902 db.py:88(fetch_owner)\\n 1 0.020 62.410 export.py:41(export_rows)\\n 5000 0.301 0.488 writer.py:12(write)')],\n },\n {\n role: 'assistant',\n at: t + 40000,\n content: [text([\n 'Found it: `export_rows` looks up each row\\'s owner one query at a time. Since the owners table grew past a million rows that lookup is no longer cached, so 5,000 rows cost 5,000 round trips.',\n '',\n '| | Before | After batching |',\n '|---|---|---|',\n '| Queries per 5,000 rows | 5,000 | 1 |',\n '| Time on the sample | 62.4 s | 0.9 s |',\n '',\n 'The fix fetches the owners for the whole batch at once:',\n '',\n '```python',\n 'owners = db.fetch_owners({row.owner_id for row in batch})',\n 'for row in batch:',\n ' write(row, owners[row.owner_id])',\n '```',\n '',\n 'Want me to apply it and add a test that counts the queries?',\n ].join('\\n'))],\n },\n ];\n },\n },\n {\n id: 'c9d3b8e2-3333-4c3d-ae5f-000000000003',\n title: 'Fix the flaky login test',\n project: 'acme-web',\n branch: 'fix/login-test',\n rows: (cwd) => {\n const t = Date.now() - 3 * HOUR;\n return [\n { role: 'user', at: t, content: 'The login test fails about one run in five on CI. Plan a fix first.' },\n {\n role: 'assistant',\n at: t + 30000,\n content: [tool('p1', 'ExitPlanMode', {\n plan: '## Fix the flaky login test\\n\\nThe test clicks **Sign in** before the form has loaded its CSRF token, so the request is sometimes rejected.\\n\\n1. Wait for the token field to be filled before submitting.\\n2. Replace the fixed 500 ms sleep with that wait.\\n3. Run the test 50 times in a row to confirm.',\n })],\n },\n { role: 'user', at: t + 90000, content: [result('p1', 'User has approved your plan. You can now start coding.')] },\n {\n role: 'assistant',\n at: t + 95000,\n content: [tool('p2', 'Edit', {\n file_path: `${cwd}/tests/login.spec.ts`,\n old_string: \" await page.waitForTimeout(500);\\n await page.click('text=Sign in');\",\n new_string: \" await expect(page.locator('input[name=csrf]')).not.toHaveValue('');\\n await page.click('text=Sign in');\",\n })],\n },\n { role: 'user', at: t + 96000, content: [result('p2', `The file ${cwd}/tests/login.spec.ts has been updated successfully.`)] },\n { role: 'assistant', at: t + 99000, content: [tool('p3', 'Bash', { command: 'npx playwright test tests/login.spec.ts --repeat-each=50', description: 'Run the login test 50 times' })] },\n { role: 'user', at: t + 160000, content: [result('p3', 'Running 50 tests using 4 workers\\n\\n 50 passed (58.2s)')] },\n { role: 'assistant', at: t + 165000, content: [text('All 50 runs pass now. The test waits for the CSRF token instead of sleeping, so it is also about 400 ms faster.')] },\n ];\n },\n },\n {\n id: 'd1e4c7f3-4444-4d4e-bf60-000000000004',\n title: 'Release notes for v2.4',\n project: 'docs-site',\n branch: 'main',\n rows: () => {\n const t = Date.now() - 26 * HOUR;\n return [\n { role: 'user', at: t, content: 'Write the release notes for v2.4 from the merged pull requests.' },\n {\n role: 'assistant',\n at: t + 50000,\n content: [text('## v2.4\\n\\n**Highlights**\\n\\n- A dark theme, which follows your system setting or can be chosen in Settings.\\n- Exports are about 60 times faster on large accounts.\\n\\n**Fixes**\\n\\n- The login page no longer fails when submitted right after it opens.\\n- Long project names no longer push the menu off screen on phones.')],\n },\n ];\n },\n },\n {\n id: 'f3a6e9b5-6666-4f60-9182-000000000006',\n title: \"Chart last week's signups\",\n project: 'data-pipeline',\n branch: 'main',\n rows: (cwd) => {\n const t = Date.now() - 50 * MIN;\n return [\n { role: 'user', at: t, content: \"Chart last week's signups by day, and make me a one-page summary I can share.\" },\n { role: 'assistant', at: t + 3000, content: [text(\"I'll read the numbers first.\"), tool('v1', 'Read', { file_path: `${cwd}/data/signups.csv` })] },\n { role: 'user', at: t + 4000, content: [result('v1', numbered(SIGNUPS_CSV.trimEnd()))] },\n { role: 'assistant', at: t + 9000, content: [tool('v2', 'mcp__visualize__show_widget', { title: 'signups_by_day', loading_messages: ['Stacking the bars'], widget_code: signupsChart() })] },\n { role: 'user', at: t + 10000, content: [result('v2', 'Shown to the user.')] },\n { role: 'assistant', at: t + 16000, content: [tool('v3', 'Write', { file_path: `${cwd}/reports/signups-week-45.html`, content: REPORT_HTML })] },\n { role: 'user', at: t + 17000, content: [result('v3', `File created successfully at: ${cwd}/reports/signups-week-45.html`)] },\n {\n role: 'assistant',\n at: t + 21000,\n content: [text('Signups grew **18%** to 12,480. Thursday was the best day (2,214), the day the newsletter went out, and the weekend stayed above the weekday average.\\n\\nThe one-pager is in `reports/signups-week-45.html`: the three numbers that matter and the chart. Open it from the step above.')],\n },\n ];\n },\n },\n {\n id: 'e2f5d8a4-5555-4e5f-8071-000000000005',\n title: 'Turn this CSV into JSON',\n project: 'scratch',\n branch: '',\n scratch: true,\n rows: (cwd) => {\n const t = Date.now() - 29 * HOUR;\n return [\n { role: 'user', at: t, content: 'Turn contacts.csv into a JSON array, one object per row.' },\n { role: 'assistant', at: t + 6000, content: [tool('s1', 'Bash', { command: \"python3 -c \\\"import csv, json; print(json.dumps(list(csv.DictReader(open('contacts.csv'))), indent=2))\\\" > contacts.json && wc -l contacts.json\", description: 'Convert the CSV to JSON' })] },\n { role: 'user', at: t + 8000, content: [result('s1', ` 1203 ${cwd}/contacts.json`)] },\n { role: 'assistant', at: t + 10000, content: [text('Done: `contacts.json` has 200 objects, with the CSV header as keys.')] },\n ];\n },\n },\n];\n\nconst SETTINGS_TSX = `import { FontSizePicker } from './FontSizePicker';\nimport { Section } from './Section';\n\nexport function Settings() {\n return (\n <Section title=\"Display\">\n <FontSizePicker />\n </Section>\n );\n}\n`;\n\nconst TOGGLE_TSX = `type Theme = 'light' | 'dark';\n\n/** Switches the page between the light and dark color values in theme.css. */\nexport function ThemeToggle({ value, onChange }: { value: Theme; onChange: (theme: Theme) => void }) {\n return (\n <label className=\"row\">\n Dark theme\n <input type=\"checkbox\" checked={value === 'dark'} onChange={(e) => onChange(e.target.checked ? 'dark' : 'light')} />\n </label>\n );\n}\n`;\n\nconst DARK_CSS = `${THEME_CSS.replace(' --accent: #2563eb;\\n}', \" --accent: #2563eb;\\n}\\n\\n:root[data-theme='dark'] {\\n --bg: #0b0b0c;\\n --fg: #ededed;\\n --muted: #a1a1a1;\\n --accent: #60a5fa;\\n}\")}\\n`;\n\nconst FILE_TEXT: Record<string, string> = {\n 'acme-web/src/styles/theme.css': `${THEME_CSS}\\n`,\n 'acme-web/src/settings/Settings.tsx': SETTINGS_TSX,\n 'acme-web/README.md': '# acme-web\\n\\nThe Acme web app.\\n',\n 'data-pipeline/README.md': '# data-pipeline\\n\\nNightly exports and the numbers behind them.\\n',\n 'data-pipeline/data/signups.csv': SIGNUPS_CSV,\n 'data-pipeline/reports/signups-week-45.html': REPORT_HTML,\n 'docs-site/README.md': '# docs-site\\n\\nThe public docs.\\n',\n};\n\n/**\n * The first project is a git repository with Claude's work so far left\n * uncommitted, so the Changes panel has something to show. Skipped without git.\n */\nasync function demoRepository(dir: string): Promise<void> {\n const env = { ...process.env, GIT_AUTHOR_NAME: 'Demo', GIT_AUTHOR_EMAIL: 'demo@example.com', GIT_COMMITTER_NAME: 'Demo', GIT_COMMITTER_EMAIL: 'demo@example.com' };\n // Your own git setup (hooks, templates, signing) has no say in the demo's repository.\n const git = (args: string[]) => run('git', ['-c', 'commit.gpgsign=false', '-c', 'core.hooksPath=/dev/null', '-c', 'core.fsmonitor=false', '-c', 'init.templateDir=', ...args], { cwd: dir, env, timeout: 10_000 });\n try {\n await git(['init', '-q', '-b', 'feat/dark-theme']);\n await rm(path.join(dir, 'src/settings/ThemeToggle.tsx'), { force: true });\n await git(['add', '-A']);\n await git(['commit', '-q', '--no-verify', '-m', 'Settings page']);\n await writeFile(path.join(dir, 'src/styles/theme.css'), DARK_CSS);\n await writeFile(path.join(dir, 'src/settings/ThemeToggle.tsx'), TOGGLE_TSX);\n } catch {\n // No git here; the panel shows the files only.\n }\n}\n\nconst DEMO_FILES: Record<string, string[]> = {\n 'acme-web': ['README.md', 'package.json', 'src/app.tsx', 'src/settings/Settings.tsx', 'src/settings/ThemeToggle.tsx', 'src/styles/theme.css', 'tests/login.spec.ts'],\n 'data-pipeline': ['README.md', 'data/signups.csv', 'pipeline/export.py', 'pipeline/db.py', 'pipeline/writer.py', 'reports/signups-week-45.html', 'tests/test_export.py'],\n 'docs-site': ['README.md', 'docs/releases.md'],\n};\n\n/** Writes the demo home and points the config (and the Claude Agent SDK) at it. */\nexport async function prepareDemo(): Promise<void> {\n const root = path.join(config.stateDir, 'demo');\n await rm(root, { recursive: true, force: true });\n const claudeHome = path.join(root, '.claude');\n for (const [project, files] of Object.entries(DEMO_FILES)) {\n for (const file of files) {\n const full = path.join(root, 'code', project, file);\n await mkdir(path.dirname(full), { recursive: true });\n await writeFile(full, FILE_TEXT[`${project}/${file}`] ?? '');\n }\n }\n await demoRepository(path.join(root, 'code', 'acme-web'));\n for (const session of SESSIONS) {\n const cwd = session.scratch ? path.join(root, 'scratch', '2026-11-04-7c2e') : path.join(root, 'code', session.project);\n await mkdir(cwd, { recursive: true });\n const dir = path.join(claudeHome, 'projects', cwd.replace(/[^a-zA-Z0-9]/g, '-'));\n await mkdir(dir, { recursive: true });\n let parent: string | null = null;\n const lines: string[] = [JSON.stringify({ type: 'custom-title', customTitle: session.title, sessionId: session.id })];\n for (const row of session.rows(cwd)) {\n const uuid = randomUUID();\n const message = row.role === 'assistant'\n ? { id: `msg_demo_${uuid.slice(0, 8)}`, type: 'message', role: 'assistant', model: 'claude-opus-5-5', content: row.content }\n : { role: 'user', content: row.content };\n lines.push(JSON.stringify({\n parentUuid: parent, isSidechain: false, userType: 'external', cwd, sessionId: session.id, version: '2.1.318',\n gitBranch: session.branch, type: row.role, message, uuid, timestamp: new Date(row.at).toISOString(),\n }));\n parent = uuid;\n }\n await writeFile(path.join(dir, `${session.id}.jsonl`), `${lines.join('\\n')}\\n`);\n }\n config.home = root;\n config.claudeHome = claudeHome;\n // Its own config and state too: the demo never reads or writes your password, token or push keys.\n config.configDir = path.join(root, 'config');\n config.stateDir = path.join(root, 'state');\n process.env.CLAUDE_CONFIG_DIR = claudeHome;\n process.env.PELICAN_SCRATCH_DIR = path.join(root, 'scratch');\n}\n\nexport const DEMO_MODELS: ModelOption[] = [\n { value: 'default', label: 'Default (recommended)', description: 'Opus \u00B7 best for everyday, complex tasks', efforts: ['low', 'medium', 'high', 'xhigh', 'max'], supportsFast: true, resolvedModel: 'claude-opus-5-5' },\n { value: 'opus', label: 'Opus', description: 'For complex work', efforts: ['low', 'medium', 'high', 'xhigh', 'max'], supportsFast: true, resolvedModel: 'claude-opus-5-5' },\n { value: 'sonnet', label: 'Sonnet', description: 'Efficient for everyday tasks', efforts: ['low', 'medium', 'high', 'xhigh', 'max'], supportsFast: false, resolvedModel: 'claude-sonnet-5-5' },\n { value: 'haiku', label: 'Haiku', description: 'Fastest for quick answers', efforts: [], supportsFast: false, resolvedModel: 'claude-haiku-5-5' },\n];\n\n/** The first demo session waits for an approval; another one is busy in Claude Desktop. */\nexport function demoRunState(sessionId: string): RunState | undefined {\n const first = SESSIONS[0]!;\n if (sessionId !== first.id) return undefined;\n const cwd = path.join(config.home, 'code', first.project);\n return {\n sessionId,\n cwd,\n busy: true,\n settings: { model: 'default', effort: 'high', permissionMode: 'default' },\n partial: [],\n permissions: [{\n id: 'demo-permission',\n sessionId,\n toolName: 'Edit',\n input: {\n file_path: `${cwd}/src/settings/Settings.tsx`,\n old_string: ' <FontSizePicker />\\n </Section>',\n new_string: ' <FontSizePicker />\\n <ThemeToggle value={theme} onChange={setTheme} />\\n </Section>',\n },\n canRemember: true,\n createdAt: Date.now() - 20_000,\n }],\n context: { percent: 31, tokens: 62_000, max: 200_000 },\n };\n}\n\nexport const DEMO_LIVE_SESSION = SESSIONS[1]!.id;\nexport const DEMO_WAITING_SESSION = SESSIONS[0]!.id;\n", "import type { ChatMessage } from './protocol';\n\nexport type Todo = { content: string; status: 'pending' | 'in_progress' | 'completed'; activeForm?: string };\n\nconst str = (value: unknown) => (typeof value === 'string' && value ? value : undefined);\nconst STATUSES = new Set(['pending', 'in_progress', 'completed']);\n\n/** The items of a TodoWrite call. */\nexport function todosOf(input: Record<string, unknown>): Todo[] {\n return Array.isArray(input.todos) ? (input.todos as Todo[]).filter((t) => t && typeof t.content === 'string') : [];\n}\n\n/**\n * Claude's task list as it stands after these messages. TodoWrite replaces\n * the whole list; TaskCreate adds one task and TaskUpdate changes one, by the\n * number Claude Code gave it (\"Task #3 created successfully\").\n */\nexport function currentTasks(messages: ChatMessage[]): Todo[] {\n const results = new Map<string, string>();\n for (const message of messages) {\n for (const block of message.blocks) {\n if (block.type !== 'tool_result') continue;\n const text = typeof block.content === 'string' ? block.content : block.content.map((c) => (c.type === 'text' ? c.text : '')).join('');\n results.set(block.tool_use_id, text);\n }\n }\n let tasks: Array<Todo & { id?: string }> = [];\n for (const message of messages) {\n if (message.role !== 'assistant' || message.parentToolUseId) continue;\n for (const block of message.blocks) {\n if (block.type !== 'tool_use') continue;\n const input = block.input;\n if (block.name === 'TodoWrite') {\n tasks = todosOf(input).map((t) => ({ ...t }));\n } else if (block.name === 'TaskCreate') {\n const id = results.get(block.id)?.match(/Task #(\\w+)/)?.[1] ?? String(tasks.length + 1);\n const activeForm = str(input.activeForm);\n tasks.push({ id, content: str(input.subject) ?? str(input.description) ?? 'Task', status: 'pending', ...(activeForm ? { activeForm } : {}) });\n } else if (block.name === 'TaskUpdate') {\n const task = tasks.find((t) => t.id !== undefined && t.id === String(input.taskId));\n if (!task) continue;\n const status = str(input.status);\n if (status === 'deleted') {\n tasks = tasks.filter((t) => t !== task);\n continue;\n }\n if (status && STATUSES.has(status)) task.status = status as Todo['status'];\n if (str(input.subject)) task.content = str(input.subject)!;\n if (str(input.activeForm)) task.activeForm = str(input.activeForm)!;\n }\n }\n }\n return tasks.map(({ id: _id, ...task }) => task);\n}\n", "import { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { config } from './config.js';\n\n/**\n * Sessions put away from the list. Only Pelican keeps this, in its state folder: the transcripts\n * are not touched, so every Claude Code app still has the session, and unarchiving is instant.\n */\nconst file = () => path.join(config.stateDir, 'archived.json');\nlet loaded: Promise<Set<string>> | undefined;\nlet writing: Promise<void> = Promise.resolve();\n\nasync function load(): Promise<Set<string>> {\n let text: string;\n try {\n text = await readFile(file(), 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return new Set();\n throw error;\n }\n try {\n const ids = (JSON.parse(text) as { ids?: unknown }).ids;\n return new Set(Array.isArray(ids) ? ids.filter((id): id is string => typeof id === 'string') : []);\n } catch {\n // Not ours to throw away: kept beside, and the list starts over.\n await rename(file(), `${file()}.bad-${Date.now()}`).catch(() => undefined);\n console.warn('[pelican] archived.json could not be read; it was set aside and the archive starts empty');\n return new Set();\n }\n}\n\nfunction archived(): Promise<Set<string>> {\n loaded ??= load().catch((error: Error) => {\n loaded = undefined;\n throw error;\n });\n return loaded;\n}\n\n/** The archived session ids, for the list. */\nexport async function archivedIds(): Promise<Set<string>> {\n return archived().catch(() => new Set<string>());\n}\n\n/** Archives or unarchives; writes go one at a time, each with the whole list as it is then. */\nexport async function setArchived(sessionId: string, on: boolean): Promise<void> {\n const ids = await archived();\n const before = ids.has(sessionId);\n if (on) ids.add(sessionId);\n else ids.delete(sessionId);\n const write = writing.then(async () => {\n await mkdir(path.dirname(file()), { recursive: true, mode: 0o700 });\n const temp = `${file()}.${randomUUID()}.tmp`;\n await writeFile(temp, `${JSON.stringify({ ids: [...ids] }, null, 2)}\\n`, { mode: 0o600 });\n await rename(temp, file());\n });\n writing = write.catch(() => undefined);\n try {\n await write;\n } catch (error) {\n if (before) ids.add(sessionId);\n else ids.delete(sessionId);\n throw error;\n }\n}\n", "import { randomBytes } from 'node:crypto';\nimport { realpathSync } from 'node:fs';\nimport { mkdir, stat } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { config } from './config.js';\n\nconst realpath = (p: string) => {\n try {\n return realpathSync(p);\n } catch {\n return p;\n }\n};\n\n/** Where \"New scratch session\" makes its folders. */\nexport const scratchRoot = process.env.PELICAN_SCRATCH_DIR ?? path.join(config.home, 'scratch');\n\n/**\n * Folders nobody chose as a project: the system's temp directories (Claude\n * Code's per-session scratchpads live there, and so do the folders tools\n * make inside them), Claude Desktop's scratch workspaces, and our own scratch\n * folders. Sessions in them are real conversations, but the folders are not\n * places to start new work in.\n */\nconst tempRoots = [...new Set(['/tmp', '/private/tmp', '/var/folders', '/private/var/folders', os.tmpdir(), realpath(os.tmpdir())])]\n .map((p) => p.replace(/\\/+$/, ''));\nconst scratchRoots = [path.join(config.home, 'Library/Application Support/Claude/scratch-workspaces'), scratchRoot]\n .map((p) => p.replace(/\\/+$/, ''));\n\nconst under = (dir: string, roots: string[]) => {\n const p = dir.replace(/\\/+$/, '');\n return roots.some((root) => p === root || p.startsWith(`${root}/`));\n};\n\n/**\n * temp: inside the system's temp directory. Mostly runs another session\n * started (Claude Code's per-session scratchpads live there); the OS cleans\n * these folders up. scratch: a session you started without a project, in\n * Claude Desktop or here; a real conversation in a throwaway folder.\n */\nexport function folderKind(dir: string): 'project' | 'scratch' | 'temp' {\n // The demo's made-up home can itself be in a temp folder (the scripts that record it put it\n // there): its folders are what they pretend to be.\n if (config.demo && under(dir, [config.home.replace(/\\/+$/, '')])) return under(dir, scratchRoots) ? 'scratch' : 'project';\n if (under(dir, tempRoots)) return 'temp';\n if (under(dir, scratchRoots)) return 'scratch';\n return 'project';\n}\n\nexport function isScratchFolder(dir: string): boolean {\n return folderKind(dir) !== 'project';\n}\n\nexport async function isDirectory(dir: string): Promise<boolean> {\n return stat(dir).then((s) => s.isDirectory(), () => false);\n}\n\n/** A new empty folder like `~/scratch/2026-11-05-a1b2`, for a session that belongs to no project. */\nexport async function createScratchFolder(): Promise<string> {\n const now = new Date();\n const day = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;\n const dir = path.join(scratchRoot, `${day}-${randomBytes(2).toString('hex')}`);\n await mkdir(dir, { recursive: true });\n return dir;\n}\n", "import { execFile } from 'node:child_process';\nimport { readdir, readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { config } from './config.js';\nimport { DEMO_LIVE_SESSION } from './demo.js';\n\n/**\n * A Claude Code process outside this app with a session open: busy, or idle and holding the\n * conversation as it last saw it. `updatedAt` is when it last worked on it.\n */\nexport type LiveProcess = { app?: string; busy: boolean; updatedAt?: number };\n\nconst run = promisify(execFile);\n\ntype Proc = { ppid: number; comm: string };\n\n/** Every process's parent and name, in one `ps` (none when ps is not there). */\nasync function processes(): Promise<Map<number, Proc> | undefined> {\n try {\n const { stdout } = await run('ps', ['-A', '-o', 'pid=,ppid=,comm='], { timeout: 3000, maxBuffer: 8 * 1024 * 1024 });\n const map = new Map<number, Proc>();\n for (const line of stdout.split('\\n')) {\n const match = /^\\s*(\\d+)\\s+(\\d+)\\s+(.*)$/.exec(line);\n if (match) map.set(Number(match[1]), { ppid: Number(match[2]), comm: match[3]!.trim() });\n }\n return map;\n } catch {\n return undefined;\n }\n}\n\n/** Started by this app, directly or through a wrapper. */\nfunction descends(pid: number, procs: Map<number, Proc>): boolean {\n for (let p = procs.get(pid)?.ppid, hops = 0; p && hops < 8; p = procs.get(p)?.ppid, hops += 1) {\n if (p === process.pid) return true;\n }\n return false;\n}\n\n/**\n * Claude Code's own clients, by the entrypoint they record. Any other program\n * that runs Claude Code is just \"elsewhere\": Pelican assumes nothing about it.\n */\nconst CLAUDE_CLIENTS: Record<string, string> = {\n 'claude-desktop': 'Claude Desktop',\n cli: 'the terminal',\n 'claude-vscode': 'VS Code',\n};\n\nlet cache: { at: number; byId: Map<string, LiveProcess> } | undefined;\n\nfunction alive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\n/**\n * Sessions other apps are running, from Claude Code's own registry: every\n * Claude Code process writes `~/.claude/sessions/<pid>.json` with its session\n * id, the app that started it and whether it is busy. This sees a turn that\n * is several minutes into a long command, which writes nothing to the\n * transcript meanwhile. Entries of exited processes are ignored.\n */\nexport async function liveProcesses(): Promise<Map<string, LiveProcess>> {\n if (config.demo) return new Map([[DEMO_LIVE_SESSION, { app: 'Claude Desktop', busy: true }]]);\n if (cache && Date.now() - cache.at < 1500) return cache.byId;\n const dir = path.join(config.claudeHome, 'sessions');\n const byId = new Map<string, LiveProcess>();\n let names: string[] = [];\n try {\n names = (await readdir(dir)).filter((n) => /^\\d+\\.json$/.test(n));\n } catch {\n // Older Claude Code, or none running.\n }\n type Entry = { pid?: number; sessionId?: string; status?: string; entrypoint?: string; updatedAt?: number; statusUpdatedAt?: number };\n const entries = (await Promise.all(names.map(async (name) => {\n try {\n const entry = JSON.parse(await readFile(path.join(dir, name), 'utf8')) as Entry;\n return entry.sessionId && typeof entry.pid === 'number' && entry.pid !== process.pid && alive(entry.pid) ? entry : undefined;\n } catch {\n return undefined; // Being rewritten; the next read gets it.\n }\n }))).filter((e): e is Entry & { pid: number; sessionId: string } => Boolean(e));\n // This app's own Claude processes descend from it; when that cannot be told, those of the SDK.\n // A pid now used by something that is not Claude Code is a registry entry left behind.\n const procs = await processes();\n for (const entry of entries) {\n const proc = procs?.get(entry.pid);\n if (procs && (!proc || !/claude|node|bun|disclaimer/i.test(proc.comm))) continue;\n const ours = procs ? descends(entry.pid, procs) : entry.entrypoint === 'sdk-ts';\n if (ours) continue;\n const busy = entry.status !== undefined && entry.status !== 'idle';\n const app = CLAUDE_CLIENTS[entry.entrypoint ?? ''];\n const known = byId.get(entry.sessionId);\n // When it last changed between working and idle: other writes to the registry are not work.\n const worked = typeof entry.statusUpdatedAt === 'number' ? entry.statusUpdatedAt : entry.updatedAt;\n const updatedAt = Math.max(known?.updatedAt ?? 0, typeof worked === 'number' ? worked : 0) || undefined;\n // A session open in two places: the busy one is what matters.\n const pick = !known || (busy && !known.busy) ? { ...(app ? { app } : {}), busy } : known;\n byId.set(entry.sessionId, { ...pick, ...(updatedAt ? { updatedAt } : {}) });\n }\n cache = { at: Date.now(), byId };\n return byId;\n}\n", "import type { Block, ChatMessage, ImageBlock, TextBlock } from '../shared/protocol.js';\n\nconst MAX_TEXT = 60_000;\nconst MAX_RESULT_TEXT = 24_000;\nconst MAX_IMAGE_BASE64 = 2_500_000;\n\ntype Raw = Record<string, unknown>;\n\nconst isRecord = (value: unknown): value is Raw => typeof value === 'object' && value !== null && !Array.isArray(value);\n\nfunction clip(text: string, max: number): TextBlock {\n return text.length > max\n ? { type: 'text', text: `${text.slice(0, max)}\\n\u2026`, truncated: true }\n : { type: 'text', text };\n}\n\nfunction image(raw: Raw): ImageBlock | undefined {\n const source = raw.source;\n if (!isRecord(source)) return undefined;\n if (source.type === 'base64' && typeof source.data === 'string') {\n const mediaType = typeof source.media_type === 'string' ? source.media_type : 'image/png';\n return source.data.length > MAX_IMAGE_BASE64\n ? { type: 'image', source: { type: 'base64', media_type: mediaType, data: '' }, omitted: true }\n : { type: 'image', source: { type: 'base64', media_type: mediaType, data: source.data } };\n }\n if (source.type === 'url' && typeof source.url === 'string') return { type: 'image', source: { type: 'url', url: source.url } };\n return undefined;\n}\n\nfunction resultContent(content: unknown): { content: string | Array<TextBlock | ImageBlock>; truncated: boolean } {\n if (typeof content === 'string') {\n const clipped = clip(content, MAX_RESULT_TEXT);\n return { content: clipped.text, truncated: Boolean(clipped.truncated) };\n }\n if (!Array.isArray(content)) return { content: '', truncated: false };\n let truncated = false;\n const parts: Array<TextBlock | ImageBlock> = [];\n for (const part of content) {\n if (!isRecord(part)) continue;\n if (part.type === 'text' && typeof part.text === 'string') {\n const clipped = clip(part.text, MAX_RESULT_TEXT);\n truncated ||= Boolean(clipped.truncated);\n parts.push(clipped);\n } else if (part.type === 'image') {\n const img = image(part);\n if (img) parts.push(img);\n }\n }\n return { content: parts, truncated };\n}\n\nfunction blocks(content: unknown): Block[] {\n if (typeof content === 'string') return content ? [clip(content, MAX_TEXT)] : [];\n if (!Array.isArray(content)) return [];\n const out: Block[] = [];\n for (const raw of content) {\n if (!isRecord(raw)) continue;\n switch (raw.type) {\n case 'text':\n if (typeof raw.text === 'string' && raw.text) out.push(clip(raw.text, MAX_TEXT));\n break;\n case 'thinking':\n if (typeof raw.thinking === 'string' && raw.thinking.trim()) out.push({ type: 'thinking', thinking: raw.thinking });\n break;\n case 'tool_use':\n case 'server_tool_use':\n if (typeof raw.id === 'string' && typeof raw.name === 'string') {\n out.push({ type: 'tool_use', id: raw.id, name: raw.name, input: isRecord(raw.input) ? raw.input : {} });\n }\n break;\n case 'tool_result': {\n if (typeof raw.tool_use_id !== 'string') break;\n const { content: resultBody, truncated } = resultContent(raw.content);\n out.push({\n type: 'tool_result',\n tool_use_id: raw.tool_use_id,\n content: resultBody,\n ...(raw.is_error === true ? { is_error: true } : {}),\n ...(truncated ? { truncated: true } : {}),\n });\n break;\n }\n case 'web_search_tool_result':\n if (typeof raw.tool_use_id === 'string') {\n const results = Array.isArray(raw.content) ? raw.content.filter(isRecord) : [];\n const text = results.map((r) => `${String(r.title ?? '')} ${String(r.url ?? '')}`.trim()).join('\\n');\n out.push({ type: 'tool_result', tool_use_id: raw.tool_use_id, content: text });\n }\n break;\n case 'image': {\n const img = image(raw);\n if (img) out.push(img);\n break;\n }\n case 'document':\n out.push({ type: 'text', text: '[document]' });\n break;\n default:\n break;\n }\n }\n return out;\n}\n\n/**\n * Converts a transcript message (`getSessionMessages`) or a streamed SDK message\n * into the shape the app renders. Returns undefined for messages with nothing\n * to show.\n */\nexport function toChatMessage(raw: Raw): ChatMessage | undefined {\n const type = raw.type;\n if (type !== 'user' && type !== 'assistant') return undefined;\n if (typeof raw.uuid !== 'string') return undefined;\n const message = isRecord(raw.message) ? raw.message : {};\n const out: ChatMessage = {\n uuid: raw.uuid,\n role: type,\n blocks: blocks(message.content),\n };\n if (out.blocks.length === 0) return undefined;\n const timestamp = typeof raw.timestamp === 'string' ? Date.parse(raw.timestamp) : NaN;\n if (!Number.isNaN(timestamp)) out.timestamp = timestamp;\n const parent = raw.parent_tool_use_id;\n if (typeof parent === 'string') out.parentToolUseId = parent;\n if (type === 'assistant') {\n if (typeof message.model === 'string') out.model = message.model;\n if (typeof message.id === 'string') out.messageId = message.id;\n }\n // Text Claude Code adds itself (an image's scale note, skill text) is meta in the transcript\n // and synthetic in the live stream; either way it is not something you typed.\n if (raw.is_meta === true || raw.isMeta === true || raw.isSynthetic === true) out.meta = true;\n if (raw.isCompactSummary === true) out.compactSummary = true;\n return out;\n}\n", "import { open, stat } from 'node:fs/promises';\n\nimport type { ChatMessage, RecordedSettings } from '../shared/protocol.js';\nimport { toChatMessage } from './normalize.js';\n\n/**\n * A session's whole conversation, read from its transcript, across compactions.\n *\n * Each row of a transcript points at the one before it (`parentUuid`), so the\n * conversation is the chain from the newest message back to the first. A\n * compaction starts a new chain: its boundary row has no parent and points\n * back only through `logicalParentUuid`. Claude Code's own reader stops there,\n * which would leave everything before the last compaction out of reach; here\n * the walk carries on through every boundary. When a boundary points at a row\n * that was never written, the walk carries on from the last message the\n * compaction kept, or else from the row just before the boundary.\n *\n * The messages a compaction keeps verbatim stay where they were, before the\n * boundary; Claude Code's reader shows them again after the summary.\n *\n * A transcript only grows while its session runs, so it is read incrementally:\n * each read parses only the bytes added since the last one.\n */\n\ntype Node = {\n uuid: string;\n parent: string | undefined;\n /** On a compaction boundary: the last row before the compaction, and the last message it kept verbatim. */\n logicalParent: string | undefined;\n preservedTail: string | undefined;\n /** Position among the rows that have a uuid, in file order. */\n index: number;\n /** What the app shows for this row; rows like attachments only link the chain. */\n message: ChatMessage | undefined;\n};\n\ntype Transcript = {\n /** The settings the session last ran with, in file order. */\n recorded: RecordedSettings;\n ino: number;\n /** Bytes parsed so far, and the start of a line not finished yet. */\n offset: number;\n carry: Buffer;\n nodes: Map<string, Node>;\n order: Node[];\n /** The newest user or assistant row: where the conversation ends. */\n leaf: Node | undefined;\n /** The last walk, reused when the conversation only grew. */\n chain: Node[];\n chainIndex: Map<string, number>;\n chainLeaf: string | undefined;\n messages: ChatMessage[];\n};\n\nconst CHUNK = 8 * 1024 * 1024;\nconst MAX_FILES = 16;\nconst transcripts = new Map<string, Transcript>();\nconst locks = new Map<string, Promise<unknown>>();\n\nconst str = (value: unknown) => (typeof value === 'string' && value ? value : undefined);\n\nfunction empty(ino: number): Transcript {\n return {\n recorded: {}, ino, offset: 0, carry: Buffer.alloc(0), nodes: new Map(), order: [], leaf: undefined,\n chain: [], chainIndex: new Map(), chainLeaf: undefined, messages: [],\n };\n}\n\nconst MODES = new Set(['default', 'acceptEdits', 'plan', 'auto', 'bypassPermissions']);\n\n/** Keeps the settings a row shows the session ran with: the latest row wins. */\nfunction note(recorded: RecordedSettings, row: Record<string, unknown>): void {\n if (row.type === 'assistant') {\n const model = str(row.requestedModel) ?? str((row.message as { model?: unknown } | undefined)?.model);\n if (model && !model.startsWith('<')) recorded.model = model;\n if (str(row.effort)) recorded.effort = str(row.effort);\n const speed = str((row.message as { usage?: { speed?: unknown } } | undefined)?.usage?.speed);\n if (speed) recorded.fast = speed === 'fast';\n recorded.ultracode ??= false;\n } else if (row.type === 'user' && str(row.permissionMode) && MODES.has(str(row.permissionMode)!)) {\n recorded.permissionMode = str(row.permissionMode) as RecordedSettings['permissionMode'];\n } else if (row.type === 'attachment') {\n const kind = str((row.attachment as { type?: unknown } | undefined)?.type) ?? '';\n // Claude Code notes ultracode turning on (and off) as attachments.\n if (kind === 'ultra_effort_enter') recorded.ultracode = true;\n else if (/^ultra_effort_(exit|leave|off)/.test(kind)) recorded.ultracode = false;\n }\n}\n\n/**\n * A message the user sent while Claude was working, which Claude read at its\n * next step: Claude Code records it as a `queued_command` attachment where it\n * was taken in, not as a user row.\n */\nfunction steeredMessage(row: Record<string, unknown>, uuid: string): ChatMessage | undefined {\n const attachment = row.attachment as { type?: string; prompt?: unknown; source_uuid?: unknown; commandMode?: unknown } | undefined;\n if (row.type !== 'attachment' || attachment?.type !== 'queued_command') return undefined;\n if (attachment.commandMode !== undefined && attachment.commandMode !== 'prompt') return undefined;\n const message = toChatMessage({ type: 'user', uuid, timestamp: row.timestamp, message: { role: 'user', content: attachment.prompt } });\n if (!message) return undefined;\n const source = str(attachment.source_uuid);\n return { ...message, steered: true, ...(source ? { sourceUuid: source } : {}) };\n}\n\nfunction addLine(t: Transcript, line: string): void {\n // Rows that take part in the conversation carry a uuid; skip the rest (titles, costs, requests) unparsed.\n if (line.length < 2 || line.charCodeAt(0) !== 123 || !line.includes('\"uuid\"')) return;\n let row: Record<string, unknown>;\n try {\n row = JSON.parse(line) as Record<string, unknown>;\n } catch {\n return;\n }\n const uuid = str(row.uuid);\n if (!uuid || row.isSidechain === true) return;\n note(t.recorded, row);\n const turn = row.type === 'user' || row.type === 'assistant';\n const message = turn ? toChatMessage(row) : steeredMessage(row, uuid);\n const existing = t.nodes.get(uuid);\n if (existing) {\n existing.message = message;\n return;\n }\n const metadata = row.compactMetadata as { preservedSegment?: { tailUuid?: unknown } } | undefined;\n const node: Node = {\n uuid,\n parent: str(row.parentUuid),\n logicalParent: str(row.logicalParentUuid),\n preservedTail: str(metadata?.preservedSegment?.tailUuid),\n index: t.order.length,\n message,\n };\n t.nodes.set(uuid, node);\n t.order.push(node);\n if (turn) t.leaf = node;\n}\n\nasync function readNew(file: string, t: Transcript, size: number): Promise<void> {\n const handle = await open(file, 'r');\n try {\n let position = t.offset;\n while (position < size) {\n const buffer = Buffer.allocUnsafe(Math.min(CHUNK, size - position));\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);\n if (bytesRead <= 0) break;\n position += bytesRead;\n const data = t.carry.length > 0 ? Buffer.concat([t.carry, buffer.subarray(0, bytesRead)]) : buffer.subarray(0, bytesRead);\n const lastBreak = data.lastIndexOf(0x0a);\n if (lastBreak < 0) {\n t.carry = Buffer.from(data);\n continue;\n }\n t.carry = Buffer.from(data.subarray(lastBreak + 1));\n for (const line of data.subarray(0, lastBreak).toString('utf8').split('\\n')) addLine(t, line);\n }\n t.offset = position;\n // A last row without its newline yet: use it if it is whole; it is read again with the rest later.\n if (t.carry.length > 0) addLine(t, t.carry.toString('utf8'));\n } finally {\n await handle.close();\n }\n}\n\n/**\n * The row a compaction boundary continues from. Its logical parent when that\n * was written; otherwise the last message the compaction kept, or the row\n * just before the boundary in the file.\n */\nfunction before(t: Transcript, boundary: Node): Node | undefined {\n return t.nodes.get(boundary.logicalParent!)\n ?? (boundary.preservedTail ? t.nodes.get(boundary.preservedTail) : undefined)\n ?? (boundary.index > 0 ? t.order[boundary.index - 1] : undefined);\n}\n\n/** The chain from `from` back to the start, oldest first; stops early where it meets `known`. */\nfunction walk(t: Transcript, from: Node, known?: Map<string, number>): { nodes: Node[]; joinedAt: number } {\n const out: Node[] = [];\n const seen = new Set<string>();\n let node: Node | undefined = from;\n while (node && !seen.has(node.uuid)) {\n const at = known?.get(node.uuid);\n if (at !== undefined) return { nodes: out.reverse(), joinedAt: at };\n seen.add(node.uuid);\n out.push(node);\n if (node.parent) node = t.nodes.get(node.parent);\n else if (node.logicalParent) node = before(t, node);\n else node = undefined;\n }\n return { nodes: out.reverse(), joinedAt: -1 };\n}\n\nfunction conversation(t: Transcript): ChatMessage[] {\n const leaf = t.leaf;\n if (!leaf) return [];\n if (t.chainLeaf === leaf.uuid) return t.messages;\n const { nodes, joinedAt } = walk(t, leaf, t.chainIndex);\n t.chain = joinedAt >= 0 ? t.chain.slice(0, joinedAt + 1).concat(nodes) : nodes;\n t.chainIndex = new Map(t.chain.map((n, i) => [n.uuid, i]));\n t.chainLeaf = leaf.uuid;\n t.messages = t.chain.flatMap((n) => (n.message ? [n.message] : []));\n return t.messages;\n}\n\nasync function read(file: string): Promise<ChatMessage[]> {\n let info;\n try {\n info = await stat(file);\n } catch {\n transcripts.delete(file);\n return [];\n }\n let t = transcripts.get(file);\n // Replaced or cut short: start over.\n if (!t || t.ino !== info.ino || info.size < t.offset) t = empty(info.ino);\n transcripts.delete(file);\n transcripts.set(file, t);\n while (transcripts.size > MAX_FILES) transcripts.delete(transcripts.keys().next().value!);\n if (info.size > t.offset) await readNew(file, t, info.size);\n return conversation(t);\n}\n\n/** What the session in a transcript file last ran with. */\nexport async function readRecordedSettings(file: string): Promise<RecordedSettings> {\n await readHistory(file);\n return { ...(transcripts.get(file)?.recorded ?? {}) };\n}\n\n/** The conversation in a transcript file, oldest message first. */\nexport function readHistory(file: string): Promise<ChatMessage[]> {\n // One read per file at a time: reads build on each other's state.\n const previous = locks.get(file) ?? Promise.resolve();\n const next = previous.catch(() => undefined).then(() => read(file));\n locks.set(file, next);\n void next.finally(() => {\n if (locks.get(file) === next) locks.delete(file);\n }).catch(() => undefined);\n return next;\n}\n", "import { createReadStream } from 'node:fs';\nimport { open, readdir, stat } from 'node:fs/promises';\nimport readline from 'node:readline';\nimport path from 'node:path';\n\nimport { config } from './config.js';\n\nconst projectsDir = () => path.join(config.claudeHome, 'projects');\n\nlet index = new Map<string, string>();\nlet indexBuiltAt = 0;\n\n/** Rebuilds the sessionId \u2192 transcript path index from `~/.claude/projects/<dir>/<id>.jsonl`. */\nasync function rebuildIndex(): Promise<void> {\n const next = new Map<string, string>();\n let dirs: string[] = [];\n try {\n dirs = await readdir(projectsDir());\n } catch {\n // No projects yet.\n }\n await Promise.all(dirs.map(async (dir) => {\n try {\n for (const name of await readdir(path.join(projectsDir(), dir))) {\n if (name.endsWith('.jsonl')) next.set(name.slice(0, -'.jsonl'.length), path.join(projectsDir(), dir, name));\n }\n } catch {\n // Not a directory, or removed meanwhile.\n }\n }));\n index = next;\n indexBuiltAt = Date.now();\n}\n\n/** Path of a session's transcript, or undefined when it does not exist (yet). */\nexport async function transcriptPath(sessionId: string): Promise<string | undefined> {\n if (!index.has(sessionId) && Date.now() - indexBuiltAt > 1000) await rebuildIndex();\n return index.get(sessionId);\n}\n\nexport async function refreshTranscriptIndex(): Promise<void> {\n await rebuildIndex();\n}\n\nconst CHUNK = 256 * 1024;\nconst MAX_SCAN = 16 * 1024 * 1024;\n\n/** Text blocks of an assistant row, as one line for a session-list preview. */\nfunction replyText(row: { message?: { content?: unknown } }): string | undefined {\n const content = row.message?.content;\n if (!Array.isArray(content)) return undefined;\n const text = content\n .filter((b): b is { type: string; text: string } => b?.type === 'text' && typeof b.text === 'string')\n .map((b) => b.text)\n .join(' ')\n .replace(/[`*_#>|]+/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n return text ? text.slice(0, 160) : undefined;\n}\n\n/**\n * The last user or assistant row, read backwards from the end: its time, the\n * folder and branch it ran in, and the text of Claude's latest reply.\n *\n * Claude Desktop appends metadata rows (titles, cost state) and queued\n * notifications to idle sessions, which moves the file's mtime although\n * nothing was said; only turns count as activity.\n */\nexport type LastTurn = {\n time: number; cwd?: string; gitBranch?: string; preview?: string;\n /** The row was copied from another session by a fork (it carries `forkedFrom`): nothing ran here then. */\n copied?: boolean;\n};\n\n/** After the last turn, how far back to look for a reply with text (tool calls in between). */\nconst PREVIEW_SCAN = 4 * 1024 * 1024;\n\nexport async function readLastTurn(file: string): Promise<LastTurn | undefined> {\n let handle: Awaited<ReturnType<typeof open>> | undefined;\n let found: LastTurn | undefined;\n let foundAt = 0;\n try {\n handle = await open(file, 'r');\n const { size } = await handle.stat();\n let end = size;\n let carry = Buffer.alloc(0);\n while (end > 0 && size - end < MAX_SCAN && !(found && foundAt - end > PREVIEW_SCAN)) {\n const start = Math.max(0, end - CHUNK);\n const chunk = Buffer.alloc(end - start);\n await handle.read(chunk, 0, chunk.length, start);\n let data = Buffer.concat([chunk, carry]);\n end = start;\n if (start > 0) {\n const firstBreak = data.indexOf(0x0a);\n if (firstBreak < 0) {\n carry = data;\n continue;\n }\n carry = data.subarray(0, firstBreak);\n data = data.subarray(firstBreak + 1);\n }\n const lines = data.toString('utf8').split('\\n');\n for (let i = lines.length - 1; i >= 0; i -= 1) {\n const line = lines[i]!;\n if (!line.includes('\"timestamp\"') || !(line.includes('\"type\":\"user\"') || line.includes('\"type\":\"assistant\"'))) continue;\n // Past the last turn, only rows with text matter: a reply, or the prompt that started the turn.\n if (found && !line.includes('\"type\":\"text\"') && !line.includes('\"content\":\"')) continue;\n try {\n const row = JSON.parse(line) as {\n type?: string; timestamp?: string; isSidechain?: boolean; cwd?: unknown; gitBranch?: unknown; message?: { content?: unknown }; forkedFrom?: unknown;\n };\n if ((row.type !== 'user' && row.type !== 'assistant') || row.isSidechain || typeof row.timestamp !== 'string') continue;\n if (!found) {\n const time = Date.parse(row.timestamp);\n if (Number.isNaN(time)) continue;\n found = {\n time,\n ...(typeof row.cwd === 'string' ? { cwd: row.cwd } : {}),\n ...(typeof row.gitBranch === 'string' ? { gitBranch: row.gitBranch } : {}),\n ...(row.forkedFrom ? { copied: true } : {}),\n };\n foundAt = end;\n }\n if (row.type === 'assistant') {\n const preview = replyText(row);\n if (preview) return { ...found, preview };\n } else if (found && row.type === 'user' && typeof (row.message as { content?: unknown })?.content === 'string') {\n // Reached the prompt before any reply text: the turn has said nothing yet.\n return found;\n }\n } catch {\n // A row can be half-written while Claude is streaming into the file.\n }\n }\n }\n } catch {\n // Missing or unreadable.\n } finally {\n await handle?.close();\n }\n return found;\n}\n\nconst lastTurnCache = new Map<string, { size: number; mtimeMs: number; value: LastTurn | undefined }>();\n\n/**\n * `readLastTurn`, cached by file size and mtime. The row also gives the\n * session's folder and branch, which the SDK misses when Desktop has put\n * metadata rows at the head of the file.\n */\nexport async function lastTurnTime(file: string): Promise<{ lastTurn?: LastTurn; mtimeMs: number; size: number } | undefined> {\n let info;\n try {\n info = await stat(file);\n } catch {\n return undefined;\n }\n const cached = lastTurnCache.get(file);\n if (cached && cached.size === info.size && cached.mtimeMs === info.mtimeMs) {\n return { lastTurn: cached.value, mtimeMs: info.mtimeMs, size: info.size };\n }\n const value = await readLastTurn(file);\n lastTurnCache.set(file, { size: info.size, mtimeMs: info.mtimeMs, value });\n return { lastTurn: value, mtimeMs: info.mtimeMs, size: info.size };\n}\n\nconst HEAD_SCAN = 64 * 1024 * 1024;\nconst headCache = new Map<string, string | null>();\n\n/**\n * The session id on the first prompt or reply of a transcript. It is the file's own id, except\n * in a copy of another session: Claude Desktop's side chats start with the whole conversation\n * they came from, rows and ids unchanged. A file only grows at its end, so an answer is kept;\n * a file that holds no turn yet is asked again next time.\n */\nexport async function firstTurnSession(file: string): Promise<string | undefined> {\n const cached = headCache.get(file);\n if (cached !== undefined) return cached ?? undefined;\n let found: string | undefined;\n let read = 0;\n try {\n const input = createReadStream(file);\n const lines = readline.createInterface({ input, crlfDelay: Infinity });\n try {\n for await (const line of lines) {\n read += line.length + 1;\n if (read > HEAD_SCAN) break;\n if (!line.includes('\"sessionId\"') || !(line.includes('\"type\":\"user\"') || line.includes('\"type\":\"assistant\"'))) continue;\n try {\n const row = JSON.parse(line) as { type?: string; sessionId?: unknown; isSidechain?: boolean };\n if ((row.type === 'user' || row.type === 'assistant') && !row.isSidechain && typeof row.sessionId === 'string') {\n found = row.sessionId;\n break;\n }\n } catch {\n // A row being written.\n }\n }\n } finally {\n lines.close();\n input.destroy();\n }\n } catch {\n return undefined; // Missing or unreadable.\n }\n if (found) {\n if (headCache.size > 10_000) headCache.clear();\n headCache.set(file, found);\n }\n return found;\n}\n\n/** An image of a prompt as the transcript holds it, in full (the message list leaves big ones out). */\nexport type RawImage = { type: 'base64'; mediaType: string; data: string } | { type: 'url' };\n\n/** One row of a transcript, as written: what it follows in the chain, and its content. */\nexport type RawEntry = { parentUuid: string | null; content: unknown };\n\n/** The transcript row with this uuid, read from the file itself. */\nexport async function rawEntry(file: string, uuid: string): Promise<RawEntry | undefined> {\n const needle = `\"uuid\":\"${uuid}\"`;\n const input = createReadStream(file);\n const lines = readline.createInterface({ input, crlfDelay: Infinity });\n try {\n for await (const line of lines) {\n if (!line.includes(needle)) continue;\n let row: { uuid?: unknown; parentUuid?: unknown; type?: unknown; message?: { content?: unknown }; attachment?: { type?: unknown; prompt?: unknown } };\n try {\n row = JSON.parse(line) as typeof row;\n } catch {\n continue;\n }\n if (row.uuid !== uuid) continue;\n // A message sent while Claude worked is an attachment row; its prompt is the content.\n const content = row.type === 'attachment' && row.attachment?.type === 'queued_command' ? row.attachment.prompt : row.message?.content;\n return { parentUuid: typeof row.parentUuid === 'string' ? row.parentUuid : null, content };\n }\n } catch {\n return undefined;\n } finally {\n lines.close();\n input.destroy();\n }\n return undefined;\n}\n\n/**\n * The images of one message, in the order and by the rules the message list uses (base64 and\n * url images; anything else is not an image there), read from the transcript row itself.\n */\nexport async function rawImages(file: string, uuid: string): Promise<RawImage[]> {\n const content = (await rawEntry(file, uuid))?.content;\n if (!Array.isArray(content)) return [];\n const images: RawImage[] = [];\n for (const block of content as Array<{ type?: unknown; source?: { type?: unknown; data?: unknown; media_type?: unknown; url?: unknown } }>) {\n if (block?.type !== 'image' || !block.source) continue;\n if (block.source.type === 'base64' && typeof block.source.data === 'string') {\n images.push({ type: 'base64', mediaType: typeof block.source.media_type === 'string' ? block.source.media_type : 'image/png', data: block.source.data });\n } else if (block.source.type === 'url' && typeof block.source.url === 'string') {\n images.push({ type: 'url' });\n }\n }\n return images;\n}\n", "import { stat } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { getSessionInfo, getSubagentMessages, listSessions, renameSession } from '@anthropic-ai/claude-agent-sdk';\nimport type { SDKSessionInfo } from '@anthropic-ai/claude-agent-sdk';\n\nimport type { ChatMessage, MessagesPage, RecordedSettings, SessionStatus, SessionSummary } from '../shared/protocol.js';\nimport { archivedIds } from './archive.js';\nimport { config } from './config.js';\nimport { folderKind } from './folders.js';\nimport { type LiveProcess, liveProcesses } from './live.js';\nimport { readHistory, readRecordedSettings } from './history.js';\nimport { toChatMessage } from './normalize.js';\nimport { firstTurnSession, lastTurnTime, transcriptPath } from './transcripts.js';\n\nexport type StatusSource = (sessionId: string) => SessionStatus | undefined;\n\nconst infoCache = new Map<string, SDKSessionInfo>();\n\nfunction titleOf(info: SDKSessionInfo): string {\n const title = info.customTitle || info.summary || info.firstPrompt || '';\n return title.replace(/\\s+/g, ' ').trim() || 'Untitled session';\n}\n\nasync function summarize(info: SDKSessionInfo, status: StatusSource, live: Map<string, LiveProcess>, archived: Set<string>): Promise<SessionSummary> {\n const file = await transcriptPath(info.sessionId);\n const times = file ? await lastTurnTime(file) : undefined;\n const lastTurn = times?.lastTurn;\n const lastActivity = lastTurn?.time ?? info.lastModified;\n const own = status(info.sessionId);\n let state: SessionStatus = own ?? 'idle';\n // Another app's Claude process says it is busy; failing that, a turn written moments ago\n // (not one a fork just copied over: a new branch is idle until someone sends to it).\n const other = live.get(info.sessionId);\n const elsewhere = own ? undefined : other;\n const recent = lastTurn && !lastTurn.copied && Date.now() - lastTurn.time < config.liveWindowMs;\n if (!own && (elsewhere?.busy || recent)) state = 'live';\n // Idle here but working in another app: that is what the session is doing.\n else if (own === 'idle' && other?.busy) state = 'live';\n const cwd = info.cwd || lastTurn?.cwd || '';\n if (!info.cwd && cwd) infoCache.set(info.sessionId, { ...info, cwd });\n const gitBranch = lastTurn?.gitBranch ?? info.gitBranch;\n const title = titleOf(info);\n // Claude Desktop's side chats: a copy of a conversation with a question or two of its own.\n const copiedFrom = file && title === 'Side chat' ? await firstTurnSession(file) : undefined;\n const sideChatOf = copiedFrom && copiedFrom !== info.sessionId ? copiedFrom : undefined;\n return {\n id: info.sessionId,\n title,\n cwd,\n project: cwd ? path.basename(cwd) : '',\n ...(gitBranch && gitBranch !== 'HEAD' ? { gitBranch } : {}),\n lastActivity,\n ...(info.createdAt ? { createdAt: info.createdAt } : {}),\n status: state,\n ...(info.fileSize ? { sizeBytes: info.fileSize } : {}),\n ...(cwd && folderKind(cwd) === 'scratch' ? { scratch: true } : {}),\n ...(cwd && folderKind(cwd) === 'temp' ? { temp: true } : {}),\n ...(state === 'live' && other?.busy && other.app ? { runningIn: other.app } : {}),\n ...(lastTurn?.preview ? { preview: lastTurn.preview } : {}),\n ...(sideChatOf ? { sideChatOf } : {}),\n ...(archived.has(info.sessionId) ? { archived: true } : {}),\n // Open, idle, in another app too: its Claude does not see what is sent here until it reopens.\n ...(other && !other.busy ? { openIn: other.app ?? 'another app' } : {}),\n };\n}\n\n/** Every session on this machine, newest activity first. */\nexport async function listSummaries(status: StatusSource): Promise<SessionSummary[]> {\n const infos = await listSessions({ includeProgrammatic: true });\n for (const info of infos) infoCache.set(info.sessionId, info);\n const [live, archived] = await Promise.all([liveProcesses(), archivedIds()]);\n const summaries = await Promise.all(infos.map((info) => summarize(info, status, live, archived)));\n return summaries.sort((a, b) => b.lastActivity - a.lastActivity);\n}\n\nexport async function sessionInfo(sessionId: string): Promise<SDKSessionInfo | undefined> {\n // Without a project dir, `getSessionInfo` leaves `cwd` out; the list has it.\n if (!infoCache.has(sessionId)) {\n for (const info of await listSessions({ includeProgrammatic: true }).catch(() => [])) infoCache.set(info.sessionId, info);\n }\n const cached = infoCache.get(sessionId);\n const info = await getSessionInfo(sessionId, cached?.cwd ? { dir: cached.cwd } : {}).catch(() => undefined);\n if (!info) return cached;\n let cwd = info.cwd || cached?.cwd;\n if (!cwd) {\n const file = await transcriptPath(sessionId);\n cwd = file ? (await lastTurnTime(file))?.lastTurn?.cwd : undefined;\n }\n const merged = { ...cached, ...info, ...(cwd ? { cwd } : {}) };\n infoCache.set(sessionId, merged);\n return merged;\n}\n\nexport async function sessionSummary(sessionId: string, status: StatusSource): Promise<SessionSummary | undefined> {\n const info = await sessionInfo(sessionId);\n return info ? summarize(info, status, await liveProcesses(), await archivedIds()) : undefined;\n}\n\nexport async function renameSessionTitle(sessionId: string, title: string): Promise<void> {\n const dir = infoCache.get(sessionId)?.cwd;\n await renameSession(sessionId, title, dir ? { dir } : {});\n infoCache.delete(sessionId);\n}\n\n/** What the session last ran with, from its transcript; empty for a session not written yet. */\nexport async function recordedSettings(sessionId: string): Promise<RecordedSettings> {\n const file = await transcriptPath(sessionId);\n return file ? readRecordedSettings(file) : {};\n}\n\n/** The whole conversation, oldest first, including what came before each compaction. */\nexport async function loadMessages(sessionId: string): Promise<ChatMessage[]> {\n const file = await transcriptPath(sessionId);\n return file ? readHistory(file) : [];\n}\n\n/**\n * Files Claude handed to the user in a session (SendUserFile, Artifact): the\n * only files outside the session's folder the app may show, and only these paths.\n */\nexport async function sentFiles(sessionId: string): Promise<Set<string>> {\n const paths = new Set<string>();\n for (const message of await loadMessages(sessionId)) {\n if (message.role !== 'assistant') continue;\n for (const block of message.blocks) {\n if (block.type !== 'tool_use') continue;\n if (block.name === 'SendUserFile' && Array.isArray(block.input.files)) {\n for (const file of block.input.files) if (typeof file === 'string') paths.add(file);\n } else if (block.name === 'Artifact' && typeof block.input.file_path === 'string') {\n paths.add(block.input.file_path);\n }\n }\n }\n return paths;\n}\n\n/** What a subagent did inside an Agent call: its own transcript, next to the session's. */\nexport async function loadSubagent(sessionId: string, agentId: string): Promise<ChatMessage[]> {\n if (!/^[\\w-]{1,80}$/.test(agentId)) return [];\n const dir = infoCache.get(sessionId)?.cwd;\n const raw = await getSubagentMessages(sessionId, agentId, dir ? { dir } : {}).catch(() => []);\n return raw\n .map((message) => toChatMessage(message as unknown as Record<string, unknown>))\n .filter((message): message is ChatMessage => Boolean(message));\n}\n\n/** A page of messages ending before index `before` (default: the end). */\nexport async function loadPage(sessionId: string, before: number | undefined, limit: number): Promise<MessagesPage> {\n const all = await loadMessages(sessionId);\n const end = Math.min(before ?? all.length, all.length);\n const offset = Math.max(0, end - limit);\n return { messages: all.slice(offset, end), total: all.length, offset };\n}\n", "import { deleteSession, forkSession } from '@anthropic-ai/claude-agent-sdk';\n\nimport type { ChatMessage } from '../shared/protocol.js';\nimport { config } from './config.js';\nimport { loadMessages, sessionInfo } from './sessions.js';\nimport { refreshTranscriptIndex } from './transcripts.js';\n\n/**\n * A copy of the session to carry on separately. With `upTo`, the copy ends at\n * that message, which is how \"edit and resend\" works: the conversation\n * branches before the prompt you change, and the original stays as it was.\n */\nexport async function fork(sessionId: string, upTo?: string, title?: string): Promise<string> {\n if (config.demo) throw new Error('This is the demo: nothing changes.');\n const info = await sessionInfo(sessionId);\n const result = await forkSession(sessionId, {\n ...(info?.cwd ? { dir: info.cwd } : {}),\n ...(upTo ? { upToMessageId: upTo } : {}),\n ...(title ? { title } : {}),\n });\n await refreshTranscriptIndex();\n return result.sessionId;\n}\n\n/**\n * Where a branch that rewrites the message `uuid` starts: the message just\n * before it, or null when `uuid` is the first one (the branch is then a new\n * session in the same folder). Undefined when the message is not there.\n */\nexport async function messageBefore(sessionId: string, uuid: string): Promise<string | null | undefined> {\n const messages = await loadMessages(sessionId);\n const at = messages.findIndex((m) => m.uuid === uuid);\n if (at < 0) return undefined;\n return at === 0 ? null : messages[at - 1]!.uuid;\n}\n\nexport async function remove(sessionId: string): Promise<void> {\n if (config.demo) throw new Error('This is the demo: nothing changes.');\n const info = await sessionInfo(sessionId);\n await deleteSession(sessionId, info?.cwd ? { dir: info.cwd } : {});\n await refreshTranscriptIndex();\n}\n\nconst toolLine = (name: string, input: Record<string, unknown>) => {\n const target = ['file_path', 'command', 'pattern', 'url', 'query', 'description'].map((k) => input[k]).find((v) => typeof v === 'string');\n return `- ${name}${target ? `: \\`${String(target).split('\\n')[0]}\\`` : ''}`;\n};\n\n/** The conversation as Markdown: prompts and replies in full, tool calls as one line each. */\nexport async function exportMarkdown(sessionId: string): Promise<{ title: string; markdown: string }> {\n const info = await sessionInfo(sessionId);\n const title = info?.customTitle || info?.summary || 'Claude Code session';\n const messages: ChatMessage[] = await loadMessages(sessionId);\n const out: string[] = [`# ${title}`, ''];\n if (info?.cwd) out.push(`Folder: \\`${info.cwd}\\``, '');\n let speaker: string | undefined;\n for (const message of messages) {\n if (message.meta || message.parentToolUseId) continue;\n const parts: string[] = [];\n const tools: string[] = [];\n for (const block of message.blocks) {\n if (block.type === 'text' && block.text.trim()) parts.push(block.text.trim());\n else if (block.type === 'tool_use') tools.push(toolLine(block.name, block.input));\n }\n if (parts.length === 0 && tools.length === 0) continue;\n const who = message.role === 'user' ? 'You' : 'Claude';\n if (who !== speaker) {\n out.push(`## ${who}`, '');\n speaker = who;\n }\n if (tools.length > 0) out.push(...tools, '');\n if (parts.length > 0) out.push(parts.join('\\n\\n'), '');\n }\n return { title, markdown: `${out.join('\\n').trim()}\\n` };\n}\n", "import { stat } from 'node:fs/promises';\n\nimport type { ChatMessage, ServerEvent } from '../shared/protocol.js';\nimport { loadMessages } from './sessions.js';\nimport { transcriptPath } from './transcripts.js';\n\nexport type Listener = (event: ServerEvent) => void;\n\nconst POLL_MS = 1000;\n\n/**\n * Delivers one session's messages to the clients viewing it.\n *\n * Messages come from two places: the stream of a turn this app runs, and the\n * transcript file, which other clients (Desktop, the terminal) also write.\n * Both carry the same ids, so each message is delivered once. The file is\n * polled while anyone is watching; a stat per second is cheap and works the\n * same on macOS and Linux, unlike file watchers across atomic rewrites.\n */\nclass Feed {\n readonly listeners = new Set<Listener>();\n private delivered = new Set<string>();\n /** Recently published messages, replayed to a client that subscribes late (a new session's first prompt). */\n private recent: ChatMessage[] = [];\n /** Ids last read from the file; one that disappears means the conversation was rewound. */\n private fromFile = new Set<string>();\n private primed?: Promise<void>;\n private size = -1;\n private mtimeMs = -1;\n private timer?: NodeJS.Timeout;\n private checking = false;\n\n constructor(readonly sessionId: string) {}\n\n private async prime(): Promise<void> {\n this.primed ??= (async () => {\n const file = await transcriptPath(this.sessionId);\n if (file) {\n const info = await stat(file).catch(() => undefined);\n if (info) {\n this.size = info.size;\n this.mtimeMs = info.mtimeMs;\n }\n }\n for (const message of await loadMessages(this.sessionId)) {\n this.delivered.add(message.uuid);\n this.fromFile.add(message.uuid);\n }\n })();\n return this.primed;\n }\n\n broadcast(event: ServerEvent): void {\n for (const listener of this.listeners) {\n try {\n listener(event);\n } catch {\n // A closed socket; its unsubscribe follows.\n }\n }\n }\n\n /** Publishes still on their way (priming reads the transcript first). */\n publishing = 0;\n\n async publish(message: ChatMessage): Promise<void> {\n this.publishing += 1;\n try {\n await this.prime();\n if (this.delivered.has(message.uuid)) return;\n this.delivered.add(message.uuid);\n this.recent.push(message);\n if (this.recent.length > 40) this.recent.shift();\n this.broadcast({ type: 'message', sessionId: this.sessionId, message });\n } finally {\n this.publishing -= 1;\n }\n }\n\n get hasRecent(): boolean {\n return this.recent.length > 0;\n }\n\n replayRecent(listener: Listener): void {\n for (const message of this.recent) listener({ type: 'message', sessionId: this.sessionId, message });\n }\n\n watch(): void {\n if (this.timer) return;\n void this.prime();\n this.timer = setInterval(() => {\n void this.check().then((changed) => {\n if (changed) sessionsChanged();\n });\n }, POLL_MS);\n }\n\n unwatch(): void {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n\n get watching(): boolean {\n return Boolean(this.timer);\n }\n\n async check(): Promise<boolean> {\n if (this.checking) return false;\n this.checking = true;\n try {\n await this.prime();\n const file = await transcriptPath(this.sessionId);\n if (!file) return false;\n const info = await stat(file).catch(() => undefined);\n if (!info || (info.size === this.size && info.mtimeMs === this.mtimeMs)) return false;\n this.size = info.size;\n this.mtimeMs = info.mtimeMs;\n const messages = await loadMessages(this.sessionId);\n const present = new Set(messages.map((m) => m.uuid));\n for (const uuid of this.fromFile) {\n if (!present.has(uuid)) {\n this.fromFile = present;\n this.delivered = new Set(present);\n this.broadcast({ type: 'reload', sessionId: this.sessionId });\n return true;\n }\n }\n this.fromFile = present;\n for (const message of messages) {\n if (this.delivered.has(message.uuid)) continue;\n this.delivered.add(message.uuid);\n this.broadcast({ type: 'message', sessionId: this.sessionId, message });\n }\n return true;\n } finally {\n this.checking = false;\n }\n }\n}\n\nconst feeds = new Map<string, Feed>();\nconst everyone = new Set<Listener>();\nlet changedTimer: NodeJS.Timeout | undefined;\n\nexport function feedFor(sessionId: string): Feed {\n let feed = feeds.get(sessionId);\n if (!feed) {\n feed = new Feed(sessionId);\n feeds.set(sessionId, feed);\n }\n return feed;\n}\n\n/** Tells every connected client the session list changed, at most every 2 s. */\nexport function sessionsChanged(): void {\n if (changedTimer) return;\n changedTimer = setTimeout(() => {\n changedTimer = undefined;\n for (const listener of everyone) {\n try {\n listener({ type: 'sessions_changed' });\n } catch {\n // Closed socket.\n }\n }\n }, 2000);\n}\n\nexport function broadcastAll(event: ServerEvent): void {\n for (const listener of everyone) {\n try {\n listener(event);\n } catch {\n // Closed socket.\n }\n }\n}\n\nexport function connect(listener: Listener): () => void {\n everyone.add(listener);\n return () => everyone.delete(listener);\n}\n\nexport function subscribe(sessionId: string, listener: Listener): () => void {\n const feed = feedFor(sessionId);\n feed.listeners.add(listener);\n feed.replayRecent(listener);\n feed.watch();\n return () => {\n feed.listeners.delete(listener);\n if (feed.listeners.size > 0) return;\n feed.unwatch();\n // Nobody watches and no run has published into it: forget it, so feeds do not pile up\n // for every session ever opened. A run's feed stays, for a client that comes back to it.\n if (!feed.hasRecent && feed.publishing === 0 && feeds.get(sessionId) === feed) feeds.delete(sessionId);\n };\n}\n", "import { randomUUID } from 'node:crypto';\nimport { mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport type { Attachment } from '../shared/protocol.js';\nimport { config } from './config.js';\n\nconst uploadsDir = () => path.join(config.stateDir, 'uploads');\nexport const MAX_UPLOAD_BYTES = 25 * 1024 * 1024;\n\nconst safeName = (name: string) => name.replace(/[/\\\\\\0]/g, '_').replace(/^\\.+/, '_').slice(0, 120) || 'file';\n\n/** Saves an uploaded file where Claude can read it, and returns its handle. */\nexport async function saveUpload(file: File): Promise<Attachment> {\n if (file.size > MAX_UPLOAD_BYTES) throw new Error(`File is larger than ${MAX_UPLOAD_BYTES / 1024 / 1024} MB`);\n const id = randomUUID();\n const dir = path.join(uploadsDir(), id);\n await mkdir(dir, { recursive: true, mode: 0o700 });\n const name = safeName(file.name);\n const filePath = path.join(dir, name);\n await writeFile(filePath, Buffer.from(await file.arrayBuffer()), { mode: 0o600 });\n return { id, name, mime: file.type || 'application/octet-stream', size: file.size, path: filePath };\n}\n\n/** Looks an upload up by id; the path is rebuilt here, never taken from the client. */\nexport async function findUpload(id: string): Promise<Attachment | undefined> {\n if (!/^[0-9a-f-]{36}$/.test(id)) return undefined;\n const dir = path.join(uploadsDir(), id);\n try {\n const [name] = await readdir(dir);\n if (!name) return undefined;\n const filePath = path.join(dir, name);\n const info = await stat(filePath);\n return { id, name, mime: mimeOf(name), size: info.size, path: filePath };\n } catch {\n return undefined;\n }\n}\n\nexport async function readUpload(id: string): Promise<{ attachment: Attachment; data: Buffer } | undefined> {\n const attachment = await findUpload(id);\n if (!attachment) return undefined;\n return { attachment, data: await readFile(attachment.path) };\n}\n\nconst MIME: Record<string, string> = {\n png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp',\n pdf: 'application/pdf', txt: 'text/plain', md: 'text/markdown', json: 'application/json', csv: 'text/csv',\n};\nconst mimeOf = (name: string) => MIME[name.split('.').pop()?.toLowerCase() ?? ''] ?? 'application/octet-stream';\n\nexport type DirListing = { path: string; parent?: string; dirs: Array<{ name: string; path: string }> };\n\n/** Subfolders of a folder, for choosing where a new session runs. */\nexport async function listDirs(dir: string | undefined, showHidden: boolean): Promise<DirListing> {\n const target = path.resolve(dir || config.home);\n const entries = await readdir(target, { withFileTypes: true });\n const dirs = entries\n .filter((e) => (e.isDirectory() || e.isSymbolicLink()) && (showHidden || !e.name.startsWith('.')))\n .map((e) => ({ name: e.name, path: path.join(target, e.name) }))\n .sort((a, b) => a.name.localeCompare(b.name));\n const parent = path.dirname(target);\n return { path: target, ...(parent !== target ? { parent } : {}), dirs };\n}\n\n/** Removes an upload (made for a message that was never sent). */\nexport async function removeUpload(id: string): Promise<void> {\n if (!/^[0-9a-f-]{36}$/.test(id)) return;\n await rm(path.join(uploadsDir(), id), { recursive: true, force: true });\n}\n", "import { realpath } from 'node:fs/promises';\nimport os from 'node:os';\n\nimport { listSessions } from '@anthropic-ai/claude-agent-sdk';\n\nimport { lastTurnTime, transcriptPath } from './transcripts.js';\n\n/** A yes holds for a while; a no is asked again soon (the folder's first session may be starting). */\nconst YES_MS = 30_000;\nconst NO_MS = 3_000;\nconst cache = new Map<string, { at: number; value: boolean }>();\n\n/**\n * Whether Claude Code has run in this very folder, so what the folder brings (its project\n * settings, hooks and MCP servers, its git config with filters and hooks) is something the user\n * already let run. A folder Pelican is only shown (a fresh clone, a download, an address another\n * site opened) runs nothing of its own: probes get the user's settings only, and git does not run.\n *\n * Claude Code files sessions by the folder's path with punctuation turned into dashes, so `a.b`\n * and `a-b` share a list, and the list may fill in a session's folder from that shared name. A\n * session counts only by the folder its own transcript records, and only if it is this one.\n */\nexport async function claudeRanIn(folder: string): Promise<boolean> {\n const dir = await realpath(folder).catch(() => folder);\n if (dir === os.homedir()) return true;\n const cached = cache.get(dir);\n if (cached && Date.now() - cached.at < (cached.value ? YES_MS : NO_MS)) return cached.value;\n let value = false;\n const sessions = await listSessions({ dir, limit: 50, includeWorktrees: false }).catch(() => []);\n for (const session of sessions) {\n const file = await transcriptPath(session.sessionId);\n const recorded = file ? (await lastTurnTime(file))?.lastTurn?.cwd : undefined;\n if (!recorded) continue;\n if (recorded === dir || (await realpath(recorded).catch(() => '')) === dir) {\n value = true;\n break;\n }\n }\n if (cache.size > 2000) cache.clear();\n cache.set(dir, { at: Date.now(), value });\n return value;\n}\n", "import { realpath } from 'node:fs/promises';\nimport os from 'node:os';\n\nimport { query } from '@anthropic-ai/claude-agent-sdk';\nimport type { McpServerStatus, ModelInfo, SDKControlGetUsageResponse, SDKUserMessage, SlashCommand } from '@anthropic-ai/claude-agent-sdk';\n\nimport type { Account, Agent, Command, McpServer, ModelOption, PlanUsage } from '../shared/protocol.js';\nimport { config } from './config.js';\nimport { DEMO_MODELS } from './demo.js';\nimport { claudeRanIn } from './trust.js';\n\nconst TTL_MS = 10 * 60 * 1000;\nconst PROBE_TIMEOUT_MS = 30_000;\nconst MAX_PROBES = 2;\n\n/** A prompt stream that never yields: the process starts, answers control requests, and sends no turn. */\nconst idlePrompt: AsyncIterable<SDKUserMessage> = {\n [Symbol.asyncIterator]: () => ({ next: () => new Promise<never>(() => {}) }),\n};\n\nlet running = 0;\nconst waiting: (() => void)[] = [];\n\n/** At most MAX_PROBES Claude processes at a time, however many requests ask. */\nasync function slot<T>(work: () => Promise<T>): Promise<T> {\n if (running >= MAX_PROBES) await new Promise<void>((resolve) => waiting.push(resolve));\n running += 1;\n try {\n return await work();\n } finally {\n running -= 1;\n waiting.shift()?.();\n }\n}\n\nconst inflight = new Map<string, Promise<unknown>>();\n\n/**\n * Starts Claude Code without a conversation in `cwd`, asks it something, and\n * stops it. Costs no tokens and writes no transcript. One probe per folder and\n * question at a time; it gives up after PROBE_TIMEOUT_MS.\n */\nasync function probe<T>(key: string, cwd: string, ask: (q: ReturnType<typeof query>) => Promise<T>): Promise<T> {\n const dir = await realpath(cwd);\n const id = `${key}:${dir}`;\n const pending = inflight.get(id);\n if (pending) return pending as Promise<T>;\n const work = slot(async () => {\n const q = query({\n prompt: idlePrompt,\n options: {\n cwd: dir,\n persistSession: false,\n settingSources: (await claudeRanIn(dir)) ? ['user', 'project', 'local'] : ['user'],\n ...(config.claudeExecutable ? { pathToClaudeCodeExecutable: config.claudeExecutable } : {}),\n },\n });\n let timer: NodeJS.Timeout | undefined;\n try {\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('Claude Code did not answer in time.')), PROBE_TIMEOUT_MS);\n });\n return await Promise.race([ask(q), timeout]);\n } finally {\n clearTimeout(timer);\n q.close();\n }\n }).finally(() => inflight.delete(id));\n inflight.set(id, work);\n return work;\n}\n\nfunction toOption(info: ModelInfo): ModelOption {\n return {\n value: info.value,\n label: info.displayName || info.value,\n description: info.description ?? '',\n efforts: info.supportedEffortLevels ?? [],\n supportsFast: Boolean(info.supportsFastMode),\n ...(info.supportsAutoMode !== undefined ? { supportsAuto: info.supportsAutoMode } : {}),\n ...(info.resolvedModel ? { resolvedModel: info.resolvedModel } : {}),\n };\n}\n\nexport type ModelList = { models: ModelOption[]; signedIn: boolean | null; account?: Account };\n\nlet cache: { at: number; value: ModelList } | undefined;\n\n/**\n * The models this account can use, with their effort levels and fast-mode\n * support, as Claude Code reports them, and whether Claude Code is signed in.\n */\nexport async function listModels(): Promise<ModelList> {\n if (config.demo) return { models: DEMO_MODELS, signedIn: true, account: { email: 'you@example.com', plan: 'max' } };\n if (cache && Date.now() - cache.at < TTL_MS) return cache.value;\n const asking = probe('models', os.homedir(), async (q) => {\n const models = (await q.supportedModels()).map(toOption);\n const account = await q.accountInfo().catch(() => undefined);\n const signedIn = account ? Boolean(account.email || account.tokenSource || account.apiKeySource || account.apiProvider) : null;\n const who: Account | undefined = account?.email || account?.organization || account?.subscriptionType\n ? {\n ...(account.email ? { email: account.email } : {}),\n ...(account.organization ? { organization: account.organization } : {}),\n ...(account.subscriptionType ? { plan: account.subscriptionType } : {}),\n }\n : undefined;\n return { models, signedIn, ...(who ? { account: who } : {}) };\n })\n .then((value) => {\n cache = { at: Date.now(), value };\n return value;\n });\n try {\n return await asking;\n } catch (error) {\n if (cache) return cache.value;\n throw error;\n }\n}\n\nconst DEMO_COMMANDS: Command[] = [\n { name: 'compact', description: 'Summarize the conversation so far to free up context', argumentHint: '' },\n { name: 'review', description: 'Review the changes on this branch', argumentHint: '' },\n { name: 'init', description: 'Write a CLAUDE.md for this project', argumentHint: '' },\n];\nconst DEMO_AGENTS: Agent[] = [{ name: 'code-reviewer', description: 'Reviews a change for bugs and style' }];\n\nexport type Completions = { commands: Command[]; agents: Agent[] };\nconst completionCache = new Map<string, { at: number; value: Completions }>();\n\n/** Slash commands (built-in, the project's and the user's, skills) and subagents available in a folder. */\nexport async function listCompletions(cwd: string): Promise<Completions> {\n if (config.demo) return { commands: DEMO_COMMANDS, agents: DEMO_AGENTS };\n const dir = await realpath(cwd);\n const cached = completionCache.get(dir);\n if (cached && Date.now() - cached.at < TTL_MS) return cached.value;\n const value = await probe('completions', dir, async (q) => {\n const [commands, agents] = await Promise.all([q.supportedCommands(), q.supportedAgents().catch(() => [])]);\n return {\n commands: commands.map((c: SlashCommand) => ({ name: c.name, description: c.description ?? '', argumentHint: c.argumentHint ?? '' })),\n agents: agents.map((a) => ({ name: a.name, description: a.description ?? '' })),\n };\n });\n completionCache.set(dir, { at: Date.now(), value });\n return value;\n}\n\nexport function toMcpServers(list: McpServerStatus[]): McpServer[] {\n return list.map((server) => ({\n name: server.name,\n status: server.status,\n ...(server.error ? { error: server.error } : {}),\n tools: server.tools?.length ?? 0,\n ...(server.scope ? { scope: server.scope } : {}),\n }));\n}\n\nconst mcpCache = new Map<string, { at: number; servers: McpServer[] }>();\n\n/** The MCP servers a session in this folder gets, and whether they connect, when no Claude runs there. */\nexport async function probeMcpServers(cwd: string): Promise<McpServer[]> {\n if (config.demo) return [{ name: 'github', status: 'connected', tools: 26, scope: 'user' }, { name: 'linear', status: 'needs-auth', tools: 0, scope: 'project' }];\n const dir = await realpath(cwd);\n const cached = mcpCache.get(dir);\n if (cached && Date.now() - cached.at < 60_000) return cached.servers;\n const servers = toMcpServers(await probe('mcp', dir, (q) => q.mcpServerStatus()));\n mcpCache.set(dir, { at: Date.now(), servers });\n return servers;\n}\n\nconst DEMO_PLAN: PlanUsage = {\n windows: [\n { type: 'five_hour', utilization: 0.42, resetsAt: Date.now() + 2.4 * 3600_000 },\n { type: 'seven_day', utilization: 0.18, resetsAt: Date.now() + 3.5 * 86400_000 },\n ],\n};\n\ntype Window = { utilization: number | null; resets_at: string | null } | null | undefined;\nlet planCache: { at: number; value: PlanUsage | null } | undefined;\n\n/**\n * The plan's usage, as Claude Code's `/usage` reads it from claude.ai: the windows of a Pro or\n * Max plan, and the monthly cap on extra usage that Team and Enterprise plans have instead.\n * Null where plan limits do not apply (an API key, Bedrock, Vertex) or the answer is not there.\n * The SDK marks this call experimental, so anything unexpected is no answer, not an error.\n */\nexport async function planUsage(): Promise<PlanUsage | null> {\n if (config.demo) return DEMO_PLAN;\n if (planCache && Date.now() - planCache.at < 60_000) return planCache.value;\n const value = await probe('usage', os.homedir(), async (q) => {\n const ask = (q as unknown as { usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET?: (o: { skipBehaviors: boolean }) => Promise<SDKControlGetUsageResponse> })\n .usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET;\n if (typeof ask !== 'function') return null;\n const answer = await ask.call(q, { skipBehaviors: true });\n const limits = answer?.rate_limits;\n if (!answer?.rate_limits_available || !limits) return null;\n const windows: PlanUsage['windows'] = [];\n const add = (type: string, window: Window, label?: string) => {\n if (!window || typeof window.utilization !== 'number') return;\n const resetsAt = window.resets_at ? Date.parse(window.resets_at) : NaN;\n windows.push({ type, ...(label ? { label } : {}), utilization: window.utilization / 100, ...(Number.isFinite(resetsAt) ? { resetsAt } : {}) });\n };\n add('five_hour', limits.five_hour);\n add('seven_day', limits.seven_day);\n add('seven_day_opus', limits.seven_day_opus);\n add('seven_day_sonnet', limits.seven_day_sonnet);\n for (const scoped of limits.model_scoped ?? []) add(`model:${scoped.display_name}`, scoped, `Weekly ${scoped.display_name} limit`);\n const extra = limits.extra_usage;\n return {\n windows,\n ...(extra ? {\n extra: {\n enabled: Boolean(extra.is_enabled),\n ...(typeof extra.used_credits === 'number' ? { usedCents: extra.used_credits } : {}),\n ...(typeof extra.monthly_limit === 'number' ? { limitCents: extra.monthly_limit } : {}),\n ...(typeof extra.utilization === 'number' ? { utilization: extra.utilization / 100 } : {}),\n ...(extra.currency ? { currency: extra.currency } : {}),\n },\n } : {}),\n };\n }).catch(() => null);\n planCache = { at: Date.now(), value };\n return value;\n}\n", "import { execFile } from 'node:child_process';\nimport { realpathSync } from 'node:fs';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { config } from './config.js';\n\nconst run = promisify(execFile);\n\n/**\n * Git runs only in folders Claude Code has worked in (see trust.ts), where the repository's\n * config is the user's own. Even there Pelican only reads, so it starts nothing git would start\n * on the side: no hooks, no file-system monitor, no external diff or text converter. It takes\n * no locks the user's own git would trip over, never asks for credentials, reads pathspecs\n * literally (`:/` is a name, not \"the top of the repository\"), and leaves submodules' work\n * trees alone.\n */\nconst SAFE = ['-c', 'core.fsmonitor=false', '-c', 'core.hooksPath=/dev/null', '-c', 'safe.bareRepository=explicit', '-c', 'core.quotepath=off'];\n\nfunction environment(): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env, GIT_OPTIONAL_LOCKS: '0', GIT_TERMINAL_PROMPT: '0', GIT_LITERAL_PATHSPECS: '1' };\n // The folder decides the repository, not whatever the server was started with.\n for (const name of Object.keys(env)) {\n if (/^GIT_(DIR|WORK_TREE|INDEX_FILE|COMMON_DIR|OBJECT_DIRECTORY|ALTERNATE_OBJECT_DIRECTORIES|NAMESPACE|CONFIG_PARAMETERS|CONFIG_COUNT|CONFIG_KEY_\\d+|CONFIG_VALUE_\\d+|CEILING_DIRECTORIES)$/.test(name)) delete env[name];\n }\n if (config.demo) {\n // The demo's folders may sit inside a repository of yours (a home folder kept in git):\n // git must not look above the demo for one.\n try {\n env.GIT_CEILING_DIRECTORIES = path.dirname(realpathSync(config.home));\n } catch {\n env.GIT_CEILING_DIRECTORIES = path.dirname(config.home);\n }\n }\n return env;\n}\n\n/** Runs a read-only git command in `cwd`; rejects on a non-zero exit, with stdout on the error. */\nexport async function git(cwd: string, args: string[], timeout = 15_000): Promise<string> {\n const command = args[0]!;\n const extra = command === 'diff' ? ['--no-ext-diff', '--no-textconv', '--ignore-submodules=dirty']\n : command === 'status' ? ['--ignore-submodules=dirty']\n : [];\n const { stdout } = await run('git', [...SAFE, command, ...extra, ...args.slice(1)], { cwd, env: environment(), maxBuffer: 32 * 1024 * 1024, timeout });\n return stdout;\n}\n", "import { readdir } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { config } from './config.js';\nimport { git } from './git.js';\nimport { claudeRanIn } from './trust.js';\n\nconst cache = new Map<string, { at: number; files: string[] }>();\nconst TTL_MS = 30_000;\nconst SKIP = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.venv', 'venv', '__pycache__', 'target', '.cache']);\nconst MAX_FILES = 20_000;\n\n/** Files of a folder as `git ls-files` sees them (ignored ones left out), or by walking it. */\nasync function projectFiles(cwd: string): Promise<string[]> {\n const cached = cache.get(cwd);\n if (cached && Date.now() - cached.at < TTL_MS) return cached.files;\n let files: string[];\n try {\n // Git only where Claude Code has run; anywhere else the folder is walked.\n if (!config.demo && !(await claudeRanIn(cwd))) throw new Error('not trusted');\n const stdout = await git(cwd, ['ls-files', '--cached', '--others', '--exclude-standard'], 5000);\n files = stdout.split('\\n').filter(Boolean).slice(0, MAX_FILES);\n } catch {\n files = [];\n const walk = async (dir: string, depth: number): Promise<void> => {\n if (depth > 8 || files.length >= MAX_FILES) return;\n let entries;\n try {\n entries = await readdir(path.join(cwd, dir), { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (files.length >= MAX_FILES) return;\n if (entry.name.startsWith('.') || SKIP.has(entry.name)) continue;\n const rel = dir ? `${dir}/${entry.name}` : entry.name;\n if (entry.isDirectory()) await walk(rel, depth + 1);\n else files.push(rel);\n }\n };\n await walk('', 0);\n }\n cache.set(cwd, { at: Date.now(), files });\n return files;\n}\n\n/** How well `query` matches `file`: higher is better, -1 for no match. */\nfunction score(file: string, query: string): number {\n const f = file.toLowerCase();\n const q = query.toLowerCase();\n const base = f.slice(f.lastIndexOf('/') + 1);\n if (base === q) return 1000;\n if (base.startsWith(q)) return 800 - base.length;\n if (base.includes(q)) return 600 - base.length;\n if (f.includes(q)) return 400 - f.length / 10;\n // Every character in order: \"stset\" finds \"src/settings/Settings.tsx\".\n let at = 0;\n for (const char of q) {\n at = f.indexOf(char, at);\n if (at === -1) return -1;\n at += 1;\n }\n return 100 - f.length / 10;\n}\n\n/** Files and folders matching a query, for `@` mentions in the composer. */\nexport async function searchFiles(cwd: string, query: string, limit = 30): Promise<string[]> {\n const files = await projectFiles(cwd);\n const dirs = new Set<string>();\n for (const file of files) {\n let cut = file.lastIndexOf('/');\n while (cut > 0) {\n dirs.add(`${file.slice(0, cut)}/`);\n cut = file.lastIndexOf('/', cut - 1);\n }\n }\n const candidates = [...files, ...dirs];\n if (!query) return files.slice().sort((a, b) => a.split('/').length - b.split('/').length || a.localeCompare(b)).slice(0, limit);\n return candidates\n .map((file) => ({ file, score: score(file, query) }))\n .filter((m) => m.score >= 0)\n .sort((a, b) => b.score - a.score || a.file.localeCompare(b.file))\n .slice(0, limit)\n .map((m) => m.file);\n}\n", "import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport webpush from 'web-push';\n\nimport { config } from './config.js';\n\ntype Subscription = webpush.PushSubscription & { openUrl?: string };\n\nconst keysFile = () => path.join(config.stateDir, 'vapid.json');\nconst subsFile = () => path.join(config.stateDir, 'push-subscriptions.json');\n\nlet keys: { publicKey: string; privateKey: string } | undefined;\nlet subscriptions: Subscription[] = [];\n\nasync function save(file: string, value: unknown): Promise<void> {\n await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });\n await writeFile(file, JSON.stringify(value, null, 2), { mode: 0o600 });\n}\n\n/** Loads this machine's VAPID keys (created on first start) and the saved subscriptions. */\nexport async function initPush(): Promise<void> {\n try {\n keys = JSON.parse(await readFile(keysFile(), 'utf8'));\n } catch {\n keys = webpush.generateVAPIDKeys();\n await save(keysFile(), keys);\n }\n webpush.setVapidDetails('https://github.com/Ki-Seki/pelican', keys!.publicKey, keys!.privateKey);\n try {\n subscriptions = JSON.parse(await readFile(subsFile(), 'utf8'));\n } catch {\n subscriptions = [];\n }\n}\n\nexport function pushKey(): string | undefined {\n return keys?.publicKey;\n}\n\nexport async function addSubscription(subscription: Subscription): Promise<void> {\n subscriptions = subscriptions.filter((s) => s.endpoint !== subscription.endpoint);\n subscriptions.push(subscription);\n await save(subsFile(), subscriptions);\n}\n\nexport async function removeSubscription(endpoint: string): Promise<void> {\n subscriptions = subscriptions.filter((s) => s.endpoint !== endpoint);\n await save(subsFile(), subscriptions);\n}\n\nexport type PushPayload = { title: string; body: string; sessionId: string; tag: string };\n\n/** Sends a notification to every subscribed device; drops subscriptions the push service rejects. */\nexport async function notify(payload: PushPayload): Promise<void> {\n if (!keys || subscriptions.length === 0) return;\n const gone: string[] = [];\n await Promise.all(subscriptions.map(async (subscription) => {\n // PELICAN_SESSION_URL wins (for a page that embeds Pelican); otherwise the page that subscribed.\n const id = encodeURIComponent(payload.sessionId);\n const url = config.sessionUrl ? config.sessionUrl.replaceAll('{id}', id)\n : subscription.openUrl ? `${subscription.openUrl}${id}` : undefined;\n const body = JSON.stringify({ ...payload, url });\n try {\n await webpush.sendNotification(subscription, body, { TTL: 3600, urgency: 'high' });\n } catch (error) {\n const status = (error as { statusCode?: number }).statusCode;\n if (status === 404 || status === 410) gone.push(subscription.endpoint);\n else console.warn('[pelican] push failed', status ?? (error as Error).message);\n }\n }));\n if (gone.length > 0) {\n subscriptions = subscriptions.filter((s) => !gone.includes(s.endpoint));\n await save(subsFile(), subscriptions);\n }\n}\n", "import { spawn } from 'node:child_process';\n\nimport type { ShellResult } from '../shared/protocol.js';\n\nconst MAX_OUTPUT = 200_000;\nconst TIMEOUT_MS = 2 * 60_000;\n\n/**\n * Runs one `!` command in a session's folder through the user's login shell,\n * like Claude Code's bash mode: no terminal, output captured up to a limit,\n * stopped after two minutes.\n */\nexport function runCommand(cwd: string, command: string): Promise<ShellResult> {\n const shell = process.env.SHELL || '/bin/bash';\n const started = Date.now();\n return new Promise((resolve) => {\n let stdout = '';\n let stderr = '';\n let truncated = false;\n const child = spawn(shell, ['-lc', command], { cwd, env: { ...process.env, PAGER: 'cat', GIT_PAGER: 'cat', TERM: 'dumb' }, stdio: ['ignore', 'pipe', 'pipe'] });\n const take = (current: string, chunk: Buffer) => {\n if (current.length >= MAX_OUTPUT) {\n truncated = true;\n return current;\n }\n const next = current + chunk.toString('utf8');\n if (next.length > MAX_OUTPUT) truncated = true;\n return next.slice(0, MAX_OUTPUT);\n };\n child.stdout.on('data', (chunk: Buffer) => { stdout = take(stdout, chunk); });\n child.stderr.on('data', (chunk: Buffer) => { stderr = take(stderr, chunk); });\n const timer = setTimeout(() => child.kill('SIGKILL'), TIMEOUT_MS);\n const finish = (code: number | null, error?: Error) => {\n clearTimeout(timer);\n resolve({\n command,\n stdout,\n stderr: error ? `${stderr}${error.message}` : stderr,\n code,\n durationMs: Date.now() - started,\n ...(truncated ? { truncated: true } : {}),\n });\n };\n child.on('error', (error) => finish(null, error));\n child.on('close', (code, signal) => finish(signal ? null : code));\n });\n}\n\nconst escape = (text: string) => text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n\n/** The command and its output the way Claude Code's bash mode writes them into the conversation. */\nexport function bashModeText(result: ShellResult): string {\n const note = result.code === null ? '\\n(stopped after two minutes)' : result.code !== 0 ? `\\n(exit code ${result.code})` : '';\n return `<bash-input>${escape(result.command)}</bash-input><bash-stdout>${escape(result.stdout)}</bash-stdout><bash-stderr>${escape(result.stderr + note)}</bash-stderr>`;\n}\n", "import type { SDKRateLimitInfo } from '@anthropic-ai/claude-agent-sdk';\n\nimport type { UsageLimit } from '../shared/protocol.js';\n\n/** The account's usage limits as Claude Code last reported them, one per kind of limit. */\nconst limits = new Map<string, UsageLimit>();\n\nexport function recordLimit(info: SDKRateLimitInfo): void {\n const type = info.rateLimitType ?? 'unknown';\n limits.set(type, {\n type,\n status: info.status,\n ...(typeof info.utilization === 'number' ? { utilization: info.utilization } : {}),\n ...(typeof info.resetsAt === 'number' ? { resetsAt: info.resetsAt < 1e12 ? info.resetsAt * 1000 : info.resetsAt } : {}),\n at: Date.now(),\n });\n}\n\nexport function usageLimits(): UsageLimit[] {\n const now = Date.now();\n // A limit that has reset since it was reported says nothing any more.\n return [...limits.values()].filter((limit) => !limit.resetsAt || limit.resetsAt > now);\n}\n", "import { randomUUID } from 'node:crypto';\nimport { readFile, stat } from 'node:fs/promises';\n\nimport { query } from '@anthropic-ai/claude-agent-sdk';\nimport type {\n CanUseTool,\n EffortLevel,\n Options,\n PermissionResult,\n PermissionUpdate,\n Query,\n SDKMessage,\n SDKUserMessage,\n} from '@anthropic-ai/claude-agent-sdk';\n\nimport type {\n Attachment,\n BackgroundTask,\n ChatMessage,\n McpServer,\n PartialBlock,\n PermissionDecision,\n PermissionRequest,\n RewindResult,\n RunSettings,\n RunState,\n SendMode,\n SessionStatus,\n ShellResult,\n} from '../shared/protocol.js';\nimport { config } from './config.js';\nimport { DEMO_WAITING_SESSION, demoRunState } from './demo.js';\nimport { toMcpServers } from './models.js';\nimport { broadcastAll, feedFor, sessionsChanged } from './feed.js';\nimport { liveProcesses } from './live.js';\nimport { toChatMessage } from './normalize.js';\nimport { notify } from './push.js';\nimport { sessionInfo } from './sessions.js';\nimport { bashModeText } from './shell.js';\nimport { lastTurnTime, refreshTranscriptIndex, transcriptPath } from './transcripts.js';\nimport { recordLimit } from './usage.js';\n\n/** Messages the user sends, handed to the Claude process as they come. */\nclass InputQueue implements AsyncIterable<SDKUserMessage> {\n private items: SDKUserMessage[] = [];\n private waiters: Array<(result: IteratorResult<SDKUserMessage>) => void> = [];\n private done = false;\n\n push(item: SDKUserMessage): void {\n const waiter = this.waiters.shift();\n if (waiter) waiter({ value: item, done: false });\n else this.items.push(item);\n }\n\n close(): void {\n this.done = true;\n for (const waiter of this.waiters.splice(0)) waiter({ value: undefined, done: true });\n }\n\n [Symbol.asyncIterator](): AsyncIterator<SDKUserMessage> {\n return {\n next: () => {\n const item = this.items.shift();\n if (item) return Promise.resolve({ value: item, done: false });\n if (this.done) return Promise.resolve({ value: undefined, done: true });\n return new Promise((resolve) => this.waiters.push(resolve));\n },\n };\n }\n}\n\ntype Pending = {\n request: PermissionRequest;\n input: Record<string, unknown>;\n suggestions?: PermissionUpdate[];\n resolve: (result: PermissionResult) => void;\n};\n\nexport type SendInput = {\n text: string;\n attachments: Attachment[];\n settings: RunSettings;\n /** Id for the user message, so the client's optimistic copy matches the transcript row. */\n uuid?: string;\n /** While a turn runs: join it at Claude's next step, or wait for it to end. */\n mode?: SendMode;\n};\n\nconst sdkModel = (model: string | undefined) => (model && model !== 'default' ? model : undefined);\n\n/** One Claude Code process driving one session. It stays alive between turns until idle. */\n/** Claude Code's error, or what it means for someone who edited their last prompt. */\nfunction readableError(text: string): string {\n if (text.includes('Resume rejected by --resume-drops-turn')) {\n return 'The conversation changed after that prompt (another app added to it), so the prompt was not replaced. Reload, then edit it again or send a new message.';\n }\n return text;\n}\n\nexport class SessionRun {\n busy = false;\n closed = false;\n error?: string;\n lastActive = Date.now();\n private partial: PartialBlock[] = [];\n private completedBlocks = 0;\n private permissions = new Map<string, Pending>();\n private input = new InputQueue();\n private q?: Query;\n private emitTimer?: NodeJS.Timeout;\n private title = '';\n /** Set by Stop: the turn then ends with an error result that is not a failure. */\n private interrupted = false;\n private lastTurn?: RunState['lastTurn'];\n private context?: RunState['context'];\n private activity?: RunState['activity'];\n private tasks: BackgroundTask[] = [];\n\n get hasBackgroundTasks(): boolean {\n return this.tasks.length > 0;\n }\n /** Messages waiting for the turn to end, published when it does. */\n private queued: ChatMessage[] = [];\n private started!: { resolve: () => void; reject: (error: Error) => void };\n /** Settles once Claude has started (its first message), or fails when it ends before that. */\n readonly ready = new Promise<void>((resolve, reject) => {\n this.started = { resolve, reject };\n });\n // Most callers never wait for it: a failure here must not count as unhandled.\n private readonly readyHandled = this.ready.catch(() => undefined);\n private suggestion?: string;\n\n constructor(\n readonly sessionId: string,\n readonly cwd: string,\n private settings: RunSettings,\n private readonly resume: boolean,\n /** Resume only up to this entry, dropping the turn after it (an edited last prompt). */\n private readonly resumeAt?: { entry: string; dropsTurn: string },\n ) {}\n\n start(): void {\n const settings = this.settings;\n const options: Options = {\n cwd: this.cwd,\n ...(this.resume ? { resume: this.sessionId } : { sessionId: this.sessionId }),\n // Claude Code checks that everything past the entry belongs to the dropped turn, so a\n // message another app added meanwhile is never thrown away unseen.\n ...(this.resume && this.resumeAt ? { resumeSessionAt: this.resumeAt.entry, resumeDropsTurn: this.resumeAt.dropsTurn } : {}),\n ...(sdkModel(settings.model) ? { model: sdkModel(settings.model) } : {}),\n ...(settings.effort ? { effort: settings.effort as EffortLevel } : {}),\n permissionMode: settings.permissionMode ?? 'default',\n allowDangerouslySkipPermissions: true,\n ...(settings.fast || settings.ultracode ? {\n settings: {\n ...(settings.fast ? { fastMode: true } : {}),\n // Ultracode needs workflows; both are session settings, like the CLI's --settings.\n ...(settings.ultracode ? { ultracode: true, enableWorkflows: true } : {}),\n },\n } : {}),\n includePartialMessages: true,\n // Lets \"Rewind code\" put files back the way they were before a prompt.\n enableFileCheckpointing: true,\n promptSuggestions: true,\n settingSources: ['user', 'project', 'local'],\n systemPrompt: { type: 'preset', preset: 'claude_code' },\n tools: { type: 'preset', preset: 'claude_code' },\n canUseTool: this.canUseTool,\n ...(config.claudeExecutable ? { pathToClaudeCodeExecutable: config.claudeExecutable } : {}),\n stderr: (data) => {\n if (process.env.PELICAN_DEBUG) process.stderr.write(`[claude ${this.sessionId.slice(0, 8)}] ${data}`);\n },\n };\n this.q = query({ prompt: this.input, options });\n void this.pump();\n void sessionInfo(this.sessionId).then((info) => {\n if (info) this.title = info.customTitle || info.summary || '';\n });\n }\n\n state(): RunState {\n return {\n sessionId: this.sessionId,\n cwd: this.cwd,\n busy: this.busy,\n settings: this.settings,\n permissions: [...this.permissions.values()].map((p) => p.request),\n partial: this.partial.slice(this.completedBlocks).filter(Boolean),\n ...(this.error ? { error: this.error } : {}),\n ...(this.lastTurn ? { lastTurn: this.lastTurn } : {}),\n ...(this.context ? { context: this.context } : {}),\n ...(this.activity ? { activity: this.activity } : {}),\n ...(this.tasks.length > 0 ? { tasks: this.tasks } : {}),\n ...(this.queued.length > 0 ? { queued: this.queued.map((m) => m.uuid) } : {}),\n ...(this.suggestion && !this.busy ? { suggestion: this.suggestion } : {}),\n };\n }\n\n status(): SessionStatus {\n if (this.permissions.size > 0) return 'waiting';\n return this.busy ? 'running' : 'idle';\n }\n\n /** Sends the run state now, or within 80 ms when called for streamed text. */\n private emit(soon = false): void {\n if (soon) {\n if (this.emitTimer) return;\n this.emitTimer = setTimeout(() => {\n this.emitTimer = undefined;\n this.emitNow();\n }, 80);\n return;\n }\n clearTimeout(this.emitTimer);\n this.emitTimer = undefined;\n this.emitNow();\n }\n\n private lastStatus?: SessionStatus;\n\n private emitNow(): void {\n const state = this.state();\n feedFor(this.sessionId).broadcast({ type: 'run', state });\n // Session lists only care about the status; streamed text stays with the session's viewers.\n const status = this.status();\n if (status !== this.lastStatus) {\n this.lastStatus = status;\n broadcastAll({ type: 'status', sessionId: this.sessionId, status });\n }\n }\n\n private setBusy(busy: boolean): void {\n if (this.busy === busy) return;\n this.busy = busy;\n this.lastActive = Date.now();\n if (!busy) {\n this.partial = [];\n this.completedBlocks = 0;\n }\n this.emit();\n sessionsChanged();\n }\n\n async send(input: SendInput): Promise<void> {\n await this.applySettings(input.settings);\n const uuid = input.uuid ?? randomUUID();\n const content: Array<Record<string, unknown>> = [];\n const fileLines: string[] = [];\n for (const attachment of input.attachments) {\n if (attachment.mime.startsWith('image/') && attachment.size <= 5 * 1024 * 1024) {\n const data = await readFile(attachment.path).catch(() => undefined);\n if (data) {\n content.push({ type: 'image', source: { type: 'base64', media_type: attachment.mime, data: data.toString('base64') } });\n continue;\n }\n }\n fileLines.push(`- ${attachment.path} (${attachment.name})`);\n }\n let text = input.text;\n if (fileLines.length > 0) text = `${text}${text ? '\\n\\n' : ''}Attached files:\\n${fileLines.join('\\n')}`;\n if (text) content.push({ type: 'text', text });\n // A running turn takes a new message at Claude's next step, unless it should wait for the turn to end.\n const queue = this.busy && input.mode === 'queue';\n const message = {\n type: 'user',\n uuid,\n session_id: this.sessionId,\n parent_tool_use_id: null,\n origin: { kind: 'human' },\n ...(this.busy ? { priority: queue ? 'later' : 'next' } : {}),\n message: { role: 'user', content },\n } as unknown as SDKUserMessage;\n this.error = undefined;\n this.suggestion = undefined;\n const chat = toChatMessage({ type: 'user', uuid, message: { role: 'user', content } });\n if (queue) {\n if (chat) this.queued.push({ ...chat, timestamp: Date.now() });\n this.emit();\n } else {\n this.setBusy(true);\n if (chat) await feedFor(this.sessionId).publish({ ...chat, timestamp: Date.now() });\n }\n this.input.push(message);\n }\n\n /**\n * Adds a `!` command and its output to the conversation without starting a\n * turn, the way Claude Code's bash mode does: Claude reads it with the next message.\n */\n async appendShell(result: ShellResult): Promise<void> {\n const uuid = randomUUID();\n const text = bashModeText(result);\n const message = {\n type: 'user',\n uuid,\n session_id: this.sessionId,\n parent_tool_use_id: null,\n origin: { kind: 'human' },\n shouldQuery: false,\n message: { role: 'user', content: text },\n } as unknown as SDKUserMessage;\n const chat = toChatMessage({ type: 'user', uuid, message: { role: 'user', content: text } });\n if (chat) await feedFor(this.sessionId).publish({ ...chat, timestamp: Date.now() });\n this.input.push(message);\n }\n\n /** Puts files back the way they were before the prompt `uuid`; `dryRun` only says what would change. */\n async rewind(uuid: string, dryRun: boolean): Promise<RewindResult> {\n if (!this.q) throw new Error('Claude is not running in this session');\n const result = await this.q.rewindFiles(uuid, { dryRun });\n return {\n canRewind: result.canRewind,\n ...(result.error ? { error: result.error } : {}),\n ...(result.filesChanged ? { files: result.filesChanged } : {}),\n ...(typeof result.insertions === 'number' ? { insertions: result.insertions } : {}),\n ...(typeof result.deletions === 'number' ? { deletions: result.deletions } : {}),\n };\n }\n\n async mcpServers(): Promise<McpServer[]> {\n if (!this.q) return [];\n return toMcpServers(await this.q.mcpServerStatus());\n }\n\n async setMcpServer(name: string, change: { enabled?: boolean; reconnect?: boolean }): Promise<void> {\n if (!this.q) throw new Error('Claude is not running in this session');\n if (change.reconnect) await this.q.reconnectMcpServer(name);\n if (change.enabled !== undefined) await this.q.toggleMcpServer(name, change.enabled);\n }\n\n async stopTask(taskId: string): Promise<void> {\n await this.q?.stopTask(taskId);\n }\n\n async applySettings(next: RunSettings): Promise<void> {\n const q = this.q;\n if (!q) return;\n const prev = this.settings;\n try {\n if ((next.model ?? 'default') !== (prev.model ?? 'default')) await q.setModel(sdkModel(next.model));\n // A new effort level sent without the ultracode key turns ultracode off, so both go together.\n if ((next.effort ?? '') !== (prev.effort ?? '') || Boolean(next.ultracode) !== Boolean(prev.ultracode)) {\n await q.applyFlagSettings({\n effortLevel: (next.effort as EffortLevel) ?? null,\n ultracode: Boolean(next.ultracode),\n ...(next.ultracode ? { enableWorkflows: true } : {}),\n });\n }\n if (Boolean(next.fast) !== Boolean(prev.fast)) await q.applyFlagSettings({ fastMode: Boolean(next.fast) });\n if (next.permissionMode && next.permissionMode !== (prev.permissionMode ?? 'default')) await q.setPermissionMode(next.permissionMode);\n } catch (error) {\n console.warn('[pelican] could not apply settings', (error as Error).message);\n }\n this.settings = { ...prev, ...next };\n }\n\n async setPermissionMode(mode: RunSettings['permissionMode']): Promise<void> {\n await this.applySettings({ ...this.settings, permissionMode: mode });\n this.emit();\n }\n\n /** Settings changed in the composer, applied to Claude now: from its next step, also mid-turn. */\n async updateSettings(next: RunSettings): Promise<void> {\n await this.applySettings({ ...this.settings, ...next });\n this.emit();\n }\n\n private canUseTool: CanUseTool = (toolName, input, { signal, suggestions, blockedPath, decisionReason }) => {\n const id = randomUUID();\n const request: PermissionRequest = {\n id,\n sessionId: this.sessionId,\n toolName,\n input,\n canRemember: Boolean(suggestions && suggestions.length > 0),\n ...(decisionReason ? { reason: decisionReason } : {}),\n ...(blockedPath ? { blockedPath } : {}),\n createdAt: Date.now(),\n };\n return new Promise<PermissionResult>((resolve) => {\n this.permissions.set(id, { request, input, ...(suggestions ? { suggestions } : {}), resolve });\n signal.addEventListener('abort', () => {\n if (this.permissions.delete(id)) {\n resolve({ behavior: 'deny', message: 'Cancelled.' });\n this.emit();\n sessionsChanged();\n }\n });\n this.emit();\n sessionsChanged();\n const what = toolName === 'AskUserQuestion' ? 'has a question for you'\n : toolName === 'ExitPlanMode' ? 'has a plan for you to review'\n : `wants to use ${toolName}`;\n void notify({ title: this.title || 'Claude Code', body: `Claude ${what}`, sessionId: this.sessionId, tag: `${this.sessionId}:ask` });\n });\n };\n\n decide(id: string, decision: PermissionDecision): boolean {\n const pending = this.permissions.get(id);\n if (!pending) return false;\n this.permissions.delete(id);\n if (decision.behavior === 'allow') {\n pending.resolve({\n behavior: 'allow',\n updatedInput: decision.updatedInput ?? pending.input,\n ...(decision.remember && pending.suggestions ? { updatedPermissions: pending.suggestions } : {}),\n });\n if (decision.permissionMode) void this.setPermissionMode(decision.permissionMode);\n } else {\n pending.resolve({ behavior: 'deny', message: decision.message?.trim() || 'The user declined this.' });\n }\n this.emit();\n sessionsChanged();\n return true;\n }\n\n async interrupt(): Promise<void> {\n this.interrupted = true;\n for (const [id] of this.permissions) this.decide(id, { behavior: 'deny', message: 'Interrupted by the user.' });\n try {\n await this.q?.interrupt();\n } catch (error) {\n console.warn('[pelican] interrupt failed', (error as Error).message);\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n for (const [id] of this.permissions) this.decide(id, { behavior: 'deny', message: 'Session closed.' });\n this.input.close();\n try {\n this.q?.close();\n } catch {\n // Already exited.\n }\n }\n\n /** How full the context window is now; shown in the session header. */\n private async readContext(): Promise<void> {\n try {\n const usage = await this.q?.getContextUsage();\n if (!usage) return;\n this.context = { percent: Math.round(usage.percentage), tokens: usage.totalTokens, max: usage.maxTokens };\n this.emit();\n } catch {\n // Older Claude Code; the header just shows no meter.\n }\n }\n\n private onStream(event: Record<string, unknown>): void {\n const index = typeof event.index === 'number' ? event.index : -1;\n switch (event.type) {\n case 'message_start':\n this.partial = [];\n this.completedBlocks = 0;\n break;\n case 'content_block_start': {\n const block = (event.content_block ?? {}) as Record<string, unknown>;\n if (block.type === 'text') this.partial[index] = { type: 'text', text: '' };\n else if (block.type === 'thinking') this.partial[index] = { type: 'thinking', thinking: '' };\n else if (block.type === 'tool_use' || block.type === 'server_tool_use') {\n this.partial[index] = { type: 'tool_use', name: String(block.name ?? ''), inputJson: '' };\n }\n break;\n }\n case 'content_block_delta': {\n const delta = (event.delta ?? {}) as Record<string, unknown>;\n const block = this.partial[index];\n if (!block) break;\n if (block.type === 'text' && delta.type === 'text_delta') block.text += String(delta.text ?? '');\n else if (block.type === 'thinking' && delta.type === 'thinking_delta') block.thinking += String(delta.thinking ?? '');\n else if (block.type === 'tool_use' && delta.type === 'input_json_delta') block.inputJson += String(delta.partial_json ?? '');\n break;\n }\n default:\n return;\n }\n this.setBusy(true);\n this.emit(true);\n }\n\n private async pump(): Promise<void> {\n const q = this.q!;\n try {\n for await (const message of q as AsyncIterable<SDKMessage>) {\n this.lastActive = Date.now();\n this.started.resolve();\n const raw = message as unknown as Record<string, unknown>;\n switch (message.type) {\n case 'stream_event':\n if (!message.parent_tool_use_id) this.onStream(message.event as unknown as Record<string, unknown>);\n break;\n case 'assistant':\n case 'user': {\n if (raw.parent_tool_use_id) break;\n if (message.type === 'assistant') this.completedBlocks += 1;\n if (this.activity?.kind === 'retrying') this.activity = undefined;\n const chat = toChatMessage(raw);\n if (chat) await feedFor(this.sessionId).publish({ ...chat, timestamp: chat.timestamp ?? Date.now() } satisfies ChatMessage);\n this.emit(true);\n break;\n }\n case 'system':\n if (message.subtype === 'session_state_changed') {\n const state = (raw as { state?: string }).state;\n if (state === 'running' || state === 'requires_action') this.setBusy(true);\n else if (state === 'idle') this.setBusy(false);\n } else if (message.subtype === 'init') {\n void refreshTranscriptIndex().then(sessionsChanged);\n } else if (message.subtype === 'status') {\n this.activity = message.status === 'compacting' ? { kind: 'compacting' } : undefined;\n this.emit();\n } else if (message.subtype === 'api_retry') {\n this.activity = { kind: 'retrying', attempt: message.attempt, max: message.max_retries, delayMs: message.retry_delay_ms };\n this.emit();\n } else if (message.subtype === 'background_tasks_changed') {\n this.tasks = message.tasks\n .filter((t) => !t.ambient)\n .map((t) => ({ id: t.task_id, description: t.description, type: t.subagent_type ?? t.task_type }));\n this.emit();\n }\n break;\n case 'rate_limit_event':\n recordLimit(message.rate_limit_info);\n break;\n case 'prompt_suggestion':\n this.suggestion = message.suggestion;\n this.emit();\n break;\n case 'result': {\n const stopped = this.interrupted;\n this.interrupted = false;\n const failed = !stopped && (message.subtype !== 'success' || message.is_error);\n this.error = failed\n ? readableError('errors' in message && message.errors.length > 0 ? message.errors.join('\\n') : ('result' in message ? String(message.result) : message.subtype))\n : undefined;\n this.lastTurn = {\n durationMs: message.duration_ms,\n ...(typeof message.total_cost_usd === 'number' ? { costUsd: message.total_cost_usd } : {}),\n ...(typeof message.usage?.output_tokens === 'number' ? { outputTokens: message.usage.output_tokens } : {}),\n };\n this.activity = undefined;\n // Messages that waited for this turn go in now: the next turn answers them.\n for (const waiting of this.queued.splice(0)) await feedFor(this.sessionId).publish(waiting);\n this.setBusy(false);\n this.emit();\n void this.readContext();\n if (!stopped) {\n void notify({\n title: this.title || 'Claude Code',\n body: failed ? `Stopped: ${this.error ?? 'error'}`.slice(0, 180) : 'Finished',\n sessionId: this.sessionId,\n tag: `${this.sessionId}:done`,\n });\n }\n break;\n }\n default:\n break;\n }\n }\n } catch (error) {\n this.error = readableError((error as Error).message);\n console.warn(`[pelican] session ${this.sessionId.slice(0, 8)} ended with an error:`, this.error);\n } finally {\n this.closed = true;\n this.started.reject(new Error(this.error ?? 'Claude Code stopped before it started'));\n for (const [, pending] of this.permissions) pending.resolve({ behavior: 'deny', message: 'Session closed.' });\n this.permissions.clear();\n this.setBusy(false);\n this.emit();\n // A newer run may have taken this session's place already (see sendMessage).\n if (runs.get(this.sessionId) === this) runs.delete(this.sessionId);\n sessionsChanged();\n }\n }\n}\n\nconst runs = new Map<string, SessionRun>();\n\nexport function getRun(sessionId: string): SessionRun | undefined {\n const run = runs.get(sessionId);\n return run && !run.closed ? run : undefined;\n}\n\n/** Status of a session this app runs; undefined when it has no Claude process here. */\nexport function runStatus(sessionId: string): SessionStatus | undefined {\n if (config.demo) return sessionId === DEMO_WAITING_SESSION ? 'waiting' : undefined;\n return getRun(sessionId)?.status();\n}\n\nexport function runState(sessionId: string): RunState | undefined {\n if (config.demo) return demoRunState(sessionId);\n return getRun(sessionId)?.state();\n}\n\nasync function isDirectory(dir: string): Promise<boolean> {\n return stat(dir).then((s) => s.isDirectory(), () => false);\n}\n\n/**\n * Sends a message, starting (or resuming) the session's Claude process when\n * none is running. Without a session id a new session starts in `cwd`.\n */\n/**\n * Whether another app (Claude Desktop, the terminal) has worked on the session since this\n * run's Claude last did. Its process then holds a conversation this one has not seen, and a\n * message sent to it would continue an old version, beside the other: it is started afresh.\n */\nasync function staleRun(run: SessionRun): Promise<boolean> {\n if (run.busy || run.hasBackgroundTasks) return false;\n const other = (await liveProcesses()).get(run.sessionId);\n if (other?.updatedAt && other.updatedAt > run.lastActive) return true;\n // An app that has closed since leaves no process to ask: a turn in the transcript newer than\n // anything this run did is the sign.\n const file = await transcriptPath(run.sessionId);\n const last = file ? (await lastTurnTime(file))?.lastTurn : undefined;\n return Boolean(last && !last.copied && last.time > run.lastActive + 2000);\n}\n\n/** The session's run, unless it holds an older version of the conversation: then it is closed. */\nasync function currentRun(sessionId: string): Promise<SessionRun | undefined> {\n const run = getRun(sessionId);\n if (run && (await staleRun(run))) {\n run.close();\n if (runs.get(sessionId) === run) runs.delete(sessionId);\n return getRun(sessionId);\n }\n return run;\n}\n\nexport async function sendMessage(target: { sessionId?: string; cwd?: string }, input: SendInput): Promise<string> {\n if (config.demo) throw new Error('This is the demo: nothing runs. Start Pelican without --demo to talk to Claude.');\n if (target.sessionId) {\n const existing = await currentRun(target.sessionId);\n if (existing) {\n await existing.send(input);\n return existing.sessionId;\n }\n const info = await sessionInfo(target.sessionId);\n const cwd = info?.cwd ?? target.cwd;\n if (!cwd || !(await isDirectory(cwd))) throw new Error(`The session's folder is missing: ${cwd ?? '(unknown)'}`);\n // Another request may have started one while this one looked the folder up.\n const started = getRun(target.sessionId);\n if (started) {\n await started.send(input);\n return started.sessionId;\n }\n const run = new SessionRun(target.sessionId, cwd, input.settings, true);\n runs.set(run.sessionId, run);\n run.start();\n await run.send(input);\n return run.sessionId;\n }\n const cwd = target.cwd;\n if (!cwd || !(await isDirectory(cwd))) throw new Error(`Not a folder: ${cwd ?? '(none)'}`);\n const run = new SessionRun(randomUUID(), cwd, input.settings, false);\n runs.set(run.sessionId, run);\n run.start();\n await run.send(input);\n return run.sessionId;\n}\n\n/**\n * Replaces the session's last turn: Claude resumes it up to `entry` (the row just before the\n * prompt being replaced) and gets `input` instead, in the same session, as rewinding does in\n * Claude Code. The old turn stays in the transcript, off the conversation's line.\n */\nexport async function replaceLastTurn(sessionId: string, cwd: string, entry: string, prompt: string, input: SendInput): Promise<string> {\n if (config.demo) throw new Error('This is the demo: nothing runs.');\n const existing = getRun(sessionId);\n if (existing?.busy) throw new Error('Stop Claude before editing a prompt.');\n if (existing) {\n existing.close();\n runs.delete(sessionId);\n }\n const run = new SessionRun(sessionId, cwd, input.settings, true, { entry, dropsTurn: prompt });\n runs.set(sessionId, run);\n run.start();\n await run.send(input);\n return sessionId;\n}\n\n/**\n * The session's Claude process, started (resumed, with no turn) when there is\n * none: for what only a running process can do, like rewinding files.\n */\nexport async function ensureRun(sessionId: string, settings: RunSettings = {}): Promise<SessionRun> {\n if (config.demo) throw new Error('This is the demo: nothing runs.');\n const existing = await currentRun(sessionId);\n if (existing) return existing;\n const info = await sessionInfo(sessionId);\n const cwd = info?.cwd;\n if (!cwd || !(await isDirectory(cwd))) throw new Error(`The session's folder is missing: ${cwd ?? '(unknown)'}`);\n const started = getRun(sessionId);\n if (started) return started;\n const run = new SessionRun(sessionId, cwd, settings, true);\n runs.set(sessionId, run);\n run.start();\n return run;\n}\n\n/** Closes Claude processes that have been idle for a while; the next message resumes them. */\nsetInterval(() => {\n for (const run of runs.values()) {\n if (!run.busy && run.status() === 'idle' && Date.now() - run.lastActive > config.idleCloseMs) run.close();\n }\n}, 60_000).unref();\n\nexport function closeAll(): void {\n for (const run of runs.values()) run.close();\n}\n", "import { open, readdir, realpath, stat } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport type { FileContent, GitFile, GitStatus, TreeEntry } from '../shared/protocol.js';\nimport { git } from './git.js';\n\nconst MAX_FILE = 512 * 1024;\nconst MAX_DIFF = 400_000;\n\n\nconst OUTSIDE = 'That path is outside the session\u2019s folder';\nconst within = (root: string, full: string) => full === root || full.startsWith(`${root}${path.sep}`);\n\n/**\n * A path inside the session's folder, or an error: the panels never reach outside it. No `..`\n * at all: resolving it by the text would cancel a linked folder before the link is checked\n * (`link/../x`), and the panels never ask for one.\n */\nexport function inside(cwd: string, relative: string): string {\n if ((relative ?? '').split(/[\\\\/]/).includes('..')) throw new Error(OUTSIDE);\n const root = path.resolve(cwd);\n const full = path.resolve(root, relative || '.');\n if (!within(root, full)) throw new Error(OUTSIDE);\n return full;\n}\n\n/** The same, for a path that exists, with links followed: a link may not lead out of the folder either. */\nasync function insideReal(cwd: string, relative: string): Promise<string> {\n const full = inside(cwd, relative);\n const [root, real] = await Promise.all([realpath(cwd), realpath(full)]);\n if (!within(root, real)) throw new Error(OUTSIDE);\n return real;\n}\n\nconst KIND: Record<string, GitFile['status']> = { M: 'modified', T: 'modified', A: 'added', D: 'deleted', R: 'renamed', C: 'added', U: 'conflicted' };\n\n/**\n * The repository the folder is in, when its work tree is the folder or a folder above it. A\n * repository whose config points its work tree somewhere else (core.worktree) would show files\n * from outside: Pelican treats that as no repository.\n */\nasync function repositoryOf(cwd: string): Promise<string | undefined> {\n try {\n const top = (await git(cwd, ['rev-parse', '--show-toplevel'], 5000)).trim();\n if (!top) return undefined;\n const [root, folder] = await Promise.all([realpath(top), realpath(cwd)]);\n return within(root, folder) ? root : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** What changed in the session's folder since the last commit, with line counts. */\nexport async function gitStatus(cwd: string): Promise<GitStatus> {\n if (!(await repositoryOf(cwd))) return { repo: false };\n let raw: string;\n let prefix = '';\n try {\n // A session in a folder inside a repository sees that folder's changes, by its own paths.\n prefix = (await git(cwd, ['rev-parse', '--show-prefix'], 5000)).trim();\n raw = await git(cwd, ['status', '--porcelain=v2', '--branch', '-z', '--untracked-files=all', '--', '.']);\n } catch {\n return { repo: false };\n }\n const local = (file: string | undefined) => (file && prefix && file.startsWith(prefix) ? file.slice(prefix.length) : file);\n const out: { repo: true; branch?: string; ahead?: number; behind?: number; files: GitFile[] } = { repo: true, files: [] };\n const entries = raw.split('\\0');\n for (let i = 0; i < entries.length; i += 1) {\n const entry = entries[i]!;\n if (entry.startsWith('# branch.head ')) {\n const head = entry.slice(14);\n if (head !== '(detached)') out.branch = head;\n } else if (entry.startsWith('# branch.ab ')) {\n const [ahead, behind] = entry.slice(12).split(' ');\n out.ahead = Number(ahead?.slice(1)) || 0;\n out.behind = Number(behind?.slice(1)) || 0;\n } else if (entry.startsWith('1 ') || entry.startsWith('2 ') || entry.startsWith('u ')) {\n const parts = entry.split(' ');\n const xy = parts[1] ?? '..';\n const code = xy[0] !== '.' ? xy[0]! : xy[1]!;\n const file = entry.startsWith('1 ') ? parts.slice(8).join(' ') : entry.startsWith('2 ') ? parts.slice(9).join(' ') : parts.slice(10).join(' ');\n const status = entry.startsWith('u ') ? 'conflicted' : KIND[code] ?? 'modified';\n const item: GitFile = { path: local(file)!, status };\n if (entry.startsWith('2 ')) {\n const from = local(entries[i + 1]);\n if (from) item.from = from;\n i += 1;\n }\n out.files.push(item);\n } else if (entry.startsWith('? ')) {\n out.files.push({ path: local(entry.slice(2))!, status: 'untracked' });\n }\n }\n // Line counts against the last commit (or the empty tree before the first one).\n try {\n const base = await git(cwd, ['rev-parse', '--verify', '-q', 'HEAD']).then(() => 'HEAD', () => '4b825dc642cb6eb9a060e54bf8d69288fbee4904');\n const numstat = await git(cwd, ['diff', '--numstat', '--relative', '-z', base]);\n const counts = new Map<string, { added?: number; removed?: number }>();\n const fields = numstat.split('\\0');\n for (let i = 0; i < fields.length; i += 1) {\n const field = fields[i]!;\n if (!field) continue;\n const [added, removed, name] = field.split('\\t');\n // A rename lists its old and new paths in the next two fields.\n const file = name === '' ? fields[(i += 2)] : name;\n if (!file) continue;\n counts.set(file, { ...(added !== '-' ? { added: Number(added) } : {}), ...(removed !== '-' ? { removed: Number(removed) } : {}) });\n }\n for (const file of out.files) Object.assign(file, counts.get(file.path) ?? {});\n } catch {\n // Counts are a nicety.\n }\n // A new file is all added lines; git does not count those for files it does not track yet.\n await Promise.all(out.files.filter((f) => f.status === 'untracked').slice(0, 200).map(async (file) => {\n try {\n const content = await readProjectFile(cwd, file.path);\n if (content.text !== undefined) file.added = content.text.split('\\n').length - (content.text.endsWith('\\n') ? 1 : 0);\n } catch {\n // Unreadable; no count.\n }\n }));\n return out;\n}\n\n/** The diff of one file against the last commit; an untracked file shows as all added. */\nexport async function gitDiff(cwd: string, file: string): Promise<string> {\n if (!(await repositoryOf(cwd))) return '';\n // The file itself may be gone (a deleted file has a diff), but the folder it is in may not lead\n // out of the session's folder through a link.\n const full = inside(cwd, file);\n const [root, parent] = await Promise.all([realpath(cwd), realpath(path.dirname(full)).catch(() => undefined)]);\n if (parent && !within(root, parent)) throw new Error(OUTSIDE);\n let diff = '';\n try {\n diff = await git(cwd, ['diff', 'HEAD', '--', file]);\n } catch {\n diff = '';\n }\n if (!diff) {\n // Untracked (or no commits yet): git diff --no-index exits with 1 when the files differ.\n // `./`: a file named `-` would otherwise be read from stdin.\n diff = await git(cwd, ['diff', '--no-index', '--', '/dev/null', `./${file}`]).catch((error: { stdout?: string }) => error.stdout ?? '');\n }\n return diff.length > MAX_DIFF ? `${diff.slice(0, MAX_DIFF)}\\n\u2026 (diff cut short)` : diff;\n}\n\nconst HIDDEN = new Set(['.git', 'node_modules', '.DS_Store']);\n\n/** One folder of the session's tree: folders first, then files. */\nexport async function listTree(cwd: string, relative: string): Promise<TreeEntry[]> {\n const dir = await insideReal(cwd, relative);\n const names = await readdir(dir, { withFileTypes: true });\n const entries: TreeEntry[] = [];\n for (const entry of names) {\n if (HIDDEN.has(entry.name)) continue;\n const rel = path.join(relative, entry.name);\n if (entry.isDirectory()) entries.push({ name: entry.name, path: rel, dir: true });\n else if (entry.isFile() || entry.isSymbolicLink()) {\n const size = await stat(path.join(dir, entry.name)).then((s) => (s.isDirectory() ? -1 : s.size), () => undefined);\n if (size === -1) entries.push({ name: entry.name, path: rel, dir: true });\n else entries.push({ name: entry.name, path: rel, dir: false, ...(size !== undefined ? { size } : {}) });\n }\n }\n return entries.sort((a, b) => Number(b.dir) - Number(a.dir) || a.name.localeCompare(b.name, undefined, { numeric: true }));\n}\n\nconst IMAGE: Record<string, string> = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp', svg: 'image/svg+xml' };\n\n/** A file to read on the phone: text up to half a megabyte, images as data, other binaries named only. */\nexport async function readProjectFile(cwd: string, relative: string): Promise<FileContent> {\n const full = await insideReal(cwd, relative);\n const info = await stat(full);\n const ext = path.extname(full).slice(1).toLowerCase();\n const handle = await open(full, 'r');\n try {\n if (IMAGE[ext] && info.size <= 4 * 1024 * 1024) {\n const data = Buffer.alloc(info.size);\n await handle.read(data, 0, info.size, 0);\n return { path: relative, size: info.size, image: `data:${IMAGE[ext]};base64,${data.toString('base64')}` };\n }\n const length = Math.min(info.size, MAX_FILE);\n const data = Buffer.alloc(length);\n await handle.read(data, 0, length, 0);\n if (data.subarray(0, 8000).includes(0)) return { path: relative, size: info.size, binary: true };\n return { path: relative, size: info.size, text: data.toString('utf8'), ...(info.size > MAX_FILE ? { truncated: true } : {}) };\n } finally {\n await handle.close();\n }\n}\n", "import { readFile, realpath, stat } from 'node:fs/promises';\nimport { isIP } from 'node:net';\nimport path from 'node:path';\n\nimport { getConnInfo } from '@hono/node-server/conninfo';\nimport { type Context, Hono } from 'hono';\nimport { bodyLimit } from 'hono/body-limit';\n\nimport pkg from '../../package.json' with { type: 'json' };\nimport type { Attachment, ChatMessage, PermissionDecision, PermissionMode, RunSettings, SendMode, ServerInfo } from '../shared/protocol.js';\nimport { currentTasks } from '../shared/tasks.js';\nimport { exportMarkdown, fork, messageBefore, remove } from './actions.js';\nimport { setArchived } from './archive.js';\nimport { authMode, beginAttempt, checkPassword, checkToken, cookieName, issueCode, newSessionCookie, redeemCode, signedIn } from './auth.js';\nimport { config } from './config.js';\nimport { sessionsChanged } from './feed.js';\nimport { findUpload, listDirs, MAX_UPLOAD_BYTES, readUpload, removeUpload, saveUpload } from './files.js';\nimport { createScratchFolder, isDirectory } from './folders.js';\nimport { listCompletions, listModels, planUsage, probeMcpServers } from './models.js';\nimport { searchFiles } from './project-files.js';\nimport { addSubscription, pushKey, removeSubscription } from './push.js';\nimport { ensureRun, getRun, replaceLastTurn, runState, runStatus, sendMessage } from './runner.js';\nimport { runCommand } from './shell.js';\nimport { usageLimits } from './usage.js';\nimport { gitDiff, gitStatus, listTree, readProjectFile } from './workspace.js';\nimport { hostAllowed, isLoopback, originAllowed } from './security.js';\nimport { listSummaries, loadMessages, loadPage, loadSubagent, recordedSettings, renameSessionTitle, sentFiles, sessionInfo, sessionSummary } from './sessions.js';\nimport { rawEntry, rawImages, transcriptPath } from './transcripts.js';\nimport { claudeRanIn } from './trust.js';\n\nexport const app = new Hono();\n\n/**\n * Who is asking, for limiting sign-in attempts: the address of the connection.\n * Behind a proxy on this machine that says so (PELICAN_TRUSTED_PROXY=1), the\n * client the proxy added last to X-Forwarded-For, when that is an IP address.\n * Anyone can write that header, so it is never believed otherwise.\n */\nfunction clientAddress(c: Context): string {\n let remote = 'unknown';\n try {\n remote = getConnInfo(c).remote.address ?? 'unknown';\n } catch {\n // Not served by Node (tests): one bucket for all.\n }\n const address = remote.replace(/^::ffff:/, '');\n if (config.trustedProxy && isLoopback(address)) {\n const forwarded = c.req.header('x-forwarded-for')?.split(',').at(-1)?.trim();\n if (forwarded && isIP(forwarded)) return forwarded;\n }\n return address;\n}\n\nconst secure = (c: { req: { header: (name: string) => string | undefined; url: string } }) =>\n c.req.header('x-forwarded-proto') === 'https' || c.req.url.startsWith('https:');\n\nconst cookieHeader = (c: Context, name: string, value: string, maxAge: number) =>\n `${name}=${value}; Path=/; Max-Age=${maxAge}; HttpOnly; SameSite=Strict${secure(c) ? '; Secure' : ''}`;\n\n/** Answers only to host names that point at this server (DNS rebinding). */\napp.use('*', async (c, next) => {\n if (!hostAllowed(c.req.header('host'))) {\n return c.text('This host name is not allowed. Add it to PELICAN_ALLOWED_HOSTS.', 403);\n }\n await next();\n});\n\n/** What the page loads without fetch, so it cannot mark: an uploaded image, a download. */\nconst unmarked = (c: Context) => c.req.method === 'GET'\n && (/^\\/api\\/uploads\\/[^/]+$/.test(c.req.path) || /^\\/api\\/sessions\\/[^/]+\\/export$/.test(c.req.path));\n\n/** The Origin header as an origin (`null`, garbage and missing are none). */\nfunction originOf(header: string | undefined): string | undefined {\n if (!header || header === 'null') return undefined;\n try {\n return new URL(header).origin;\n } catch {\n return undefined;\n }\n}\n\napp.use('/api/*', async (c, next) => {\n const origin = c.req.header('origin');\n const parsed = originOf(origin);\n const allowedOrigin = Boolean(parsed && config.allowedOrigins.includes(parsed));\n // Another site's page can make a browser send requests here, reads included, and some reads\n // run programs (git, Claude Code). Browsers say where a request comes from: only Pelican's own\n // page, or a page the config allows, gets in.\n const site = c.req.header('sec-fetch-site');\n if (site && site !== 'same-origin' && site !== 'none' && !allowedOrigin) {\n return c.json({ error: 'Request from another site' }, 403);\n }\n // Pelican's page also marks every request. Another site cannot add a header without asking\n // first (CORS), and Pelican never says yes, so this holds in browsers that send no Sec-Fetch-Site.\n if (!unmarked(c) && c.req.header('x-pelican') !== '1') {\n return c.json({ error: 'Requests must come from Pelican (send the header X-Pelican: 1)' }, 403);\n }\n if (c.req.method !== 'GET' && c.req.method !== 'HEAD'\n && !originAllowed(origin, c.req.header('host'), c.req.header('x-forwarded-host'))) {\n return c.json({ error: 'Request from another site' }, 403);\n }\n if (!c.req.path.startsWith('/api/auth') && !signedIn(c.req.header('cookie'), c.req.header('authorization'))) {\n return c.json({ error: 'Sign in to continue', signIn: true }, 401);\n }\n if (config.demo && c.req.method !== 'GET' && c.req.method !== 'HEAD') {\n return c.json({ error: 'This is the demo: nothing runs and nothing changes. Start Pelican without --demo to talk to Claude.' }, 403);\n }\n await next();\n});\n\n// Bodies are small, except an upload; nothing is buffered past these sizes. Only signed-in\n// requests get this far, apart from signing in itself, whose limit is 4 KB.\nconst tooLarge = (c: Context) => c.json({ error: 'Request too large' }, 413);\nconst limits = {\n auth: bodyLimit({ maxSize: 4 * 1024, onError: tooLarge }),\n upload: bodyLimit({ maxSize: MAX_UPLOAD_BYTES + 64 * 1024, onError: tooLarge }),\n json: bodyLimit({ maxSize: 1024 * 1024, onError: tooLarge }),\n};\napp.use('/api/*', (c, next) => (c.req.path.startsWith('/api/auth') ? limits.auth : c.req.path === '/api/uploads' ? limits.upload : limits.json)(c, next));\n\napp.onError((error, c) => {\n console.warn('[pelican]', c.req.method, c.req.path, error.message);\n return c.json({ error: error.message }, 500);\n});\n\napp.get('/api/auth', (c) => c.json({\n required: authMode() !== 'none',\n mode: authMode(),\n signedIn: signedIn(c.req.header('cookie'), c.req.header('authorization')),\n}));\n\napp.post('/api/auth/login', async (c) => {\n const succeeded = beginAttempt(clientAddress(c));\n if (!succeeded) return c.json({ error: 'Too many attempts. Try again in a few minutes.' }, 429);\n const body = await c.req.json<{ password?: unknown; token?: unknown }>().catch(() => ({} as { password?: unknown; token?: unknown }));\n const mode = authMode();\n const ok = mode === 'password' ? typeof body.password === 'string' && (await checkPassword(body.password))\n : mode === 'token' ? typeof body.token === 'string' && checkToken(body.token.trim())\n : false;\n if (!ok) return c.json({ error: mode === 'token' ? 'That is not the access token' : 'Wrong password', mode }, 401);\n succeeded();\n const cookie = newSessionCookie();\n c.header('Set-Cookie', cookieHeader(c, cookie.name, cookie.value, cookie.maxAge));\n return c.json({ ok: true });\n});\n\napp.post('/api/auth/logout', (c) => {\n c.header('Set-Cookie', cookieHeader(c, cookieName(), '', 0));\n return c.json({ ok: true });\n});\n\n/** A one-time code for the QR code that signs a phone in: good once, for two minutes. */\napp.post('/api/access/code', (c) => {\n if (authMode() === 'none') return c.json({ code: null });\n return c.json({ code: issueCode() });\n});\n\napp.get('/api/info', (c) => {\n const info: ServerInfo = { machine: config.machine, home: config.home, version: pkg.version, auth: authMode() !== 'none', demo: config.demo };\n const key = pushKey();\n if (key) info.pushKey = key;\n return c.json(info);\n});\n\napp.get('/api/sessions', async (c) => c.json({ sessions: await listSummaries(runStatus) }));\n\napp.get('/api/sessions/:id', async (c) => {\n const id = c.req.param('id');\n const session = await sessionSummary(id, runStatus);\n const run = runState(id);\n if (!session && !run) return c.json({ error: 'No such session' }, 404);\n // The task list needs the whole conversation; the page may only have loaded its end.\n const tasks = currentTasks(await loadMessages(id));\n return c.json({ session: session ?? null, run: run ?? null, tasks, settings: await recordedSettings(id) });\n});\n\napp.get('/api/sessions/:id/messages', async (c) => {\n const before = c.req.query('before');\n const limit = Math.min(Number(c.req.query('limit') ?? 120) || 120, 500);\n return c.json(await loadPage(c.req.param('id'), before === undefined ? undefined : Number(before), limit));\n});\n\ntype SendBody = { text?: string; attachments?: string[]; settings?: RunSettings; uuid?: string; cwd?: string; scratch?: boolean; mode?: SendMode };\n\nconst PERMISSION_MODES: PermissionMode[] = ['default', 'acceptEdits', 'plan', 'auto', 'bypassPermissions'];\nconst permissionMode = (value: unknown): PermissionMode | undefined =>\n (PERMISSION_MODES as unknown[]).includes(value) ? (value as PermissionMode) : undefined;\n\n/** The settings a request may carry, and only those, each of its own type. */\nfunction cleanSettings(raw: unknown): RunSettings {\n const value = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>;\n const text = (v: unknown, max: number) => (typeof v === 'string' && v.length > 0 && v.length <= max ? v : undefined);\n const out: RunSettings = {};\n const model = text(value.model, 200);\n const effort = text(value.effort, 20);\n const mode = permissionMode(value.permissionMode);\n if (model) out.model = model;\n if (effort) out.effort = effort;\n if (typeof value.fast === 'boolean') out.fast = value.fast;\n if (typeof value.ultracode === 'boolean') out.ultracode = value.ultracode;\n if (mode) out.permissionMode = mode;\n return out;\n}\n\nconst sendMode = (value: unknown): SendMode | undefined => (value === 'steer' || value === 'queue' ? value : undefined);\nconst uuidOf = (value: unknown) => (typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value) ? value : undefined);\n\n/** A message body of the right types, or why not: a wrong one is the client's error (400), not ours. */\nfunction sendBody(raw: unknown): SendBody | string {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return 'Not a message';\n const body = raw as Record<string, unknown>;\n if (body.text !== undefined && typeof body.text !== 'string') return 'The text must be a string';\n if (body.attachments !== undefined && (!Array.isArray(body.attachments) || body.attachments.length > 50 || !body.attachments.every((a) => typeof a === 'string'))) return 'Attachments must be upload ids';\n if (body.settings !== undefined && (typeof body.settings !== 'object' || body.settings === null)) return 'Settings must be an object';\n if (body.cwd !== undefined && typeof body.cwd !== 'string') return 'The folder must be a path';\n if (body.scratch !== undefined && typeof body.scratch !== 'boolean') return 'scratch must be true or false';\n return body as SendBody;\n}\n\nasync function attachmentsFrom(ids: string[] | undefined): Promise<Attachment[]> {\n const found = await Promise.all((ids ?? []).map((id) => findUpload(id)));\n return found.filter((a): a is Attachment => Boolean(a));\n}\n\napp.post('/api/sessions', async (c) => {\n const body = sendBody(await c.req.json().catch(() => undefined));\n if (typeof body === 'string') return c.json({ error: body }, 400);\n // A scratch session gets its folder only when its first message is sent, so backing out leaves nothing behind.\n const cwd = body.scratch ? await createScratchFolder() : await checkedFolder(body.cwd);\n if (!cwd) return c.json({ error: 'No such folder' }, 400);\n const uuid = uuidOf(body.uuid);\n const sessionId = await sendMessage({ cwd }, {\n text: typeof body.text === 'string' ? body.text : '',\n attachments: await attachmentsFrom(body.attachments),\n settings: cleanSettings(body.settings),\n ...(uuid ? { uuid } : {}),\n });\n return c.json({ sessionId });\n});\n\napp.post('/api/sessions/:id/messages', async (c) => {\n const body = sendBody(await c.req.json().catch(() => undefined));\n if (typeof body === 'string') return c.json({ error: body }, 400);\n const uuid = uuidOf(body.uuid);\n const mode = sendMode(body.mode);\n const sessionId = await sendMessage({ sessionId: c.req.param('id') }, {\n text: typeof body.text === 'string' ? body.text : '',\n attachments: await attachmentsFrom(body.attachments),\n settings: cleanSettings(body.settings),\n ...(uuid ? { uuid } : {}),\n ...(mode ? { mode } : {}),\n });\n return c.json({ sessionId });\n});\n\n/** Edits made, and being made, by the id the editor gave them: a retry gets the same result, never a second one. */\ntype Edited = { sessionId: string; inPlace: boolean };\nconst edits = new Map<string, { at: number } & Edited>();\nconst editing = new Map<string, Promise<Edited>>();\n\n/** A prompt the user wrote (text or images), not a tool's result or injected context. */\nconst isPrompt = (m: ChatMessage) => m.role === 'user' && !m.meta && m.blocks.some((b) => b.type === 'text' || b.type === 'image');\n\n/** The images of a prompt, saved as uploads again so the edited prompt carries the ones kept. */\nasync function imagesOf(sessionId: string, uuid: string, keep: unknown): Promise<Attachment[]> {\n const file = await transcriptPath(sessionId);\n if (!file) return [];\n const images = await rawImages(file, uuid);\n const wanted = Array.isArray(keep) ? new Set(keep.filter((i): i is number => Number.isInteger(i))) : new Set(images.map((_, i) => i));\n const kept: Attachment[] = [];\n for (const [i, image] of images.entries()) {\n if (!wanted.has(i) || image.type !== 'base64' || !image.data) continue;\n const ext = image.mediaType.split('/')[1]?.replace('jpeg', 'jpg') ?? 'png';\n kept.push(await saveUpload(new File([Buffer.from(image.data, 'base64')], `image-${i + 1}.${ext}`, { type: image.mediaType })));\n }\n return kept;\n}\n\n/**\n * Edit a prompt and send it again, as one request.\n * - The last prompt is replaced in place, as rewinding does in Claude Code: the session goes on\n * from just before it with the new text (the old turn stays in the transcript, off the line).\n * - An earlier one branches: a copy of the conversation up to just before it gets the new text\n * (the first prompt: a new session in the same folder), and this session stays as it was.\n * A branch is new work: it never starts in Bypass.\n * If the send fails, a branch and the copied images are removed, so nothing is left behind.\n */\ntype EditFailure = { error: string; status: 404 | 409 };\n\n/** Waits until the Claude of a session it just started is running; a start that fails throws. */\nasync function claudeStarted(sessionId: string): Promise<void> {\n const run = getRun(sessionId);\n if (!run) return;\n let timer: NodeJS.Timeout | undefined;\n try {\n await Promise.race([run.ready, new Promise<void>((resolve) => {\n timer = setTimeout(resolve, 45_000);\n })]);\n } finally {\n clearTimeout(timer);\n }\n}\n\nasync function makeEdit(id: string, before: string, body: { text: string; settings?: unknown; keepImages?: unknown }, key: string): Promise<Edited | EditFailure> {\n const info = await sessionInfo(id);\n if (!info?.cwd || !(await isDirectory(info.cwd))) return { error: 'The session\u2019s folder is missing', status: 404 };\n if (getRun(id)?.busy) return { error: 'Stop Claude before editing an earlier prompt.', status: 409 };\n const messages = await loadMessages(id);\n const at = messages.findIndex((m) => m.uuid === before);\n if (at < 0) return { error: 'That message is not in this session', status: 404 };\n const previous = at === 0 ? null : messages[at - 1]!.uuid;\n const last = !messages.slice(at + 1).some(isPrompt);\n const file = await transcriptPath(id);\n // In place only for the last prompt, after another one (the first has nothing to go on from),\n // when no background task would be cut off by restarting Claude.\n const entry = last && previous !== null && file && !getRun(id)?.hasBackgroundTasks ? (await rawEntry(file, before))?.parentUuid : undefined;\n const settings = cleanSettings(body.settings);\n if (!entry && settings.permissionMode === 'bypassPermissions') settings.permissionMode = 'default';\n const attachments = await imagesOf(id, before, body.keepImages);\n const message = { text: body.text, attachments, settings, uuid: key };\n let branch: string | undefined;\n let started: string | undefined;\n try {\n if (entry) {\n started = await replaceLastTurn(id, info.cwd, entry, before, message);\n await claudeStarted(started);\n return { sessionId: started, inPlace: true };\n }\n if (previous !== null) branch = await fork(id, previous);\n started = await sendMessage(branch ? { sessionId: branch } : { cwd: info.cwd }, message);\n // Only a Claude that started makes the branch real; one that could not start leaves nothing.\n await claudeStarted(started);\n return { sessionId: started, inPlace: false };\n } catch (error) {\n if (branch) await remove(branch).catch(() => undefined);\n else if (started && !entry) await remove(started).catch(() => undefined);\n await Promise.all(attachments.map((a) => removeUpload(a.id)));\n throw error;\n }\n}\n\n/**\n * Edit a prompt and send it again, as one request.\n * - The last prompt is replaced in place, as rewinding does in Claude Code: the session goes on\n * from just before it with the new text (the old turn stays in the transcript, off the line).\n * - An earlier one branches: a copy of the conversation up to just before it gets the new text\n * (the first prompt: a new session in the same folder), and this session stays as it was.\n * A branch is new work: it never starts in Bypass.\n * If Claude cannot start, a branch and the copied images are removed, so nothing is left behind.\n * Requests with the same `uuid` share one edit, also while it is still being made.\n */\napp.post('/api/sessions/:id/edit', async (c) => {\n const id = c.req.param('id');\n const body = await c.req.json<{ before?: unknown; text?: unknown; settings?: unknown; uuid?: unknown; keepImages?: unknown }>()\n .catch(() => ({}) as Record<string, unknown>);\n const key = uuidOf(body.uuid);\n const before = uuidOf(body.before);\n if (!key || !before || typeof body.text !== 'string' || !body.text.trim()) return c.json({ error: 'Nothing to send' }, 400);\n for (const [k, v] of edits) if (Date.now() - v.at > 10 * 60_000) edits.delete(k);\n const done = edits.get(key);\n if (done) return c.json({ sessionId: done.sessionId, inPlace: done.inPlace });\n // Registered before anything is awaited, so a second request with this id waits for this one.\n let work = editing.get(key) as Promise<Edited | EditFailure> | undefined;\n if (!work) {\n const made = makeEdit(id, before, { text: body.text, settings: body.settings, keepImages: body.keepImages }, key);\n work = made;\n editing.set(key, made as Promise<Edited>);\n void made.then((result) => {\n if (!('error' in result)) edits.set(key, { at: Date.now(), ...result });\n }, () => undefined).finally(() => editing.delete(key));\n }\n const result = await work;\n if ('error' in result) return c.json({ error: result.error }, result.status);\n return c.json(result);\n});\n\napp.post('/api/sessions/:id/interrupt', async (c) => {\n await getRun(c.req.param('id'))?.interrupt();\n return c.json({ ok: true });\n});\n\napp.post('/api/sessions/:id/permissions/:requestId', async (c) => {\n const decision = await c.req.json<PermissionDecision>().catch(() => undefined);\n const raw = (decision ?? {}) as Record<string, unknown>;\n const valid = (raw.behavior === 'allow' || raw.behavior === 'deny')\n && (raw.message === undefined || typeof raw.message === 'string')\n && (raw.remember === undefined || typeof raw.remember === 'boolean')\n && (raw.updatedInput === undefined || (typeof raw.updatedInput === 'object' && raw.updatedInput !== null && !Array.isArray(raw.updatedInput)))\n && (raw.permissionMode === undefined || Boolean(permissionMode(raw.permissionMode)));\n if (!decision || !valid) return c.json({ error: 'Not a decision' }, 400);\n const ok = getRun(c.req.param('id'))?.decide(c.req.param('requestId'), decision) ?? false;\n return ok ? c.json({ ok }) : c.json({ error: 'That request is no longer pending' }, 409);\n});\n\n/** Model, effort, speed, ultracode or mode changed while Claude runs: it switches at its next step. */\napp.post('/api/sessions/:id/settings', async (c) => {\n const body = await c.req.json<{ settings?: unknown }>().catch(() => ({}) as { settings?: unknown });\n if (!body.settings || typeof body.settings !== 'object') return c.json({ error: 'No settings' }, 400);\n await getRun(c.req.param('id'))?.updateSettings(cleanSettings(body.settings));\n return c.json({ ok: true });\n});\n\napp.post('/api/sessions/:id/mode', async (c) => {\n const body = await c.req.json<{ permissionMode?: unknown }>();\n const mode = permissionMode(body.permissionMode);\n if (!mode) return c.json({ error: 'No such mode' }, 400);\n await getRun(c.req.param('id'))?.setPermissionMode(mode);\n return c.json({ ok: true });\n});\n\napp.patch('/api/sessions/:id', async (c) => {\n const { title } = await c.req.json<{ title?: string }>();\n if (!title?.trim()) return c.json({ error: 'Title is empty' }, 400);\n await renameSessionTitle(c.req.param('id'), title.trim());\n return c.json({ ok: true });\n});\n\napp.post('/api/sessions/:id/fork', async (c) => {\n const id = c.req.param('id');\n const body = await c.req.json<{ upTo?: string; before?: string; title?: string }>().catch(() => ({} as { upTo?: string; before?: string; title?: string }));\n let upTo = body.upTo;\n if (body.before) {\n const previous = await messageBefore(id, body.before);\n if (previous === undefined) return c.json({ error: 'That message is not in this session' }, 404);\n // Nothing comes before the first prompt: the caller starts a new session instead.\n if (previous === null) return c.json({ sessionId: null });\n upTo = previous;\n }\n return c.json({ sessionId: await fork(id, upTo, body.title) });\n});\n\n/** Put a session away from the list, or bring it back; nothing else changes. */\napp.post('/api/sessions/:id/archive', async (c) => {\n const id = uuidOf(c.req.param('id'));\n const body = await c.req.json<{ archived?: unknown }>().catch(() => ({}) as { archived?: unknown });\n if (!id || typeof body.archived !== 'boolean') return c.json({ error: 'Say archived: true or false' }, 400);\n await setArchived(id, body.archived);\n sessionsChanged();\n return c.json({ ok: true });\n});\n\napp.delete('/api/sessions/:id', async (c) => {\n const id = c.req.param('id');\n if (getRun(id)?.busy) return c.json({ error: 'Stop Claude in this session before deleting it.' }, 409);\n getRun(id)?.close();\n await remove(id);\n return c.json({ ok: true });\n});\n\napp.get('/api/sessions/:id/export', async (c) => {\n const { title, markdown } = await exportMarkdown(c.req.param('id'));\n const name = `${title.replace(/[^\\p{L}\\p{N} _-]+/gu, '').trim().slice(0, 60) || 'session'}.md`;\n return c.body(markdown, 200, {\n 'Content-Type': 'text/markdown; charset=utf-8',\n 'Content-Disposition': `attachment; filename*=UTF-8''${encodeURIComponent(name)}`,\n 'X-Content-Type-Options': 'nosniff',\n });\n});\n\napp.get('/api/models', async (c) => c.json(await listModels()));\n\napp.get('/api/commands', async (c) => {\n const cwd = await folderOf(c);\n if (!cwd) return c.json({ commands: [], agents: [] });\n return c.json(await listCompletions(cwd));\n});\n\n/** What `/usage` shows in Claude Code: asked of claude.ai when Settings opens, kept a minute. */\napp.get('/api/usage/plan', async (c) => c.json({ plan: await planUsage() }));\n\napp.get('/api/usage', (c) => c.json({\n limits: config.demo\n ? [\n { type: 'five_hour', status: 'allowed', utilization: 0.42, resetsAt: Date.now() + 2.4 * 3600_000, at: Date.now() },\n { type: 'seven_day', status: 'allowed', utilization: 0.18, resetsAt: Date.now() + 3.5 * 86400_000, at: Date.now() },\n ]\n : usageLimits(),\n}));\n\n/** The folder a workspace request is about: the session's own, which must exist. */\n/** Whether a path is inside the demo's own folder (the demo never shows anything else). */\nasync function inDemo(dir: string): Promise<boolean> {\n try {\n const [real, root] = await Promise.all([realpath(dir), realpath(config.home)]);\n return real === root || real.startsWith(`${root}${path.sep}`);\n } catch {\n return false;\n }\n}\n\n/** A folder named in a request: an existing absolute path, and in the demo only the demo's own. */\nasync function checkedFolder(cwd: string | undefined): Promise<string | undefined> {\n if (!cwd || !path.isAbsolute(cwd) || !(await isDirectory(cwd))) return undefined;\n if (config.demo && !(await inDemo(cwd))) return undefined;\n return cwd;\n}\n\nconst folderOf = (c: Context) => checkedFolder(c.req.query('cwd'));\n\n/** Whether a folder is in a git repository, by its `.git` and its parents' (this runs nothing). */\nasync function insideRepository(cwd: string): Promise<boolean> {\n for (let dir = path.resolve(cwd); ; dir = path.dirname(dir)) {\n if (await stat(path.join(dir, '.git')).then(() => true, () => false)) return true;\n if (path.dirname(dir) === dir) return false;\n }\n}\n\n/** Git runs only where Claude Code has run: a folder you are only shown runs nothing of its own. */\nconst gitAllowed = (cwd: string) => (config.demo ? Promise.resolve(true) : claudeRanIn(cwd));\n\napp.get('/api/git', async (c) => {\n const cwd = await folderOf(c);\n if (!cwd) return c.json({ error: 'No such folder' }, 404);\n if (!(await gitAllowed(cwd))) return c.json(await insideRepository(cwd) ? { repo: false, untrusted: true } : { repo: false });\n return c.json(await gitStatus(cwd));\n});\n\napp.get('/api/git/diff', async (c) => {\n const cwd = await folderOf(c);\n const file = c.req.query('path');\n if (!cwd || !file) return c.json({ error: 'No such file' }, 404);\n if (!(await gitAllowed(cwd))) return c.json({ diff: '' });\n return c.json({ diff: await gitDiff(cwd, file) });\n});\n\napp.get('/api/tree', async (c) => {\n const cwd = await folderOf(c);\n if (!cwd) return c.json({ error: 'No such folder' }, 404);\n return c.json({ entries: await listTree(cwd, c.req.query('path') ?? '') });\n});\n\napp.get('/api/file', async (c) => {\n const cwd = await folderOf(c);\n const file = c.req.query('path');\n if (!cwd || !file) return c.json({ error: 'No such file' }, 404);\n return c.json(await readProjectFile(cwd, file));\n});\n\napp.post('/api/sessions/:id/shell', async (c) => {\n const id = c.req.param('id');\n const { command } = await c.req.json<{ command?: string }>().catch(() => ({ command: undefined }));\n if (!command?.trim()) return c.json({ error: 'No command' }, 400);\n const info = await sessionInfo(id);\n if (!info?.cwd || !(await isDirectory(info.cwd))) return c.json({ error: 'The session\u2019s folder is missing' }, 404);\n const result = await runCommand(info.cwd, command.trim());\n // Into the conversation without a turn: Claude reads it with the next message.\n await (await ensureRun(id)).appendShell(result);\n return c.json(result);\n});\n\napp.post('/api/sessions/:id/rewind', async (c) => {\n const { uuid, dryRun } = await c.req.json<{ uuid?: string; dryRun?: boolean }>().catch(() => ({ uuid: undefined, dryRun: undefined }));\n if (!uuid) return c.json({ error: 'Which prompt?' }, 400);\n const run = await ensureRun(c.req.param('id'));\n if (run.busy) return c.json({ error: 'Stop Claude before rewinding files.' }, 409);\n return c.json(await run.rewind(uuid, dryRun !== false));\n});\n\napp.get('/api/sessions/:id/mcp', async (c) => {\n const id = c.req.param('id');\n const run = getRun(id);\n if (run) return c.json({ servers: await run.mcpServers(), live: true });\n const info = await sessionInfo(id);\n if (!info?.cwd || !(await isDirectory(info.cwd))) return c.json({ servers: [], live: false });\n return c.json({ servers: await probeMcpServers(info.cwd), live: false });\n});\n\napp.post('/api/sessions/:id/mcp/:name', async (c) => {\n const run = getRun(c.req.param('id'));\n if (!run) return c.json({ error: 'Claude is not running in this session' }, 409);\n const change = await c.req.json<{ enabled?: boolean; reconnect?: boolean }>().catch(() => ({}));\n await run.setMcpServer(c.req.param('name'), change);\n return c.json({ servers: await run.mcpServers() });\n});\n\napp.post('/api/sessions/:id/tasks/:task/stop', async (c) => {\n await getRun(c.req.param('id'))?.stopTask(c.req.param('task'));\n return c.json({ ok: true });\n});\n\napp.get('/api/sessions/:id/sent', async (c) => {\n const file = c.req.query('path') ?? '';\n if (!file || !(await sentFiles(c.req.param('id'))).has(file)) return c.json({ error: 'Not a file Claude sent in this session' }, 404);\n try {\n return c.json(await readProjectFile(path.dirname(file), path.basename(file)));\n } catch {\n return c.json({ error: 'The file is gone' }, 404);\n }\n});\n\napp.get('/api/sessions/:id/agents/:agent', async (c) => {\n return c.json({ messages: await loadSubagent(c.req.param('id'), c.req.param('agent')) });\n});\n\napp.get('/api/files', async (c) => {\n const cwd = await folderOf(c);\n if (!cwd) return c.json({ files: [] });\n return c.json({ files: await searchFiles(cwd, c.req.query('q') ?? '') });\n});\n\n/**\n * Folders to start a new session in: those of recent sessions, without temp\n * and scratch folders and without folders that are gone. Two folders with the\n * same name are told apart by their parent.\n */\napp.get('/api/projects', async (c) => {\n const seen = new Map<string, { path: string; name: string; lastActivity: number; sessions: number }>();\n for (const s of await listSummaries(runStatus)) {\n if (!s.cwd || s.scratch || s.temp) continue;\n const entry = seen.get(s.cwd);\n if (entry) entry.sessions += 1;\n else seen.set(s.cwd, { path: s.cwd, name: s.project, lastActivity: s.lastActivity, sessions: 1 });\n }\n const existing = (await Promise.all([...seen.values()].map(async (p) => ((await isDirectory(p.path)) ? p : undefined))))\n .filter((p): p is NonNullable<typeof p> => Boolean(p));\n const counts = new Map<string, number>();\n for (const p of existing) counts.set(p.name, (counts.get(p.name) ?? 0) + 1);\n for (const p of existing) {\n if ((counts.get(p.name) ?? 0) > 1) p.name = `${path.basename(path.dirname(p.path))}/${p.name}`;\n }\n return c.json({ projects: existing });\n});\n\napp.get('/api/dirs', async (c) => {\n const asked = c.req.query('path');\n if (config.demo && asked && !(await inDemo(asked))) return c.json({ error: 'Not part of the demo' }, 404);\n try {\n return c.json(await listDirs(asked, c.req.query('hidden') === '1'));\n } catch (error) {\n return c.json({ error: (error as Error).message }, 400);\n }\n});\n\napp.post('/api/uploads', async (c) => {\n const body = await c.req.parseBody();\n const file = body.file;\n if (!(file instanceof File)) return c.json({ error: 'No file' }, 400);\n return c.json({ attachment: await saveUpload(file) });\n});\n\napp.get('/api/uploads/:id', async (c) => {\n const found = await readUpload(c.req.param('id'));\n if (!found) return c.notFound();\n // Shown in the page only as an image; anything else downloads. Either way it runs nothing:\n // no sniffing, no scripts, no frames.\n const image = found.attachment.mime.startsWith('image/') && found.attachment.mime !== 'image/svg+xml';\n return c.body(new Uint8Array(found.data), 200, {\n 'Content-Type': found.attachment.mime,\n 'Cache-Control': 'private, max-age=86400',\n 'X-Content-Type-Options': 'nosniff',\n 'Content-Security-Policy': \"sandbox; default-src 'none'; img-src 'self' data:; frame-ancestors 'self'\",\n 'X-Frame-Options': 'SAMEORIGIN',\n ...(image ? {} : { 'Content-Disposition': `attachment; filename*=UTF-8''${encodeURIComponent(found.attachment.name)}` }),\n });\n});\n\napp.get('/api/push/key', (c) => c.json({ key: pushKey() ?? null }));\n\n/** A push subscription as browsers make them: an https endpoint, a P-256 key, a 16-byte secret. */\nfunction validSubscription(value: unknown): value is { endpoint: string; keys: { p256dh: string; auth: string } } {\n const { endpoint, keys } = (value ?? {}) as { endpoint?: unknown; keys?: { p256dh?: unknown; auth?: unknown } };\n const bytes = (text: unknown) => (typeof text === 'string' && /^[\\w-]+=*$/.test(text) ? Buffer.from(text, 'base64url').length : -1);\n return typeof endpoint === 'string' && endpoint.length <= 2048 && endpoint.startsWith('https://')\n && bytes(keys?.p256dh) === 65 && bytes(keys?.auth) === 16;\n}\n\nconst pageUrl = (value: unknown) => typeof value === 'string' && value.length <= 2048 && /^https?:\\/\\//.test(value);\n\napp.post('/api/push/subscribe', async (c) => {\n const body = await c.req.json<{ subscription?: unknown; openUrl?: unknown }>().catch(() => ({}) as { subscription?: unknown; openUrl?: unknown });\n if (!validSubscription(body.subscription)) return c.json({ error: 'Not a push subscription' }, 400);\n const { endpoint, keys } = body.subscription;\n await addSubscription({ endpoint, keys: { p256dh: keys.p256dh, auth: keys.auth }, ...(pageUrl(body.openUrl) ? { openUrl: body.openUrl as string } : {}) });\n return c.json({ ok: true });\n});\n\napp.post('/api/push/unsubscribe', async (c) => {\n const { endpoint } = await c.req.json<{ endpoint?: unknown }>().catch(() => ({ endpoint: undefined }));\n if (typeof endpoint !== 'string') return c.json({ error: 'No endpoint' }, 400);\n await removeSubscription(endpoint);\n return c.json({ ok: true });\n});\n\napp.all('/api/*', (c) => c.json({ error: 'Not found' }, 404));\n\nconst TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8',\n '.svg': 'image/svg+xml', '.png': 'image/png', '.ico': 'image/x-icon', '.webmanifest': 'application/manifest+json',\n '.json': 'application/json', '.woff2': 'font/woff2', '.txt': 'text/plain; charset=utf-8',\n};\n\n/**\n * - Only Pelican's own origin, and pages the config names, may show it in a frame: any other page\n * could lay its own buttons over Pelican's and have you approve something (clickjacking).\n * - Images and media come only from Pelican: a picture in Claude's reply, or in a page it wrote,\n * could otherwise carry what is in the conversation to another server just by loading. The\n * preview frames inherit this, on top of their own policy (Preview.tsx).\n */\nconst POLICY: Record<string, string> = {\n 'Content-Security-Policy': [\n `frame-ancestors 'self'${config.frameAncestors.map((o) => ` ${o}`).join('')}`,\n \"frame-src 'self' blob: data:\",\n \"img-src 'self' data: blob:\",\n \"media-src 'self' data: blob:\",\n \"object-src 'none'\",\n \"base-uri 'self'\",\n ].join('; '),\n ...(config.frameAncestors.length === 0 ? { 'X-Frame-Options': 'SAMEORIGIN' } : {}),\n};\n\n/** The web app. Every URL in it is relative, so it works under any path a proxy mounts it at. */\napp.get('/*', async (c) => {\n if (!config.webDir) return c.text('The web app is not built. Run `npm run build`, or use `npm run dev`.', 503);\n const root = config.webDir;\n let pathname: string;\n try {\n pathname = decodeURIComponent(new URL(c.req.url).pathname);\n } catch {\n return c.text('Bad path', 400);\n }\n // Opening the link Pelican printed (\u2026/?token=\u2026), or the QR code from Settings (\u2026/?code=\u2026),\n // signs this browser in, then drops the secret from the address.\n const token = c.req.query('token');\n const code = c.req.query('code');\n if (token !== undefined || code !== undefined) {\n const url = new URL(c.req.url);\n url.searchParams.delete('token');\n url.searchParams.delete('code');\n // Relative to where the page is, so it works under a proxy's path prefix, and can never\n // name another site (`//other.example/` would).\n const location = `./${url.pathname.split('/').pop() ?? ''}${url.search}`;\n const ok = token !== undefined ? checkToken(token) : redeemCode(code);\n if (!ok) return c.body(null, 302, { Location: location, 'Cache-Control': 'no-store' });\n const cookie = newSessionCookie();\n return c.body(null, 302, { Location: location, 'Set-Cookie': cookieHeader(c, cookie.name, cookie.value, cookie.maxAge), 'Cache-Control': 'no-store' });\n }\n const requested = path.normalize(pathname).replace(/^(\\.\\.[/\\\\])+/, '');\n let file = path.join(root, requested);\n if (!file.startsWith(root) || requested === '/' || !path.extname(file)) file = path.join(root, 'index.html');\n try {\n const data = await readFile(file);\n const ext = path.extname(file);\n const hashed = file.includes(`${path.sep}assets${path.sep}`);\n return c.body(new Uint8Array(data), 200, {\n 'Content-Type': TYPES[ext] ?? 'application/octet-stream',\n 'Cache-Control': hashed ? 'public, max-age=31536000, immutable' : 'no-cache',\n 'X-Content-Type-Options': 'nosniff',\n 'Referrer-Policy': 'same-origin',\n ...POLICY,\n });\n } catch {\n return c.notFound();\n }\n});\n", "import type { IncomingMessage } from 'node:http';\n\nimport { serve } from '@hono/node-server';\nimport { WebSocketServer, type WebSocket } from 'ws';\n\nimport type { ClientEvent, ServerEvent } from '../shared/protocol.js';\nimport { app } from './app.js';\nimport { loadAuth, signedIn } from './auth.js';\nimport { config } from './config.js';\nimport { connect, subscribe } from './feed.js';\nimport { initPush } from './push.js';\nimport { closeAll, runState } from './runner.js';\nimport { hostAllowed, originAllowed } from './security.js';\n\nconst SESSION_ID = /^[\\w-]{1,100}$/;\n/** A browser follows a handful of sessions at once; more from one socket is not a browser. */\nconst MAX_SUBSCRIPTIONS = 64;\n\n/** A client event, or undefined for anything else. */\nfunction clientEvent(text: string): ClientEvent | undefined {\n let value: unknown;\n try {\n value = JSON.parse(text);\n } catch {\n return undefined;\n }\n if (!value || typeof value !== 'object') return undefined;\n const { type, sessionId } = value as Record<string, unknown>;\n if ((type !== 'subscribe' && type !== 'unsubscribe') || typeof sessionId !== 'string' || !SESSION_ID.test(sessionId)) return undefined;\n return { type, sessionId };\n}\n\n/** Starts the HTTP server and the WebSocket that streams sessions. Resolves once it listens. */\nexport async function startServer(): Promise<{ url: string }> {\n await loadAuth();\n await initPush();\n\n const url = await new Promise<string>((resolve, reject) => {\n const server = serve({ fetch: app.fetch, hostname: config.host, port: config.port }, (info) => {\n const host = info.family === 'IPv6' ? `[${info.address}]` : info.address;\n resolve(`http://${host}:${info.port}/`);\n });\n server.on('error', reject);\n\n // Client events are a few hundred bytes; nothing bigger is read.\n const wss = new WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });\n\n server.on('upgrade', (req: IncomingMessage, socket, head) => {\n const pathname = new URL(req.url ?? '/', 'http://x').pathname;\n const forwarded = req.headers['x-forwarded-host'];\n // The same checks as every request: a known Host, a page of Pelican's own, a signed-in browser.\n const ok = pathname.endsWith('/api/ws')\n && hostAllowed(req.headers.host)\n && originAllowed(req.headers.origin, req.headers.host, Array.isArray(forwarded) ? forwarded[0] : forwarded)\n && signedIn(req.headers.cookie, req.headers.authorization);\n if (!ok) {\n socket.destroy();\n return;\n }\n wss.handleUpgrade(req, socket, head, (ws) => wss.emit('connection', ws, req));\n });\n\n wss.on('connection', (ws: WebSocket) => {\n const send = (event: ServerEvent) => {\n if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(event));\n };\n const disconnect = connect(send);\n const subscriptions = new Map<string, () => void>();\n // Proxies close idle sockets (Cloudflare after 100 s); a ping every 25 s keeps it open.\n const ping = setInterval(() => ws.ping(), 25_000);\n // A frame too large or malformed closes this socket; unheard, the error would stop the server.\n ws.on('error', () => ws.terminate());\n\n ws.on('message', (data) => {\n const event = clientEvent(String(data));\n if (!event) return;\n if (event.type === 'subscribe' && !subscriptions.has(event.sessionId)) {\n if (subscriptions.size >= MAX_SUBSCRIPTIONS) return;\n subscriptions.set(event.sessionId, subscribe(event.sessionId, send));\n const state = runState(event.sessionId);\n if (state) send({ type: 'run', state });\n } else if (event.type === 'unsubscribe') {\n subscriptions.get(event.sessionId)?.();\n subscriptions.delete(event.sessionId);\n }\n });\n\n ws.on('close', () => {\n clearInterval(ping);\n disconnect();\n for (const unsubscribe of subscriptions.values()) unsubscribe();\n });\n });\n\n const shutdown = () => {\n closeAll();\n server.close();\n setTimeout(() => process.exit(0), 500).unref();\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n });\n return { url };\n}\n", "import qrcode from 'qrcode-generator';\n\nconst QUIET = 2;\n\n/** The dark and light modules of a QR code for `text`, with a quiet border. */\nexport function qrModules(text: string): boolean[][] {\n const qr = qrcode(0, 'M');\n qr.addData(text);\n qr.make();\n const size = qr.getModuleCount();\n const rows: boolean[][] = [];\n for (let y = -QUIET; y < size + QUIET; y += 1) {\n const row: boolean[] = [];\n for (let x = -QUIET; x < size + QUIET; x += 1) row.push(x >= 0 && y >= 0 && x < size && y < size && qr.isDark(y, x));\n rows.push(row);\n }\n return rows;\n}\n\n/** The same code as an SVG, black on white whatever the theme. */\nexport function qrSvg(text: string): string {\n const rows = qrModules(text);\n const size = rows.length;\n let path = '';\n rows.forEach((row, y) => row.forEach((dark, x) => {\n if (dark) path += `M${x} ${y}h1v1h-1z`;\n }));\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ${size} ${size}\" shape-rendering=\"crispEdges\"><rect width=\"${size}\" height=\"${size}\" fill=\"#fff\"/><path d=\"${path}\" fill=\"#000\"/></svg>`;\n}\n", "import { qrModules } from '../shared/qr.js';\n\n/** A QR code for a terminal: two rows per line, drawn in black and white whatever the theme. */\nexport function qrTerminal(text: string): string {\n const rows = qrModules(text);\n const lines: string[] = [];\n for (let y = 0; y < rows.length; y += 2) {\n let line = '';\n for (let x = 0; x < rows[y]!.length; x += 1) {\n const top = rows[y]![x] ? 16 : 231;\n const bottom = rows[y + 1]?.[x] ? 16 : 231;\n line += `\\x1b[38;5;${top};48;5;${bottom}m\u2580`;\n }\n lines.push(`${line}\\x1b[0m`);\n }\n return lines.join('\\n');\n}\n", "import { spawn } from 'node:child_process';\nimport { mkdir, rm, writeFile } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { parseArgs } from 'node:util';\n\nimport pkg from '../../package.json' with { type: 'json' };\nimport { config } from './config.js';\nimport { listensLocally } from './security.js';\n\nconst DOCS = 'https://ki-seki.github.io/pelican/docs.html';\n\nconst HELP = `Pelican ${pkg.version}: your Claude Code sessions, from any browser.\n\nUsage\n pelican [options] Start the server\n pelican token Print the link that signs a browser in\n pelican token --reset Make a new access token; old links stop working\n pelican password Sign in with a password instead of the token\n pelican password --off Remove the password; the token signs in again\n\nOptions\n -p, --port <port> Port to listen on (default 7860, or PELICAN_PORT)\n -H, --host <address> Address to listen on (default 127.0.0.1, or PELICAN_HOST)\n --name <name> Name shown for this machine (default: its host name)\n --no-auth Ask no one to sign in. Only when something in front\n of Pelican already authenticates.\n --demo Show made-up sessions; nothing runs\n --open Open the browser\n -v, --version Print the version\n -h, --help Show this help\n\nPelican runs Claude Code, and so commands, as you. It listens on 127.0.0.1, and\nevery browser signs in, with the access token in the link Pelican prints or\nwith your password. To use it from your phone, see ${DOCS}#phone\n`;\n\n/** Reads a line from the terminal without echoing it. */\nfunction askHidden(prompt: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const input = process.stdin;\n if (!input.isTTY) {\n reject(new Error('Run `pelican password` in a terminal, or pipe the password in with --stdin.'));\n return;\n }\n process.stdout.write(prompt);\n input.setRawMode(true);\n input.resume();\n input.setEncoding('utf8');\n let value = '';\n const onData = (chunk: string) => {\n for (const char of chunk) {\n if (char === '\\r' || char === '\\n') {\n input.setRawMode(false);\n input.pause();\n input.off('data', onData);\n process.stdout.write('\\n');\n resolve(value);\n return;\n }\n if (char === '\\u0003') {\n process.stdout.write('\\n');\n process.exit(130);\n }\n if (char === '\\u007f' || char === '\\b') value = value.slice(0, -1);\n else value += char;\n }\n };\n input.on('data', onData);\n });\n}\n\nasync function readStdin(): Promise<string> {\n let data = '';\n for await (const chunk of process.stdin) data += chunk;\n return data.replace(/\\r?\\n$/, '');\n}\n\nasync function passwordCommand(off: boolean, stdin: boolean): Promise<void> {\n const { clearPassword, loadAuth, setPassword } = await import('./auth.js');\n await loadAuth();\n if (off) {\n await clearPassword();\n console.log('Password removed: browsers sign in with the access token again (`pelican token`). Restart Pelican to apply.');\n return;\n }\n const password = stdin ? await readStdin() : await askHidden('New password: ');\n if (password.length < 8) throw new Error('Use at least 8 characters.');\n if (!stdin && (await askHidden('Repeat it: ')) !== password) throw new Error('The two did not match.');\n await setPassword(password);\n console.log(`Password saved in ${config.configDir}. Restart Pelican to apply; signed-in browsers are signed out.`);\n}\n\n/** The link with the access token, for this computer's own address. */\nconst tokenLink = (base: string, token: string) => `${base}?token=${token}`;\n\n/** Pelican's address on this computer: localhost, unless it listens on one other address only. */\nfunction localAddress(): string {\n const h = config.host;\n const host = ['0.0.0.0', '::', '127.0.0.1', 'localhost', '::1'].includes(h) ? 'localhost' : h.includes(':') ? `[${h}]` : h;\n return `http://${host}:${config.port}/`;\n}\n\nasync function tokenCommand(reset: boolean): Promise<void> {\n const { accessToken, authMode, loadAuth, resetToken } = await import('./auth.js');\n await loadAuth();\n if (authMode() === 'none') {\n console.log('Sign-in is off (--no-auth or PELICAN_AUTH=none), so there is no access token.');\n return;\n }\n if (reset) {\n await resetToken();\n console.log('New access token made. Restart Pelican to apply; browsers signed in with the old one are signed out.');\n }\n if (authMode() === 'password') {\n console.log('A password is set, so browsers sign in with it. `pelican password --off` goes back to the token.');\n return;\n }\n const token = accessToken();\n if (token) console.log(tokenLink(localAddress(), token));\n}\n\n/**\n * Opens Pelican in the browser. The link with the token is never put on a command line, where\n * other accounts could read it: on macOS a page only you can read redirects to it (and is\n * removed); elsewhere, where a browser may not open such a page, Pelican opens to its sign-in.\n */\nasync function openPelican(address: string, token: string | undefined): Promise<void> {\n if (!token || process.platform !== 'darwin') {\n openBrowser(address);\n return;\n }\n const file = path.join(config.configDir, 'open.html');\n try {\n await mkdir(config.configDir, { recursive: true, mode: 0o700 });\n await rm(file, { force: true });\n const link = tokenLink(address, token);\n await writeFile(file, `<!doctype html><meta http-equiv=\"refresh\" content=\"0;url=${link}\"><a href=\"${link}\">Open Pelican</a>\\n`, { mode: 0o600 });\n openBrowser(pathToFileURL(file).href);\n setTimeout(() => void rm(file, { force: true }), 30_000).unref();\n } catch {\n openBrowser(address);\n }\n}\n\nfunction openBrowser(url: string): void {\n const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';\n try {\n spawn(opener, [url], { stdio: 'ignore', detached: true, shell: process.platform === 'win32' }).on('error', () => {}).unref();\n } catch {\n // Opening a browser is a convenience; the address is printed above.\n }\n}\n\nasync function main(): Promise<void> {\n const { values, positionals } = parseArgs({\n allowPositionals: true,\n options: {\n port: { type: 'string', short: 'p' },\n host: { type: 'string', short: 'H' },\n name: { type: 'string' },\n 'no-auth': { type: 'boolean' },\n demo: { type: 'boolean' },\n open: { type: 'boolean' },\n off: { type: 'boolean' },\n reset: { type: 'boolean' },\n stdin: { type: 'boolean' },\n version: { type: 'boolean', short: 'v' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n if (values.help) return void process.stdout.write(HELP);\n if (values.version) return void console.log(pkg.version);\n\n if (values.port) config.port = Number(values.port);\n if (values.host) config.host = values.host;\n if (values.name) config.machine = values.name;\n if (values['no-auth']) config.auth = 'none';\n if (!Number.isInteger(config.port) || config.port <= 0 || config.port > 65535) throw new Error(`Not a port: ${values.port}`);\n\n if (positionals[0] === 'password') return passwordCommand(Boolean(values.off), Boolean(values.stdin));\n if (positionals[0] === 'token') return tokenCommand(Boolean(values.reset));\n if (positionals.length > 0) throw new Error(`Unknown command: ${positionals.join(' ')}. See pelican --help.`);\n\n // --no-auth, or PELICAN_AUTH=none, is an explicit choice; the demo asks no one to sign in on its own.\n const noAuth = config.auth === 'none';\n if (values.demo) {\n config.demo = true;\n const { prepareDemo } = await import('./demo.js');\n await prepareDemo();\n if (!values.name) config.machine = 'demo';\n }\n\n const local = listensLocally(config.host);\n if (config.demo) {\n // The demo holds nothing private, but it signs no one in: beyond this computer only when asked to.\n if (!local && !noAuth) throw new Error('The demo signs no one in, so it listens on 127.0.0.1 only. Pass --no-auth to show it on your network.');\n config.auth = 'none';\n }\n const { accessToken, authMode, loadAuth } = await import('./auth.js');\n await loadAuth();\n\n const { configWarnings } = await import('./config.js');\n for (const warning of configWarnings) console.warn(`pelican: ${warning}`);\n\n const { startServer } = await import('./server.js');\n const { url } = await startServer();\n const shown = url.replace('://127.0.0.1:', '://localhost:');\n const token = accessToken();\n const mode = authMode();\n // The link with the token is a standing key: shown on a terminal, never written to a log\n // (a service's output file, the journal). `pelican token` prints it on demand.\n const interactive = Boolean(process.stdout.isTTY);\n const open = token && interactive ? tokenLink(shown, token) : shown;\n const protection = config.demo ? 'demo: made-up sessions, nothing runs'\n : mode === 'password' ? 'browsers sign in with your password'\n : mode === 'token' ? (interactive ? 'the link signs a browser in (`pelican token` prints it again)' : 'browsers sign in with the link `pelican token` prints')\n : local ? 'no sign-in (--no-auth)' : 'no sign-in (--no-auth): anyone who reaches this address can run commands as you';\n console.log(`\\n Pelican ${pkg.version} \u00B7 ${config.machine}\\n ${open}\\n ${protection}\\n`);\n if (!config.webDir) console.log(' The web app is not built: run `npm run build` (or use `npm run dev`).\\n');\n // Listening beyond this computer: the address a phone on the same network can open, as a QR code.\n if (!local) {\n const { qrTerminal } = await import('./qr.js');\n const lan = Object.values(os.networkInterfaces()).flat().find((a) => a && a.family === 'IPv4' && !a.internal);\n const base = config.host === '0.0.0.0' || config.host === '::' ? (lan ? `http://${lan.address}:${config.port}/` : undefined) : url;\n const phone = base && token && interactive ? tokenLink(base, token) : base;\n if (phone) console.log(` On your phone: ${phone}\\n${interactive ? `\\n${qrTerminal(phone).replace(/^/gm, ' ')}\\n` : ''}`);\n } else if (!config.demo) {\n console.log(` From your phone: ${DOCS}#phone\\n`);\n }\n if (values.open) await openPelican(shown, token);\n}\n\nmain().catch((error: Error) => {\n console.error(`pelican: ${error.message}`);\n process.exit(1);\n});\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["os", "path", "text", "file", "cookieHeader", "code", "mkdir", "rm", "writeFile", "path", "git", "file", "text", "randomUUID", "mkdir", "readFile", "writeFile", "path", "text", "loaded", "before", "randomBytes", "mkdir", "os", "path", "execFile", "readFile", "path", "promisify", "run", "app", "text", "stat", "str", "file", "open", "readdir", "stat", "path", "text", "file", "CHUNK", "read", "path", "archived", "file", "recent", "info", "before", "result", "stat", "file", "randomUUID", "mkdir", "readdir", "readFile", "rm", "stat", "writeFile", "path", "file", "realpath", "os", "listSessions", "cache", "file", "realpath", "os", "cache", "signedIn", "list", "limits", "execFile", "realpathSync", "path", "promisify", "run", "readdir", "path", "cache", "TTL_MS", "MAX_FILES", "walk", "file", "query", "mkdir", "readFile", "writeFile", "path", "file", "result", "note", "text", "randomUUID", "readFile", "stat", "query", "text", "run", "isDirectory", "file", "cwd", "result", "index", "waiting", "open", "readdir", "realpath", "stat", "path", "file", "data", "readFile", "realpath", "stat", "path", "text", "file", "image", "run", "before", "keys", "limits", "result", "token", "text", "subscriptions", "text", "text", "init_qr", "spawn", "mkdir", "rm", "writeFile", "os", "path", "clearPassword", "loadAuth", "setPassword", "password", "token", "accessToken", "authMode", "resetToken", "file", "prepareDemo", "configWarnings", "startServer", "open", "qrTerminal"]
|
|
7
7
|
}
|