@branch-fiction/extension-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-BcwliPFy.mjs","names":["dirname"],"sources":["../src/dev/config.ts","../src/dev/db.ts","../src/dev/fs.ts","../src/dev/proxy.ts","../src/dev/setup-ui.ts","../src/dev/tasks.ts","../src/dev/tokens.ts","../src/dev/server.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\n\nimport { isUseSlotRequirement, type ExtensionManifestV1 } from '../manifest';\nimport type { DevConfig } from './types';\n\nexport function readDevConfig(path: string): DevConfig {\n if (!existsSync(path)) return {};\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as DevConfig;\n } catch {\n return {};\n }\n}\n\nexport function writeDevConfig(path: string, config: DevConfig): void {\n writeFileSync(path, JSON.stringify(config, null, 2) + '\\n');\n}\n\nexport type DevConfigStatus = {\n ok: boolean;\n missing: string[];\n};\n\n// ensure every provider requirement supplies a binding (with API key when needed)\nexport function checkDevConfig(\n manifest: ExtensionManifestV1,\n config: DevConfig\n): DevConfigStatus {\n const missing: string[] = [];\n const reqs = manifest.providers ?? [];\n for (const req of reqs) {\n const binding = config.providers?.[req.key];\n if (!binding) {\n missing.push(`providers.${req.key}`);\n continue;\n }\n if (isUseSlotRequirement(req)) {\n if (binding.kind !== 'useSlot') {\n missing.push(`providers.${req.key} (expected useSlot binding)`);\n continue;\n }\n if (!binding.providerType || !binding.modelKey || !binding.baseURL) {\n missing.push(`providers.${req.key} (incomplete useSlot binding)`);\n }\n if (binding.auth?.kind !== 'none' && !binding.apiKey) {\n missing.push(`providers.${req.key}.apiKey`);\n }\n } else {\n if (binding.kind !== 'options') {\n missing.push(`providers.${req.key} (expected options binding)`);\n continue;\n }\n const opt = req.options[binding.useIndex];\n if (!opt) {\n missing.push(`providers.${req.key}.useIndex out of range`);\n continue;\n }\n if (opt.auth.kind !== 'none' && !binding.apiKey) {\n missing.push(`providers.${req.key}.apiKey`);\n }\n }\n }\n return { ok: missing.length === 0, missing };\n}\n","import Database, { type Database as SqliteDB } from 'better-sqlite3';\n\nlet cached: { path: string; db: SqliteDB } | null = null;\n\nfunction open(path: string): SqliteDB {\n if (cached && cached.path === path) return cached.db;\n if (cached) cached.db.close();\n const db = new Database(path);\n db.pragma('journal_mode = WAL');\n db.pragma('busy_timeout = 5000');\n db.pragma('foreign_keys = ON');\n cached = { path, db };\n return db;\n}\n\nexport type DbQueryRequest = { sql: string; params?: unknown[] };\nexport type DbQueryResponse = { rows: Record<string, unknown>[]; changes: number };\n\nexport function dbQuery(dbPath: string, req: DbQueryRequest): DbQueryResponse {\n const db = open(dbPath);\n const trimmed = req.sql.trimStart().toLowerCase();\n const isQuery =\n trimmed.startsWith('select') ||\n trimmed.startsWith('with') ||\n trimmed.startsWith('pragma') ||\n trimmed.startsWith('explain');\n const stmt = db.prepare(req.sql);\n if (isQuery) {\n const rows = stmt.all(...(req.params ?? [])) as Record<string, unknown>[];\n return { rows, changes: 0 };\n }\n const info = stmt.run(...(req.params ?? []));\n return { rows: [], changes: Number(info.changes ?? 0) };\n}\n","import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, join, normalize, relative } from 'node:path';\n\nexport function safeJoin(root: string, rel: string): string {\n if (rel.startsWith('/') || isAbsolute(rel)) {\n throw new Error(`absolute path not allowed: ${rel}`);\n }\n const joined = normalize(join(root, rel));\n const inside = relative(root, joined);\n if (inside.startsWith('..') || isAbsolute(inside)) {\n throw new Error(`path escapes assets dir: ${rel}`);\n }\n return joined;\n}\n\nexport function fsRead(assetsDir: string, relPath: string): { bytesBase64: string } {\n const path = safeJoin(assetsDir, relPath);\n const bytes = readFileSync(path);\n return { bytesBase64: bytes.toString('base64') };\n}\n\nexport function fsWrite(\n assetsDir: string,\n relPath: string,\n bytesBase64: string\n): { ok: true } {\n const path = safeJoin(assetsDir, relPath);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, Buffer.from(bytesBase64, 'base64'));\n return { ok: true };\n}\n\nexport function fsList(\n assetsDir: string,\n relPath: string | null\n): { name: string; isDirectory: boolean }[] {\n const path = safeJoin(assetsDir, relPath ?? '');\n let entries: string[];\n try {\n entries = readdirSync(path);\n } catch {\n return [];\n }\n return entries.map((name) => {\n let isDirectory = false;\n try {\n isDirectory = statSync(join(path, name)).isDirectory();\n } catch {\n // ignore\n }\n return { name, isDirectory };\n });\n}\n","import type { Context } from 'hono';\n\nimport type { ResolvedProvider } from './tokens';\n\nconst STRIP_HEADERS = new Set([\n 'host',\n 'authorization',\n 'x-goog-api-key',\n 'content-length',\n 'connection'\n]);\n\n// Mirrors `src-tauri/src/extension_proxy.rs`.\nexport async function proxyToProvider(\n c: Context,\n label: string,\n resolved: ResolvedProvider,\n rest: string\n): Promise<Response> {\n const target = buildTargetUrl(resolved.baseURL, rest, c.req.url);\n const outbound = sanitizeHeaders(c.req.raw.headers, resolved);\n if (resolved.apiKey) {\n applyAuth(resolved, outbound, target);\n }\n const init: RequestInit = {\n method: c.req.method,\n headers: outbound,\n redirect: 'manual'\n };\n // Buffer body: undici needs a replayable source and throws on streamed bodies.\n if (c.req.method !== 'GET' && c.req.method !== 'HEAD') {\n init.body = await c.req.raw.arrayBuffer();\n }\n if (resolved.auth.kind === 'body') {\n const rewrite = applyBodyAuth(\n resolved.auth.field,\n resolved.apiKey ?? '',\n c.req.method,\n outbound,\n init.body as ArrayBuffer | undefined\n );\n if (!rewrite.ok) {\n return new Response(rewrite.error, { status: 400 });\n }\n init.body = rewrite.body;\n }\n let upstream: Response;\n try {\n upstream = await fetch(target, init);\n } catch (e) {\n console.error(`[proxy] ${label} ${c.req.method} ${target} -> send error:`, e);\n throw e;\n }\n console.log(`[proxy] ${label} ${c.req.method} ${target} -> ${upstream.status}`);\n // Stream response back unchanged except for hop-by-hop headers.\n const headers = new Headers(upstream.headers);\n headers.delete('content-length');\n headers.delete('content-encoding');\n headers.delete('transfer-encoding');\n return new Response(upstream.body, { status: upstream.status, headers });\n}\n\nfunction buildTargetUrl(baseURL: string, rest: string, reqUrl: string): URL {\n const trimmedBase = baseURL.replace(/\\/+$/, '');\n const trimmedRest = rest.replace(/^\\/+/, '');\n const joined = trimmedRest ? `${trimmedBase}/${trimmedRest}` : trimmedBase;\n const url = new URL(joined);\n // Carry through caller's query string.\n const callerQuery = new URL(reqUrl).search;\n if (callerQuery) {\n const callerParams = new URLSearchParams(callerQuery);\n for (const [k, v] of callerParams) {\n url.searchParams.append(k, v);\n }\n }\n return url;\n}\n\nfunction sanitizeHeaders(incoming: Headers, resolved: ResolvedProvider): Headers {\n const out = new Headers();\n const stripCustom =\n resolved.auth.kind === 'header' ? resolved.auth.header.toLowerCase() : null;\n for (const [name, value] of incoming) {\n const n = name.toLowerCase();\n if (STRIP_HEADERS.has(n)) continue;\n if (stripCustom && n === stripCustom) continue;\n out.append(name, value);\n }\n return out;\n}\n\nfunction applyAuth(resolved: ResolvedProvider, headers: Headers, url: URL): void {\n const apiKey = resolved.apiKey ?? '';\n switch (resolved.auth.kind) {\n case 'none':\n return;\n case 'bearer':\n headers.set('authorization', `Bearer ${apiKey}`);\n return;\n case 'header':\n headers.set(resolved.auth.header, apiKey);\n return;\n case 'queryParam':\n url.searchParams.set(resolved.auth.param, apiKey);\n return;\n case 'body':\n // Body rewriting happens after the request body is buffered.\n return;\n }\n}\n\ntype BodyAuthResult = { ok: true; body: string } | { ok: false; error: string };\n\nfunction applyBodyAuth(\n field: string,\n apiKey: string,\n method: string,\n headers: Headers,\n body: ArrayBuffer | undefined\n): BodyAuthResult {\n if (method === 'GET' || method === 'HEAD' || !body || body.byteLength === 0) {\n return { ok: false, error: 'body auth requires a JSON request body' };\n }\n const contentType = headers.get('content-type') ?? '';\n if (contentType && !/^application\\/json\\b/i.test(contentType)) {\n return { ok: false, error: 'body auth requires application/json content-type' };\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(new TextDecoder().decode(body));\n } catch (e) {\n return { ok: false, error: `body auth: invalid json: ${(e as Error).message}` };\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return { ok: false, error: 'body auth: JSON root must be an object' };\n }\n (parsed as Record<string, unknown>)[field] = apiKey;\n headers.set('content-type', 'application/json');\n return { ok: true, body: JSON.stringify(parsed) };\n}\n","export const SETUP_UI_HTML = String.raw`<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<title>Extension dev setup</title>\n<style>\n :root {\n color-scheme: dark light;\n --fg: #1a1a1a;\n --bg: #fafafa;\n --muted: #707070;\n --border: #ddd;\n --primary: #2658d3;\n --warn: #b04a00;\n --ok: #1a7a3a;\n }\n @media (prefers-color-scheme: dark) {\n :root {\n --fg: #eee;\n --bg: #18181a;\n --muted: #999;\n --border: #333;\n --primary: #6691ff;\n }\n }\n * { box-sizing: border-box; }\n body {\n font: 14px/1.45 system-ui, -apple-system, sans-serif;\n margin: 0; padding: 32px 24px; max-width: 760px; margin-inline: auto;\n color: var(--fg); background: var(--bg);\n }\n h1 { font-size: 22px; margin: 0 0 4px; }\n h2 { font-size: 16px; margin: 24px 0 8px; }\n p.sub { color: var(--muted); margin: 0 0 24px; }\n .card { border: 1px solid var(--border); border-radius: 8px; padding: 16px; margin: 12px 0; }\n .row { display: grid; grid-template-columns: 160px 1fr; gap: 12px; margin: 8px 0; align-items: center; }\n label { color: var(--muted); }\n input, select, textarea {\n font: inherit; padding: 6px 8px; border: 1px solid var(--border);\n border-radius: 4px; background: var(--bg); color: var(--fg); width: 100%;\n }\n input[type=checkbox] { width: auto; }\n button {\n font: inherit; padding: 8px 14px; border-radius: 4px; border: 1px solid var(--primary);\n background: var(--primary); color: white; cursor: pointer;\n }\n button.secondary { background: transparent; color: var(--primary); }\n button:disabled, button.secondary:disabled {\n background: transparent; color: var(--muted); border-color: var(--border);\n cursor: not-allowed; opacity: 0.7;\n }\n .small { font-size: 12px; color: var(--muted); }\n code { font: 12px/1.4 ui-monospace, \"SF Mono\", Menlo, monospace; padding: 1px 4px;\n background: var(--border); border-radius: 3px; }\n .actions { display: flex; gap: 8px; margin-top: 24px; }\n .err { color: var(--warn); margin: 8px 0; }\n</style>\n</head>\n<body>\n<h1>Extension dev setup</h1>\n<p class=\"sub\">Configure provider bindings + pick a book. Saved to <code>dev.config.json</code> in your extension dir.</p>\n<div id=\"root\">Loading…</div>\n<script type=\"module\">\nconst root = document.getElementById('root');\n\nasync function fetchStatus() {\n const r = await fetch('/__dev__/api/status');\n if (!r.ok) throw new Error('status: ' + r.status);\n return r.json();\n}\n\nfunction inputRow(label, value, onInput, opts = {}) {\n const wrap = document.createElement('div');\n wrap.className = 'row';\n const lab = document.createElement('label');\n lab.textContent = label;\n const input = document.createElement('input');\n input.type = opts.type ?? 'text';\n if (opts.placeholder) input.placeholder = opts.placeholder;\n input.value = value ?? '';\n input.addEventListener('input', () => onInput(input.value));\n wrap.append(lab, input);\n return wrap;\n}\n\nfunction selectRow(label, value, options, onChange) {\n const wrap = document.createElement('div');\n wrap.className = 'row';\n const lab = document.createElement('label');\n lab.textContent = label;\n const sel = document.createElement('select');\n for (const opt of options) {\n const o = document.createElement('option');\n o.value = opt.value;\n o.textContent = opt.label;\n if (opt.value === value) o.selected = true;\n sel.appendChild(o);\n }\n sel.addEventListener('change', () => onChange(sel.value));\n wrap.append(lab, sel);\n return wrap;\n}\n\nconst PROVIDER_TYPES = [\n { value: 'google_gemini', label: 'Google Gemini', baseURL: 'https://generativelanguage.googleapis.com/v1beta', auth: { kind: 'header', header: 'x-goog-api-key' } },\n { value: 'openai', label: 'OpenAI', baseURL: 'https://api.openai.com/v1', auth: { kind: 'bearer' } },\n { value: 'anthropic', label: 'Anthropic', baseURL: 'https://api.anthropic.com', auth: { kind: 'header', header: 'x-api-key' } },\n { value: 'openrouter', label: 'OpenRouter', baseURL: 'https://openrouter.ai/api/v1', auth: { kind: 'bearer' } },\n { value: 'xai', label: 'xAI', baseURL: 'https://api.x.ai/v1', auth: { kind: 'bearer' } },\n { value: 'fal', label: 'Fal', baseURL: 'https://fal.run', auth: { kind: 'bearer', headerPrefix: 'Key' } },\n { value: 'ollama', label: 'Ollama', baseURL: 'http://localhost:11434', auth: { kind: 'none' } },\n { value: 'openai_compatible', label: 'OpenAI Compatible', baseURL: '', auth: { kind: 'bearer' } }\n];\n\nfunction defaultBindingForReq(req, existing) {\n if ('useSlot' in req) {\n const e = existing && existing.kind === 'useSlot' ? existing : null;\n const preset = PROVIDER_TYPES[0];\n return {\n kind: 'useSlot',\n providerType: e?.providerType ?? preset.value,\n modelKey: e?.modelKey ?? '',\n baseURL: e?.baseURL ?? preset.baseURL,\n auth: e?.auth ?? preset.auth,\n apiKey: e?.apiKey ?? '',\n reasoning: e?.reasoning\n };\n }\n const e = existing && existing.kind === 'options' ? existing : null;\n return {\n kind: 'options',\n useIndex: e?.useIndex ?? 0,\n fullURL: e?.fullURL,\n apiKey: e?.apiKey ?? ''\n };\n}\n\nfunction renderRequirement(req, binding, onChange) {\n const card = document.createElement('div');\n card.className = 'card';\n const h = document.createElement('h2');\n h.textContent = req.role ? req.role + ' (' + req.key + ')' : req.key;\n card.appendChild(h);\n\n if ('useSlot' in req) {\n const small = document.createElement('p');\n small.className = 'small';\n small.textContent = 'useSlot: ' + req.useSlot + ' — pick any provider+model that fits this slot.';\n card.appendChild(small);\n\n card.appendChild(selectRow('Provider type', binding.providerType,\n PROVIDER_TYPES.map((p) => ({ value: p.value, label: p.label })),\n (v) => {\n const preset = PROVIDER_TYPES.find((p) => p.value === v);\n binding.providerType = v;\n if (preset) {\n binding.baseURL = preset.baseURL;\n binding.auth = preset.auth;\n }\n onChange();\n }));\n card.appendChild(inputRow('Model id', binding.modelKey, (v) => { binding.modelKey = v; onChange(); }, { placeholder: 'e.g. gemini-2.5-flash' }));\n card.appendChild(inputRow('Base URL', binding.baseURL, (v) => { binding.baseURL = v; onChange(); }));\n card.appendChild(inputRow('API key', binding.apiKey, (v) => { binding.apiKey = v; onChange(); }, { type: 'password' }));\n } else {\n card.appendChild(selectRow('Option', String(binding.useIndex),\n req.options.map((o, i) => ({ value: String(i), label: (o.providerName ?? '') + ' ' + (o.baseURL ?? o.fullURL) + (o.model ? ' / ' + o.model : '') })),\n (v) => { binding.useIndex = Number(v); onChange(); }));\n const opt = req.options[binding.useIndex];\n if (opt && 'fullURL' in opt && opt.fullURL) {\n card.appendChild(inputRow('Endpoint URL', binding.fullURL ?? opt.fullURL, (v) => { binding.fullURL = v; onChange(); }));\n }\n if (opt && opt.auth.kind !== 'none') {\n card.appendChild(inputRow('API key', binding.apiKey, (v) => { binding.apiKey = v; onChange(); }, { type: 'password' }));\n }\n }\n return card;\n}\n\nfunction renderConfigField(field, value, onChange) {\n const wrap = document.createElement('div');\n if (field.type === 'boolean') {\n wrap.className = 'row';\n const lab = document.createElement('label');\n lab.textContent = field.label;\n const cb = document.createElement('input');\n cb.type = 'checkbox';\n cb.checked = !!value;\n cb.addEventListener('change', () => onChange(cb.checked));\n wrap.append(lab, cb);\n } else if (field.type === 'select') {\n wrap.appendChild(selectRow(field.label, String(value ?? field.default ?? ''),\n field.options.map((o) => ({ value: o.value, label: o.label })),\n (v) => onChange(v)));\n } else {\n wrap.appendChild(inputRow(field.label, value ?? field.default ?? '', (v) => onChange(v), { placeholder: field.placeholder }));\n }\n return wrap;\n}\n\nlet manifest, config;\nlet books = [];\n\nasync function saveConfig() {\n const r = await fetch('/__dev__/api/save', {\n method: 'POST', headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(config)\n });\n if (!r.ok) throw new Error(await r.text());\n}\n\nasync function init() {\n let status;\n try {\n status = await fetchStatus();\n } catch (e) {\n root.innerHTML = '<div class=\"err\">Failed to load status: ' + e.message + '</div>';\n return;\n }\n\n manifest = status.manifest;\n config = status.config ?? {};\n config.providers ??= {};\n config.config ??= {};\n books = status.books ?? [];\n\n for (const req of manifest.providers ?? []) {\n config.providers[req.key] = defaultBindingForReq(req, config.providers[req.key]);\n }\n\n const ready = status.ok && books.length > 0 && !!config.bookId\n && books.some((b) => b.id === config.bookId);\n\n // Auto-launch when Vite redirected us here (?auto=1) and there's nothing\n // left to configure. Manual visits always show the wizard.\n const params = new URLSearchParams(window.location.search);\n if (params.has('auto') && ready) {\n root.innerHTML = '<p class=\"small\">Launching…</p>';\n try {\n const r = await fetch('/__dev__/api/launch-url');\n const j = await r.json().catch(() => ({}));\n if (r.ok && j.ok !== false && j.url) {\n window.location.replace(j.url);\n return;\n }\n console.warn('[extension-dev] auto-launch failed:', j.error ?? r.statusText);\n } catch (e) {\n console.warn('[extension-dev] auto-launch failed:', e);\n }\n }\n\n render();\n}\n\nlet darkMode = false;\n\nfunction render() {\n root.innerHTML = '';\n\n if ((manifest.providers ?? []).length > 0) {\n const head = document.createElement('h2');\n head.textContent = 'Provider requirements';\n root.appendChild(head);\n for (const req of manifest.providers ?? []) {\n root.appendChild(renderRequirement(req, config.providers[req.key], () => undefined));\n }\n }\n\n if ((manifest.config ?? []).length > 0) {\n const head = document.createElement('h2');\n head.textContent = 'Extension config';\n root.appendChild(head);\n const card = document.createElement('div');\n card.className = 'card';\n for (const field of manifest.config) {\n card.appendChild(renderConfigField(field, config.config[field.key], (v) => {\n config.config[field.key] = v;\n }));\n }\n root.appendChild(card);\n }\n\n const bookHead = document.createElement('h2');\n bookHead.textContent = 'Book context';\n const bookCard = document.createElement('div');\n bookCard.className = 'card';\n if (books.length === 0) {\n bookCard.textContent = 'No books found in your app DB. Import a book in Branch Fiction first, then reload.';\n } else {\n if (!config.bookId || !books.some((b) => b.id === config.bookId)) {\n config.bookId = books[0].id;\n }\n bookCard.appendChild(selectRow('Book', config.bookId,\n books.map((b) => ({ value: b.id, label: b.title + ' (' + b.id + ')' })),\n (v) => { config.bookId = v; }));\n }\n root.append(bookHead, bookCard);\n\n const darkRow = document.createElement('div');\n darkRow.className = 'row';\n const darkLab = document.createElement('label');\n darkLab.style.display = 'flex';\n darkLab.style.alignItems = 'center';\n darkLab.style.gap = '8px';\n darkLab.style.cursor = 'pointer';\n const darkCb = document.createElement('input');\n darkCb.type = 'checkbox';\n darkCb.checked = darkMode;\n darkCb.addEventListener('change', () => { darkMode = darkCb.checked; });\n darkLab.append(darkCb, document.createTextNode('Dark mode?'));\n darkRow.appendChild(darkLab);\n root.appendChild(darkRow);\n\n const actions = document.createElement('div');\n actions.className = 'actions';\n const launch = document.createElement('button');\n launch.textContent = 'Save & launch';\n launch.disabled = books.length === 0;\n actions.append(launch);\n root.appendChild(actions);\n\n const err = document.createElement('div');\n err.className = 'err';\n root.appendChild(err);\n\n launch.addEventListener('click', async () => {\n err.textContent = '';\n if (!config.bookId) {\n err.textContent = 'Pick a book before launching.';\n return;\n }\n try {\n await saveConfig();\n } catch (e) {\n err.textContent = 'Save failed: ' + e.message;\n return;\n }\n const r = await fetch('/__dev__/api/launch-url');\n const j = await r.json().catch(() => ({}));\n if (!r.ok || j.ok === false) {\n err.textContent = 'Launch failed: ' + (j.error ?? r.statusText);\n return;\n }\n const u = new URL(j.url, window.location.href);\n if (darkMode) u.searchParams.set('dark', '1');\n window.location.href = u.toString();\n });\n}\n\nvoid init();\n</script>\n</body>\n</html>`;\n","import { type ChildProcess, spawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { createInterface } from 'node:readline';\n\nimport type { ProviderHandle } from './tokens';\n\nexport type StartTaskRequest = { task: string; payload?: unknown };\n\nexport type WorkerEvent =\n | { kind: 'log'; args: unknown[] }\n | { kind: 'result'; value: unknown }\n | { kind: 'error'; message: string };\n\nexport type WorkerHandle = {\n taskId: string;\n events: AsyncIterable<WorkerEvent>;\n cancel(): void;\n};\n\nexport type SpawnArgs = {\n extensionId: string;\n bookId: string | null;\n extensionDir: string;\n workerEntry: string;\n extensionHostBundle: string;\n denoBin: string;\n // root that contains both the SQLite db and the assets dir.\n // used for Deno `--allow-read/write`\n extensionDataRoot: string;\n // passed to the worker as its dataDir.\n assetsDir: string;\n dbPath: string;\n providers: Record<string, ProviderHandle>;\n config: Record<string, unknown>;\n netAllowlist: string[];\n hostPort: number;\n task: string;\n payload: unknown;\n controllers: Map<string, AbortController>;\n};\n\nexport function spawnWorker(args: SpawnArgs): WorkerHandle {\n const taskId = `ptk_${randomUUID().replace(/-/g, '')}`;\n const ac = new AbortController();\n args.controllers.set(taskId, ac);\n\n const allowRead = [\n args.extensionDataRoot,\n args.extensionDir,\n dirname(args.extensionHostBundle)\n ]\n .filter(Boolean)\n .join(',');\n const allowWrite = args.extensionDataRoot;\n const allowNet = [\n `127.0.0.1:${args.hostPort}`,\n `localhost:${args.hostPort}`,\n ...args.netAllowlist\n ].join(',');\n\n const denoArgs = [\n 'run',\n '--no-config',\n `--allow-read=${allowRead}`,\n `--allow-write=${allowWrite}`,\n `--allow-net=${allowNet}`,\n args.extensionHostBundle\n ];\n\n const child = spawn(args.denoBin, denoArgs, {\n stdio: ['pipe', 'pipe', 'pipe'],\n signal: ac.signal\n });\n\n const events = pump(child, args, ac, args.controllers, taskId);\n\n return {\n taskId,\n events,\n cancel: () => ac.abort()\n };\n}\n\nfunction dirname(p: string): string {\n const idx = p.lastIndexOf('/');\n return idx === -1 ? p : p.slice(0, idx);\n}\n\nconst INIT_REQ_ID = 1;\nconst RUN_REQ_ID = 2;\n\nasync function* pump(\n child: ChildProcess,\n args: SpawnArgs,\n _ac: AbortController,\n controllers: Map<string, AbortController>,\n taskId: string\n): AsyncGenerator<WorkerEvent> {\n const buffer: WorkerEvent[] = [];\n let resolve: (() => void) | null = null;\n let done = false;\n let error: Error | null = null;\n\n const onErr = (e: Error) => {\n if (e.name === 'AbortError') return;\n error = e;\n done = true;\n resolve?.();\n };\n\n const initReq =\n JSON.stringify({\n jsonrpc: '2.0',\n id: INIT_REQ_ID,\n method: 'init',\n params: [\n {\n extensionId: args.extensionId,\n bookId: args.bookId,\n providers: serializableProviders(args.providers),\n config: args.config,\n dbPath: args.dbPath,\n dataDir: args.assetsDir,\n extensionWorkerPath: `${args.extensionDir.replace(/\\/+$/, '')}/${args.workerEntry.replace(/^\\.?\\/+/, '')}`\n }\n ]\n }) + '\\n';\n\n child.stdin?.write(initReq);\n\n if (child.stdout) {\n const rl = createInterface({ input: child.stdout });\n rl.on('line', (line) => {\n const trimmed = line.trim();\n if (!trimmed) return;\n let msg: Record<string, unknown>;\n try {\n msg = JSON.parse(trimmed);\n } catch {\n return;\n }\n const id = typeof msg.id === 'number' ? msg.id : null;\n if (id === null) {\n if (msg.method === 'host.log') {\n const params = (msg.params as { args?: unknown[] }) ?? {};\n buffer.push({ kind: 'log', args: params.args ?? [] });\n resolve?.();\n }\n return;\n }\n if (id === INIT_REQ_ID) {\n if (msg.error) {\n buffer.push({\n kind: 'error',\n message: errorMessage(msg.error) || 'init failed'\n });\n done = true;\n resolve?.();\n return;\n }\n const runReq =\n JSON.stringify({\n jsonrpc: '2.0',\n id: RUN_REQ_ID,\n method: 'runTask',\n params: [{ task: args.task, payload: args.payload }]\n }) + '\\n';\n child.stdin?.write(runReq);\n } else if (id === RUN_REQ_ID) {\n if (msg.error) {\n buffer.push({ kind: 'error', message: errorMessage(msg.error) });\n } else {\n const result = (msg.result as { result?: unknown } | undefined)?.result ?? null;\n buffer.push({ kind: 'result', value: result });\n }\n done = true;\n resolve?.();\n }\n });\n }\n\n if (child.stderr) {\n child.stderr.on('data', (chunk: Buffer) => {\n const text = chunk.toString('utf8').trimEnd();\n if (text) console.error(`[extension-host:${args.extensionId}]`, text);\n });\n }\n\n child.on('error', onErr);\n child.on('exit', () => {\n if (!done) {\n buffer.push({\n kind: 'error',\n message: 'extension worker exited without returning a result'\n });\n done = true;\n }\n resolve?.();\n });\n\n try {\n while (true) {\n while (buffer.length > 0) {\n const ev = buffer.shift()!;\n yield ev;\n if (ev.kind === 'result' || ev.kind === 'error') return;\n }\n if (done) {\n if (error) throw error;\n return;\n }\n await new Promise<void>((r) => {\n resolve = r;\n });\n resolve = null;\n }\n } finally {\n controllers.delete(taskId);\n if (!child.killed) {\n try {\n child.kill();\n } catch {\n /* ignore */\n }\n }\n }\n}\n\nfunction serializableProviders(\n providers: Record<string, ProviderHandle>\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(providers)) {\n out[k] = { ...v };\n }\n return out;\n}\n\nfunction errorMessage(err: unknown): string {\n if (err && typeof err === 'object' && 'message' in err) {\n return String((err as { message: unknown }).message);\n }\n return 'unknown error';\n}\n","import { randomUUID } from 'node:crypto';\n\nimport type { ThinkingLevel } from '@earendil-works/pi-ai';\n\nimport {\n isUseSlotRequirement,\n optionURL,\n type ExtensionManifestV1,\n type ExtensionProviderRequirement\n} from '../manifest';\nimport type { ProviderAuthShape } from '../types';\nimport type { DevConfig, DevProviderBinding } from './types';\n\nexport type ResolvedProvider = {\n baseURL: string;\n auth: ProviderAuthShape;\n apiKey?: string;\n};\n\nexport type ProviderHandle = {\n baseURL: string;\n proxyBaseURL: string;\n modelKey?: string;\n providerType?: string;\n reasoning?: ThinkingLevel;\n};\n\ntype ProxySession = { providerKey: string };\n\nclass TokenRegistry {\n private dataToken: string | null = null;\n private proxyTokens = new Map<string, ProxySession>();\n private tokenByKey = new Map<string, string>();\n private resolved = new Map<string, ResolvedProvider>();\n private handles: Record<string, ProviderHandle> = {};\n\n mintDataToken(): string {\n if (!this.dataToken) {\n this.dataToken = `pdt_${randomUUID().replace(/-/g, '')}`;\n }\n return this.dataToken;\n }\n\n getDataToken(): string {\n return this.mintDataToken();\n }\n\n buildProviders(\n manifest: ExtensionManifestV1,\n config: DevConfig,\n hostOrigin: string\n ): Record<string, ProviderHandle> {\n this.resolved.clear();\n this.handles = {};\n const reqs = manifest.providers ?? [];\n for (const req of reqs) {\n const binding = config.providers?.[req.key];\n if (!binding) continue;\n const resolved = resolveBinding(req, binding);\n if (!resolved) continue;\n let token = this.tokenByKey.get(req.key);\n if (!token) {\n token = `pps_${randomUUID().replace(/-/g, '')}`;\n this.tokenByKey.set(req.key, token);\n this.proxyTokens.set(token, { providerKey: req.key });\n }\n this.resolved.set(req.key, resolved);\n const proxyBaseURL = `${hostOrigin}/extension-providers/${token}/${encodeURIComponent(req.key)}`;\n const handle: ProviderHandle = {\n baseURL: resolved.baseURL,\n proxyBaseURL\n };\n if (isUseSlotRequirement(req) && binding.kind === 'useSlot') {\n handle.modelKey = binding.modelKey;\n handle.providerType = binding.providerType;\n if (binding.reasoning) handle.reasoning = binding.reasoning;\n } else if (binding.kind === 'options' && !isUseSlotRequirement(req)) {\n const opt = req.options[binding.useIndex];\n if (opt?.model) handle.modelKey = opt.model;\n }\n this.handles[req.key] = handle;\n }\n return this.handles;\n }\n\n resolveProxyForKey(token: string, providerKey: string): ResolvedProvider | null {\n const session = this.proxyTokens.get(token);\n if (!session || session.providerKey !== providerKey) return null;\n return this.resolved.get(providerKey) ?? null;\n }\n\n isValidDataToken(token: string): boolean {\n return this.dataToken !== null && token === this.dataToken;\n }\n}\n\nfunction resolveBinding(\n req: ExtensionProviderRequirement,\n binding: DevProviderBinding\n): ResolvedProvider | null {\n if (isUseSlotRequirement(req)) {\n if (binding.kind !== 'useSlot') return null;\n return {\n baseURL: binding.baseURL,\n auth: binding.auth,\n apiKey: binding.apiKey\n };\n }\n if (binding.kind !== 'options') return null;\n const opt = req.options[binding.useIndex];\n if (!opt) return null;\n // Author can override `fullURL` placeholders via dev.config.json.\n const baseURL = binding.fullURL ?? optionURL(opt);\n return {\n baseURL,\n auth: opt.auth,\n apiKey: binding.apiKey\n };\n}\n\nexport const registry = new TokenRegistry();\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport Database from 'better-sqlite3';\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { streamSSE } from 'hono/streaming';\n\nimport { type ExtensionManifestV1, validateManifest } from '../manifest';\nimport { extensionSdkSource } from '../sdk-source';\nimport { checkDevConfig, readDevConfig, writeDevConfig } from './config';\nimport { dbQuery } from './db';\nimport { fsList, fsRead, fsWrite, safeJoin } from './fs';\nimport { proxyToProvider } from './proxy';\nimport { SETUP_UI_HTML } from './setup-ui';\nimport { spawnWorker } from './tasks';\nimport { registry } from './tokens';\nimport type { DevConfig, DevRuntimeOptions } from './types';\n\nexport type DevServer = {\n app: Hono;\n};\n\nconst ASSET_MIME: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n gif: 'image/gif',\n svg: 'image/svg+xml',\n mp4: 'video/mp4',\n webm: 'video/webm'\n};\n\nexport function createDevServer(opts: DevRuntimeOptions): DevServer {\n const app = new Hono();\n app.use('*', cors({ origin: '*', allowHeaders: ['*'], allowMethods: ['*'] }));\n\n const dataDir = opts.dataDir;\n const configPath = opts.configPath ?? join(opts.extensionDir, 'dev.config.json');\n const dbPath = opts.dbPath;\n const assetsDir = opts.assetsDir;\n const taskControllers = new Map();\n // singleton claim key -> owning task id, mirrors the Tauri host's gate\n const singletonTasks = new Map<string, string>();\n const hostOrigin = `http://localhost:${opts.hostPort}`;\n\n function loadManifest(): ExtensionManifestV1 {\n const raw = readFileSync(join(opts.extensionDir, 'manifest.json'), 'utf8');\n const manifest = JSON.parse(raw) as ExtensionManifestV1;\n validateManifest(manifest);\n return manifest;\n }\n\n function listBooks(): { id: string; title: string }[] {\n if (!existsSync(dbPath)) return [];\n const db = new Database(dbPath, { readonly: true, fileMustExist: true });\n try {\n return db\n .prepare(\"SELECT id, title FROM books WHERE status = 'completed' ORDER BY title\")\n .all() as { id: string; title: string }[];\n } catch {\n return [];\n } finally {\n db.close();\n }\n }\n\n app.get('/extension-sdk.js', (c) => {\n c.header('Content-Type', 'application/javascript; charset=utf-8');\n return c.body(extensionSdkSource());\n });\n\n app.get('/extension-data/:token/context', (c) => {\n const token = c.req.param('token');\n if (!registry.isValidDataToken(token)) return c.text('unauthorized', 401);\n const manifest = loadManifest();\n const config = readDevConfig(configPath);\n const providers = registry.buildProviders(manifest, config, hostOrigin);\n return c.json({\n extensionId: manifest.id,\n bookId: config.bookId ?? null,\n providers,\n config: config.config ?? {}\n });\n });\n\n app.post('/extension-data/:token/db/query', async (c) => {\n const token = c.req.param('token');\n if (!registry.isValidDataToken(token)) return c.text('unauthorized', 401);\n const body = (await c.req.json()) as { sql: string; params?: unknown[] };\n try {\n return c.json(dbQuery(dbPath, body));\n } catch (e) {\n return c.text(`db.query: ${(e as Error).message}`, 400);\n }\n });\n\n app.post('/extension-data/:token/fs/read', async (c) => {\n const token = c.req.param('token');\n if (!registry.isValidDataToken(token)) return c.text('unauthorized', 401);\n const body = (await c.req.json()) as { relPath: string };\n try {\n return c.json(fsRead(assetsDir, body.relPath));\n } catch (e) {\n return c.text(`fs.read: ${(e as Error).message}`, 400);\n }\n });\n app.post('/extension-data/:token/fs/write', async (c) => {\n const token = c.req.param('token');\n if (!registry.isValidDataToken(token)) return c.text('unauthorized', 401);\n const body = (await c.req.json()) as { relPath: string; bytesBase64: string };\n try {\n return c.json(fsWrite(assetsDir, body.relPath, body.bytesBase64));\n } catch (e) {\n return c.text(`fs.write: ${(e as Error).message}`, 400);\n }\n });\n app.post('/extension-data/:token/fs/list', async (c) => {\n const token = c.req.param('token');\n if (!registry.isValidDataToken(token)) return c.text('unauthorized', 401);\n const body = (await c.req.json().catch(() => ({}))) as { relPath?: string | null };\n return c.json(fsList(assetsDir, body.relPath ?? null));\n });\n\n app.post('/extension-data/:token/task/start', async (c) => {\n const token = c.req.param('token');\n if (!registry.isValidDataToken(token)) return c.text('unauthorized', 401);\n const body = (await c.req.json()) as {\n task: string;\n payload?: unknown;\n singletonKey?: string | null;\n };\n const manifest = loadManifest();\n const workerEntry = manifest.path?.worker;\n if (!workerEntry) return c.text('extension has no path.worker entry', 400);\n const config = readDevConfig(configPath);\n const providers = registry.buildProviders(manifest, config, hostOrigin);\n\n const singletonKey = body.singletonKey\n ? `${manifest.id}|${config.bookId ?? ''}|${body.singletonKey}`\n : null;\n if (singletonKey && singletonTasks.has(singletonKey)) {\n return c.text(`task already running: ${body.singletonKey}`, 409);\n }\n\n const handle = spawnWorker({\n extensionId: manifest.id,\n bookId: config.bookId ?? null,\n extensionDir: opts.extensionDir,\n workerEntry,\n extensionHostBundle: opts.extensionHostBundle,\n denoBin: opts.denoBin,\n extensionDataRoot: dataDir,\n assetsDir,\n dbPath,\n providers,\n config: config.config ?? {},\n netAllowlist: manifest.net ?? [],\n hostPort: opts.hostPort,\n task: body.task,\n payload: body.payload ?? null,\n controllers: taskControllers\n });\n if (singletonKey) singletonTasks.set(singletonKey, handle.taskId);\n\n return streamSSE(c, async (stream) => {\n await stream.writeSSE({\n event: 'started',\n data: JSON.stringify({ taskId: handle.taskId })\n });\n try {\n for await (const ev of handle.events) {\n if (ev.kind === 'log') {\n await stream.writeSSE({\n event: 'log',\n data: JSON.stringify({ args: ev.args })\n });\n } else if (ev.kind === 'result') {\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({ value: ev.value })\n });\n return;\n } else {\n await stream.writeSSE({\n event: 'error',\n data: JSON.stringify({ message: ev.message })\n });\n return;\n }\n }\n } finally {\n handle.cancel();\n if (singletonKey && singletonTasks.get(singletonKey) === handle.taskId) {\n singletonTasks.delete(singletonKey);\n }\n }\n });\n });\n\n app.post('/extension-data/:token/task/:taskId/cancel', (c) => {\n const token = c.req.param('token');\n if (!registry.isValidDataToken(token)) return c.text('unauthorized', 401);\n const taskId = c.req.param('taskId');\n const ac = taskControllers.get(taskId);\n if (ac) ac.abort();\n return c.body(null, 204);\n });\n\n app.all('/extension-providers/:token/:providerKey/*', async (c) => {\n const token = c.req.param('token');\n const providerKey = c.req.param('providerKey');\n const path = c.req.path;\n const prefix = `/extension-providers/${token}/${providerKey}/`;\n const rest = path.startsWith(prefix) ? path.slice(prefix.length) : '';\n const found = registry.resolveProxyForKey(token, providerKey);\n if (!found) {\n console.warn(\n `[proxy] (unknown token ${token.slice(0, 12)}… or key ${providerKey}) ${c.req.method} /${rest} -> 401`\n );\n return c.text('unknown proxy token', 401);\n }\n return proxyToProvider(c, providerKey, found, rest);\n });\n\n app.get('/extension-assets/:extensionId/*', (c) => {\n if (c.req.param('extensionId') !== loadManifest().id) {\n return c.text('unknown extension', 404);\n }\n const marker = '/assets/';\n const idx = c.req.path.indexOf(marker);\n const relPath =\n idx >= 0 ? decodeURIComponent(c.req.path.slice(idx + marker.length)) : '';\n if (!relPath) return c.text('missing path', 400);\n let fullPath: string;\n try {\n fullPath = safeJoin(assetsDir, relPath);\n } catch (e) {\n return c.text((e as Error).message, 400);\n }\n if (!existsSync(fullPath)) return c.text('not found', 404);\n const ext = relPath.slice(relPath.lastIndexOf('.') + 1).toLowerCase();\n const mime = ASSET_MIME[ext] ?? 'application/octet-stream';\n c.header('Content-Type', mime);\n c.header('Cache-Control', 'no-cache');\n return c.body(readFileSync(fullPath));\n });\n\n // Mirrors the Tauri `public_asset_handler` route so extension code that\n // builds `/extension-data/<id>/assets/...` URLs works the same in dev.\n app.get('/extension-data/:extensionId/assets/*', (c) => {\n if (c.req.param('extensionId') !== loadManifest().id) {\n return c.text('unknown extension', 404);\n }\n const marker = '/assets/';\n const idx = c.req.path.indexOf(marker);\n const relPath =\n idx >= 0 ? decodeURIComponent(c.req.path.slice(idx + marker.length)) : '';\n if (!relPath) return c.text('missing path', 400);\n let fullPath: string;\n try {\n fullPath = safeJoin(assetsDir, relPath);\n } catch (e) {\n return c.text((e as Error).message, 400);\n }\n if (!existsSync(fullPath)) return c.text('not found', 404);\n const ext = relPath.slice(relPath.lastIndexOf('.') + 1).toLowerCase();\n const mime = ASSET_MIME[ext] ?? 'application/octet-stream';\n c.header('Content-Type', mime);\n c.header('Cache-Control', 'no-cache');\n return c.body(readFileSync(fullPath));\n });\n\n app.get('/__dev__/setup', (c) => {\n c.header('Content-Type', 'text/html; charset=utf-8');\n return c.body(SETUP_UI_HTML);\n });\n\n app.get('/__dev__/api/status', (c) => {\n const manifest = loadManifest();\n const config = readDevConfig(configPath);\n const status = checkDevConfig(manifest, config);\n return c.json({\n extensionId: manifest.id,\n manifest,\n config,\n ok: status.ok,\n missing: status.missing,\n configPath,\n books: listBooks()\n });\n });\n\n app.post('/__dev__/api/save', async (c) => {\n const next = (await c.req.json()) as DevConfig;\n writeDevConfig(configPath, next);\n return c.json({ ok: true });\n });\n\n app.get('/__dev__/api/launch-url', (c) => {\n const config = readDevConfig(configPath);\n if (!config.bookId) {\n return c.json({ ok: false, error: 'select a book before launching' }, 400);\n }\n const books = listBooks();\n if (!books.some((b) => b.id === config.bookId)) {\n return c.json(\n {\n ok: false,\n error: `selected book ${config.bookId} is not in the dev DB — re-seed?`\n },\n 400\n );\n }\n const token = registry.mintDataToken();\n const url = `${opts.viteOrigin}?token=${encodeURIComponent(token)}&host=${encodeURIComponent(hostOrigin)}`;\n return c.json({ ok: true, url });\n });\n\n return { app };\n}\n"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ //#region src/types.d.ts
2
+ type ProviderAuthShape = {
3
+ kind: 'none';
4
+ } | {
5
+ kind: 'bearer';
6
+ headerPrefix?: string;
7
+ } | {
8
+ kind: 'header';
9
+ header: string;
10
+ } | {
11
+ kind: 'queryParam';
12
+ param: string;
13
+ } | {
14
+ kind: 'body';
15
+ field: string;
16
+ };
17
+ type Slot = 'piText' | 'piTextLight';
18
+ declare const SLOT_KEYS: readonly Slot[];
19
+ declare const SLOT_LABELS: Record<Slot, string>;
20
+ declare function isKnownSlot(s: string): s is Slot;
21
+ //#endregion
22
+ export { isKnownSlot as a, Slot as i, SLOT_KEYS as n, SLOT_LABELS as r, ProviderAuthShape as t };
23
+ //# sourceMappingURL=types-ZCFYu2MY.d.mts.map
package/dist/vite.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ //#region src/vite-plugin.d.ts
2
+ type BranchFictionExtensionDevOptions = {
3
+ hostPort?: number;
4
+ showDragRegionIndicator?: boolean;
5
+ };
6
+ declare function branchFictionExtensionDev(opts?: BranchFictionExtensionDevOptions): {
7
+ name: string;
8
+ configResolved(config: {
9
+ command: "serve" | "build";
10
+ }): void;
11
+ transformIndexHtml(): ({
12
+ tag: string;
13
+ children: string;
14
+ injectTo: "head";
15
+ attrs?: undefined;
16
+ } | {
17
+ tag: string;
18
+ attrs: {
19
+ class: string;
20
+ };
21
+ children: string;
22
+ injectTo: "body-prepend";
23
+ })[] | undefined;
24
+ config(): {
25
+ server: {
26
+ proxy: {
27
+ '/extension-sdk.js': string;
28
+ '/extension-data': string;
29
+ '/extension-providers': string;
30
+ '/__dev__': string;
31
+ };
32
+ watch: {
33
+ ignored: string[];
34
+ };
35
+ };
36
+ };
37
+ configureServer(server: {
38
+ middlewares: {
39
+ use: (fn: (req: {
40
+ url?: string;
41
+ method?: string;
42
+ headers: Record<string, string | string[] | undefined>;
43
+ }, res: {
44
+ statusCode: number;
45
+ setHeader: (k: string, v: string) => void;
46
+ end: () => void;
47
+ }, next: () => void) => void) => void;
48
+ };
49
+ }): void;
50
+ };
51
+ //#endregion
52
+ export { BranchFictionExtensionDevOptions, branchFictionExtensionDev };
53
+ //# sourceMappingURL=vite.d.ts.map
package/dist/vite.js ADDED
@@ -0,0 +1,83 @@
1
+ //#region src/vite-plugin.ts
2
+ function branchFictionExtensionDev(opts = {}) {
3
+ const target = `http://localhost:${opts.hostPort ?? 1422}`;
4
+ const showDragRegionIndicator = opts.showDragRegionIndicator ?? true;
5
+ let isServe = false;
6
+ return {
7
+ name: "branch-fiction-extension-dev",
8
+ configResolved(config) {
9
+ isServe = config.command === "serve";
10
+ },
11
+ transformIndexHtml() {
12
+ if (!isServe || !showDragRegionIndicator) return;
13
+ return [{
14
+ tag: "style",
15
+ children: `
16
+ .__bf-drag-region-indicator__ {
17
+ position: fixed;
18
+ top: 0;
19
+ left: 0;
20
+ right: 0;
21
+ height: 24px;
22
+ background: repeating-linear-gradient(
23
+ 45deg,
24
+ rgba(0, 0, 0, 0.18),
25
+ rgba(0, 0, 0, 0.18) 8px,
26
+ rgba(0, 0, 0, 0.06) 8px,
27
+ rgba(0, 0, 0, 0.06) 16px
28
+ );
29
+ color: rgba(0, 0, 0, 0.65);
30
+ font: 11px/24px system-ui, -apple-system, sans-serif;
31
+ text-align: center;
32
+ pointer-events: none;
33
+ user-select: none;
34
+ z-index: 2147483647;
35
+ }
36
+ `,
37
+ injectTo: "head"
38
+ }, {
39
+ tag: "div",
40
+ attrs: { class: "__bf-drag-region-indicator__" },
41
+ children: "Reserved for window drag — keep important UI below",
42
+ injectTo: "body-prepend"
43
+ }];
44
+ },
45
+ config() {
46
+ return { server: {
47
+ proxy: {
48
+ "/extension-sdk.js": target,
49
+ "/extension-data": target,
50
+ "/extension-providers": target,
51
+ "/__dev__": target
52
+ },
53
+ watch: { ignored: ["**/src/worker.ts", "**/src/worker/**"] }
54
+ } };
55
+ },
56
+ configureServer(server) {
57
+ server.middlewares.use((req, res, next) => {
58
+ if (req.method && req.method !== "GET" && req.method !== "HEAD") return next();
59
+ const accept = req.headers.accept;
60
+ if (!(Array.isArray(accept) ? accept.join(",") : accept ?? "").includes("text/html")) return next();
61
+ const url = new URL(req.url ?? "/", "http://localhost");
62
+ if (url.pathname.startsWith("/__dev__")) return next();
63
+ const redirectToSetup = () => {
64
+ res.statusCode = 302;
65
+ res.setHeader("Location", "/__dev__/setup?auto=1");
66
+ res.end();
67
+ };
68
+ const token = url.searchParams.get("token");
69
+ if (!token) return redirectToSetup();
70
+ fetch(`${target}/extension-data/${encodeURIComponent(token)}/context`).then((r) => {
71
+ if (r.ok) return next();
72
+ redirectToSetup();
73
+ }).catch(() => {
74
+ next();
75
+ });
76
+ });
77
+ }
78
+ };
79
+ }
80
+ //#endregion
81
+ export { branchFictionExtensionDev };
82
+
83
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.js","names":[],"sources":["../src/vite-plugin.ts"],"sourcesContent":["// Vite plugin that proxies /extension-sdk.js and SDK data routes to the dev runtime's Hono server.\n\nexport type BranchFictionExtensionDevOptions = {\n // Defaults to 1422.\n hostPort?: number;\n // Show a striped overlay at the top of the page during `vite dev` so\n // extension authors don't put important UI under the Tauri window drag region.\n // Defaults to true.\n showDragRegionIndicator?: boolean;\n};\n\nexport function branchFictionExtensionDev(opts: BranchFictionExtensionDevOptions = {}) {\n const hostPort = opts.hostPort ?? 1422;\n const target = `http://localhost:${hostPort}`;\n const showDragRegionIndicator = opts.showDragRegionIndicator ?? true;\n let isServe = false;\n return {\n name: 'branch-fiction-extension-dev',\n configResolved(config: { command: 'serve' | 'build' }) {\n isServe = config.command === 'serve';\n },\n transformIndexHtml() {\n if (!isServe || !showDragRegionIndicator) return;\n return [\n {\n tag: 'style',\n children: `\n .__bf-drag-region-indicator__ {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 24px;\n background: repeating-linear-gradient(\n 45deg,\n rgba(0, 0, 0, 0.18),\n rgba(0, 0, 0, 0.18) 8px,\n rgba(0, 0, 0, 0.06) 8px,\n rgba(0, 0, 0, 0.06) 16px\n );\n color: rgba(0, 0, 0, 0.65);\n font: 11px/24px system-ui, -apple-system, sans-serif;\n text-align: center;\n pointer-events: none;\n user-select: none;\n z-index: 2147483647;\n }\n `,\n injectTo: 'head' as const\n },\n {\n tag: 'div',\n attrs: { class: '__bf-drag-region-indicator__' },\n children: 'Reserved for window drag — keep important UI below',\n injectTo: 'body-prepend' as const\n }\n ];\n },\n config() {\n return {\n server: {\n proxy: {\n '/extension-sdk.js': target,\n '/extension-data': target,\n '/extension-providers': target,\n '/__dev__': target\n },\n // Worker sources are owned by tsdown, not vite\n watch: {\n ignored: ['**/src/worker.ts', '**/src/worker/**']\n }\n }\n };\n },\n configureServer(server: {\n middlewares: {\n use: (\n fn: (\n req: {\n url?: string;\n method?: string;\n headers: Record<string, string | string[] | undefined>;\n },\n res: {\n statusCode: number;\n setHeader: (k: string, v: string) => void;\n end: () => void;\n },\n next: () => void\n ) => void\n ) => void;\n };\n }) {\n // Redirect bare Vite URL visits with no/stale token to the setup UI.\n server.middlewares.use((req, res, next) => {\n if (req.method && req.method !== 'GET' && req.method !== 'HEAD') return next();\n const accept = req.headers.accept;\n const acceptStr = Array.isArray(accept) ? accept.join(',') : (accept ?? '');\n if (!acceptStr.includes('text/html')) return next();\n const url = new URL(req.url ?? '/', 'http://localhost');\n if (url.pathname.startsWith('/__dev__')) return next();\n const redirectToSetup = () => {\n res.statusCode = 302;\n res.setHeader('Location', '/__dev__/setup?auto=1');\n res.end();\n };\n const token = url.searchParams.get('token');\n if (!token) return redirectToSetup();\n fetch(`${target}/extension-data/${encodeURIComponent(token)}/context`)\n .then((r) => {\n if (r.ok) return next();\n redirectToSetup();\n })\n .catch(() => {\n // shrug, we tried\n next();\n });\n });\n }\n };\n}\n"],"mappings":";AAWA,SAAgB,0BAA0B,OAAyC,CAAC,GAAG;CAErF,MAAM,SAAS,oBADE,KAAK,YAAY;CAElC,MAAM,0BAA0B,KAAK,2BAA2B;CAChE,IAAI,UAAU;CACd,OAAO;EACL,MAAM;EACN,eAAe,QAAwC;GACrD,UAAU,OAAO,YAAY;EAC/B;EACA,qBAAqB;GACnB,IAAI,CAAC,WAAW,CAAC,yBAAyB;GAC1C,OAAO,CACL;IACE,KAAK;IACL,UAAU;;;;;;;;;;;;;;;;;;;;;;IAsBV,UAAU;GACZ,GACA;IACE,KAAK;IACL,OAAO,EAAE,OAAO,+BAA+B;IAC/C,UAAU;IACV,UAAU;GACZ,CACF;EACF;EACA,SAAS;GACP,OAAO,EACL,QAAQ;IACN,OAAO;KACL,qBAAqB;KACrB,mBAAmB;KACnB,wBAAwB;KACxB,YAAY;IACd;IAEA,OAAO,EACL,SAAS,CAAC,oBAAoB,kBAAkB,EAClD;GACF,EACF;EACF;EACA,gBAAgB,QAkBb;GAED,OAAO,YAAY,KAAK,KAAK,KAAK,SAAS;IACzC,IAAI,IAAI,UAAU,IAAI,WAAW,SAAS,IAAI,WAAW,QAAQ,OAAO,KAAK;IAC7E,MAAM,SAAS,IAAI,QAAQ;IAE3B,IAAI,EADc,MAAM,QAAQ,MAAM,IAAI,OAAO,KAAK,GAAG,IAAK,UAAU,GAAA,CACzD,SAAS,WAAW,GAAG,OAAO,KAAK;IAClD,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB;IACtD,IAAI,IAAI,SAAS,WAAW,UAAU,GAAG,OAAO,KAAK;IACrD,MAAM,wBAAwB;KAC5B,IAAI,aAAa;KACjB,IAAI,UAAU,YAAY,uBAAuB;KACjD,IAAI,IAAI;IACV;IACA,MAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;IAC1C,IAAI,CAAC,OAAO,OAAO,gBAAgB;IACnC,MAAM,GAAG,OAAO,kBAAkB,mBAAmB,KAAK,EAAE,SAAS,CAAC,CACnE,MAAM,MAAM;KACX,IAAI,EAAE,IAAI,OAAO,KAAK;KACtB,gBAAgB;IAClB,CAAC,CAAC,CACD,YAAY;KAEX,KAAK;IACP,CAAC;GACL,CAAC;EACH;CACF;AACF"}
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@branch-fiction/extension-sdk",
3
+ "version": "0.1.0",
4
+ "description": "SDK and dev tooling for building Branch Fiction extensions.",
5
+ "license": "MIT",
6
+ "author": "Chris McC",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/chrisvariety/branch-fiction.git",
10
+ "directory": "packages/extension-sdk"
11
+ },
12
+ "homepage": "https://github.com/chrisvariety/branch-fiction/tree/main/packages/extension-sdk",
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "bin": {
17
+ "branch-fiction-extension-dev": "./dist/dev-cli.js"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "type": "module",
23
+ "main": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "./manifest": {
31
+ "types": "./dist/manifest.d.ts",
32
+ "default": "./dist/manifest.js"
33
+ },
34
+ "./pi-handle": {
35
+ "types": "./dist/pi-handle.d.ts",
36
+ "default": "./dist/pi-handle.js"
37
+ },
38
+ "./models-catalog": {
39
+ "types": "./dist/models-catalog.d.ts",
40
+ "default": "./dist/models-catalog.js"
41
+ },
42
+ "./sdk-source": {
43
+ "types": "./dist/sdk-source.d.ts",
44
+ "default": "./dist/sdk-source.js"
45
+ },
46
+ "./dev": {
47
+ "types": "./dist/dev.d.ts",
48
+ "default": "./dist/dev.js"
49
+ },
50
+ "./vite": {
51
+ "types": "./dist/vite.d.ts",
52
+ "default": "./dist/vite.js"
53
+ },
54
+ "./db": {
55
+ "types": "./dist/db/types.d.ts",
56
+ "default": "./dist/db/types.js"
57
+ },
58
+ "./db/iframe": {
59
+ "types": "./dist/db/iframe.d.ts",
60
+ "default": "./dist/db/iframe.js"
61
+ },
62
+ "./db/boolean-plugin": {
63
+ "types": "./dist/db/boolean-plugin.d.ts",
64
+ "default": "./dist/db/boolean-plugin.js"
65
+ }
66
+ },
67
+ "dependencies": {
68
+ "@earendil-works/pi-ai": "^0.79.3",
69
+ "@hono/node-server": "^2.0.4",
70
+ "better-sqlite3": "^12.10.1",
71
+ "hono": "^4.12.25",
72
+ "kysely": "^0.29.2",
73
+ "kysely-plugin-serialize": "^0.8.2"
74
+ },
75
+ "devDependencies": {
76
+ "@types/better-sqlite3": "^7.6.13",
77
+ "@types/node": "^25.9.3",
78
+ "concurrently": "^10.0.3",
79
+ "oxfmt": "^0.54.0",
80
+ "oxlint": "^1.69.0",
81
+ "tsdown": "^0.22.2",
82
+ "typescript": "^6.0.3"
83
+ },
84
+ "scripts": {
85
+ "build": "tsdown && pnpm vendor:bundle",
86
+ "vendor:bundle": "pnpm --filter extension-host bundle && node scripts/copy-bundle.mjs",
87
+ "typecheck": "tsc",
88
+ "lint": "oxlint",
89
+ "lint:fix": "oxlint --fix",
90
+ "fmt": "oxfmt",
91
+ "fmt:check": "oxfmt --check",
92
+ "ci": "concurrently \"pnpm lint --fix\" \"pnpm fmt\""
93
+ }
94
+ }