@phenx-inc/ctlsurf 0.1.12 → 0.1.15
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/out/headless/index.mjs +1 -1
- package/out/headless/index.mjs.map +1 -1
- package/out/main/index.js +1 -1
- package/out/renderer/assets/{cssMode-CY6x0qXW.js → cssMode-woG3Vtms.js} +3 -3
- package/out/renderer/assets/{freemarker2-BXSW9BAX.js → freemarker2-DgtqmvCd.js} +1 -1
- package/out/renderer/assets/{handlebars-BYUZ1IOs.js → handlebars-YEqC-zOn.js} +1 -1
- package/out/renderer/assets/{html-DPocQM4t.js → html-CwvkGxeB.js} +1 -1
- package/out/renderer/assets/{htmlMode-CsPinKYA.js → htmlMode-DzdyF21q.js} +3 -3
- package/out/renderer/assets/{index-Bml7oDn9.js → index-CpDga7fF.js} +44 -31
- package/out/renderer/assets/{javascript-_HVGB-lj.js → javascript-Dy9cX9Fu.js} +2 -2
- package/out/renderer/assets/{jsonMode-JbrRQBOU.js → jsonMode-CPhJ_ION.js} +3 -3
- package/out/renderer/assets/{liquid-B7izKdqo.js → liquid-BJdFPWrv.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-DzxH499X.js → lspLanguageFeatures-BMM3SRX4.js} +1 -1
- package/out/renderer/assets/{mdx-CmvUeYLw.js → mdx-kg2kuPRK.js} +1 -1
- package/out/renderer/assets/{python-DJqYTFoi.js → python-aIrmxkmH.js} +1 -1
- package/out/renderer/assets/{razor-CGEA5nUK.js → razor-DdymcPOc.js} +1 -1
- package/out/renderer/assets/{tsMode-CN0FOHMy.js → tsMode-D7kqIJ_o.js} +1 -1
- package/out/renderer/assets/{typescript-CIn-DSfY.js → typescript-Cq3g-z7h.js} +1 -1
- package/out/renderer/assets/{xml-C5t3U2jS.js → xml-BuBP5jOc.js} +1 -1
- package/out/renderer/assets/{yaml-n-Jb6xf1.js → yaml-B-3hWmol.js} +1 -1
- package/out/renderer/index.html +1 -1
- package/package.json +1 -1
- package/src/main/bridge.ts +1 -1
- package/src/renderer/App.tsx +12 -0
- package/src/renderer/components/TerminalPanel.tsx +4 -1
package/out/headless/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../node_modules/electron/index.js", "../../src/main/orchestrator.ts", "../../src/main/pty.ts", "../../src/main/agents.ts", "../../src/main/ctlsurfApi.ts", "../../src/main/bridge.ts", "../../src/main/workerWs.ts", "../../src/main/settingsDir.ts", "../../src/main/tui.ts", "../../src/main/headless.ts"],
|
|
4
|
-
"sourcesContent": ["const fs = require('fs');\nconst path = require('path');\n\nconst pathFile = path.join(__dirname, 'path.txt');\n\nfunction getElectronPath () {\n let executablePath;\n if (fs.existsSync(pathFile)) {\n executablePath = fs.readFileSync(pathFile, 'utf-8');\n }\n if (process.env.ELECTRON_OVERRIDE_DIST_PATH) {\n return path.join(process.env.ELECTRON_OVERRIDE_DIST_PATH, executablePath || 'electron');\n }\n if (executablePath) {\n return path.join(__dirname, 'dist', executablePath);\n } else {\n throw new Error('Electron failed to install correctly, please delete node_modules/electron and try installing again');\n }\n}\n\nmodule.exports = getElectronPath();\n", "import path from 'path'\nimport fs from 'fs'\nimport os from 'os'\n\nimport { PtyManager } from './pty'\nimport { AgentConfig, isCodingAgent } from './agents'\nimport { CtlsurfApi } from './ctlsurfApi'\nimport { ConversationBridge } from './bridge'\nimport { WorkerWsClient, type WorkerWsStatus, type IncomingMessage } from './workerWs'\n\nfunction log(...args: unknown[]): void {\n try { console.log(...args) } catch { /* EPIPE safe */ }\n}\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface Profile {\n name: string\n apiKey: string\n baseUrl: string\n dataspacePageId: string\n}\n\nexport interface SettingsData {\n activeProfile: string\n profiles: Record<string, Profile>\n ctlsurfApiKey?: string\n ctlsurfBaseUrl?: string\n ctlsurfDataspacePageId?: string\n}\n\nexport interface OrchestratorEvents {\n onPtyData: (data: string) => void\n onPtyExit: (code: number) => void\n onWorkerStatus: (status: string) => void\n onWorkerMessage: (message: IncomingMessage) => void\n onWorkerRegistered: (data: { worker_id: string; folder_id: string | null; status: string }) => void\n onCwdChanged: () => void\n}\n\n// \u2500\u2500\u2500 Orchestrator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst DEFAULT_PROFILES: Record<string, Profile> = {\n production: {\n name: 'Production',\n apiKey: '',\n baseUrl: 'https://app.ctlsurf.com',\n dataspacePageId: '',\n },\n}\n\nconst TERM_STREAM_INTERVAL_MS = 50\n\nexport class Orchestrator {\n private settingsDir: string\n private events: OrchestratorEvents\n\n // Core services\n readonly ctlsurfApi = new CtlsurfApi()\n readonly bridge = new ConversationBridge()\n readonly workerWs: WorkerWsClient\n\n // State\n private ptyManager: PtyManager | null = null\n private currentAgent: AgentConfig | null = null\n private currentCwd: string | null = null\n private settings: SettingsData = {\n activeProfile: 'production',\n profiles: { ...DEFAULT_PROFILES },\n }\n\n // Terminal stream batching\n private termStreamBuffer = ''\n private termStreamTimer: ReturnType<typeof setTimeout> | null = null\n\n constructor(settingsDir: string, events: OrchestratorEvents) {\n this.settingsDir = settingsDir\n this.events = events\n\n this.workerWs = new WorkerWsClient({\n onStatusChange: (status: WorkerWsStatus) => {\n log(`[worker-ws] Status: ${status}`)\n events.onWorkerStatus(status)\n },\n onMessage: (message: IncomingMessage) => {\n log(`[worker-ws] Incoming message: ${message.id} (${message.type})`)\n events.onWorkerMessage(message)\n this.workerWs.sendAck(message.id)\n\n if (message.type === 'prompt' || message.type === 'task_dispatch') {\n if (this.ptyManager) {\n this.ptyManager.write(message.content + '\\r')\n this.bridge.feedInput(message.content)\n }\n }\n },\n onRegistered: (data) => {\n log(`[worker-ws] Registered: worker_id=${data.worker_id}, folder_id=${data.folder_id}, status=${data.status}`)\n events.onWorkerRegistered(data)\n if (!data.folder_id) {\n events.onWorkerStatus('no_project')\n }\n },\n onTerminalInput: (data: string) => {\n this.ptyManager?.write(data)\n },\n })\n\n this.bridge.setWsClient(this.workerWs)\n }\n\n // \u2500\u2500\u2500 Settings \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getActiveProfile(): Profile {\n return this.settings.profiles[this.settings.activeProfile] || this.settings.profiles.production || DEFAULT_PROFILES.production\n }\n\n get settingsData(): SettingsData {\n return this.settings\n }\n\n get cwd(): string | null {\n return this.currentCwd\n }\n\n get agent(): AgentConfig | null {\n return this.currentAgent\n }\n\n applyProfile(profile: Profile): void {\n const apiKey = profile.apiKey || process.env.CTLSURF_API_KEY || ''\n if (apiKey) {\n this.ctlsurfApi.setApiKey(apiKey)\n this.workerWs.setApiKey(apiKey)\n } else {\n this.ctlsurfApi.setApiKey('')\n this.workerWs.setApiKey(null)\n }\n\n const baseUrl = profile.baseUrl || process.env.CTLSURF_BASE_URL || 'https://app.ctlsurf.com'\n this.ctlsurfApi.setBaseUrl(baseUrl)\n this.workerWs.setBaseUrl(baseUrl)\n\n log(`[settings] Profile applied: ${profile.name} (${baseUrl})`)\n }\n\n loadSettings(): void {\n // Ensure settings dir exists\n try { fs.mkdirSync(this.settingsDir, { recursive: true }) } catch { /* ignore */ }\n\n const settingsPath = path.join(this.settingsDir, 'settings.json')\n try {\n if (fs.existsSync(settingsPath)) {\n const raw = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))\n\n if (!raw.profiles) {\n this.settings = {\n activeProfile: 'production',\n profiles: {\n production: {\n name: 'Production',\n apiKey: raw.ctlsurfApiKey || '',\n baseUrl: raw.ctlsurfBaseUrl || 'https://app.ctlsurf.com',\n dataspacePageId: raw.ctlsurfDataspacePageId || '',\n },\n },\n }\n this.saveSettings()\n log('[settings] Migrated legacy settings to profiles')\n } else {\n this.settings = raw as SettingsData\n if (!this.settings.profiles.production) {\n this.settings.profiles.production = { ...DEFAULT_PROFILES.production }\n }\n }\n }\n } catch {\n this.settings = {\n activeProfile: 'production',\n profiles: { ...DEFAULT_PROFILES },\n }\n }\n\n this.applyProfile(this.getActiveProfile())\n }\n\n saveSettings(): void {\n const settingsPath = path.join(this.settingsDir, 'settings.json')\n try {\n fs.mkdirSync(this.settingsDir, { recursive: true })\n fs.writeFileSync(settingsPath, JSON.stringify(this.settings, null, 2))\n } catch (err: any) {\n log('[settings] Failed to save:', err.message)\n }\n }\n\n overrideApiKey(key: string): void {\n this.ctlsurfApi.setApiKey(key)\n this.workerWs.setApiKey(key)\n }\n\n overrideBaseUrl(url: string): void {\n this.ctlsurfApi.setBaseUrl(url)\n this.workerWs.setBaseUrl(url)\n }\n\n // \u2500\u2500\u2500 Profile CRUD \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n listProfiles() {\n return {\n activeProfile: this.settings.activeProfile,\n profiles: Object.entries(this.settings.profiles).map(([id, p]) => ({\n id,\n name: p.name,\n baseUrl: p.baseUrl,\n hasApiKey: !!p.apiKey,\n dataspacePageId: p.dataspacePageId || null,\n })),\n }\n }\n\n getProfile(profileId: string) {\n const p = this.settings.profiles[profileId]\n if (!p) return null\n return {\n id: profileId,\n name: p.name,\n baseUrl: p.baseUrl,\n hasApiKey: !!p.apiKey,\n dataspacePageId: p.dataspacePageId || '',\n }\n }\n\n saveProfile(profileId: string, data: { name: string; apiKey?: string; baseUrl: string; dataspacePageId: string }) {\n const existing = this.settings.profiles[profileId]\n this.settings.profiles[profileId] = {\n name: data.name,\n apiKey: data.apiKey !== undefined ? data.apiKey : (existing?.apiKey || ''),\n baseUrl: data.baseUrl || 'https://app.ctlsurf.com',\n dataspacePageId: data.dataspacePageId || '',\n }\n this.saveSettings()\n\n if (profileId === this.settings.activeProfile) {\n this.applyProfile(this.settings.profiles[profileId])\n if (this.currentAgent && this.currentCwd) {\n this.workerWs.disconnect()\n this.connectWorkerWs(this.currentAgent, this.currentCwd)\n }\n }\n }\n\n switchProfile(profileId: string): { ok: boolean; error?: string } {\n if (!this.settings.profiles[profileId]) return { ok: false, error: 'Profile not found' }\n this.workerWs.disconnect()\n this.settings.activeProfile = profileId\n this.saveSettings()\n this.applyProfile(this.getActiveProfile())\n if (this.currentAgent && this.currentCwd) {\n this.connectWorkerWs(this.currentAgent, this.currentCwd)\n }\n return { ok: true }\n }\n\n deleteProfile(profileId: string): { ok: boolean; error?: string } {\n if (profileId === 'production') return { ok: false, error: 'Cannot delete Production profile' }\n if (!this.settings.profiles[profileId]) return { ok: false, error: 'Profile not found' }\n\n if (this.settings.activeProfile === profileId) {\n this.workerWs.disconnect()\n this.settings.activeProfile = 'production'\n this.applyProfile(this.getActiveProfile())\n if (this.currentAgent && this.currentCwd) {\n this.connectWorkerWs(this.currentAgent, this.currentCwd)\n }\n }\n\n delete this.settings.profiles[profileId]\n this.saveSettings()\n return { ok: true }\n }\n\n // \u2500\u2500\u2500 PTY & Agent \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async spawnAgent(agent: AgentConfig, cwd: string): Promise<void> {\n if (this.ptyManager) {\n this.bridge.endSession()\n this.ptyManager.kill()\n }\n\n this.currentAgent = agent\n const prevCwd = this.currentCwd\n this.currentCwd = cwd\n if (prevCwd !== cwd) {\n this.events.onCwdChanged()\n }\n\n this.ptyManager = new PtyManager(agent, cwd)\n\n this.ptyManager.onData((data: string) => {\n this.events.onPtyData(data)\n this.bridge.feedOutput(data)\n this.streamTerminalData(data)\n })\n\n const thisPtyManager = this.ptyManager\n\n this.ptyManager.onExit(async (exitCode: number) => {\n this.events.onPtyExit(exitCode)\n this.bridge.endSession()\n if (thisPtyManager === this.ptyManager && this.currentAgent && isCodingAgent(this.currentAgent)) {\n this.workerWs.disconnect()\n }\n })\n\n this.bridge.startSession()\n\n if (isCodingAgent(agent)) {\n this.connectWorkerWs(agent, cwd)\n } else {\n this.workerWs.disconnect()\n this.checkProjectStatus(cwd)\n }\n }\n\n writePty(data: string): void {\n this.ptyManager?.write(data)\n this.bridge.feedInput(data)\n }\n\n resizePty(cols: number, rows: number): void {\n this.ptyManager?.resize(cols, rows)\n this.bridge.resize(cols, rows)\n this.workerWs.sendTerminalResize(cols, rows)\n }\n\n async killAgent(): Promise<void> {\n this.bridge.endSession()\n this.ptyManager?.kill()\n this.ptyManager = null\n if (this.currentAgent && isCodingAgent(this.currentAgent)) {\n this.workerWs.disconnect()\n }\n }\n\n // \u2500\u2500\u2500 Worker WebSocket \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n connectWorkerWs(agent: AgentConfig, cwd: string): void {\n const profile = this.getActiveProfile()\n const apiKey = profile.apiKey || process.env.CTLSURF_API_KEY\n if (!apiKey) {\n log('[worker-ws] No API key, skipping WS connect')\n return\n }\n\n this.workerWs.connect({\n machine: os.hostname(),\n cwd,\n agent: agent.name,\n })\n }\n\n private async checkProjectStatus(cwd: string): Promise<void> {\n if (!this.ctlsurfApi.getApiKey()) {\n this.events.onWorkerStatus('no_project')\n return\n }\n try {\n const folder = await this.ctlsurfApi.findFolderByPath(cwd)\n if (!folder?.id) {\n this.events.onWorkerStatus('no_project')\n }\n } catch {\n this.events.onWorkerStatus('no_project')\n }\n }\n\n private streamTerminalData(data: string): void {\n this.termStreamBuffer += data\n if (!this.termStreamTimer) {\n this.termStreamTimer = setTimeout(() => {\n if (this.termStreamBuffer) {\n this.workerWs.sendTerminalData(this.termStreamBuffer)\n this.termStreamBuffer = ''\n }\n this.termStreamTimer = null\n }, TERM_STREAM_INTERVAL_MS)\n }\n }\n\n // \u2500\u2500\u2500 Shutdown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async shutdown(): Promise<void> {\n this.bridge.endSession()\n this.ptyManager?.kill()\n this.ptyManager = null\n this.workerWs.disconnect()\n if (this.termStreamTimer) {\n clearTimeout(this.termStreamTimer)\n this.termStreamTimer = null\n }\n }\n}\n", "import { createRequire } from 'module'\nimport { AgentConfig } from './agents'\n\n// Use createRequire to load native module at runtime, bypassing bundler\nconst require = createRequire(import.meta.url)\nconst pty = require('node-pty')\n\nexport class PtyManager {\n private process: any | null = null\n private dataCallbacks: ((data: string) => void)[] = []\n private exitCallbacks: ((code: number) => void)[] = []\n\n constructor(agent: AgentConfig, cwd: string) {\n const shell = agent.command\n const args = agent.args || []\n\n try {\n console.log(`[pty] Spawning: ${shell} ${args.join(' ')} in ${cwd}`)\n } catch {\n // Ignore EPIPE errors when stdout is closed\n }\n\n this.process = pty.spawn(shell, args, {\n name: 'xterm-256color',\n cwd,\n env: process.env as Record<string, string>,\n cols: 80,\n rows: 24\n })\n\n this.process.onData((data: string) => {\n for (const cb of this.dataCallbacks) {\n cb(data)\n }\n })\n\n this.process.onExit(({ exitCode }: { exitCode: number }) => {\n for (const cb of this.exitCallbacks) {\n cb(exitCode)\n }\n this.process = null\n })\n }\n\n write(data: string): void {\n this.process?.write(data)\n }\n\n resize(cols: number, rows: number): void {\n this.process?.resize(cols, rows)\n }\n\n kill(): void {\n this.process?.kill()\n this.process = null\n }\n\n onData(cb: (data: string) => void): void {\n this.dataCallbacks.push(cb)\n }\n\n onExit(cb: (code: number) => void): void {\n this.exitCallbacks.push(cb)\n }\n}\n", "export interface AgentConfig {\n id: string\n name: string\n command: string\n args: string[]\n description: string\n}\n\nfunction getShellCommand(): string {\n if (process.platform === 'win32') return 'powershell.exe'\n return process.env.SHELL || '/bin/zsh'\n}\n\nexport function getBuiltinAgents(): AgentConfig[] {\n return [\n {\n id: 'shell',\n name: 'Shell',\n command: getShellCommand(),\n args: ['-l'], // login shell to load PATH\n description: 'Default system shell'\n },\n {\n id: 'claude',\n name: 'Claude Code',\n command: 'claude',\n args: [],\n description: 'Anthropic Claude Code CLI'\n },\n {\n id: 'codex',\n name: 'Codex CLI',\n command: 'codex',\n args: [],\n description: 'OpenAI Codex CLI'\n }\n ]\n}\n\nexport function getDefaultAgent(): AgentConfig {\n return getBuiltinAgents()[0]\n}\n\nexport function isCodingAgent(agent: AgentConfig): boolean {\n return agent.id !== 'shell'\n}\n", "const CTLSURF_BASE_URL = 'https://app.ctlsurf.com/api'\n\nexport class CtlsurfApi {\n private baseUrl: string\n private apiKey: string | null = null\n\n constructor(baseUrl?: string) {\n this.baseUrl = baseUrl || CTLSURF_BASE_URL\n }\n\n setApiKey(key: string): void {\n this.apiKey = key\n }\n\n setBaseUrl(url: string): void {\n this.baseUrl = url.endsWith('/api') ? url : `${url}/api`\n }\n\n getApiKey(): string | null {\n return this.apiKey\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' }\n if (this.apiKey) {\n h['Authorization'] = `Bearer ${this.apiKey}`\n }\n return h\n }\n\n private async request(method: string, path: string, body?: unknown): Promise<any> {\n const url = `${this.baseUrl}${path}`\n const opts: RequestInit = {\n method,\n headers: this.headers()\n }\n if (body) {\n opts.body = JSON.stringify(body)\n }\n\n const res = await fetch(url, opts)\n if (!res.ok) {\n const text = await res.text()\n throw new Error(`ctlsurf API ${method} ${path}: ${res.status} ${text}`)\n }\n return res.json()\n }\n\n // \u2500\u2500\u2500 Pages \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async createPage(params: {\n title: string\n type?: string\n parent_id?: string\n folder_id?: string\n cwd?: string\n tags?: string[]\n }): Promise<any> {\n return this.request('POST', '/pages', params)\n }\n\n async findPageByRootPath(rootPath: string): Promise<any> {\n return this.request('POST', '/pages/find-by-root-path', { root_path: rootPath })\n }\n\n // \u2500\u2500\u2500 Blocks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async createBlock(pageId: string, params: {\n type: string\n title?: string\n props?: Record<string, unknown>\n }): Promise<any> {\n return this.request('POST', `/blocks/page/${pageId}`, params)\n }\n\n async getBlock(blockId: string): Promise<any> {\n return this.request('GET', `/blocks/${blockId}`)\n }\n\n async updateBlock(blockId: string, params: {\n props?: Record<string, unknown>\n title?: string\n }): Promise<any> {\n return this.request('PUT', `/blocks/${blockId}`, params)\n }\n\n // \u2500\u2500\u2500 Folders \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async createFolder(params: { name: string; root_path: string }): Promise<any> {\n return this.request('POST', '/folders', params)\n }\n\n // \u2500\u2500\u2500 Workers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async getAuthCode(): Promise<{ code: string }> {\n return this.request('POST', '/workers/token-exchange')\n }\n\n async findFolderByPath(rootPath: string): Promise<any> {\n return this.request('POST', '/folders/find-by-path', { root_path: rootPath })\n }\n\n async getFolderPages(folderId: string): Promise<any[]> {\n const folder = await this.request('GET', `/folders/${folderId}`)\n return folder?.pages || []\n }\n\n async findFolderByGitRemote(gitRemote: string): Promise<any> {\n // Search folders by listing all and matching git_remote\n const folders = await this.request('GET', '/folders')\n return folders?.find((f: any) => f.git_remote === gitRemote || f.root_path === gitRemote) || null\n }\n\n // \u2500\u2500\u2500 Log convenience \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async appendLog(blockId: string, action: string, message: string, data?: Record<string, unknown>): Promise<any> {\n // Read-modify-write: get current entries, append, put back\n const block = await this.getBlock(blockId)\n const props = block.props || {}\n const entries = Array.isArray(props.entries) ? [...props.entries] : []\n const maxEntries = props.max_entries || 1000\n\n const entry: Record<string, unknown> = {\n _id: `log_${entries.length}`,\n _timestamp: new Date().toISOString(),\n action,\n message\n }\n if (data) {\n entry.data = data\n }\n\n entries.push(entry)\n\n // Trim oldest if over max\n const trimmed = entries.length > maxEntries ? entries.slice(-maxEntries) : entries\n\n return this.updateBlock(blockId, {\n props: { ...props, entries: trimmed }\n })\n }\n}\n", "import { WorkerWsClient } from './workerWs'\n\n/**\n * Conversation Bridge\n *\n * Captures PTY output, strips ANSI codes and terminal artifacts,\n * and sends cleaned text to the backend via WebSocket for chat logging.\n * Uses a simple buffer + byte threshold approach (no timers, since\n * setTimeout/setInterval don't reliably fire under TUI raw mode).\n */\nexport class ConversationBridge {\n private wsClient: WorkerWsClient | null = null\n private sessionActive: boolean = false\n private inputBuffer: string = ''\n private outputBuffer: string = ''\n\n private readonly FLUSH_BYTES = 2000\n\n setWsClient(ws: WorkerWsClient): void {\n this.wsClient = ws\n }\n\n startSession(): void {\n this.outputBuffer = ''\n this.inputBuffer = ''\n this.sessionActive = true\n console.log('[bridge] Session started')\n }\n\n feedOutput(data: string): void {\n if (!this.sessionActive) return\n\n this.outputBuffer += data\n\n if (this.outputBuffer.length >= this.FLUSH_BYTES) {\n this.flush()\n }\n }\n\n feedInput(data: string): void {\n if (!this.sessionActive) return\n this.inputBuffer += data\n\n if (data.includes('\\r') || data.includes('\\n')) {\n const cleaned = cleanInput(this.inputBuffer)\n if (cleaned.length > 0) {\n this.sendEntry('user_input', cleaned)\n }\n this.inputBuffer = ''\n }\n }\n\n resize(_cols: number, _rows: number): void {\n // no-op now (was used for xterm)\n }\n\n private flush(): void {\n if (this.outputBuffer.length === 0) return\n\n const raw = this.outputBuffer\n this.outputBuffer = ''\n\n const cleaned = cleanOutput(raw)\n if (cleaned.length === 0) return\n\n this.sendEntry('terminal_output', cleaned)\n }\n\n private sendEntry(type: string, content: string): void {\n if (!this.wsClient) return\n this.wsClient.sendChatLog({\n ts: new Date().toISOString(),\n type,\n content,\n })\n }\n\n endSession(): void {\n if (!this.sessionActive) return\n\n this.flush()\n\n this.sessionActive = false\n this.inputBuffer = ''\n this.outputBuffer = ''\n console.log('[bridge] Session ended')\n }\n}\n\n/**\n * Strip ANSI escape codes from a string.\n */\nfunction stripAnsi(str: string): string {\n return str\n // CSI sequences (e.g. \\x1b[0m, \\x1b[?2004h, \\x1b[1;32m, \\x1b[38;2;r;g;bm)\n .replace(/\\x1b\\[[\\x30-\\x3f]*[\\x20-\\x2f]*[\\x40-\\x7e]/g, '')\n // OSC sequences (e.g. \\x1b]0;title\\x07, \\x1b]...\\x1b\\\\)\n .replace(/\\x1b\\][^\\x07\\x1b]*(?:\\x07|\\x1b\\\\)/g, '')\n // DCS/PM/APC sequences\n .replace(/\\x1b[PX^_][^\\x1b]*\\x1b\\\\/g, '')\n // Other escape sequences (charset, keypad mode, etc.)\n .replace(/\\x1b[^[\\]PX^_](.|$)/g, '')\n // Remaining single ESC\n .replace(/\\x1b/g, '')\n}\n\n/**\n * Process backspace characters: each \\x7f or \\b deletes the preceding char.\n */\nfunction processBackspaces(str: string): string {\n const result: string[] = []\n for (const ch of str) {\n if (ch === '\\x7f' || ch === '\\b') {\n result.pop()\n } else {\n result.push(ch)\n }\n }\n return result.join('')\n}\n\n/**\n * Clean user input: strip ANSI, process backspaces, remove control chars.\n */\nfunction cleanInput(str: string): string {\n let cleaned = stripAnsi(str)\n cleaned = processBackspaces(cleaned)\n // eslint-disable-next-line no-control-regex\n cleaned = cleaned.replace(/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/g, '')\n return cleaned.trim()\n}\n\n/**\n * Clean terminal output: strip ANSI, remove control chars, collapse noise.\n */\nfunction cleanOutput(str: string): string {\n let cleaned = stripAnsi(str)\n // Remove carriage returns\n cleaned = cleaned.replace(/\\r/g, '')\n // Remove control characters except newline/tab\n // eslint-disable-next-line no-control-regex\n cleaned = cleaned.replace(/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/g, '')\n // Collapse 3+ consecutive newlines into 2\n cleaned = cleaned.replace(/\\n{3,}/g, '\\n\\n')\n // Remove lines that are only whitespace\n cleaned = cleaned.split('\\n').filter(line => line.trim().length > 0).join('\\n')\n return cleaned.trim()\n}\n", "import os from 'os'\nimport crypto from 'crypto'\nimport WsModule from 'ws'\n\n// Use native WebSocket if available (Node 22+), otherwise fall back to ws package\nconst WS: typeof WebSocket = typeof WebSocket !== 'undefined' ? WebSocket : WsModule as any\n\nfunction log(...args: unknown[]): void {\n try { console.log(...args) } catch { /* EPIPE safe */ }\n}\n\nconst HEARTBEAT_INTERVAL_MS = 30_000\nconst RECONNECT_DELAY_MS = 5_000\nconst MAX_RECONNECT_DELAY_MS = 60_000\n\nexport interface WorkerRegistration {\n machine: string\n cwd: string\n repo_url?: string\n agent: string\n fingerprint: string\n}\n\nexport interface WorkerWsEvents {\n onStatusChange: (status: WorkerWsStatus) => void\n onMessage: (message: IncomingMessage) => void\n onRegistered: (data: { worker_id: string; folder_id: string | null; status: string; pending_messages?: IncomingMessage[] }) => void\n onTerminalInput?: (data: string) => void\n}\n\nexport interface IncomingMessage {\n id: string\n type: string\n content: string\n metadata?: Record<string, unknown> | null\n parent_id?: string | null\n}\n\nexport type WorkerWsStatus = 'disconnected' | 'connecting' | 'connected' | 'pending_approval'\n\nexport class WorkerWsClient {\n private ws: WebSocket | null = null\n private apiKey: string | null = null\n private baseUrl: string\n private events: WorkerWsEvents\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null\n private reconnectDelay = RECONNECT_DELAY_MS\n private registration: WorkerRegistration | null = null\n private workerId: string | null = null\n private _status: WorkerWsStatus = 'disconnected'\n private shouldReconnect = false\n private fingerprint: string\n\n constructor(events: WorkerWsEvents, baseUrl?: string) {\n this.events = events\n this.baseUrl = baseUrl || 'wss://app.ctlsurf.com'\n // Generate a stable machine fingerprint\n this.fingerprint = this.generateFingerprint()\n }\n\n get status(): WorkerWsStatus {\n return this._status\n }\n\n get currentWorkerId(): string | null {\n return this.workerId\n }\n\n setApiKey(key: string | null): void {\n this.apiKey = key\n }\n\n setBaseUrl(url: string): void {\n this.baseUrl = url\n }\n\n private generateFingerprint(): string {\n const data = `${os.hostname()}:${os.userInfo().username}:${os.platform()}:${os.arch()}`\n return crypto.createHash('sha256').update(data).digest('hex').slice(0, 32)\n }\n\n private setStatus(status: WorkerWsStatus): void {\n if (this._status !== status) {\n this._status = status\n this.events.onStatusChange(status)\n }\n }\n\n connect(registration: WorkerRegistration): void {\n this.registration = { ...registration, fingerprint: this.fingerprint }\n this.shouldReconnect = true\n this.doConnect()\n }\n\n disconnect(): void {\n this.shouldReconnect = false\n this.clearTimers()\n if (this.ws) {\n const oldWs = this.ws\n this.ws = null\n // Remove handlers before closing to prevent stale onclose from firing\n oldWs.onopen = null\n oldWs.onmessage = null\n oldWs.onclose = null\n oldWs.onerror = null\n try { oldWs.close(1000, 'client disconnect') } catch { /* ignore */ }\n }\n this.setStatus('disconnected')\n }\n\n sendResponse(parentId: string, content: string, metadata?: Record<string, unknown>): void {\n this.send({\n type: 'response',\n parent_id: parentId,\n content,\n metadata,\n })\n }\n\n sendStatusUpdate(status: string): void {\n this.send({ type: 'status_update', status })\n }\n\n sendAck(messageId: string): void {\n this.send({ type: 'ack', message_id: messageId })\n }\n\n sendTerminalData(data: string): void {\n this.send({ type: 'terminal_stream', data })\n }\n\n sendTerminalResize(cols: number, rows: number): void {\n this.send({ type: 'terminal_resize', cols, rows })\n }\n\n sendChatLog(entry: { type: string; content: string; ts?: string }): void {\n this.send({ type: 'chat_log', entry })\n }\n\n private doConnect(): void {\n if (!this.apiKey || !this.registration) {\n log('[worker-ws] No API key or registration, skipping connect')\n return\n }\n\n this.clearTimers()\n if (this.ws) {\n const oldWs = this.ws\n this.ws = null\n oldWs.onopen = null\n oldWs.onmessage = null\n oldWs.onclose = null\n oldWs.onerror = null\n try { oldWs.close() } catch { /* ignore */ }\n // Let the old connection fully close before opening a new one\n setTimeout(() => this.doConnectNow(), 500)\n return\n }\n\n this.doConnectNow()\n }\n\n private doConnectNow(): void {\n if (!this.apiKey || !this.registration) return\n if (!this.shouldReconnect) {\n log('[worker-ws] shouldReconnect is false, aborting connect')\n return\n }\n\n this.setStatus('connecting')\n\n // Use ws:// for localhost, wss:// for remote\n const wsBase = this.baseUrl.replace(/^http/, 'ws')\n const url = `${wsBase}/api/ws/worker?token=${encodeURIComponent(this.apiKey)}`\n\n log(`[worker-ws] Connecting to ${url.replace(/token=.*/, 'token=***')}...`)\n\n try {\n this.ws = new WS(url) as unknown as WebSocket\n } catch (err) {\n log('[worker-ws] Failed to create WebSocket:', err)\n this.scheduleReconnect()\n return\n }\n\n this.ws.onopen = () => {\n log('[worker-ws] Connected, sending register')\n this.reconnectDelay = RECONNECT_DELAY_MS // Reset backoff\n this.send({\n type: 'register',\n ...this.registration,\n })\n this.startHeartbeat()\n }\n\n this.ws.onmessage = (event) => {\n try {\n const data = JSON.parse(String(event.data))\n this.handleMessage(data)\n } catch (err) {\n log('[worker-ws] Failed to parse message:', err)\n }\n }\n\n this.ws.onclose = (event) => {\n log(`[worker-ws] Disconnected: ${event.code} ${event.reason}`)\n this.ws = null\n this.clearHeartbeat()\n this.setStatus('disconnected')\n if (this.shouldReconnect) {\n this.scheduleReconnect()\n }\n }\n\n this.ws.onerror = () => {\n log('[worker-ws] WebSocket error')\n }\n }\n\n private handleMessage(data: Record<string, unknown>): void {\n const msgType = data.type as string\n\n switch (msgType) {\n case 'registered': {\n this.workerId = data.worker_id as string\n const workerStatus = data.status as string\n console.log(`[worker-ws] Registered as ${this.workerId}, status: ${workerStatus}`)\n\n if (workerStatus === 'pending_approval') {\n this.setStatus('pending_approval')\n } else {\n this.setStatus('connected')\n }\n\n const pendingMessages = (data.pending_messages || []) as IncomingMessage[]\n this.events.onRegistered({\n worker_id: this.workerId,\n folder_id: data.folder_id as string | null,\n status: workerStatus,\n pending_messages: pendingMessages,\n })\n\n // Deliver pending messages\n for (const msg of pendingMessages) {\n this.events.onMessage(msg)\n }\n break\n }\n\n case 'approved': {\n log('[worker-ws] Worker approved!')\n this.setStatus('connected')\n break\n }\n\n case 'message': {\n const msg = data.message as IncomingMessage\n if (msg) {\n console.log(`[worker-ws] Received message: ${msg.id}`)\n this.events.onMessage(msg)\n }\n break\n }\n\n case 'terminal_input': {\n const inputData = data.data as string\n if (inputData && this.events.onTerminalInput) {\n this.events.onTerminalInput(inputData)\n }\n break\n }\n\n case 'heartbeat_ack':\n break\n\n default:\n console.log(`[worker-ws] Unknown message type: ${msgType}`)\n }\n }\n\n private send(data: Record<string, unknown>): void {\n if (this.ws && this.ws.readyState === WS.OPEN) {\n this.ws.send(JSON.stringify(data))\n }\n }\n\n\n private startHeartbeat(): void {\n this.clearHeartbeat()\n this.heartbeatTimer = setInterval(() => {\n this.send({ type: 'heartbeat' })\n }, HEARTBEAT_INTERVAL_MS)\n }\n\n private clearHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n private scheduleReconnect(): void {\n if (!this.shouldReconnect) return\n console.log(`[worker-ws] Reconnecting in ${this.reconnectDelay / 1000}s...`)\n this.reconnectTimer = setTimeout(() => {\n this.doConnect()\n }, this.reconnectDelay)\n // Exponential backoff\n this.reconnectDelay = Math.min(this.reconnectDelay * 2, MAX_RECONNECT_DELAY_MS)\n }\n\n private clearTimers(): void {\n this.clearHeartbeat()\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n }\n}\n", "import path from 'path'\nimport os from 'os'\n\nexport function getSettingsDir(useElectron: boolean): string {\n if (useElectron) {\n const { app } = require('electron')\n return app.getPath('userData')\n }\n\n if (process.platform === 'darwin') {\n return path.join(os.homedir(), 'Library', 'Application Support', 'ctlsurf-worker')\n }\n return path.join(\n process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'),\n 'ctlsurf-worker'\n )\n}\n", "/**\n * Terminal UI (TUI) renderer\n *\n * Draws a title bar and status bar around the PTY output area.\n * Uses ANSI escape codes and scroll regions \u2014 no external dependencies.\n */\n\nconst ESC = '\\x1b'\nconst CSI = `${ESC}[`\n\n// Colors (Tokyo Night palette)\nconst BG_BAR = `${CSI}48;2;22;22;30m` // #16161e\nconst FG_TITLE = `${CSI}38;2;192;202;245m` // #c0caf5\nconst FG_DIM = `${CSI}38;2;86;95;137m` // #565f89\nconst FG_ACCENT = `${CSI}38;2;122;162;247m` // #7aa2f7\nconst FG_GREEN = `${CSI}38;2;158;206;106m` // #9ece6a\nconst FG_RED = `${CSI}38;2;247;118;142m` // #f7768e\nconst FG_YELLOW = `${CSI}38;2;224;175;104m` // #e0af68\nconst FG_WHITE = `${CSI}38;2;169;177;214m` // #a9b1d6\nconst BG_MODAL = `${CSI}48;2;31;35;53m` // #1f2335\nconst BG_SELECTED = `${CSI}48;2;42;43;61m` // #2a2b3d\nconst RESET = `${CSI}0m`\n\nexport interface TuiState {\n agentName: string\n cwd: string\n wsStatus: string\n workerId: string | null\n mode: string\n}\n\nexport class Tui {\n private rows: number = 0\n private cols: number = 0\n private state: TuiState = {\n agentName: '',\n cwd: '',\n wsStatus: 'disconnected',\n workerId: null,\n mode: 'terminal',\n }\n\n constructor() {\n this.rows = process.stdout.rows || 24\n this.cols = process.stdout.columns || 80\n }\n\n /**\n * Initialize the TUI: alternate screen, hide cursor reporting, set scroll region\n */\n init(): void {\n // Alternate screen buffer\n this.write(`${CSI}?1049h`)\n // Set scroll region: lines 2 to (rows - 1), leaving line 1 for title, line rows for status\n this.setScrollRegion()\n // Move cursor to the PTY area\n this.moveToPtyArea()\n // Draw chrome\n this.drawTitleBar()\n this.drawStatusBar()\n }\n\n /**\n * Restore terminal to normal state\n */\n destroy(): void {\n // Reset scroll region\n this.write(`${CSI}r`)\n // Leave alternate screen\n this.write(`${CSI}?1049l`)\n // Show cursor\n this.write(`${CSI}?25h`)\n }\n\n /**\n * Handle terminal resize\n */\n resize(cols: number, rows: number): void {\n this.cols = cols\n this.rows = rows\n this.setScrollRegion()\n this.drawTitleBar()\n this.drawStatusBar()\n this.moveToPtyArea()\n }\n\n /**\n * Get the PTY dimensions (main area minus title + status bars)\n */\n getPtySize(): { cols: number; rows: number } {\n return {\n cols: this.cols,\n rows: Math.max(1, this.rows - 2),\n }\n }\n\n /**\n * Update state and redraw bars\n */\n update(partial: Partial<TuiState>): void {\n Object.assign(this.state, partial)\n // Save cursor, draw bars, restore cursor\n this.write(`${CSI}s`) // save cursor\n this.drawTitleBar()\n this.drawStatusBar()\n this.write(`${CSI}u`) // restore cursor\n }\n\n /**\n * Write PTY output to the scroll region.\n * Cursor is assumed to be in the PTY area already.\n */\n writePtyData(data: string): void {\n // PTY data goes straight to stdout \u2014 it's already in the scroll region\n this.write(data)\n }\n\n /**\n * Update the terminal window/tab title via OSC escape sequence.\n * Works in passthrough mode (no chrome) \u2014 doesn't conflict with the agent's TUI.\n */\n setTerminalTitle(title: string): void {\n this.write(`${ESC}]0;${title}\\x07`)\n }\n\n /**\n * Build a title string from current state for the terminal tab.\n */\n updateTerminalTitle(): void {\n const { agentName, wsStatus, cwd } = this.state\n const displayCwd = this.shortenPath(cwd)\n const statusIcon = {\n connected: '\\u25CF',\n connecting: '\\u25CB',\n disconnected: '\\u25CB',\n pending_approval: '\\u25CB',\n no_project: '\\u25CB',\n }[wsStatus] || '\\u25CB'\n const statusLabel = {\n connected: 'Connected',\n connecting: 'Connecting...',\n disconnected: 'Disconnected',\n pending_approval: 'Pending',\n no_project: 'No Project',\n }[wsStatus] || wsStatus\n\n this.setTerminalTitle(`ctlsurf \\u00B7 ${agentName} \\u00B7 ${statusIcon} ${statusLabel} \\u00B7 ${displayCwd}`)\n }\n\n /**\n * Show an interactive agent picker modal.\n * Returns a promise that resolves with the selected agent index.\n */\n showAgentPicker(agents: { name: string; description: string }[]): Promise<number> {\n return new Promise((resolve) => {\n let selected = 0\n const modalWidth = 44\n const modalHeight = agents.length + 4 // border + title + items + border\n const startCol = Math.max(1, Math.floor((this.cols - modalWidth) / 2))\n const startRow = Math.max(1, Math.floor((this.rows - modalHeight) / 2))\n\n // Enter alternate screen if not already\n this.write(`${CSI}?1049h`)\n // Hide cursor\n this.write(`${CSI}?25l`)\n\n const drawModal = () => {\n const topBorder = '\\u250c' + '\\u2500'.repeat(modalWidth - 2) + '\\u2510'\n const botBorder = '\\u2514' + '\\u2500'.repeat(modalWidth - 2) + '\\u2518'\n const emptyLine = '\\u2502' + ' '.repeat(modalWidth - 2) + '\\u2502'\n\n // Draw background fill\n for (let r = 0; r < this.rows; r++) {\n this.write(`${CSI}${r + 1};1H${BG_BAR}${CSI}2K${RESET}`)\n }\n\n // Draw logo/branding centered\n const brand = 'ctlsurf'\n const brandCol = Math.max(1, Math.floor((this.cols - brand.length) / 2))\n this.write(`${CSI}${startRow - 2};${brandCol}H${FG_ACCENT}${brand}${RESET}`)\n\n // Top border\n this.write(`${CSI}${startRow};${startCol}H${BG_MODAL}${FG_DIM}${topBorder}${RESET}`)\n\n // Title\n const title = ' Select Agent'\n const titlePad = ' '.repeat(Math.max(0, modalWidth - 2 - title.length))\n this.write(`${CSI}${startRow + 1};${startCol}H${BG_MODAL}${FG_DIM}\\u2502${RESET}${BG_MODAL}${FG_TITLE}${title}${titlePad}${FG_DIM}\\u2502${RESET}`)\n\n // Separator\n const sep = '\\u251c' + '\\u2500'.repeat(modalWidth - 2) + '\\u2524'\n this.write(`${CSI}${startRow + 2};${startCol}H${BG_MODAL}${FG_DIM}${sep}${RESET}`)\n\n // Agent items\n for (let i = 0; i < agents.length; i++) {\n const agent = agents[i]\n const row = startRow + 3 + i\n const isSelected = i === selected\n const bg = isSelected ? BG_SELECTED : BG_MODAL\n const pointer = isSelected ? `${FG_ACCENT}\\u25B8 ` : ' '\n const nameFg = isSelected ? FG_ACCENT : FG_WHITE\n const descFg = FG_DIM\n const nameStr = agent.name\n const descStr = agent.description ? ` ${FG_DIM}\u2014 ${agent.description.slice(0, 20)}` : ''\n const content = `${pointer}${nameFg}${nameStr}${descStr}`\n const contentLen = (isSelected ? 2 : 2) + nameStr.length + (agent.description ? 3 + Math.min(20, agent.description.length) : 0)\n const pad = ' '.repeat(Math.max(0, modalWidth - 2 - contentLen))\n this.write(`${CSI}${row};${startCol}H${bg}${FG_DIM}\\u2502${RESET}${bg}${content}${pad}${RESET}${BG_MODAL}${FG_DIM}\\u2502${RESET}`)\n }\n\n // Bottom border\n const botRow = startRow + 3 + agents.length\n this.write(`${CSI}${botRow};${startCol}H${BG_MODAL}${FG_DIM}${botBorder}${RESET}`)\n\n // Hint\n const hint = '\\u2191\\u2193 navigate \u00B7 Enter select \u00B7 q quit'\n const hintCol = Math.max(1, Math.floor((this.cols - hint.length) / 2))\n this.write(`${CSI}${botRow + 2};${hintCol}H${FG_DIM}${hint}${RESET}`)\n }\n\n drawModal()\n\n // Set raw mode to capture keypresses\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(true)\n }\n process.stdin.resume()\n\n const onKey = (data: Buffer) => {\n const key = data.toString()\n\n if (key === '\\x1b[A' || key === 'k') {\n // Up\n selected = (selected - 1 + agents.length) % agents.length\n drawModal()\n } else if (key === '\\x1b[B' || key === 'j') {\n // Down\n selected = (selected + 1) % agents.length\n drawModal()\n } else if (key === '\\r' || key === '\\n') {\n // Enter \u2014 select\n cleanup()\n resolve(selected)\n } else if (key === 'q' || key === '\\x1b' || key === '\\x03') {\n // q, Escape, Ctrl+C \u2014 quit\n cleanup()\n this.write(`${CSI}?25h`) // show cursor\n this.write(`${CSI}?1049l`) // leave alt screen\n process.exit(0)\n }\n }\n\n const cleanup = () => {\n process.stdin.removeListener('data', onKey)\n // Show cursor again\n this.write(`${CSI}?25h`)\n // Clear the modal (will be redrawn by init())\n }\n\n process.stdin.on('data', onKey)\n })\n }\n\n // \u2500\u2500\u2500 Internal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private write(data: string): void {\n try {\n process.stdout.write(data)\n } catch { /* EPIPE safe */ }\n }\n\n private setScrollRegion(): void {\n // Scroll region from line 2 to line (rows - 1)\n this.write(`${CSI}2;${this.rows - 1}r`)\n }\n\n private moveToPtyArea(): void {\n // Move cursor to top of PTY area (line 2)\n this.write(`${CSI}2;1H`)\n }\n\n private drawTitleBar(): void {\n const { agentName, cwd, wsStatus } = this.state\n // Move to line 1\n this.write(`${CSI}1;1H`)\n // Clear line and draw\n this.write(`${BG_BAR}${CSI}2K`)\n\n const displayCwd = this.shortenPath(cwd)\n\n // WS status indicator\n const statusColor = {\n connected: FG_GREEN,\n connecting: FG_YELLOW,\n disconnected: FG_RED,\n pending_approval: FG_YELLOW,\n no_project: FG_DIM,\n }[wsStatus] || FG_DIM\n const statusLabel = {\n connected: 'Connected',\n connecting: 'Connecting...',\n disconnected: 'Disconnected',\n pending_approval: 'Pending',\n no_project: 'No Project',\n }[wsStatus] || wsStatus\n const wsIndicator = `${statusColor}\\u25CF ${statusLabel}${RESET}${BG_BAR}`\n\n const left = ` ${FG_ACCENT}ctlsurf${RESET}${BG_BAR} ${FG_DIM}\\u2502${RESET}${BG_BAR} ${FG_TITLE}${agentName || 'starting...'}${RESET}${BG_BAR} ${FG_DIM}\\u2502${RESET}${BG_BAR} ${FG_DIM}${displayCwd}${RESET}${BG_BAR}`\n const right = `${wsIndicator} ${RESET}${BG_BAR}`\n\n this.write(left)\n const pad = Math.max(0, this.cols - this.visibleLen(left) - this.visibleLen(right))\n this.write(' '.repeat(pad))\n this.write(right)\n this.write(RESET)\n }\n\n private drawStatusBar(): void {\n const { wsStatus, workerId, cwd } = this.state\n // Move to last line\n this.write(`${CSI}${this.rows};1H`)\n // Clear line and draw\n this.write(`${BG_BAR}${CSI}2K`)\n\n const statusColor = {\n connected: FG_GREEN,\n connecting: FG_YELLOW,\n disconnected: FG_RED,\n pending_approval: FG_YELLOW,\n no_project: FG_DIM,\n }[wsStatus] || FG_DIM\n\n const statusDot = `${statusColor}\\u25CF${RESET}${BG_BAR}`\n const statusLabel = {\n connected: 'Connected',\n connecting: 'Connecting...',\n disconnected: 'Disconnected',\n pending_approval: 'Pending Approval',\n no_project: 'No Project',\n }[wsStatus] || wsStatus\n\n const displayCwd = this.shortenPath(cwd)\n const left = ` ${statusDot} ${FG_DIM}${statusLabel}${RESET}${BG_BAR}`\n const right = `${FG_DIM}Ctrl+\\\\ exit${RESET}${BG_BAR} ${FG_DIM}${displayCwd} ${RESET}${BG_BAR}`\n\n this.write(left)\n const pad = Math.max(0, this.cols - this.visibleLen(left) - this.visibleLen(right))\n this.write(' '.repeat(pad))\n this.write(right)\n this.write(RESET)\n }\n\n private shortenPath(p: string): string {\n if (!p) return ''\n const home = process.env.HOME || ''\n if (home && p.startsWith(home)) {\n return '~' + p.slice(home.length)\n }\n return p\n }\n\n private visibleLen(s: string): number {\n // Strip ANSI codes to get visible length\n return s.replace(/\\x1b\\[[^m]*m/g, '').length\n }\n}\n", "#!/usr/bin/env node\n\n/**\n * ctlsurf terminal mode (TUI)\n *\n * Runs the agent in a PTY with a terminal UI: title bar, status bar,\n * conversation logging, and WebSocket control. No Electron required.\n *\n * Usage:\n * ctlsurf --terminal [--agent claude] [--cwd /path] [--api-key KEY] [--base-url URL] [--profile NAME]\n *\n * If no --agent is given, shows an interactive agent picker.\n * Press Ctrl+\\ to exit at any time.\n */\n\n// Prevent EPIPE crashes\nprocess.stdout?.on?.('error', () => {})\nprocess.stderr?.on?.('error', () => {})\nprocess.on('uncaughtException', (err) => {\n if (err.message === 'write EPIPE') return\n try { console.error('[uncaught]', err) } catch { /* ignore */ }\n})\n\nimport { Orchestrator } from './orchestrator'\nimport { getSettingsDir } from './settingsDir'\nimport { getBuiltinAgents, getDefaultAgent, isCodingAgent, type AgentConfig } from './agents'\nimport { Tui } from './tui'\n\n// \u2500\u2500\u2500 CLI arg parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface CliArgs {\n agent: string | null\n cwd: string\n apiKey: string | null\n baseUrl: string | null\n profile: string | null\n}\n\nfunction parseArgs(argv: string[]): CliArgs {\n const args: CliArgs = {\n agent: null,\n cwd: process.env.CTLSURF_WORKER_CWD || process.cwd(),\n apiKey: null,\n baseUrl: null,\n profile: null,\n }\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n const next = argv[i + 1]\n switch (arg) {\n case '--agent': args.agent = next; i++; break\n case '--cwd': args.cwd = next; i++; break\n case '--api-key': args.apiKey = next; i++; break\n case '--base-url': args.baseUrl = next; i++; break\n case '--profile': args.profile = next; i++; break\n case '--terminal': break\n case '--desktop': break\n }\n }\n return args\n}\n\n// \u2500\u2500\u2500 Main \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function main() {\n const args = parseArgs(process.argv.slice(2))\n const settingsDir = getSettingsDir(false)\n\n const tui = new Tui()\n const agents = getBuiltinAgents()\n\n // \u2500\u2500\u2500 Agent selection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n let agent: AgentConfig\n\n if (args.agent) {\n const found = agents.find(a => a.id === args.agent)\n agent = found || {\n id: args.agent,\n name: args.agent,\n command: args.agent,\n args: [],\n description: `Custom agent: ${args.agent}`,\n }\n } else {\n // Show interactive picker\n const selectedIdx = await tui.showAgentPicker(agents)\n agent = agents[selectedIdx]\n }\n\n // \u2500\u2500\u2500 Start TUI + agent \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n tui.update({\n agentName: agent.name,\n cwd: args.cwd,\n mode: 'terminal',\n })\n\n tui.init()\n\n const orchestrator = new Orchestrator(settingsDir, {\n onPtyData: (data) => {\n tui.writePtyData(data)\n },\n onPtyExit: (code) => {\n tui.destroy()\n console.log(`Agent exited with code ${code}`)\n orchestrator.shutdown().then(() => process.exit(code))\n },\n onWorkerStatus: (status) => {\n tui.update({ wsStatus: status })\n },\n onWorkerMessage: () => {},\n onWorkerRegistered: () => {\n tui.update({ wsStatus: 'connected' })\n },\n onCwdChanged: () => {\n tui.update({ cwd: orchestrator.cwd || '' })\n },\n })\n\n orchestrator.loadSettings()\n\n if (args.profile) orchestrator.switchProfile(args.profile)\n if (args.apiKey) orchestrator.overrideApiKey(args.apiKey)\n if (args.baseUrl) orchestrator.overrideBaseUrl(args.baseUrl)\n\n // Spawn agent with PTY sized to fit the TUI content area\n const ptySize = tui.getPtySize()\n await orchestrator.spawnAgent(agent, args.cwd)\n orchestrator.resizePty(ptySize.cols, ptySize.rows)\n\n // For coding agents, send an initial prompt to kick-start them\n if (isCodingAgent(agent)) {\n setTimeout(() => {\n orchestrator.writePty('hello\\r')\n }, 1000)\n }\n\n // Pipe stdin to PTY, with Ctrl+\\ as the exit key\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(true)\n process.stdin.resume()\n process.stdin.on('data', (data) => {\n const str = data.toString()\n // Ctrl+\\ (0x1c) = exit\n if (str === '\\x1c') {\n shutdown()\n return\n }\n orchestrator.writePty(str)\n })\n }\n\n // Handle terminal resize\n process.stdout.on('resize', () => {\n const cols = process.stdout.columns || 80\n const rows = process.stdout.rows || 24\n tui.resize(cols, rows)\n const size = tui.getPtySize()\n orchestrator.resizePty(size.cols, size.rows)\n })\n\n // Graceful shutdown\n const shutdown = async () => {\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false)\n }\n tui.destroy()\n await orchestrator.shutdown()\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n}\n\nmain().catch((err) => {\n process.stdout.write('\\x1b[?1049l')\n console.error('Fatal error:', err)\n process.exit(1)\n})\n"],
|
|
4
|
+
"sourcesContent": ["const fs = require('fs');\nconst path = require('path');\n\nconst pathFile = path.join(__dirname, 'path.txt');\n\nfunction getElectronPath () {\n let executablePath;\n if (fs.existsSync(pathFile)) {\n executablePath = fs.readFileSync(pathFile, 'utf-8');\n }\n if (process.env.ELECTRON_OVERRIDE_DIST_PATH) {\n return path.join(process.env.ELECTRON_OVERRIDE_DIST_PATH, executablePath || 'electron');\n }\n if (executablePath) {\n return path.join(__dirname, 'dist', executablePath);\n } else {\n throw new Error('Electron failed to install correctly, please delete node_modules/electron and try installing again');\n }\n}\n\nmodule.exports = getElectronPath();\n", "import path from 'path'\nimport fs from 'fs'\nimport os from 'os'\n\nimport { PtyManager } from './pty'\nimport { AgentConfig, isCodingAgent } from './agents'\nimport { CtlsurfApi } from './ctlsurfApi'\nimport { ConversationBridge } from './bridge'\nimport { WorkerWsClient, type WorkerWsStatus, type IncomingMessage } from './workerWs'\n\nfunction log(...args: unknown[]): void {\n try { console.log(...args) } catch { /* EPIPE safe */ }\n}\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface Profile {\n name: string\n apiKey: string\n baseUrl: string\n dataspacePageId: string\n}\n\nexport interface SettingsData {\n activeProfile: string\n profiles: Record<string, Profile>\n ctlsurfApiKey?: string\n ctlsurfBaseUrl?: string\n ctlsurfDataspacePageId?: string\n}\n\nexport interface OrchestratorEvents {\n onPtyData: (data: string) => void\n onPtyExit: (code: number) => void\n onWorkerStatus: (status: string) => void\n onWorkerMessage: (message: IncomingMessage) => void\n onWorkerRegistered: (data: { worker_id: string; folder_id: string | null; status: string }) => void\n onCwdChanged: () => void\n}\n\n// \u2500\u2500\u2500 Orchestrator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst DEFAULT_PROFILES: Record<string, Profile> = {\n production: {\n name: 'Production',\n apiKey: '',\n baseUrl: 'https://app.ctlsurf.com',\n dataspacePageId: '',\n },\n}\n\nconst TERM_STREAM_INTERVAL_MS = 50\n\nexport class Orchestrator {\n private settingsDir: string\n private events: OrchestratorEvents\n\n // Core services\n readonly ctlsurfApi = new CtlsurfApi()\n readonly bridge = new ConversationBridge()\n readonly workerWs: WorkerWsClient\n\n // State\n private ptyManager: PtyManager | null = null\n private currentAgent: AgentConfig | null = null\n private currentCwd: string | null = null\n private settings: SettingsData = {\n activeProfile: 'production',\n profiles: { ...DEFAULT_PROFILES },\n }\n\n // Terminal stream batching\n private termStreamBuffer = ''\n private termStreamTimer: ReturnType<typeof setTimeout> | null = null\n\n constructor(settingsDir: string, events: OrchestratorEvents) {\n this.settingsDir = settingsDir\n this.events = events\n\n this.workerWs = new WorkerWsClient({\n onStatusChange: (status: WorkerWsStatus) => {\n log(`[worker-ws] Status: ${status}`)\n events.onWorkerStatus(status)\n },\n onMessage: (message: IncomingMessage) => {\n log(`[worker-ws] Incoming message: ${message.id} (${message.type})`)\n events.onWorkerMessage(message)\n this.workerWs.sendAck(message.id)\n\n if (message.type === 'prompt' || message.type === 'task_dispatch') {\n if (this.ptyManager) {\n this.ptyManager.write(message.content + '\\r')\n this.bridge.feedInput(message.content)\n }\n }\n },\n onRegistered: (data) => {\n log(`[worker-ws] Registered: worker_id=${data.worker_id}, folder_id=${data.folder_id}, status=${data.status}`)\n events.onWorkerRegistered(data)\n if (!data.folder_id) {\n events.onWorkerStatus('no_project')\n }\n },\n onTerminalInput: (data: string) => {\n this.ptyManager?.write(data)\n },\n })\n\n this.bridge.setWsClient(this.workerWs)\n }\n\n // \u2500\u2500\u2500 Settings \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getActiveProfile(): Profile {\n return this.settings.profiles[this.settings.activeProfile] || this.settings.profiles.production || DEFAULT_PROFILES.production\n }\n\n get settingsData(): SettingsData {\n return this.settings\n }\n\n get cwd(): string | null {\n return this.currentCwd\n }\n\n get agent(): AgentConfig | null {\n return this.currentAgent\n }\n\n applyProfile(profile: Profile): void {\n const apiKey = profile.apiKey || process.env.CTLSURF_API_KEY || ''\n if (apiKey) {\n this.ctlsurfApi.setApiKey(apiKey)\n this.workerWs.setApiKey(apiKey)\n } else {\n this.ctlsurfApi.setApiKey('')\n this.workerWs.setApiKey(null)\n }\n\n const baseUrl = profile.baseUrl || process.env.CTLSURF_BASE_URL || 'https://app.ctlsurf.com'\n this.ctlsurfApi.setBaseUrl(baseUrl)\n this.workerWs.setBaseUrl(baseUrl)\n\n log(`[settings] Profile applied: ${profile.name} (${baseUrl})`)\n }\n\n loadSettings(): void {\n // Ensure settings dir exists\n try { fs.mkdirSync(this.settingsDir, { recursive: true }) } catch { /* ignore */ }\n\n const settingsPath = path.join(this.settingsDir, 'settings.json')\n try {\n if (fs.existsSync(settingsPath)) {\n const raw = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))\n\n if (!raw.profiles) {\n this.settings = {\n activeProfile: 'production',\n profiles: {\n production: {\n name: 'Production',\n apiKey: raw.ctlsurfApiKey || '',\n baseUrl: raw.ctlsurfBaseUrl || 'https://app.ctlsurf.com',\n dataspacePageId: raw.ctlsurfDataspacePageId || '',\n },\n },\n }\n this.saveSettings()\n log('[settings] Migrated legacy settings to profiles')\n } else {\n this.settings = raw as SettingsData\n if (!this.settings.profiles.production) {\n this.settings.profiles.production = { ...DEFAULT_PROFILES.production }\n }\n }\n }\n } catch {\n this.settings = {\n activeProfile: 'production',\n profiles: { ...DEFAULT_PROFILES },\n }\n }\n\n this.applyProfile(this.getActiveProfile())\n }\n\n saveSettings(): void {\n const settingsPath = path.join(this.settingsDir, 'settings.json')\n try {\n fs.mkdirSync(this.settingsDir, { recursive: true })\n fs.writeFileSync(settingsPath, JSON.stringify(this.settings, null, 2))\n } catch (err: any) {\n log('[settings] Failed to save:', err.message)\n }\n }\n\n overrideApiKey(key: string): void {\n this.ctlsurfApi.setApiKey(key)\n this.workerWs.setApiKey(key)\n }\n\n overrideBaseUrl(url: string): void {\n this.ctlsurfApi.setBaseUrl(url)\n this.workerWs.setBaseUrl(url)\n }\n\n // \u2500\u2500\u2500 Profile CRUD \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n listProfiles() {\n return {\n activeProfile: this.settings.activeProfile,\n profiles: Object.entries(this.settings.profiles).map(([id, p]) => ({\n id,\n name: p.name,\n baseUrl: p.baseUrl,\n hasApiKey: !!p.apiKey,\n dataspacePageId: p.dataspacePageId || null,\n })),\n }\n }\n\n getProfile(profileId: string) {\n const p = this.settings.profiles[profileId]\n if (!p) return null\n return {\n id: profileId,\n name: p.name,\n baseUrl: p.baseUrl,\n hasApiKey: !!p.apiKey,\n dataspacePageId: p.dataspacePageId || '',\n }\n }\n\n saveProfile(profileId: string, data: { name: string; apiKey?: string; baseUrl: string; dataspacePageId: string }) {\n const existing = this.settings.profiles[profileId]\n this.settings.profiles[profileId] = {\n name: data.name,\n apiKey: data.apiKey !== undefined ? data.apiKey : (existing?.apiKey || ''),\n baseUrl: data.baseUrl || 'https://app.ctlsurf.com',\n dataspacePageId: data.dataspacePageId || '',\n }\n this.saveSettings()\n\n if (profileId === this.settings.activeProfile) {\n this.applyProfile(this.settings.profiles[profileId])\n if (this.currentAgent && this.currentCwd) {\n this.workerWs.disconnect()\n this.connectWorkerWs(this.currentAgent, this.currentCwd)\n }\n }\n }\n\n switchProfile(profileId: string): { ok: boolean; error?: string } {\n if (!this.settings.profiles[profileId]) return { ok: false, error: 'Profile not found' }\n this.workerWs.disconnect()\n this.settings.activeProfile = profileId\n this.saveSettings()\n this.applyProfile(this.getActiveProfile())\n if (this.currentAgent && this.currentCwd) {\n this.connectWorkerWs(this.currentAgent, this.currentCwd)\n }\n return { ok: true }\n }\n\n deleteProfile(profileId: string): { ok: boolean; error?: string } {\n if (profileId === 'production') return { ok: false, error: 'Cannot delete Production profile' }\n if (!this.settings.profiles[profileId]) return { ok: false, error: 'Profile not found' }\n\n if (this.settings.activeProfile === profileId) {\n this.workerWs.disconnect()\n this.settings.activeProfile = 'production'\n this.applyProfile(this.getActiveProfile())\n if (this.currentAgent && this.currentCwd) {\n this.connectWorkerWs(this.currentAgent, this.currentCwd)\n }\n }\n\n delete this.settings.profiles[profileId]\n this.saveSettings()\n return { ok: true }\n }\n\n // \u2500\u2500\u2500 PTY & Agent \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async spawnAgent(agent: AgentConfig, cwd: string): Promise<void> {\n if (this.ptyManager) {\n this.bridge.endSession()\n this.ptyManager.kill()\n }\n\n this.currentAgent = agent\n const prevCwd = this.currentCwd\n this.currentCwd = cwd\n if (prevCwd !== cwd) {\n this.events.onCwdChanged()\n }\n\n this.ptyManager = new PtyManager(agent, cwd)\n\n this.ptyManager.onData((data: string) => {\n this.events.onPtyData(data)\n this.bridge.feedOutput(data)\n this.streamTerminalData(data)\n })\n\n const thisPtyManager = this.ptyManager\n\n this.ptyManager.onExit(async (exitCode: number) => {\n this.events.onPtyExit(exitCode)\n this.bridge.endSession()\n if (thisPtyManager === this.ptyManager && this.currentAgent && isCodingAgent(this.currentAgent)) {\n this.workerWs.disconnect()\n }\n })\n\n this.bridge.startSession()\n\n if (isCodingAgent(agent)) {\n this.connectWorkerWs(agent, cwd)\n } else {\n this.workerWs.disconnect()\n this.checkProjectStatus(cwd)\n }\n }\n\n writePty(data: string): void {\n this.ptyManager?.write(data)\n this.bridge.feedInput(data)\n }\n\n resizePty(cols: number, rows: number): void {\n this.ptyManager?.resize(cols, rows)\n this.bridge.resize(cols, rows)\n this.workerWs.sendTerminalResize(cols, rows)\n }\n\n async killAgent(): Promise<void> {\n this.bridge.endSession()\n this.ptyManager?.kill()\n this.ptyManager = null\n if (this.currentAgent && isCodingAgent(this.currentAgent)) {\n this.workerWs.disconnect()\n }\n }\n\n // \u2500\u2500\u2500 Worker WebSocket \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n connectWorkerWs(agent: AgentConfig, cwd: string): void {\n const profile = this.getActiveProfile()\n const apiKey = profile.apiKey || process.env.CTLSURF_API_KEY\n if (!apiKey) {\n log('[worker-ws] No API key, skipping WS connect')\n return\n }\n\n this.workerWs.connect({\n machine: os.hostname(),\n cwd,\n agent: agent.name,\n })\n }\n\n private async checkProjectStatus(cwd: string): Promise<void> {\n if (!this.ctlsurfApi.getApiKey()) {\n this.events.onWorkerStatus('no_project')\n return\n }\n try {\n const folder = await this.ctlsurfApi.findFolderByPath(cwd)\n if (!folder?.id) {\n this.events.onWorkerStatus('no_project')\n }\n } catch {\n this.events.onWorkerStatus('no_project')\n }\n }\n\n private streamTerminalData(data: string): void {\n this.termStreamBuffer += data\n if (!this.termStreamTimer) {\n this.termStreamTimer = setTimeout(() => {\n if (this.termStreamBuffer) {\n this.workerWs.sendTerminalData(this.termStreamBuffer)\n this.termStreamBuffer = ''\n }\n this.termStreamTimer = null\n }, TERM_STREAM_INTERVAL_MS)\n }\n }\n\n // \u2500\u2500\u2500 Shutdown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async shutdown(): Promise<void> {\n this.bridge.endSession()\n this.ptyManager?.kill()\n this.ptyManager = null\n this.workerWs.disconnect()\n if (this.termStreamTimer) {\n clearTimeout(this.termStreamTimer)\n this.termStreamTimer = null\n }\n }\n}\n", "import { createRequire } from 'module'\nimport { AgentConfig } from './agents'\n\n// Use createRequire to load native module at runtime, bypassing bundler\nconst require = createRequire(import.meta.url)\nconst pty = require('node-pty')\n\nexport class PtyManager {\n private process: any | null = null\n private dataCallbacks: ((data: string) => void)[] = []\n private exitCallbacks: ((code: number) => void)[] = []\n\n constructor(agent: AgentConfig, cwd: string) {\n const shell = agent.command\n const args = agent.args || []\n\n try {\n console.log(`[pty] Spawning: ${shell} ${args.join(' ')} in ${cwd}`)\n } catch {\n // Ignore EPIPE errors when stdout is closed\n }\n\n this.process = pty.spawn(shell, args, {\n name: 'xterm-256color',\n cwd,\n env: process.env as Record<string, string>,\n cols: 80,\n rows: 24\n })\n\n this.process.onData((data: string) => {\n for (const cb of this.dataCallbacks) {\n cb(data)\n }\n })\n\n this.process.onExit(({ exitCode }: { exitCode: number }) => {\n for (const cb of this.exitCallbacks) {\n cb(exitCode)\n }\n this.process = null\n })\n }\n\n write(data: string): void {\n this.process?.write(data)\n }\n\n resize(cols: number, rows: number): void {\n this.process?.resize(cols, rows)\n }\n\n kill(): void {\n this.process?.kill()\n this.process = null\n }\n\n onData(cb: (data: string) => void): void {\n this.dataCallbacks.push(cb)\n }\n\n onExit(cb: (code: number) => void): void {\n this.exitCallbacks.push(cb)\n }\n}\n", "export interface AgentConfig {\n id: string\n name: string\n command: string\n args: string[]\n description: string\n}\n\nfunction getShellCommand(): string {\n if (process.platform === 'win32') return 'powershell.exe'\n return process.env.SHELL || '/bin/zsh'\n}\n\nexport function getBuiltinAgents(): AgentConfig[] {\n return [\n {\n id: 'shell',\n name: 'Shell',\n command: getShellCommand(),\n args: ['-l'], // login shell to load PATH\n description: 'Default system shell'\n },\n {\n id: 'claude',\n name: 'Claude Code',\n command: 'claude',\n args: [],\n description: 'Anthropic Claude Code CLI'\n },\n {\n id: 'codex',\n name: 'Codex CLI',\n command: 'codex',\n args: [],\n description: 'OpenAI Codex CLI'\n }\n ]\n}\n\nexport function getDefaultAgent(): AgentConfig {\n return getBuiltinAgents()[0]\n}\n\nexport function isCodingAgent(agent: AgentConfig): boolean {\n return agent.id !== 'shell'\n}\n", "const CTLSURF_BASE_URL = 'https://app.ctlsurf.com/api'\n\nexport class CtlsurfApi {\n private baseUrl: string\n private apiKey: string | null = null\n\n constructor(baseUrl?: string) {\n this.baseUrl = baseUrl || CTLSURF_BASE_URL\n }\n\n setApiKey(key: string): void {\n this.apiKey = key\n }\n\n setBaseUrl(url: string): void {\n this.baseUrl = url.endsWith('/api') ? url : `${url}/api`\n }\n\n getApiKey(): string | null {\n return this.apiKey\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' }\n if (this.apiKey) {\n h['Authorization'] = `Bearer ${this.apiKey}`\n }\n return h\n }\n\n private async request(method: string, path: string, body?: unknown): Promise<any> {\n const url = `${this.baseUrl}${path}`\n const opts: RequestInit = {\n method,\n headers: this.headers()\n }\n if (body) {\n opts.body = JSON.stringify(body)\n }\n\n const res = await fetch(url, opts)\n if (!res.ok) {\n const text = await res.text()\n throw new Error(`ctlsurf API ${method} ${path}: ${res.status} ${text}`)\n }\n return res.json()\n }\n\n // \u2500\u2500\u2500 Pages \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async createPage(params: {\n title: string\n type?: string\n parent_id?: string\n folder_id?: string\n cwd?: string\n tags?: string[]\n }): Promise<any> {\n return this.request('POST', '/pages', params)\n }\n\n async findPageByRootPath(rootPath: string): Promise<any> {\n return this.request('POST', '/pages/find-by-root-path', { root_path: rootPath })\n }\n\n // \u2500\u2500\u2500 Blocks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async createBlock(pageId: string, params: {\n type: string\n title?: string\n props?: Record<string, unknown>\n }): Promise<any> {\n return this.request('POST', `/blocks/page/${pageId}`, params)\n }\n\n async getBlock(blockId: string): Promise<any> {\n return this.request('GET', `/blocks/${blockId}`)\n }\n\n async updateBlock(blockId: string, params: {\n props?: Record<string, unknown>\n title?: string\n }): Promise<any> {\n return this.request('PUT', `/blocks/${blockId}`, params)\n }\n\n // \u2500\u2500\u2500 Folders \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async createFolder(params: { name: string; root_path: string }): Promise<any> {\n return this.request('POST', '/folders', params)\n }\n\n // \u2500\u2500\u2500 Workers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async getAuthCode(): Promise<{ code: string }> {\n return this.request('POST', '/workers/token-exchange')\n }\n\n async findFolderByPath(rootPath: string): Promise<any> {\n return this.request('POST', '/folders/find-by-path', { root_path: rootPath })\n }\n\n async getFolderPages(folderId: string): Promise<any[]> {\n const folder = await this.request('GET', `/folders/${folderId}`)\n return folder?.pages || []\n }\n\n async findFolderByGitRemote(gitRemote: string): Promise<any> {\n // Search folders by listing all and matching git_remote\n const folders = await this.request('GET', '/folders')\n return folders?.find((f: any) => f.git_remote === gitRemote || f.root_path === gitRemote) || null\n }\n\n // \u2500\u2500\u2500 Log convenience \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async appendLog(blockId: string, action: string, message: string, data?: Record<string, unknown>): Promise<any> {\n // Read-modify-write: get current entries, append, put back\n const block = await this.getBlock(blockId)\n const props = block.props || {}\n const entries = Array.isArray(props.entries) ? [...props.entries] : []\n const maxEntries = props.max_entries || 1000\n\n const entry: Record<string, unknown> = {\n _id: `log_${entries.length}`,\n _timestamp: new Date().toISOString(),\n action,\n message\n }\n if (data) {\n entry.data = data\n }\n\n entries.push(entry)\n\n // Trim oldest if over max\n const trimmed = entries.length > maxEntries ? entries.slice(-maxEntries) : entries\n\n return this.updateBlock(blockId, {\n props: { ...props, entries: trimmed }\n })\n }\n}\n", "import { WorkerWsClient } from './workerWs'\n\n/**\n * Conversation Bridge\n *\n * Captures PTY output, strips ANSI codes and terminal artifacts,\n * and sends cleaned text to the backend via WebSocket for chat logging.\n * Uses a simple buffer + byte threshold approach (no timers, since\n * setTimeout/setInterval don't reliably fire under TUI raw mode).\n */\nexport class ConversationBridge {\n private wsClient: WorkerWsClient | null = null\n private sessionActive: boolean = false\n private inputBuffer: string = ''\n private outputBuffer: string = ''\n\n private readonly FLUSH_BYTES = 500\n\n setWsClient(ws: WorkerWsClient): void {\n this.wsClient = ws\n }\n\n startSession(): void {\n this.outputBuffer = ''\n this.inputBuffer = ''\n this.sessionActive = true\n console.log('[bridge] Session started')\n }\n\n feedOutput(data: string): void {\n if (!this.sessionActive) return\n\n this.outputBuffer += data\n\n if (this.outputBuffer.length >= this.FLUSH_BYTES) {\n this.flush()\n }\n }\n\n feedInput(data: string): void {\n if (!this.sessionActive) return\n this.inputBuffer += data\n\n if (data.includes('\\r') || data.includes('\\n')) {\n const cleaned = cleanInput(this.inputBuffer)\n if (cleaned.length > 0) {\n this.sendEntry('user_input', cleaned)\n }\n this.inputBuffer = ''\n }\n }\n\n resize(_cols: number, _rows: number): void {\n // no-op now (was used for xterm)\n }\n\n private flush(): void {\n if (this.outputBuffer.length === 0) return\n\n const raw = this.outputBuffer\n this.outputBuffer = ''\n\n const cleaned = cleanOutput(raw)\n if (cleaned.length === 0) return\n\n this.sendEntry('terminal_output', cleaned)\n }\n\n private sendEntry(type: string, content: string): void {\n if (!this.wsClient) return\n this.wsClient.sendChatLog({\n ts: new Date().toISOString(),\n type,\n content,\n })\n }\n\n endSession(): void {\n if (!this.sessionActive) return\n\n this.flush()\n\n this.sessionActive = false\n this.inputBuffer = ''\n this.outputBuffer = ''\n console.log('[bridge] Session ended')\n }\n}\n\n/**\n * Strip ANSI escape codes from a string.\n */\nfunction stripAnsi(str: string): string {\n return str\n // CSI sequences (e.g. \\x1b[0m, \\x1b[?2004h, \\x1b[1;32m, \\x1b[38;2;r;g;bm)\n .replace(/\\x1b\\[[\\x30-\\x3f]*[\\x20-\\x2f]*[\\x40-\\x7e]/g, '')\n // OSC sequences (e.g. \\x1b]0;title\\x07, \\x1b]...\\x1b\\\\)\n .replace(/\\x1b\\][^\\x07\\x1b]*(?:\\x07|\\x1b\\\\)/g, '')\n // DCS/PM/APC sequences\n .replace(/\\x1b[PX^_][^\\x1b]*\\x1b\\\\/g, '')\n // Other escape sequences (charset, keypad mode, etc.)\n .replace(/\\x1b[^[\\]PX^_](.|$)/g, '')\n // Remaining single ESC\n .replace(/\\x1b/g, '')\n}\n\n/**\n * Process backspace characters: each \\x7f or \\b deletes the preceding char.\n */\nfunction processBackspaces(str: string): string {\n const result: string[] = []\n for (const ch of str) {\n if (ch === '\\x7f' || ch === '\\b') {\n result.pop()\n } else {\n result.push(ch)\n }\n }\n return result.join('')\n}\n\n/**\n * Clean user input: strip ANSI, process backspaces, remove control chars.\n */\nfunction cleanInput(str: string): string {\n let cleaned = stripAnsi(str)\n cleaned = processBackspaces(cleaned)\n // eslint-disable-next-line no-control-regex\n cleaned = cleaned.replace(/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/g, '')\n return cleaned.trim()\n}\n\n/**\n * Clean terminal output: strip ANSI, remove control chars, collapse noise.\n */\nfunction cleanOutput(str: string): string {\n let cleaned = stripAnsi(str)\n // Remove carriage returns\n cleaned = cleaned.replace(/\\r/g, '')\n // Remove control characters except newline/tab\n // eslint-disable-next-line no-control-regex\n cleaned = cleaned.replace(/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/g, '')\n // Collapse 3+ consecutive newlines into 2\n cleaned = cleaned.replace(/\\n{3,}/g, '\\n\\n')\n // Remove lines that are only whitespace\n cleaned = cleaned.split('\\n').filter(line => line.trim().length > 0).join('\\n')\n return cleaned.trim()\n}\n", "import os from 'os'\nimport crypto from 'crypto'\nimport WsModule from 'ws'\n\n// Use native WebSocket if available (Node 22+), otherwise fall back to ws package\nconst WS: typeof WebSocket = typeof WebSocket !== 'undefined' ? WebSocket : WsModule as any\n\nfunction log(...args: unknown[]): void {\n try { console.log(...args) } catch { /* EPIPE safe */ }\n}\n\nconst HEARTBEAT_INTERVAL_MS = 30_000\nconst RECONNECT_DELAY_MS = 5_000\nconst MAX_RECONNECT_DELAY_MS = 60_000\n\nexport interface WorkerRegistration {\n machine: string\n cwd: string\n repo_url?: string\n agent: string\n fingerprint: string\n}\n\nexport interface WorkerWsEvents {\n onStatusChange: (status: WorkerWsStatus) => void\n onMessage: (message: IncomingMessage) => void\n onRegistered: (data: { worker_id: string; folder_id: string | null; status: string; pending_messages?: IncomingMessage[] }) => void\n onTerminalInput?: (data: string) => void\n}\n\nexport interface IncomingMessage {\n id: string\n type: string\n content: string\n metadata?: Record<string, unknown> | null\n parent_id?: string | null\n}\n\nexport type WorkerWsStatus = 'disconnected' | 'connecting' | 'connected' | 'pending_approval'\n\nexport class WorkerWsClient {\n private ws: WebSocket | null = null\n private apiKey: string | null = null\n private baseUrl: string\n private events: WorkerWsEvents\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null\n private reconnectDelay = RECONNECT_DELAY_MS\n private registration: WorkerRegistration | null = null\n private workerId: string | null = null\n private _status: WorkerWsStatus = 'disconnected'\n private shouldReconnect = false\n private fingerprint: string\n\n constructor(events: WorkerWsEvents, baseUrl?: string) {\n this.events = events\n this.baseUrl = baseUrl || 'wss://app.ctlsurf.com'\n // Generate a stable machine fingerprint\n this.fingerprint = this.generateFingerprint()\n }\n\n get status(): WorkerWsStatus {\n return this._status\n }\n\n get currentWorkerId(): string | null {\n return this.workerId\n }\n\n setApiKey(key: string | null): void {\n this.apiKey = key\n }\n\n setBaseUrl(url: string): void {\n this.baseUrl = url\n }\n\n private generateFingerprint(): string {\n const data = `${os.hostname()}:${os.userInfo().username}:${os.platform()}:${os.arch()}`\n return crypto.createHash('sha256').update(data).digest('hex').slice(0, 32)\n }\n\n private setStatus(status: WorkerWsStatus): void {\n if (this._status !== status) {\n this._status = status\n this.events.onStatusChange(status)\n }\n }\n\n connect(registration: WorkerRegistration): void {\n this.registration = { ...registration, fingerprint: this.fingerprint }\n this.shouldReconnect = true\n this.doConnect()\n }\n\n disconnect(): void {\n this.shouldReconnect = false\n this.clearTimers()\n if (this.ws) {\n const oldWs = this.ws\n this.ws = null\n // Remove handlers before closing to prevent stale onclose from firing\n oldWs.onopen = null\n oldWs.onmessage = null\n oldWs.onclose = null\n oldWs.onerror = null\n try { oldWs.close(1000, 'client disconnect') } catch { /* ignore */ }\n }\n this.setStatus('disconnected')\n }\n\n sendResponse(parentId: string, content: string, metadata?: Record<string, unknown>): void {\n this.send({\n type: 'response',\n parent_id: parentId,\n content,\n metadata,\n })\n }\n\n sendStatusUpdate(status: string): void {\n this.send({ type: 'status_update', status })\n }\n\n sendAck(messageId: string): void {\n this.send({ type: 'ack', message_id: messageId })\n }\n\n sendTerminalData(data: string): void {\n this.send({ type: 'terminal_stream', data })\n }\n\n sendTerminalResize(cols: number, rows: number): void {\n this.send({ type: 'terminal_resize', cols, rows })\n }\n\n sendChatLog(entry: { type: string; content: string; ts?: string }): void {\n this.send({ type: 'chat_log', entry })\n }\n\n private doConnect(): void {\n if (!this.apiKey || !this.registration) {\n log('[worker-ws] No API key or registration, skipping connect')\n return\n }\n\n this.clearTimers()\n if (this.ws) {\n const oldWs = this.ws\n this.ws = null\n oldWs.onopen = null\n oldWs.onmessage = null\n oldWs.onclose = null\n oldWs.onerror = null\n try { oldWs.close() } catch { /* ignore */ }\n // Let the old connection fully close before opening a new one\n setTimeout(() => this.doConnectNow(), 500)\n return\n }\n\n this.doConnectNow()\n }\n\n private doConnectNow(): void {\n if (!this.apiKey || !this.registration) return\n if (!this.shouldReconnect) {\n log('[worker-ws] shouldReconnect is false, aborting connect')\n return\n }\n\n this.setStatus('connecting')\n\n // Use ws:// for localhost, wss:// for remote\n const wsBase = this.baseUrl.replace(/^http/, 'ws')\n const url = `${wsBase}/api/ws/worker?token=${encodeURIComponent(this.apiKey)}`\n\n log(`[worker-ws] Connecting to ${url.replace(/token=.*/, 'token=***')}...`)\n\n try {\n this.ws = new WS(url) as unknown as WebSocket\n } catch (err) {\n log('[worker-ws] Failed to create WebSocket:', err)\n this.scheduleReconnect()\n return\n }\n\n this.ws.onopen = () => {\n log('[worker-ws] Connected, sending register')\n this.reconnectDelay = RECONNECT_DELAY_MS // Reset backoff\n this.send({\n type: 'register',\n ...this.registration,\n })\n this.startHeartbeat()\n }\n\n this.ws.onmessage = (event) => {\n try {\n const data = JSON.parse(String(event.data))\n this.handleMessage(data)\n } catch (err) {\n log('[worker-ws] Failed to parse message:', err)\n }\n }\n\n this.ws.onclose = (event) => {\n log(`[worker-ws] Disconnected: ${event.code} ${event.reason}`)\n this.ws = null\n this.clearHeartbeat()\n this.setStatus('disconnected')\n if (this.shouldReconnect) {\n this.scheduleReconnect()\n }\n }\n\n this.ws.onerror = () => {\n log('[worker-ws] WebSocket error')\n }\n }\n\n private handleMessage(data: Record<string, unknown>): void {\n const msgType = data.type as string\n\n switch (msgType) {\n case 'registered': {\n this.workerId = data.worker_id as string\n const workerStatus = data.status as string\n console.log(`[worker-ws] Registered as ${this.workerId}, status: ${workerStatus}`)\n\n if (workerStatus === 'pending_approval') {\n this.setStatus('pending_approval')\n } else {\n this.setStatus('connected')\n }\n\n const pendingMessages = (data.pending_messages || []) as IncomingMessage[]\n this.events.onRegistered({\n worker_id: this.workerId,\n folder_id: data.folder_id as string | null,\n status: workerStatus,\n pending_messages: pendingMessages,\n })\n\n // Deliver pending messages\n for (const msg of pendingMessages) {\n this.events.onMessage(msg)\n }\n break\n }\n\n case 'approved': {\n log('[worker-ws] Worker approved!')\n this.setStatus('connected')\n break\n }\n\n case 'message': {\n const msg = data.message as IncomingMessage\n if (msg) {\n console.log(`[worker-ws] Received message: ${msg.id}`)\n this.events.onMessage(msg)\n }\n break\n }\n\n case 'terminal_input': {\n const inputData = data.data as string\n if (inputData && this.events.onTerminalInput) {\n this.events.onTerminalInput(inputData)\n }\n break\n }\n\n case 'heartbeat_ack':\n break\n\n default:\n console.log(`[worker-ws] Unknown message type: ${msgType}`)\n }\n }\n\n private send(data: Record<string, unknown>): void {\n if (this.ws && this.ws.readyState === WS.OPEN) {\n this.ws.send(JSON.stringify(data))\n }\n }\n\n\n private startHeartbeat(): void {\n this.clearHeartbeat()\n this.heartbeatTimer = setInterval(() => {\n this.send({ type: 'heartbeat' })\n }, HEARTBEAT_INTERVAL_MS)\n }\n\n private clearHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n private scheduleReconnect(): void {\n if (!this.shouldReconnect) return\n console.log(`[worker-ws] Reconnecting in ${this.reconnectDelay / 1000}s...`)\n this.reconnectTimer = setTimeout(() => {\n this.doConnect()\n }, this.reconnectDelay)\n // Exponential backoff\n this.reconnectDelay = Math.min(this.reconnectDelay * 2, MAX_RECONNECT_DELAY_MS)\n }\n\n private clearTimers(): void {\n this.clearHeartbeat()\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n }\n}\n", "import path from 'path'\nimport os from 'os'\n\nexport function getSettingsDir(useElectron: boolean): string {\n if (useElectron) {\n const { app } = require('electron')\n return app.getPath('userData')\n }\n\n if (process.platform === 'darwin') {\n return path.join(os.homedir(), 'Library', 'Application Support', 'ctlsurf-worker')\n }\n return path.join(\n process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'),\n 'ctlsurf-worker'\n )\n}\n", "/**\n * Terminal UI (TUI) renderer\n *\n * Draws a title bar and status bar around the PTY output area.\n * Uses ANSI escape codes and scroll regions \u2014 no external dependencies.\n */\n\nconst ESC = '\\x1b'\nconst CSI = `${ESC}[`\n\n// Colors (Tokyo Night palette)\nconst BG_BAR = `${CSI}48;2;22;22;30m` // #16161e\nconst FG_TITLE = `${CSI}38;2;192;202;245m` // #c0caf5\nconst FG_DIM = `${CSI}38;2;86;95;137m` // #565f89\nconst FG_ACCENT = `${CSI}38;2;122;162;247m` // #7aa2f7\nconst FG_GREEN = `${CSI}38;2;158;206;106m` // #9ece6a\nconst FG_RED = `${CSI}38;2;247;118;142m` // #f7768e\nconst FG_YELLOW = `${CSI}38;2;224;175;104m` // #e0af68\nconst FG_WHITE = `${CSI}38;2;169;177;214m` // #a9b1d6\nconst BG_MODAL = `${CSI}48;2;31;35;53m` // #1f2335\nconst BG_SELECTED = `${CSI}48;2;42;43;61m` // #2a2b3d\nconst RESET = `${CSI}0m`\n\nexport interface TuiState {\n agentName: string\n cwd: string\n wsStatus: string\n workerId: string | null\n mode: string\n}\n\nexport class Tui {\n private rows: number = 0\n private cols: number = 0\n private state: TuiState = {\n agentName: '',\n cwd: '',\n wsStatus: 'disconnected',\n workerId: null,\n mode: 'terminal',\n }\n\n constructor() {\n this.rows = process.stdout.rows || 24\n this.cols = process.stdout.columns || 80\n }\n\n /**\n * Initialize the TUI: alternate screen, hide cursor reporting, set scroll region\n */\n init(): void {\n // Alternate screen buffer\n this.write(`${CSI}?1049h`)\n // Set scroll region: lines 2 to (rows - 1), leaving line 1 for title, line rows for status\n this.setScrollRegion()\n // Move cursor to the PTY area\n this.moveToPtyArea()\n // Draw chrome\n this.drawTitleBar()\n this.drawStatusBar()\n }\n\n /**\n * Restore terminal to normal state\n */\n destroy(): void {\n // Reset scroll region\n this.write(`${CSI}r`)\n // Leave alternate screen\n this.write(`${CSI}?1049l`)\n // Show cursor\n this.write(`${CSI}?25h`)\n }\n\n /**\n * Handle terminal resize\n */\n resize(cols: number, rows: number): void {\n this.cols = cols\n this.rows = rows\n this.setScrollRegion()\n this.drawTitleBar()\n this.drawStatusBar()\n this.moveToPtyArea()\n }\n\n /**\n * Get the PTY dimensions (main area minus title + status bars)\n */\n getPtySize(): { cols: number; rows: number } {\n return {\n cols: this.cols,\n rows: Math.max(1, this.rows - 2),\n }\n }\n\n /**\n * Update state and redraw bars\n */\n update(partial: Partial<TuiState>): void {\n Object.assign(this.state, partial)\n // Save cursor, draw bars, restore cursor\n this.write(`${CSI}s`) // save cursor\n this.drawTitleBar()\n this.drawStatusBar()\n this.write(`${CSI}u`) // restore cursor\n }\n\n /**\n * Write PTY output to the scroll region.\n * Cursor is assumed to be in the PTY area already.\n */\n writePtyData(data: string): void {\n // PTY data goes straight to stdout \u2014 it's already in the scroll region\n this.write(data)\n }\n\n /**\n * Update the terminal window/tab title via OSC escape sequence.\n * Works in passthrough mode (no chrome) \u2014 doesn't conflict with the agent's TUI.\n */\n setTerminalTitle(title: string): void {\n this.write(`${ESC}]0;${title}\\x07`)\n }\n\n /**\n * Build a title string from current state for the terminal tab.\n */\n updateTerminalTitle(): void {\n const { agentName, wsStatus, cwd } = this.state\n const displayCwd = this.shortenPath(cwd)\n const statusIcon = {\n connected: '\\u25CF',\n connecting: '\\u25CB',\n disconnected: '\\u25CB',\n pending_approval: '\\u25CB',\n no_project: '\\u25CB',\n }[wsStatus] || '\\u25CB'\n const statusLabel = {\n connected: 'Connected',\n connecting: 'Connecting...',\n disconnected: 'Disconnected',\n pending_approval: 'Pending',\n no_project: 'No Project',\n }[wsStatus] || wsStatus\n\n this.setTerminalTitle(`ctlsurf \\u00B7 ${agentName} \\u00B7 ${statusIcon} ${statusLabel} \\u00B7 ${displayCwd}`)\n }\n\n /**\n * Show an interactive agent picker modal.\n * Returns a promise that resolves with the selected agent index.\n */\n showAgentPicker(agents: { name: string; description: string }[]): Promise<number> {\n return new Promise((resolve) => {\n let selected = 0\n const modalWidth = 44\n const modalHeight = agents.length + 4 // border + title + items + border\n const startCol = Math.max(1, Math.floor((this.cols - modalWidth) / 2))\n const startRow = Math.max(1, Math.floor((this.rows - modalHeight) / 2))\n\n // Enter alternate screen if not already\n this.write(`${CSI}?1049h`)\n // Hide cursor\n this.write(`${CSI}?25l`)\n\n const drawModal = () => {\n const topBorder = '\\u250c' + '\\u2500'.repeat(modalWidth - 2) + '\\u2510'\n const botBorder = '\\u2514' + '\\u2500'.repeat(modalWidth - 2) + '\\u2518'\n const emptyLine = '\\u2502' + ' '.repeat(modalWidth - 2) + '\\u2502'\n\n // Draw background fill\n for (let r = 0; r < this.rows; r++) {\n this.write(`${CSI}${r + 1};1H${BG_BAR}${CSI}2K${RESET}`)\n }\n\n // Draw logo/branding centered\n const brand = 'ctlsurf'\n const brandCol = Math.max(1, Math.floor((this.cols - brand.length) / 2))\n this.write(`${CSI}${startRow - 2};${brandCol}H${FG_ACCENT}${brand}${RESET}`)\n\n // Top border\n this.write(`${CSI}${startRow};${startCol}H${BG_MODAL}${FG_DIM}${topBorder}${RESET}`)\n\n // Title\n const title = ' Select Agent'\n const titlePad = ' '.repeat(Math.max(0, modalWidth - 2 - title.length))\n this.write(`${CSI}${startRow + 1};${startCol}H${BG_MODAL}${FG_DIM}\\u2502${RESET}${BG_MODAL}${FG_TITLE}${title}${titlePad}${FG_DIM}\\u2502${RESET}`)\n\n // Separator\n const sep = '\\u251c' + '\\u2500'.repeat(modalWidth - 2) + '\\u2524'\n this.write(`${CSI}${startRow + 2};${startCol}H${BG_MODAL}${FG_DIM}${sep}${RESET}`)\n\n // Agent items\n for (let i = 0; i < agents.length; i++) {\n const agent = agents[i]\n const row = startRow + 3 + i\n const isSelected = i === selected\n const bg = isSelected ? BG_SELECTED : BG_MODAL\n const pointer = isSelected ? `${FG_ACCENT}\\u25B8 ` : ' '\n const nameFg = isSelected ? FG_ACCENT : FG_WHITE\n const descFg = FG_DIM\n const nameStr = agent.name\n const descStr = agent.description ? ` ${FG_DIM}\u2014 ${agent.description.slice(0, 20)}` : ''\n const content = `${pointer}${nameFg}${nameStr}${descStr}`\n const contentLen = (isSelected ? 2 : 2) + nameStr.length + (agent.description ? 3 + Math.min(20, agent.description.length) : 0)\n const pad = ' '.repeat(Math.max(0, modalWidth - 2 - contentLen))\n this.write(`${CSI}${row};${startCol}H${bg}${FG_DIM}\\u2502${RESET}${bg}${content}${pad}${RESET}${BG_MODAL}${FG_DIM}\\u2502${RESET}`)\n }\n\n // Bottom border\n const botRow = startRow + 3 + agents.length\n this.write(`${CSI}${botRow};${startCol}H${BG_MODAL}${FG_DIM}${botBorder}${RESET}`)\n\n // Hint\n const hint = '\\u2191\\u2193 navigate \u00B7 Enter select \u00B7 q quit'\n const hintCol = Math.max(1, Math.floor((this.cols - hint.length) / 2))\n this.write(`${CSI}${botRow + 2};${hintCol}H${FG_DIM}${hint}${RESET}`)\n }\n\n drawModal()\n\n // Set raw mode to capture keypresses\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(true)\n }\n process.stdin.resume()\n\n const onKey = (data: Buffer) => {\n const key = data.toString()\n\n if (key === '\\x1b[A' || key === 'k') {\n // Up\n selected = (selected - 1 + agents.length) % agents.length\n drawModal()\n } else if (key === '\\x1b[B' || key === 'j') {\n // Down\n selected = (selected + 1) % agents.length\n drawModal()\n } else if (key === '\\r' || key === '\\n') {\n // Enter \u2014 select\n cleanup()\n resolve(selected)\n } else if (key === 'q' || key === '\\x1b' || key === '\\x03') {\n // q, Escape, Ctrl+C \u2014 quit\n cleanup()\n this.write(`${CSI}?25h`) // show cursor\n this.write(`${CSI}?1049l`) // leave alt screen\n process.exit(0)\n }\n }\n\n const cleanup = () => {\n process.stdin.removeListener('data', onKey)\n // Show cursor again\n this.write(`${CSI}?25h`)\n // Clear the modal (will be redrawn by init())\n }\n\n process.stdin.on('data', onKey)\n })\n }\n\n // \u2500\u2500\u2500 Internal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private write(data: string): void {\n try {\n process.stdout.write(data)\n } catch { /* EPIPE safe */ }\n }\n\n private setScrollRegion(): void {\n // Scroll region from line 2 to line (rows - 1)\n this.write(`${CSI}2;${this.rows - 1}r`)\n }\n\n private moveToPtyArea(): void {\n // Move cursor to top of PTY area (line 2)\n this.write(`${CSI}2;1H`)\n }\n\n private drawTitleBar(): void {\n const { agentName, cwd, wsStatus } = this.state\n // Move to line 1\n this.write(`${CSI}1;1H`)\n // Clear line and draw\n this.write(`${BG_BAR}${CSI}2K`)\n\n const displayCwd = this.shortenPath(cwd)\n\n // WS status indicator\n const statusColor = {\n connected: FG_GREEN,\n connecting: FG_YELLOW,\n disconnected: FG_RED,\n pending_approval: FG_YELLOW,\n no_project: FG_DIM,\n }[wsStatus] || FG_DIM\n const statusLabel = {\n connected: 'Connected',\n connecting: 'Connecting...',\n disconnected: 'Disconnected',\n pending_approval: 'Pending',\n no_project: 'No Project',\n }[wsStatus] || wsStatus\n const wsIndicator = `${statusColor}\\u25CF ${statusLabel}${RESET}${BG_BAR}`\n\n const left = ` ${FG_ACCENT}ctlsurf${RESET}${BG_BAR} ${FG_DIM}\\u2502${RESET}${BG_BAR} ${FG_TITLE}${agentName || 'starting...'}${RESET}${BG_BAR} ${FG_DIM}\\u2502${RESET}${BG_BAR} ${FG_DIM}${displayCwd}${RESET}${BG_BAR}`\n const right = `${wsIndicator} ${RESET}${BG_BAR}`\n\n this.write(left)\n const pad = Math.max(0, this.cols - this.visibleLen(left) - this.visibleLen(right))\n this.write(' '.repeat(pad))\n this.write(right)\n this.write(RESET)\n }\n\n private drawStatusBar(): void {\n const { wsStatus, workerId, cwd } = this.state\n // Move to last line\n this.write(`${CSI}${this.rows};1H`)\n // Clear line and draw\n this.write(`${BG_BAR}${CSI}2K`)\n\n const statusColor = {\n connected: FG_GREEN,\n connecting: FG_YELLOW,\n disconnected: FG_RED,\n pending_approval: FG_YELLOW,\n no_project: FG_DIM,\n }[wsStatus] || FG_DIM\n\n const statusDot = `${statusColor}\\u25CF${RESET}${BG_BAR}`\n const statusLabel = {\n connected: 'Connected',\n connecting: 'Connecting...',\n disconnected: 'Disconnected',\n pending_approval: 'Pending Approval',\n no_project: 'No Project',\n }[wsStatus] || wsStatus\n\n const displayCwd = this.shortenPath(cwd)\n const left = ` ${statusDot} ${FG_DIM}${statusLabel}${RESET}${BG_BAR}`\n const right = `${FG_DIM}Ctrl+\\\\ exit${RESET}${BG_BAR} ${FG_DIM}${displayCwd} ${RESET}${BG_BAR}`\n\n this.write(left)\n const pad = Math.max(0, this.cols - this.visibleLen(left) - this.visibleLen(right))\n this.write(' '.repeat(pad))\n this.write(right)\n this.write(RESET)\n }\n\n private shortenPath(p: string): string {\n if (!p) return ''\n const home = process.env.HOME || ''\n if (home && p.startsWith(home)) {\n return '~' + p.slice(home.length)\n }\n return p\n }\n\n private visibleLen(s: string): number {\n // Strip ANSI codes to get visible length\n return s.replace(/\\x1b\\[[^m]*m/g, '').length\n }\n}\n", "#!/usr/bin/env node\n\n/**\n * ctlsurf terminal mode (TUI)\n *\n * Runs the agent in a PTY with a terminal UI: title bar, status bar,\n * conversation logging, and WebSocket control. No Electron required.\n *\n * Usage:\n * ctlsurf --terminal [--agent claude] [--cwd /path] [--api-key KEY] [--base-url URL] [--profile NAME]\n *\n * If no --agent is given, shows an interactive agent picker.\n * Press Ctrl+\\ to exit at any time.\n */\n\n// Prevent EPIPE crashes\nprocess.stdout?.on?.('error', () => {})\nprocess.stderr?.on?.('error', () => {})\nprocess.on('uncaughtException', (err) => {\n if (err.message === 'write EPIPE') return\n try { console.error('[uncaught]', err) } catch { /* ignore */ }\n})\n\nimport { Orchestrator } from './orchestrator'\nimport { getSettingsDir } from './settingsDir'\nimport { getBuiltinAgents, getDefaultAgent, isCodingAgent, type AgentConfig } from './agents'\nimport { Tui } from './tui'\n\n// \u2500\u2500\u2500 CLI arg parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface CliArgs {\n agent: string | null\n cwd: string\n apiKey: string | null\n baseUrl: string | null\n profile: string | null\n}\n\nfunction parseArgs(argv: string[]): CliArgs {\n const args: CliArgs = {\n agent: null,\n cwd: process.env.CTLSURF_WORKER_CWD || process.cwd(),\n apiKey: null,\n baseUrl: null,\n profile: null,\n }\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n const next = argv[i + 1]\n switch (arg) {\n case '--agent': args.agent = next; i++; break\n case '--cwd': args.cwd = next; i++; break\n case '--api-key': args.apiKey = next; i++; break\n case '--base-url': args.baseUrl = next; i++; break\n case '--profile': args.profile = next; i++; break\n case '--terminal': break\n case '--desktop': break\n }\n }\n return args\n}\n\n// \u2500\u2500\u2500 Main \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function main() {\n const args = parseArgs(process.argv.slice(2))\n const settingsDir = getSettingsDir(false)\n\n const tui = new Tui()\n const agents = getBuiltinAgents()\n\n // \u2500\u2500\u2500 Agent selection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n let agent: AgentConfig\n\n if (args.agent) {\n const found = agents.find(a => a.id === args.agent)\n agent = found || {\n id: args.agent,\n name: args.agent,\n command: args.agent,\n args: [],\n description: `Custom agent: ${args.agent}`,\n }\n } else {\n // Show interactive picker\n const selectedIdx = await tui.showAgentPicker(agents)\n agent = agents[selectedIdx]\n }\n\n // \u2500\u2500\u2500 Start TUI + agent \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n tui.update({\n agentName: agent.name,\n cwd: args.cwd,\n mode: 'terminal',\n })\n\n tui.init()\n\n const orchestrator = new Orchestrator(settingsDir, {\n onPtyData: (data) => {\n tui.writePtyData(data)\n },\n onPtyExit: (code) => {\n tui.destroy()\n console.log(`Agent exited with code ${code}`)\n orchestrator.shutdown().then(() => process.exit(code))\n },\n onWorkerStatus: (status) => {\n tui.update({ wsStatus: status })\n },\n onWorkerMessage: () => {},\n onWorkerRegistered: () => {\n tui.update({ wsStatus: 'connected' })\n },\n onCwdChanged: () => {\n tui.update({ cwd: orchestrator.cwd || '' })\n },\n })\n\n orchestrator.loadSettings()\n\n if (args.profile) orchestrator.switchProfile(args.profile)\n if (args.apiKey) orchestrator.overrideApiKey(args.apiKey)\n if (args.baseUrl) orchestrator.overrideBaseUrl(args.baseUrl)\n\n // Spawn agent with PTY sized to fit the TUI content area\n const ptySize = tui.getPtySize()\n await orchestrator.spawnAgent(agent, args.cwd)\n orchestrator.resizePty(ptySize.cols, ptySize.rows)\n\n // For coding agents, send an initial prompt to kick-start them\n if (isCodingAgent(agent)) {\n setTimeout(() => {\n orchestrator.writePty('hello\\r')\n }, 1000)\n }\n\n // Pipe stdin to PTY, with Ctrl+\\ as the exit key\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(true)\n process.stdin.resume()\n process.stdin.on('data', (data) => {\n const str = data.toString()\n // Ctrl+\\ (0x1c) = exit\n if (str === '\\x1c') {\n shutdown()\n return\n }\n orchestrator.writePty(str)\n })\n }\n\n // Handle terminal resize\n process.stdout.on('resize', () => {\n const cols = process.stdout.columns || 80\n const rows = process.stdout.rows || 24\n tui.resize(cols, rows)\n const size = tui.getPtySize()\n orchestrator.resizePty(size.cols, size.rows)\n })\n\n // Graceful shutdown\n const shutdown = async () => {\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false)\n }\n tui.destroy()\n await orchestrator.shutdown()\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n}\n\nmain().catch((err) => {\n process.stdout.write('\\x1b[?1049l')\n console.error('Fatal error:', err)\n process.exit(1)\n})\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;AAAA;AAAA;AAAA,QAAMA,MAAK,UAAQ,IAAI;AACvB,QAAMC,QAAO,UAAQ,MAAM;AAE3B,QAAM,WAAWA,MAAK,KAAK,WAAW,UAAU;AAEhD,aAAS,kBAAmB;AAC1B,UAAI;AACJ,UAAID,IAAG,WAAW,QAAQ,GAAG;AAC3B,yBAAiBA,IAAG,aAAa,UAAU,OAAO;AAAA,MACpD;AACA,UAAI,QAAQ,IAAI,6BAA6B;AAC3C,eAAOC,MAAK,KAAK,QAAQ,IAAI,6BAA6B,kBAAkB,UAAU;AAAA,MACxF;AACA,UAAI,gBAAgB;AAClB,eAAOA,MAAK,KAAK,WAAW,QAAQ,cAAc;AAAA,MACpD,OAAO;AACL,cAAM,IAAI,MAAM,oGAAoG;AAAA,MACtH;AAAA,IACF;AAEA,WAAO,UAAU,gBAAgB;AAAA;AAAA;;;ACpBjC,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,OAAOC,SAAQ;;;ACFf,SAAS,qBAAqB;AAI9B,IAAMC,WAAU,cAAc,YAAY,GAAG;AAC7C,IAAM,MAAMA,SAAQ,UAAU;AAEvB,IAAM,aAAN,MAAiB;AAAA,EACd,UAAsB;AAAA,EACtB,gBAA4C,CAAC;AAAA,EAC7C,gBAA4C,CAAC;AAAA,EAErD,YAAY,OAAoB,KAAa;AAC3C,UAAM,QAAQ,MAAM;AACpB,UAAM,OAAO,MAAM,QAAQ,CAAC;AAE5B,QAAI;AACF,cAAQ,IAAI,mBAAmB,KAAK,IAAI,KAAK,KAAK,GAAG,CAAC,OAAO,GAAG,EAAE;AAAA,IACpE,QAAQ;AAAA,IAER;AAEA,SAAK,UAAU,IAAI,MAAM,OAAO,MAAM;AAAA,MACpC,MAAM;AAAA,MACN;AAAA,MACA,KAAK,QAAQ;AAAA,MACb,MAAM;AAAA,MACN,MAAM;AAAA,IACR,CAAC;AAED,SAAK,QAAQ,OAAO,CAAC,SAAiB;AACpC,iBAAW,MAAM,KAAK,eAAe;AACnC,WAAG,IAAI;AAAA,MACT;AAAA,IACF,CAAC;AAED,SAAK,QAAQ,OAAO,CAAC,EAAE,SAAS,MAA4B;AAC1D,iBAAW,MAAM,KAAK,eAAe;AACnC,WAAG,QAAQ;AAAA,MACb;AACA,WAAK,UAAU;AAAA,IACjB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAoB;AACxB,SAAK,SAAS,MAAM,IAAI;AAAA,EAC1B;AAAA,EAEA,OAAO,MAAc,MAAoB;AACvC,SAAK,SAAS,OAAO,MAAM,IAAI;AAAA,EACjC;AAAA,EAEA,OAAa;AACX,SAAK,SAAS,KAAK;AACnB,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,OAAO,IAAkC;AACvC,SAAK,cAAc,KAAK,EAAE;AAAA,EAC5B;AAAA,EAEA,OAAO,IAAkC;AACvC,SAAK,cAAc,KAAK,EAAE;AAAA,EAC5B;AACF;;;ACxDA,SAAS,kBAA0B;AACjC,MAAI,QAAQ,aAAa,QAAS,QAAO;AACzC,SAAO,QAAQ,IAAI,SAAS;AAC9B;AAEO,SAAS,mBAAkC;AAChD,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,SAAS,gBAAgB;AAAA,MACzB,MAAM,CAAC,IAAI;AAAA;AAAA,MACX,aAAa;AAAA,IACf;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,SAAS;AAAA,MACT,MAAM,CAAC;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,SAAS;AAAA,MACT,MAAM,CAAC;AAAA,MACP,aAAa;AAAA,IACf;AAAA,EACF;AACF;AAMO,SAAS,cAAc,OAA6B;AACzD,SAAO,MAAM,OAAO;AACtB;;;AC7CA,IAAM,mBAAmB;AAElB,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EACA,SAAwB;AAAA,EAEhC,YAAY,SAAkB;AAC5B,SAAK,UAAU,WAAW;AAAA,EAC5B;AAAA,EAEA,UAAU,KAAmB;AAC3B,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,WAAW,KAAmB;AAC5B,SAAK,UAAU,IAAI,SAAS,MAAM,IAAI,MAAM,GAAG,GAAG;AAAA,EACpD;AAAA,EAEA,YAA2B;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,UAAkC;AACxC,UAAM,IAA4B,EAAE,gBAAgB,mBAAmB;AACvE,QAAI,KAAK,QAAQ;AACf,QAAE,eAAe,IAAI,UAAU,KAAK,MAAM;AAAA,IAC5C;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,QAAQ,QAAgBC,OAAc,MAA8B;AAChF,UAAM,MAAM,GAAG,KAAK,OAAO,GAAGA,KAAI;AAClC,UAAM,OAAoB;AAAA,MACxB;AAAA,MACA,SAAS,KAAK,QAAQ;AAAA,IACxB;AACA,QAAI,MAAM;AACR,WAAK,OAAO,KAAK,UAAU,IAAI;AAAA,IACjC;AAEA,UAAM,MAAM,MAAM,MAAM,KAAK,IAAI;AACjC,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,YAAM,IAAI,MAAM,eAAe,MAAM,IAAIA,KAAI,KAAK,IAAI,MAAM,IAAI,IAAI,EAAE;AAAA,IACxE;AACA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA,EAIA,MAAM,WAAW,QAOA;AACf,WAAO,KAAK,QAAQ,QAAQ,UAAU,MAAM;AAAA,EAC9C;AAAA,EAEA,MAAM,mBAAmB,UAAgC;AACvD,WAAO,KAAK,QAAQ,QAAQ,4BAA4B,EAAE,WAAW,SAAS,CAAC;AAAA,EACjF;AAAA;AAAA,EAIA,MAAM,YAAY,QAAgB,QAIjB;AACf,WAAO,KAAK,QAAQ,QAAQ,gBAAgB,MAAM,IAAI,MAAM;AAAA,EAC9D;AAAA,EAEA,MAAM,SAAS,SAA+B;AAC5C,WAAO,KAAK,QAAQ,OAAO,WAAW,OAAO,EAAE;AAAA,EACjD;AAAA,EAEA,MAAM,YAAY,SAAiB,QAGlB;AACf,WAAO,KAAK,QAAQ,OAAO,WAAW,OAAO,IAAI,MAAM;AAAA,EACzD;AAAA;AAAA,EAIA,MAAM,aAAa,QAA2D;AAC5E,WAAO,KAAK,QAAQ,QAAQ,YAAY,MAAM;AAAA,EAChD;AAAA;AAAA,EAIA,MAAM,cAAyC;AAC7C,WAAO,KAAK,QAAQ,QAAQ,yBAAyB;AAAA,EACvD;AAAA,EAEA,MAAM,iBAAiB,UAAgC;AACrD,WAAO,KAAK,QAAQ,QAAQ,yBAAyB,EAAE,WAAW,SAAS,CAAC;AAAA,EAC9E;AAAA,EAEA,MAAM,eAAe,UAAkC;AACrD,UAAM,SAAS,MAAM,KAAK,QAAQ,OAAO,YAAY,QAAQ,EAAE;AAC/D,WAAO,QAAQ,SAAS,CAAC;AAAA,EAC3B;AAAA,EAEA,MAAM,sBAAsB,WAAiC;AAE3D,UAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,UAAU;AACpD,WAAO,SAAS,KAAK,CAAC,MAAW,EAAE,eAAe,aAAa,EAAE,cAAc,SAAS,KAAK;AAAA,EAC/F;AAAA;AAAA,EAIA,MAAM,UAAU,SAAiB,QAAgB,SAAiB,MAA8C;AAE9G,UAAM,QAAQ,MAAM,KAAK,SAAS,OAAO;AACzC,UAAM,QAAQ,MAAM,SAAS,CAAC;AAC9B,UAAM,UAAU,MAAM,QAAQ,MAAM,OAAO,IAAI,CAAC,GAAG,MAAM,OAAO,IAAI,CAAC;AACrE,UAAM,aAAa,MAAM,eAAe;AAExC,UAAM,QAAiC;AAAA,MACrC,KAAK,OAAO,QAAQ,MAAM;AAAA,MAC1B,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC;AAAA,MACA;AAAA,IACF;AACA,QAAI,MAAM;AACR,YAAM,OAAO;AAAA,IACf;AAEA,YAAQ,KAAK,KAAK;AAGlB,UAAM,UAAU,QAAQ,SAAS,aAAa,QAAQ,MAAM,CAAC,UAAU,IAAI;AAE3E,WAAO,KAAK,YAAY,SAAS;AAAA,MAC/B,OAAO,EAAE,GAAG,OAAO,SAAS,QAAQ;AAAA,IACtC,CAAC;AAAA,EACH;AACF;;;ACnIO,IAAM,qBAAN,MAAyB;AAAA,EACtB,WAAkC;AAAA,EAClC,gBAAyB;AAAA,EACzB,cAAsB;AAAA,EACtB,eAAuB;AAAA,EAEd,cAAc;AAAA,EAE/B,YAAY,IAA0B;AACpC,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,eAAqB;AACnB,SAAK,eAAe;AACpB,SAAK,cAAc;AACnB,SAAK,gBAAgB;AACrB,YAAQ,IAAI,0BAA0B;AAAA,EACxC;AAAA,EAEA,WAAW,MAAoB;AAC7B,QAAI,CAAC,KAAK,cAAe;AAEzB,SAAK,gBAAgB;AAErB,QAAI,KAAK,aAAa,UAAU,KAAK,aAAa;AAChD,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA,EAEA,UAAU,MAAoB;AAC5B,QAAI,CAAC,KAAK,cAAe;AACzB,SAAK,eAAe;AAEpB,QAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG;AAC9C,YAAM,UAAU,WAAW,KAAK,WAAW;AAC3C,UAAI,QAAQ,SAAS,GAAG;AACtB,aAAK,UAAU,cAAc,OAAO;AAAA,MACtC;AACA,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,OAAO,OAAe,OAAqB;AAAA,EAE3C;AAAA,EAEQ,QAAc;AACpB,QAAI,KAAK,aAAa,WAAW,EAAG;AAEpC,UAAM,MAAM,KAAK;AACjB,SAAK,eAAe;AAEpB,UAAM,UAAU,YAAY,GAAG;AAC/B,QAAI,QAAQ,WAAW,EAAG;AAE1B,SAAK,UAAU,mBAAmB,OAAO;AAAA,EAC3C;AAAA,EAEQ,UAAU,MAAc,SAAuB;AACrD,QAAI,CAAC,KAAK,SAAU;AACpB,SAAK,SAAS,YAAY;AAAA,MACxB,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,aAAmB;AACjB,QAAI,CAAC,KAAK,cAAe;AAEzB,SAAK,MAAM;AAEX,SAAK,gBAAgB;AACrB,SAAK,cAAc;AACnB,SAAK,eAAe;AACpB,YAAQ,IAAI,wBAAwB;AAAA,EACtC;AACF;AAKA,SAAS,UAAU,KAAqB;AACtC,SAAO,IAEJ,QAAQ,8CAA8C,EAAE,EAExD,QAAQ,sCAAsC,EAAE,EAEhD,QAAQ,6BAA6B,EAAE,EAEvC,QAAQ,wBAAwB,EAAE,EAElC,QAAQ,SAAS,EAAE;AACxB;AAKA,SAAS,kBAAkB,KAAqB;AAC9C,QAAM,SAAmB,CAAC;AAC1B,aAAW,MAAM,KAAK;AACpB,QAAI,OAAO,UAAU,OAAO,MAAM;AAChC,aAAO,IAAI;AAAA,IACb,OAAO;AACL,aAAO,KAAK,EAAE;AAAA,IAChB;AAAA,EACF;AACA,SAAO,OAAO,KAAK,EAAE;AACvB;AAKA,SAAS,WAAW,KAAqB;AACvC,MAAI,UAAU,UAAU,GAAG;AAC3B,YAAU,kBAAkB,OAAO;AAEnC,YAAU,QAAQ,QAAQ,qCAAqC,EAAE;AACjE,SAAO,QAAQ,KAAK;AACtB;AAKA,SAAS,YAAY,KAAqB;AACxC,MAAI,UAAU,UAAU,GAAG;AAE3B,YAAU,QAAQ,QAAQ,OAAO,EAAE;AAGnC,YAAU,QAAQ,QAAQ,qCAAqC,EAAE;AAEjE,YAAU,QAAQ,QAAQ,WAAW,MAAM;AAE3C,YAAU,QAAQ,MAAM,IAAI,EAAE,OAAO,UAAQ,KAAK,KAAK,EAAE,SAAS,CAAC,EAAE,KAAK,IAAI;AAC9E,SAAO,QAAQ,KAAK;AACtB;;;ACnJA,OAAO,QAAQ;AACf,OAAO,YAAY;AACnB,OAAO,cAAc;AAGrB,IAAM,KAAuB,OAAO,cAAc,cAAc,YAAY;AAE5E,SAAS,OAAO,MAAuB;AACrC,MAAI;AAAE,YAAQ,IAAI,GAAG,IAAI;AAAA,EAAE,QAAQ;AAAA,EAAmB;AACxD;AAEA,IAAM,wBAAwB;AAC9B,IAAM,qBAAqB;AAC3B,IAAM,yBAAyB;AA2BxB,IAAM,iBAAN,MAAqB;AAAA,EAClB,KAAuB;AAAA,EACvB,SAAwB;AAAA,EACxB;AAAA,EACA;AAAA,EACA,iBAAwD;AAAA,EACxD,iBAAuD;AAAA,EACvD,iBAAiB;AAAA,EACjB,eAA0C;AAAA,EAC1C,WAA0B;AAAA,EAC1B,UAA0B;AAAA,EAC1B,kBAAkB;AAAA,EAClB;AAAA,EAER,YAAY,QAAwB,SAAkB;AACpD,SAAK,SAAS;AACd,SAAK,UAAU,WAAW;AAE1B,SAAK,cAAc,KAAK,oBAAoB;AAAA,EAC9C;AAAA,EAEA,IAAI,SAAyB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,kBAAiC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAU,KAA0B;AAClC,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,WAAW,KAAmB;AAC5B,SAAK,UAAU;AAAA,EACjB;AAAA,EAEQ,sBAA8B;AACpC,UAAM,OAAO,GAAG,GAAG,SAAS,CAAC,IAAI,GAAG,SAAS,EAAE,QAAQ,IAAI,GAAG,SAAS,CAAC,IAAI,GAAG,KAAK,CAAC;AACrF,WAAO,OAAO,WAAW,QAAQ,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAAA,EAC3E;AAAA,EAEQ,UAAU,QAA8B;AAC9C,QAAI,KAAK,YAAY,QAAQ;AAC3B,WAAK,UAAU;AACf,WAAK,OAAO,eAAe,MAAM;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,QAAQ,cAAwC;AAC9C,SAAK,eAAe,EAAE,GAAG,cAAc,aAAa,KAAK,YAAY;AACrE,SAAK,kBAAkB;AACvB,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,aAAmB;AACjB,SAAK,kBAAkB;AACvB,SAAK,YAAY;AACjB,QAAI,KAAK,IAAI;AACX,YAAM,QAAQ,KAAK;AACnB,WAAK,KAAK;AAEV,YAAM,SAAS;AACf,YAAM,YAAY;AAClB,YAAM,UAAU;AAChB,YAAM,UAAU;AAChB,UAAI;AAAE,cAAM,MAAM,KAAM,mBAAmB;AAAA,MAAE,QAAQ;AAAA,MAAe;AAAA,IACtE;AACA,SAAK,UAAU,cAAc;AAAA,EAC/B;AAAA,EAEA,aAAa,UAAkB,SAAiB,UAA0C;AACxF,SAAK,KAAK;AAAA,MACR,MAAM;AAAA,MACN,WAAW;AAAA,MACX;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,iBAAiB,QAAsB;AACrC,SAAK,KAAK,EAAE,MAAM,iBAAiB,OAAO,CAAC;AAAA,EAC7C;AAAA,EAEA,QAAQ,WAAyB;AAC/B,SAAK,KAAK,EAAE,MAAM,OAAO,YAAY,UAAU,CAAC;AAAA,EAClD;AAAA,EAEA,iBAAiB,MAAoB;AACnC,SAAK,KAAK,EAAE,MAAM,mBAAmB,KAAK,CAAC;AAAA,EAC7C;AAAA,EAEA,mBAAmB,MAAc,MAAoB;AACnD,SAAK,KAAK,EAAE,MAAM,mBAAmB,MAAM,KAAK,CAAC;AAAA,EACnD;AAAA,EAEA,YAAY,OAA6D;AACvE,SAAK,KAAK,EAAE,MAAM,YAAY,MAAM,CAAC;AAAA,EACvC;AAAA,EAEQ,YAAkB;AACxB,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,cAAc;AACtC,UAAI,0DAA0D;AAC9D;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,QAAI,KAAK,IAAI;AACX,YAAM,QAAQ,KAAK;AACnB,WAAK,KAAK;AACV,YAAM,SAAS;AACf,YAAM,YAAY;AAClB,YAAM,UAAU;AAChB,YAAM,UAAU;AAChB,UAAI;AAAE,cAAM,MAAM;AAAA,MAAE,QAAQ;AAAA,MAAe;AAE3C,iBAAW,MAAM,KAAK,aAAa,GAAG,GAAG;AACzC;AAAA,IACF;AAEA,SAAK,aAAa;AAAA,EACpB;AAAA,EAEQ,eAAqB;AAC3B,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,aAAc;AACxC,QAAI,CAAC,KAAK,iBAAiB;AACzB,UAAI,wDAAwD;AAC5D;AAAA,IACF;AAEA,SAAK,UAAU,YAAY;AAG3B,UAAM,SAAS,KAAK,QAAQ,QAAQ,SAAS,IAAI;AACjD,UAAM,MAAM,GAAG,MAAM,wBAAwB,mBAAmB,KAAK,MAAM,CAAC;AAE5E,QAAI,6BAA6B,IAAI,QAAQ,YAAY,WAAW,CAAC,KAAK;AAE1E,QAAI;AACF,WAAK,KAAK,IAAI,GAAG,GAAG;AAAA,IACtB,SAAS,KAAK;AACZ,UAAI,2CAA2C,GAAG;AAClD,WAAK,kBAAkB;AACvB;AAAA,IACF;AAEA,SAAK,GAAG,SAAS,MAAM;AACrB,UAAI,yCAAyC;AAC7C,WAAK,iBAAiB;AACtB,WAAK,KAAK;AAAA,QACR,MAAM;AAAA,QACN,GAAG,KAAK;AAAA,MACV,CAAC;AACD,WAAK,eAAe;AAAA,IACtB;AAEA,SAAK,GAAG,YAAY,CAAC,UAAU;AAC7B,UAAI;AACF,cAAM,OAAO,KAAK,MAAM,OAAO,MAAM,IAAI,CAAC;AAC1C,aAAK,cAAc,IAAI;AAAA,MACzB,SAAS,KAAK;AACZ,YAAI,wCAAwC,GAAG;AAAA,MACjD;AAAA,IACF;AAEA,SAAK,GAAG,UAAU,CAAC,UAAU;AAC3B,UAAI,6BAA6B,MAAM,IAAI,IAAI,MAAM,MAAM,EAAE;AAC7D,WAAK,KAAK;AACV,WAAK,eAAe;AACpB,WAAK,UAAU,cAAc;AAC7B,UAAI,KAAK,iBAAiB;AACxB,aAAK,kBAAkB;AAAA,MACzB;AAAA,IACF;AAEA,SAAK,GAAG,UAAU,MAAM;AACtB,UAAI,6BAA6B;AAAA,IACnC;AAAA,EACF;AAAA,EAEQ,cAAc,MAAqC;AACzD,UAAM,UAAU,KAAK;AAErB,YAAQ,SAAS;AAAA,MACf,KAAK,cAAc;AACjB,aAAK,WAAW,KAAK;AACrB,cAAM,eAAe,KAAK;AAC1B,gBAAQ,IAAI,6BAA6B,KAAK,QAAQ,aAAa,YAAY,EAAE;AAEjF,YAAI,iBAAiB,oBAAoB;AACvC,eAAK,UAAU,kBAAkB;AAAA,QACnC,OAAO;AACL,eAAK,UAAU,WAAW;AAAA,QAC5B;AAEA,cAAM,kBAAmB,KAAK,oBAAoB,CAAC;AACnD,aAAK,OAAO,aAAa;AAAA,UACvB,WAAW,KAAK;AAAA,UAChB,WAAW,KAAK;AAAA,UAChB,QAAQ;AAAA,UACR,kBAAkB;AAAA,QACpB,CAAC;AAGD,mBAAW,OAAO,iBAAiB;AACjC,eAAK,OAAO,UAAU,GAAG;AAAA,QAC3B;AACA;AAAA,MACF;AAAA,MAEA,KAAK,YAAY;AACf,YAAI,8BAA8B;AAClC,aAAK,UAAU,WAAW;AAC1B;AAAA,MACF;AAAA,MAEA,KAAK,WAAW;AACd,cAAM,MAAM,KAAK;AACjB,YAAI,KAAK;AACP,kBAAQ,IAAI,iCAAiC,IAAI,EAAE,EAAE;AACrD,eAAK,OAAO,UAAU,GAAG;AAAA,QAC3B;AACA;AAAA,MACF;AAAA,MAEA,KAAK,kBAAkB;AACrB,cAAM,YAAY,KAAK;AACvB,YAAI,aAAa,KAAK,OAAO,iBAAiB;AAC5C,eAAK,OAAO,gBAAgB,SAAS;AAAA,QACvC;AACA;AAAA,MACF;AAAA,MAEA,KAAK;AACH;AAAA,MAEF;AACE,gBAAQ,IAAI,qCAAqC,OAAO,EAAE;AAAA,IAC9D;AAAA,EACF;AAAA,EAEQ,KAAK,MAAqC;AAChD,QAAI,KAAK,MAAM,KAAK,GAAG,eAAe,GAAG,MAAM;AAC7C,WAAK,GAAG,KAAK,KAAK,UAAU,IAAI,CAAC;AAAA,IACnC;AAAA,EACF;AAAA,EAGQ,iBAAuB;AAC7B,SAAK,eAAe;AACpB,SAAK,iBAAiB,YAAY,MAAM;AACtC,WAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAAA,IACjC,GAAG,qBAAqB;AAAA,EAC1B;AAAA,EAEQ,iBAAuB;AAC7B,QAAI,KAAK,gBAAgB;AACvB,oBAAc,KAAK,cAAc;AACjC,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,oBAA0B;AAChC,QAAI,CAAC,KAAK,gBAAiB;AAC3B,YAAQ,IAAI,+BAA+B,KAAK,iBAAiB,GAAI,MAAM;AAC3E,SAAK,iBAAiB,WAAW,MAAM;AACrC,WAAK,UAAU;AAAA,IACjB,GAAG,KAAK,cAAc;AAEtB,SAAK,iBAAiB,KAAK,IAAI,KAAK,iBAAiB,GAAG,sBAAsB;AAAA,EAChF;AAAA,EAEQ,cAAoB;AAC1B,SAAK,eAAe;AACpB,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AACF;;;ALrTA,SAASC,QAAO,MAAuB;AACrC,MAAI;AAAE,YAAQ,IAAI,GAAG,IAAI;AAAA,EAAE,QAAQ;AAAA,EAAmB;AACxD;AA8BA,IAAM,mBAA4C;AAAA,EAChD,YAAY;AAAA,IACV,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,iBAAiB;AAAA,EACnB;AACF;AAEA,IAAM,0BAA0B;AAEzB,IAAM,eAAN,MAAmB;AAAA,EAChB;AAAA,EACA;AAAA;AAAA,EAGC,aAAa,IAAI,WAAW;AAAA,EAC5B,SAAS,IAAI,mBAAmB;AAAA,EAChC;AAAA;AAAA,EAGD,aAAgC;AAAA,EAChC,eAAmC;AAAA,EACnC,aAA4B;AAAA,EAC5B,WAAyB;AAAA,IAC/B,eAAe;AAAA,IACf,UAAU,EAAE,GAAG,iBAAiB;AAAA,EAClC;AAAA;AAAA,EAGQ,mBAAmB;AAAA,EACnB,kBAAwD;AAAA,EAEhE,YAAY,aAAqB,QAA4B;AAC3D,SAAK,cAAc;AACnB,SAAK,SAAS;AAEd,SAAK,WAAW,IAAI,eAAe;AAAA,MACjC,gBAAgB,CAAC,WAA2B;AAC1C,QAAAA,KAAI,uBAAuB,MAAM,EAAE;AACnC,eAAO,eAAe,MAAM;AAAA,MAC9B;AAAA,MACA,WAAW,CAAC,YAA6B;AACvC,QAAAA,KAAI,iCAAiC,QAAQ,EAAE,KAAK,QAAQ,IAAI,GAAG;AACnE,eAAO,gBAAgB,OAAO;AAC9B,aAAK,SAAS,QAAQ,QAAQ,EAAE;AAEhC,YAAI,QAAQ,SAAS,YAAY,QAAQ,SAAS,iBAAiB;AACjE,cAAI,KAAK,YAAY;AACnB,iBAAK,WAAW,MAAM,QAAQ,UAAU,IAAI;AAC5C,iBAAK,OAAO,UAAU,QAAQ,OAAO;AAAA,UACvC;AAAA,QACF;AAAA,MACF;AAAA,MACA,cAAc,CAAC,SAAS;AACtB,QAAAA,KAAI,qCAAqC,KAAK,SAAS,eAAe,KAAK,SAAS,YAAY,KAAK,MAAM,EAAE;AAC7G,eAAO,mBAAmB,IAAI;AAC9B,YAAI,CAAC,KAAK,WAAW;AACnB,iBAAO,eAAe,YAAY;AAAA,QACpC;AAAA,MACF;AAAA,MACA,iBAAiB,CAAC,SAAiB;AACjC,aAAK,YAAY,MAAM,IAAI;AAAA,MAC7B;AAAA,IACF,CAAC;AAED,SAAK,OAAO,YAAY,KAAK,QAAQ;AAAA,EACvC;AAAA;AAAA,EAIA,mBAA4B;AAC1B,WAAO,KAAK,SAAS,SAAS,KAAK,SAAS,aAAa,KAAK,KAAK,SAAS,SAAS,cAAc,iBAAiB;AAAA,EACtH;AAAA,EAEA,IAAI,eAA6B;AAC/B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,MAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,QAA4B;AAC9B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,aAAa,SAAwB;AACnC,UAAM,SAAS,QAAQ,UAAU,QAAQ,IAAI,mBAAmB;AAChE,QAAI,QAAQ;AACV,WAAK,WAAW,UAAU,MAAM;AAChC,WAAK,SAAS,UAAU,MAAM;AAAA,IAChC,OAAO;AACL,WAAK,WAAW,UAAU,EAAE;AAC5B,WAAK,SAAS,UAAU,IAAI;AAAA,IAC9B;AAEA,UAAM,UAAU,QAAQ,WAAW,QAAQ,IAAI,oBAAoB;AACnE,SAAK,WAAW,WAAW,OAAO;AAClC,SAAK,SAAS,WAAW,OAAO;AAEhC,IAAAA,KAAI,+BAA+B,QAAQ,IAAI,KAAK,OAAO,GAAG;AAAA,EAChE;AAAA,EAEA,eAAqB;AAEnB,QAAI;AAAE,SAAG,UAAU,KAAK,aAAa,EAAE,WAAW,KAAK,CAAC;AAAA,IAAE,QAAQ;AAAA,IAAe;AAEjF,UAAM,eAAe,KAAK,KAAK,KAAK,aAAa,eAAe;AAChE,QAAI;AACF,UAAI,GAAG,WAAW,YAAY,GAAG;AAC/B,cAAM,MAAM,KAAK,MAAM,GAAG,aAAa,cAAc,OAAO,CAAC;AAE7D,YAAI,CAAC,IAAI,UAAU;AACjB,eAAK,WAAW;AAAA,YACd,eAAe;AAAA,YACf,UAAU;AAAA,cACR,YAAY;AAAA,gBACV,MAAM;AAAA,gBACN,QAAQ,IAAI,iBAAiB;AAAA,gBAC7B,SAAS,IAAI,kBAAkB;AAAA,gBAC/B,iBAAiB,IAAI,0BAA0B;AAAA,cACjD;AAAA,YACF;AAAA,UACF;AACA,eAAK,aAAa;AAClB,UAAAA,KAAI,iDAAiD;AAAA,QACvD,OAAO;AACL,eAAK,WAAW;AAChB,cAAI,CAAC,KAAK,SAAS,SAAS,YAAY;AACtC,iBAAK,SAAS,SAAS,aAAa,EAAE,GAAG,iBAAiB,WAAW;AAAA,UACvE;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AACN,WAAK,WAAW;AAAA,QACd,eAAe;AAAA,QACf,UAAU,EAAE,GAAG,iBAAiB;AAAA,MAClC;AAAA,IACF;AAEA,SAAK,aAAa,KAAK,iBAAiB,CAAC;AAAA,EAC3C;AAAA,EAEA,eAAqB;AACnB,UAAM,eAAe,KAAK,KAAK,KAAK,aAAa,eAAe;AAChE,QAAI;AACF,SAAG,UAAU,KAAK,aAAa,EAAE,WAAW,KAAK,CAAC;AAClD,SAAG,cAAc,cAAc,KAAK,UAAU,KAAK,UAAU,MAAM,CAAC,CAAC;AAAA,IACvE,SAAS,KAAU;AACjB,MAAAA,KAAI,8BAA8B,IAAI,OAAO;AAAA,IAC/C;AAAA,EACF;AAAA,EAEA,eAAe,KAAmB;AAChC,SAAK,WAAW,UAAU,GAAG;AAC7B,SAAK,SAAS,UAAU,GAAG;AAAA,EAC7B;AAAA,EAEA,gBAAgB,KAAmB;AACjC,SAAK,WAAW,WAAW,GAAG;AAC9B,SAAK,SAAS,WAAW,GAAG;AAAA,EAC9B;AAAA;AAAA,EAIA,eAAe;AACb,WAAO;AAAA,MACL,eAAe,KAAK,SAAS;AAAA,MAC7B,UAAU,OAAO,QAAQ,KAAK,SAAS,QAAQ,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO;AAAA,QACjE;AAAA,QACA,MAAM,EAAE;AAAA,QACR,SAAS,EAAE;AAAA,QACX,WAAW,CAAC,CAAC,EAAE;AAAA,QACf,iBAAiB,EAAE,mBAAmB;AAAA,MACxC,EAAE;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,WAAW,WAAmB;AAC5B,UAAM,IAAI,KAAK,SAAS,SAAS,SAAS;AAC1C,QAAI,CAAC,EAAG,QAAO;AACf,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,MAAM,EAAE;AAAA,MACR,SAAS,EAAE;AAAA,MACX,WAAW,CAAC,CAAC,EAAE;AAAA,MACf,iBAAiB,EAAE,mBAAmB;AAAA,IACxC;AAAA,EACF;AAAA,EAEA,YAAY,WAAmB,MAAmF;AAChH,UAAM,WAAW,KAAK,SAAS,SAAS,SAAS;AACjD,SAAK,SAAS,SAAS,SAAS,IAAI;AAAA,MAClC,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK,WAAW,SAAY,KAAK,SAAU,UAAU,UAAU;AAAA,MACvE,SAAS,KAAK,WAAW;AAAA,MACzB,iBAAiB,KAAK,mBAAmB;AAAA,IAC3C;AACA,SAAK,aAAa;AAElB,QAAI,cAAc,KAAK,SAAS,eAAe;AAC7C,WAAK,aAAa,KAAK,SAAS,SAAS,SAAS,CAAC;AACnD,UAAI,KAAK,gBAAgB,KAAK,YAAY;AACxC,aAAK,SAAS,WAAW;AACzB,aAAK,gBAAgB,KAAK,cAAc,KAAK,UAAU;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AAAA,EAEA,cAAc,WAAoD;AAChE,QAAI,CAAC,KAAK,SAAS,SAAS,SAAS,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,oBAAoB;AACvF,SAAK,SAAS,WAAW;AACzB,SAAK,SAAS,gBAAgB;AAC9B,SAAK,aAAa;AAClB,SAAK,aAAa,KAAK,iBAAiB,CAAC;AACzC,QAAI,KAAK,gBAAgB,KAAK,YAAY;AACxC,WAAK,gBAAgB,KAAK,cAAc,KAAK,UAAU;AAAA,IACzD;AACA,WAAO,EAAE,IAAI,KAAK;AAAA,EACpB;AAAA,EAEA,cAAc,WAAoD;AAChE,QAAI,cAAc,aAAc,QAAO,EAAE,IAAI,OAAO,OAAO,mCAAmC;AAC9F,QAAI,CAAC,KAAK,SAAS,SAAS,SAAS,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,oBAAoB;AAEvF,QAAI,KAAK,SAAS,kBAAkB,WAAW;AAC7C,WAAK,SAAS,WAAW;AACzB,WAAK,SAAS,gBAAgB;AAC9B,WAAK,aAAa,KAAK,iBAAiB,CAAC;AACzC,UAAI,KAAK,gBAAgB,KAAK,YAAY;AACxC,aAAK,gBAAgB,KAAK,cAAc,KAAK,UAAU;AAAA,MACzD;AAAA,IACF;AAEA,WAAO,KAAK,SAAS,SAAS,SAAS;AACvC,SAAK,aAAa;AAClB,WAAO,EAAE,IAAI,KAAK;AAAA,EACpB;AAAA;AAAA,EAIA,MAAM,WAAW,OAAoB,KAA4B;AAC/D,QAAI,KAAK,YAAY;AACnB,WAAK,OAAO,WAAW;AACvB,WAAK,WAAW,KAAK;AAAA,IACvB;AAEA,SAAK,eAAe;AACpB,UAAM,UAAU,KAAK;AACrB,SAAK,aAAa;AAClB,QAAI,YAAY,KAAK;AACnB,WAAK,OAAO,aAAa;AAAA,IAC3B;AAEA,SAAK,aAAa,IAAI,WAAW,OAAO,GAAG;AAE3C,SAAK,WAAW,OAAO,CAAC,SAAiB;AACvC,WAAK,OAAO,UAAU,IAAI;AAC1B,WAAK,OAAO,WAAW,IAAI;AAC3B,WAAK,mBAAmB,IAAI;AAAA,IAC9B,CAAC;AAED,UAAM,iBAAiB,KAAK;AAE5B,SAAK,WAAW,OAAO,OAAO,aAAqB;AACjD,WAAK,OAAO,UAAU,QAAQ;AAC9B,WAAK,OAAO,WAAW;AACvB,UAAI,mBAAmB,KAAK,cAAc,KAAK,gBAAgB,cAAc,KAAK,YAAY,GAAG;AAC/F,aAAK,SAAS,WAAW;AAAA,MAC3B;AAAA,IACF,CAAC;AAED,SAAK,OAAO,aAAa;AAEzB,QAAI,cAAc,KAAK,GAAG;AACxB,WAAK,gBAAgB,OAAO,GAAG;AAAA,IACjC,OAAO;AACL,WAAK,SAAS,WAAW;AACzB,WAAK,mBAAmB,GAAG;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,SAAS,MAAoB;AAC3B,SAAK,YAAY,MAAM,IAAI;AAC3B,SAAK,OAAO,UAAU,IAAI;AAAA,EAC5B;AAAA,EAEA,UAAU,MAAc,MAAoB;AAC1C,SAAK,YAAY,OAAO,MAAM,IAAI;AAClC,SAAK,OAAO,OAAO,MAAM,IAAI;AAC7B,SAAK,SAAS,mBAAmB,MAAM,IAAI;AAAA,EAC7C;AAAA,EAEA,MAAM,YAA2B;AAC/B,SAAK,OAAO,WAAW;AACvB,SAAK,YAAY,KAAK;AACtB,SAAK,aAAa;AAClB,QAAI,KAAK,gBAAgB,cAAc,KAAK,YAAY,GAAG;AACzD,WAAK,SAAS,WAAW;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA,EAIA,gBAAgB,OAAoB,KAAmB;AACrD,UAAM,UAAU,KAAK,iBAAiB;AACtC,UAAM,SAAS,QAAQ,UAAU,QAAQ,IAAI;AAC7C,QAAI,CAAC,QAAQ;AACX,MAAAA,KAAI,6CAA6C;AACjD;AAAA,IACF;AAEA,SAAK,SAAS,QAAQ;AAAA,MACpB,SAASC,IAAG,SAAS;AAAA,MACrB;AAAA,MACA,OAAO,MAAM;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,mBAAmB,KAA4B;AAC3D,QAAI,CAAC,KAAK,WAAW,UAAU,GAAG;AAChC,WAAK,OAAO,eAAe,YAAY;AACvC;AAAA,IACF;AACA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,WAAW,iBAAiB,GAAG;AACzD,UAAI,CAAC,QAAQ,IAAI;AACf,aAAK,OAAO,eAAe,YAAY;AAAA,MACzC;AAAA,IACF,QAAQ;AACN,WAAK,OAAO,eAAe,YAAY;AAAA,IACzC;AAAA,EACF;AAAA,EAEQ,mBAAmB,MAAoB;AAC7C,SAAK,oBAAoB;AACzB,QAAI,CAAC,KAAK,iBAAiB;AACzB,WAAK,kBAAkB,WAAW,MAAM;AACtC,YAAI,KAAK,kBAAkB;AACzB,eAAK,SAAS,iBAAiB,KAAK,gBAAgB;AACpD,eAAK,mBAAmB;AAAA,QAC1B;AACA,aAAK,kBAAkB;AAAA,MACzB,GAAG,uBAAuB;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,WAA0B;AAC9B,SAAK,OAAO,WAAW;AACvB,SAAK,YAAY,KAAK;AACtB,SAAK,aAAa;AAClB,SAAK,SAAS,WAAW;AACzB,QAAI,KAAK,iBAAiB;AACxB,mBAAa,KAAK,eAAe;AACjC,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AACF;;;AMlZA,OAAOC,WAAU;AACjB,OAAOC,SAAQ;AAER,SAAS,eAAe,aAA8B;AAC3D,MAAI,aAAa;AACf,UAAM,EAAE,IAAI,IAAI;AAChB,WAAO,IAAI,QAAQ,UAAU;AAAA,EAC/B;AAEA,MAAI,QAAQ,aAAa,UAAU;AACjC,WAAOD,MAAK,KAAKC,IAAG,QAAQ,GAAG,WAAW,uBAAuB,gBAAgB;AAAA,EACnF;AACA,SAAOD,MAAK;AAAA,IACV,QAAQ,IAAI,mBAAmBA,MAAK,KAAKC,IAAG,QAAQ,GAAG,SAAS;AAAA,IAChE;AAAA,EACF;AACF;;;ACTA,IAAM,MAAM;AACZ,IAAM,MAAM,GAAG,GAAG;AAGlB,IAAM,SAAS,GAAG,GAAG;AACrB,IAAM,WAAW,GAAG,GAAG;AACvB,IAAM,SAAS,GAAG,GAAG;AACrB,IAAM,YAAY,GAAG,GAAG;AACxB,IAAM,WAAW,GAAG,GAAG;AACvB,IAAM,SAAS,GAAG,GAAG;AACrB,IAAM,YAAY,GAAG,GAAG;AACxB,IAAM,WAAW,GAAG,GAAG;AACvB,IAAM,WAAW,GAAG,GAAG;AACvB,IAAM,cAAc,GAAG,GAAG;AAC1B,IAAM,QAAQ,GAAG,GAAG;AAUb,IAAM,MAAN,MAAU;AAAA,EACP,OAAe;AAAA,EACf,OAAe;AAAA,EACf,QAAkB;AAAA,IACxB,WAAW;AAAA,IACX,KAAK;AAAA,IACL,UAAU;AAAA,IACV,UAAU;AAAA,IACV,MAAM;AAAA,EACR;AAAA,EAEA,cAAc;AACZ,SAAK,OAAO,QAAQ,OAAO,QAAQ;AACnC,SAAK,OAAO,QAAQ,OAAO,WAAW;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AAEX,SAAK,MAAM,GAAG,GAAG,QAAQ;AAEzB,SAAK,gBAAgB;AAErB,SAAK,cAAc;AAEnB,SAAK,aAAa;AAClB,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AAEd,SAAK,MAAM,GAAG,GAAG,GAAG;AAEpB,SAAK,MAAM,GAAG,GAAG,QAAQ;AAEzB,SAAK,MAAM,GAAG,GAAG,MAAM;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,MAAc,MAAoB;AACvC,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,gBAAgB;AACrB,SAAK,aAAa;AAClB,SAAK,cAAc;AACnB,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,aAA6C;AAC3C,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,MAAM,KAAK,IAAI,GAAG,KAAK,OAAO,CAAC;AAAA,IACjC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,SAAkC;AACvC,WAAO,OAAO,KAAK,OAAO,OAAO;AAEjC,SAAK,MAAM,GAAG,GAAG,GAAG;AACpB,SAAK,aAAa;AAClB,SAAK,cAAc;AACnB,SAAK,MAAM,GAAG,GAAG,GAAG;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,MAAoB;AAE/B,SAAK,MAAM,IAAI;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiB,OAAqB;AACpC,SAAK,MAAM,GAAG,GAAG,MAAM,KAAK,MAAM;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,sBAA4B;AAC1B,UAAM,EAAE,WAAW,UAAU,IAAI,IAAI,KAAK;AAC1C,UAAM,aAAa,KAAK,YAAY,GAAG;AACvC,UAAM,aAAa;AAAA,MACjB,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,kBAAkB;AAAA,MAClB,YAAY;AAAA,IACd,EAAE,QAAQ,KAAK;AACf,UAAM,cAAc;AAAA,MAClB,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,kBAAkB;AAAA,MAClB,YAAY;AAAA,IACd,EAAE,QAAQ,KAAK;AAEf,SAAK,iBAAiB,gBAAkB,SAAS,SAAW,UAAU,IAAI,WAAW,SAAW,UAAU,EAAE;AAAA,EAC9G;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAgB,QAAkE;AAChF,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAI,WAAW;AACf,YAAM,aAAa;AACnB,YAAM,cAAc,OAAO,SAAS;AACpC,YAAM,WAAW,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,OAAO,cAAc,CAAC,CAAC;AACrE,YAAM,WAAW,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,OAAO,eAAe,CAAC,CAAC;AAGtE,WAAK,MAAM,GAAG,GAAG,QAAQ;AAEzB,WAAK,MAAM,GAAG,GAAG,MAAM;AAEvB,YAAM,YAAY,MAAM;AACtB,cAAM,YAAY,WAAW,SAAS,OAAO,aAAa,CAAC,IAAI;AAC/D,cAAM,YAAY,WAAW,SAAS,OAAO,aAAa,CAAC,IAAI;AAC/D,cAAM,YAAY,WAAW,IAAI,OAAO,aAAa,CAAC,IAAI;AAG1D,iBAAS,IAAI,GAAG,IAAI,KAAK,MAAM,KAAK;AAClC,eAAK,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,MAAM,GAAG,GAAG,KAAK,KAAK,EAAE;AAAA,QACzD;AAGA,cAAM,QAAQ;AACd,cAAM,WAAW,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,OAAO,MAAM,UAAU,CAAC,CAAC;AACvE,aAAK,MAAM,GAAG,GAAG,GAAG,WAAW,CAAC,IAAI,QAAQ,IAAI,SAAS,GAAG,KAAK,GAAG,KAAK,EAAE;AAG3E,aAAK,MAAM,GAAG,GAAG,GAAG,QAAQ,IAAI,QAAQ,IAAI,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,EAAE;AAGnF,cAAM,QAAQ;AACd,cAAM,WAAW,IAAI,OAAO,KAAK,IAAI,GAAG,aAAa,IAAI,MAAM,MAAM,CAAC;AACtE,aAAK,MAAM,GAAG,GAAG,GAAG,WAAW,CAAC,IAAI,QAAQ,IAAI,QAAQ,GAAG,MAAM,SAAS,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,SAAS,KAAK,EAAE;AAGjJ,cAAM,MAAM,WAAW,SAAS,OAAO,aAAa,CAAC,IAAI;AACzD,aAAK,MAAM,GAAG,GAAG,GAAG,WAAW,CAAC,IAAI,QAAQ,IAAI,QAAQ,GAAG,MAAM,GAAG,GAAG,GAAG,KAAK,EAAE;AAGjF,iBAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,gBAAM,QAAQ,OAAO,CAAC;AACtB,gBAAM,MAAM,WAAW,IAAI;AAC3B,gBAAM,aAAa,MAAM;AACzB,gBAAM,KAAK,aAAa,cAAc;AACtC,gBAAM,UAAU,aAAa,GAAG,SAAS,YAAY;AACrD,gBAAM,SAAS,aAAa,YAAY;AACxC,gBAAM,SAAS;AACf,gBAAM,UAAU,MAAM;AACtB,gBAAM,UAAU,MAAM,cAAc,IAAI,MAAM,UAAK,MAAM,YAAY,MAAM,GAAG,EAAE,CAAC,KAAK;AACtF,gBAAM,UAAU,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO;AACvD,gBAAM,cAAc,aAAa,IAAI,KAAK,QAAQ,UAAU,MAAM,cAAc,IAAI,KAAK,IAAI,IAAI,MAAM,YAAY,MAAM,IAAI;AAC7H,gBAAM,MAAM,IAAI,OAAO,KAAK,IAAI,GAAG,aAAa,IAAI,UAAU,CAAC;AAC/D,eAAK,MAAM,GAAG,GAAG,GAAG,GAAG,IAAI,QAAQ,IAAI,EAAE,GAAG,MAAM,SAAS,KAAK,GAAG,EAAE,GAAG,OAAO,GAAG,GAAG,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,SAAS,KAAK,EAAE;AAAA,QACnI;AAGA,cAAM,SAAS,WAAW,IAAI,OAAO;AACrC,aAAK,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,QAAQ,IAAI,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,EAAE;AAGjF,cAAM,OAAO;AACb,cAAM,UAAU,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,OAAO,KAAK,UAAU,CAAC,CAAC;AACrE,aAAK,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,GAAG,KAAK,EAAE;AAAA,MACtE;AAEA,gBAAU;AAGV,UAAI,QAAQ,MAAM,OAAO;AACvB,gBAAQ,MAAM,WAAW,IAAI;AAAA,MAC/B;AACA,cAAQ,MAAM,OAAO;AAErB,YAAM,QAAQ,CAAC,SAAiB;AAC9B,cAAM,MAAM,KAAK,SAAS;AAE1B,YAAI,QAAQ,YAAY,QAAQ,KAAK;AAEnC,sBAAY,WAAW,IAAI,OAAO,UAAU,OAAO;AACnD,oBAAU;AAAA,QACZ,WAAW,QAAQ,YAAY,QAAQ,KAAK;AAE1C,sBAAY,WAAW,KAAK,OAAO;AACnC,oBAAU;AAAA,QACZ,WAAW,QAAQ,QAAQ,QAAQ,MAAM;AAEvC,kBAAQ;AACR,kBAAQ,QAAQ;AAAA,QAClB,WAAW,QAAQ,OAAO,QAAQ,UAAU,QAAQ,KAAQ;AAE1D,kBAAQ;AACR,eAAK,MAAM,GAAG,GAAG,MAAM;AACvB,eAAK,MAAM,GAAG,GAAG,QAAQ;AACzB,kBAAQ,KAAK,CAAC;AAAA,QAChB;AAAA,MACF;AAEA,YAAM,UAAU,MAAM;AACpB,gBAAQ,MAAM,eAAe,QAAQ,KAAK;AAE1C,aAAK,MAAM,GAAG,GAAG,MAAM;AAAA,MAEzB;AAEA,cAAQ,MAAM,GAAG,QAAQ,KAAK;AAAA,IAChC,CAAC;AAAA,EACH;AAAA;AAAA,EAIQ,MAAM,MAAoB;AAChC,QAAI;AACF,cAAQ,OAAO,MAAM,IAAI;AAAA,IAC3B,QAAQ;AAAA,IAAmB;AAAA,EAC7B;AAAA,EAEQ,kBAAwB;AAE9B,SAAK,MAAM,GAAG,GAAG,KAAK,KAAK,OAAO,CAAC,GAAG;AAAA,EACxC;AAAA,EAEQ,gBAAsB;AAE5B,SAAK,MAAM,GAAG,GAAG,MAAM;AAAA,EACzB;AAAA,EAEQ,eAAqB;AAC3B,UAAM,EAAE,WAAW,KAAK,SAAS,IAAI,KAAK;AAE1C,SAAK,MAAM,GAAG,GAAG,MAAM;AAEvB,SAAK,MAAM,GAAG,MAAM,GAAG,GAAG,IAAI;AAE9B,UAAM,aAAa,KAAK,YAAY,GAAG;AAGvC,UAAM,cAAc;AAAA,MAClB,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,kBAAkB;AAAA,MAClB,YAAY;AAAA,IACd,EAAE,QAAQ,KAAK;AACf,UAAM,cAAc;AAAA,MAClB,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,kBAAkB;AAAA,MAClB,YAAY;AAAA,IACd,EAAE,QAAQ,KAAK;AACf,UAAM,cAAc,GAAG,WAAW,UAAU,WAAW,GAAG,KAAK,GAAG,MAAM;AAExE,UAAM,OAAO,IAAI,SAAS,UAAU,KAAK,GAAG,MAAM,IAAI,MAAM,SAAS,KAAK,GAAG,MAAM,IAAI,QAAQ,GAAG,aAAa,aAAa,GAAG,KAAK,GAAG,MAAM,IAAI,MAAM,SAAS,KAAK,GAAG,MAAM,IAAI,MAAM,GAAG,UAAU,GAAG,KAAK,GAAG,MAAM;AACtN,UAAM,QAAQ,GAAG,WAAW,IAAI,KAAK,GAAG,MAAM;AAE9C,SAAK,MAAM,IAAI;AACf,UAAM,MAAM,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,KAAK,CAAC;AAClF,SAAK,MAAM,IAAI,OAAO,GAAG,CAAC;AAC1B,SAAK,MAAM,KAAK;AAChB,SAAK,MAAM,KAAK;AAAA,EAClB;AAAA,EAEQ,gBAAsB;AAC5B,UAAM,EAAE,UAAU,UAAU,IAAI,IAAI,KAAK;AAEzC,SAAK,MAAM,GAAG,GAAG,GAAG,KAAK,IAAI,KAAK;AAElC,SAAK,MAAM,GAAG,MAAM,GAAG,GAAG,IAAI;AAE9B,UAAM,cAAc;AAAA,MAClB,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,kBAAkB;AAAA,MAClB,YAAY;AAAA,IACd,EAAE,QAAQ,KAAK;AAEf,UAAM,YAAY,GAAG,WAAW,SAAS,KAAK,GAAG,MAAM;AACvD,UAAM,cAAc;AAAA,MAClB,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,kBAAkB;AAAA,MAClB,YAAY;AAAA,IACd,EAAE,QAAQ,KAAK;AAEf,UAAM,aAAa,KAAK,YAAY,GAAG;AACvC,UAAM,OAAO,IAAI,SAAS,IAAI,MAAM,GAAG,WAAW,GAAG,KAAK,GAAG,MAAM;AACnE,UAAM,QAAQ,GAAG,MAAM,eAAe,KAAK,GAAG,MAAM,IAAI,MAAM,GAAG,UAAU,IAAI,KAAK,GAAG,MAAM;AAE7F,SAAK,MAAM,IAAI;AACf,UAAM,MAAM,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,KAAK,CAAC;AAClF,SAAK,MAAM,IAAI,OAAO,GAAG,CAAC;AAC1B,SAAK,MAAM,KAAK;AAChB,SAAK,MAAM,KAAK;AAAA,EAClB;AAAA,EAEQ,YAAY,GAAmB;AACrC,QAAI,CAAC,EAAG,QAAO;AACf,UAAM,OAAO,QAAQ,IAAI,QAAQ;AACjC,QAAI,QAAQ,EAAE,WAAW,IAAI,GAAG;AAC9B,aAAO,MAAM,EAAE,MAAM,KAAK,MAAM;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,WAAW,GAAmB;AAEpC,WAAO,EAAE,QAAQ,iBAAiB,EAAE,EAAE;AAAA,EACxC;AACF;;;AC7VA,QAAQ,QAAQ,KAAK,SAAS,MAAM;AAAC,CAAC;AACtC,QAAQ,QAAQ,KAAK,SAAS,MAAM;AAAC,CAAC;AACtC,QAAQ,GAAG,qBAAqB,CAAC,QAAQ;AACvC,MAAI,IAAI,YAAY,cAAe;AACnC,MAAI;AAAE,YAAQ,MAAM,cAAc,GAAG;AAAA,EAAE,QAAQ;AAAA,EAAe;AAChE,CAAC;AAiBD,SAAS,UAAU,MAAyB;AAC1C,QAAM,OAAgB;AAAA,IACpB,OAAO;AAAA,IACP,KAAK,QAAQ,IAAI,sBAAsB,QAAQ,IAAI;AAAA,IACnD,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AAEA,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,UAAM,OAAO,KAAK,IAAI,CAAC;AACvB,YAAQ,KAAK;AAAA,MACX,KAAK;AAAW,aAAK,QAAQ;AAAM;AAAK;AAAA,MACxC,KAAK;AAAS,aAAK,MAAM;AAAM;AAAK;AAAA,MACpC,KAAK;AAAa,aAAK,SAAS;AAAM;AAAK;AAAA,MAC3C,KAAK;AAAc,aAAK,UAAU;AAAM;AAAK;AAAA,MAC7C,KAAK;AAAa,aAAK,UAAU;AAAM;AAAK;AAAA,MAC5C,KAAK;AAAc;AAAA,MACnB,KAAK;AAAa;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;AAIA,eAAe,OAAO;AACpB,QAAM,OAAO,UAAU,QAAQ,KAAK,MAAM,CAAC,CAAC;AAC5C,QAAM,cAAc,eAAe,KAAK;AAExC,QAAM,MAAM,IAAI,IAAI;AACpB,QAAM,SAAS,iBAAiB;AAGhC,MAAI;AAEJ,MAAI,KAAK,OAAO;AACd,UAAM,QAAQ,OAAO,KAAK,OAAK,EAAE,OAAO,KAAK,KAAK;AAClD,YAAQ,SAAS;AAAA,MACf,IAAI,KAAK;AAAA,MACT,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,MAAM,CAAC;AAAA,MACP,aAAa,iBAAiB,KAAK,KAAK;AAAA,IAC1C;AAAA,EACF,OAAO;AAEL,UAAM,cAAc,MAAM,IAAI,gBAAgB,MAAM;AACpD,YAAQ,OAAO,WAAW;AAAA,EAC5B;AAIA,MAAI,OAAO;AAAA,IACT,WAAW,MAAM;AAAA,IACjB,KAAK,KAAK;AAAA,IACV,MAAM;AAAA,EACR,CAAC;AAED,MAAI,KAAK;AAET,QAAM,eAAe,IAAI,aAAa,aAAa;AAAA,IACjD,WAAW,CAAC,SAAS;AACnB,UAAI,aAAa,IAAI;AAAA,IACvB;AAAA,IACA,WAAW,CAAC,SAAS;AACnB,UAAI,QAAQ;AACZ,cAAQ,IAAI,0BAA0B,IAAI,EAAE;AAC5C,mBAAa,SAAS,EAAE,KAAK,MAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,IACvD;AAAA,IACA,gBAAgB,CAAC,WAAW;AAC1B,UAAI,OAAO,EAAE,UAAU,OAAO,CAAC;AAAA,IACjC;AAAA,IACA,iBAAiB,MAAM;AAAA,IAAC;AAAA,IACxB,oBAAoB,MAAM;AACxB,UAAI,OAAO,EAAE,UAAU,YAAY,CAAC;AAAA,IACtC;AAAA,IACA,cAAc,MAAM;AAClB,UAAI,OAAO,EAAE,KAAK,aAAa,OAAO,GAAG,CAAC;AAAA,IAC5C;AAAA,EACF,CAAC;AAED,eAAa,aAAa;AAE1B,MAAI,KAAK,QAAS,cAAa,cAAc,KAAK,OAAO;AACzD,MAAI,KAAK,OAAQ,cAAa,eAAe,KAAK,MAAM;AACxD,MAAI,KAAK,QAAS,cAAa,gBAAgB,KAAK,OAAO;AAG3D,QAAM,UAAU,IAAI,WAAW;AAC/B,QAAM,aAAa,WAAW,OAAO,KAAK,GAAG;AAC7C,eAAa,UAAU,QAAQ,MAAM,QAAQ,IAAI;AAGjD,MAAI,cAAc,KAAK,GAAG;AACxB,eAAW,MAAM;AACf,mBAAa,SAAS,SAAS;AAAA,IACjC,GAAG,GAAI;AAAA,EACT;AAGA,MAAI,QAAQ,MAAM,OAAO;AACvB,YAAQ,MAAM,WAAW,IAAI;AAC7B,YAAQ,MAAM,OAAO;AACrB,YAAQ,MAAM,GAAG,QAAQ,CAAC,SAAS;AACjC,YAAM,MAAM,KAAK,SAAS;AAE1B,UAAI,QAAQ,KAAQ;AAClB,iBAAS;AACT;AAAA,MACF;AACA,mBAAa,SAAS,GAAG;AAAA,IAC3B,CAAC;AAAA,EACH;AAGA,UAAQ,OAAO,GAAG,UAAU,MAAM;AAChC,UAAM,OAAO,QAAQ,OAAO,WAAW;AACvC,UAAM,OAAO,QAAQ,OAAO,QAAQ;AACpC,QAAI,OAAO,MAAM,IAAI;AACrB,UAAM,OAAO,IAAI,WAAW;AAC5B,iBAAa,UAAU,KAAK,MAAM,KAAK,IAAI;AAAA,EAC7C,CAAC;AAGD,QAAM,WAAW,YAAY;AAC3B,QAAI,QAAQ,MAAM,OAAO;AACvB,cAAQ,MAAM,WAAW,KAAK;AAAA,IAChC;AACA,QAAI,QAAQ;AACZ,UAAM,aAAa,SAAS;AAC5B,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAChC;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,OAAO,MAAM,aAAa;AAClC,UAAQ,MAAM,gBAAgB,GAAG;AACjC,UAAQ,KAAK,CAAC;AAChB,CAAC;",
|
|
6
6
|
"names": ["fs", "path", "os", "require", "path", "log", "os", "path", "os"]
|
|
7
7
|
}
|
package/out/main/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as createWebWorker, l as languages } from "./index-
|
|
2
|
-
import { C as CompletionAdapter, H as HoverAdapter, D as DocumentHighlightAdapter, a as DefinitionAdapter, R as ReferenceAdapter, b as DocumentSymbolAdapter, c as RenameAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, e as DiagnosticsAdapter, S as SelectionRangeAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider } from "./lspLanguageFeatures-
|
|
3
|
-
import { h, i, j, t, k } from "./lspLanguageFeatures-
|
|
1
|
+
import { c as createWebWorker, l as languages } from "./index-CpDga7fF.js";
|
|
2
|
+
import { C as CompletionAdapter, H as HoverAdapter, D as DocumentHighlightAdapter, a as DefinitionAdapter, R as ReferenceAdapter, b as DocumentSymbolAdapter, c as RenameAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, e as DiagnosticsAdapter, S as SelectionRangeAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider } from "./lspLanguageFeatures-BMM3SRX4.js";
|
|
3
|
+
import { h, i, j, t, k } from "./lspLanguageFeatures-BMM3SRX4.js";
|
|
4
4
|
const STOP_WHEN_IDLE_FOR = 2 * 60 * 1e3;
|
|
5
5
|
class WorkerManager {
|
|
6
6
|
constructor(defaults) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as createWebWorker, l as languages } from "./index-
|
|
2
|
-
import { H as HoverAdapter, D as DocumentHighlightAdapter, h as DocumentLinkAdapter, F as FoldingRangeAdapter, b as DocumentSymbolAdapter, S as SelectionRangeAdapter, c as RenameAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter } from "./lspLanguageFeatures-
|
|
3
|
-
import { a, e, d, R, i, j, t, k } from "./lspLanguageFeatures-
|
|
1
|
+
import { c as createWebWorker, l as languages } from "./index-CpDga7fF.js";
|
|
2
|
+
import { H as HoverAdapter, D as DocumentHighlightAdapter, h as DocumentLinkAdapter, F as FoldingRangeAdapter, b as DocumentSymbolAdapter, S as SelectionRangeAdapter, c as RenameAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter } from "./lspLanguageFeatures-BMM3SRX4.js";
|
|
3
|
+
import { a, e, d, R, i, j, t, k } from "./lspLanguageFeatures-BMM3SRX4.js";
|
|
4
4
|
const STOP_WHEN_IDLE_FOR = 2 * 60 * 1e3;
|
|
5
5
|
class WorkerManager {
|
|
6
6
|
constructor(defaults) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-woG3Vtms.js","./lspLanguageFeatures-BMM3SRX4.js","./htmlMode-DzdyF21q.js","./jsonMode-CPhJ_ION.js","./javascript-Dy9cX9Fu.js","./typescript-Cq3g-z7h.js"])))=>i.map(i=>d[i]);
|
|
2
2
|
function getDefaultExportFromCjs(x) {
|
|
3
3
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
4
4
|
}
|
|
@@ -18755,6 +18755,7 @@ function TerminalPanel({ agent, onSpawn, onExit }) {
|
|
|
18755
18755
|
brightWhite: "#c0caf5"
|
|
18756
18756
|
},
|
|
18757
18757
|
scrollback: 1e4,
|
|
18758
|
+
scrollOnUserInput: true,
|
|
18758
18759
|
allowTransparency: false
|
|
18759
18760
|
});
|
|
18760
18761
|
const fitAddon = new addonFitExports.FitAddon();
|
|
@@ -18781,6 +18782,7 @@ function TerminalPanel({ agent, onSpawn, onExit }) {
|
|
|
18781
18782
|
resizeTimeout = setTimeout(() => {
|
|
18782
18783
|
if (fitAddonRef.current && terminalRef.current) {
|
|
18783
18784
|
fitAddonRef.current.fit();
|
|
18785
|
+
terminalRef.current.scrollToBottom();
|
|
18784
18786
|
const { cols, rows } = terminalRef.current;
|
|
18785
18787
|
window.worker.resizePty(cols, rows);
|
|
18786
18788
|
}
|
|
@@ -18808,6 +18810,7 @@ function TerminalPanel({ agent, onSpawn, onExit }) {
|
|
|
18808
18810
|
onSpawn(agent).then(() => {
|
|
18809
18811
|
if (fitAddonRef.current && terminalRef.current) {
|
|
18810
18812
|
fitAddonRef.current.fit();
|
|
18813
|
+
terminalRef.current.scrollToBottom();
|
|
18811
18814
|
const { cols, rows } = terminalRef.current;
|
|
18812
18815
|
window.worker.resizePty(cols, rows);
|
|
18813
18816
|
}
|
|
@@ -206489,7 +206492,7 @@ const lessDefaults = new LanguageServiceDefaultsImpl$3(
|
|
|
206489
206492
|
modeConfigurationDefault$2
|
|
206490
206493
|
);
|
|
206491
206494
|
function getMode$3() {
|
|
206492
|
-
return __vitePreload(() => import("./cssMode-
|
|
206495
|
+
return __vitePreload(() => import("./cssMode-woG3Vtms.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
|
|
206493
206496
|
}
|
|
206494
206497
|
languages.onLanguage("less", () => {
|
|
206495
206498
|
getMode$3().then((mode2) => mode2.setupMode(lessDefaults));
|
|
@@ -206594,7 +206597,7 @@ const razorLanguageService = registerHTMLLanguageService(
|
|
|
206594
206597
|
);
|
|
206595
206598
|
const razorDefaults = razorLanguageService.defaults;
|
|
206596
206599
|
function getMode$2() {
|
|
206597
|
-
return __vitePreload(() => import("./htmlMode-
|
|
206600
|
+
return __vitePreload(() => import("./htmlMode-DzdyF21q.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
|
|
206598
206601
|
}
|
|
206599
206602
|
function registerHTMLLanguageService(languageId, options = optionsDefault, modeConfiguration = getConfigurationDefault(languageId)) {
|
|
206600
206603
|
const defaults = new LanguageServiceDefaultsImpl$2(languageId, options, modeConfiguration);
|
|
@@ -206678,7 +206681,7 @@ const jsonDefaults = new LanguageServiceDefaultsImpl$1(
|
|
|
206678
206681
|
);
|
|
206679
206682
|
const getWorker$1 = () => getMode$1().then((mode2) => mode2.getWorker());
|
|
206680
206683
|
function getMode$1() {
|
|
206681
|
-
return __vitePreload(() => import("./jsonMode-
|
|
206684
|
+
return __vitePreload(() => import("./jsonMode-CPhJ_ION.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
|
|
206682
206685
|
}
|
|
206683
206686
|
languages.register({
|
|
206684
206687
|
id: "json",
|
|
@@ -206924,7 +206927,7 @@ const getJavaScriptWorker = () => {
|
|
|
206924
206927
|
return getMode().then((mode) => mode.getJavaScriptWorker());
|
|
206925
206928
|
};
|
|
206926
206929
|
function getMode() {
|
|
206927
|
-
return __vitePreload(() => import("./tsMode-
|
|
206930
|
+
return __vitePreload(() => import("./tsMode-D7kqIJ_o.js"), true ? [] : void 0, import.meta.url);
|
|
206928
206931
|
}
|
|
206929
206932
|
languages.onLanguage("typescript", () => {
|
|
206930
206933
|
return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults));
|
|
@@ -207119,49 +207122,49 @@ registerLanguage({
|
|
|
207119
207122
|
extensions: [".ftl", ".ftlh", ".ftlx"],
|
|
207120
207123
|
aliases: ["FreeMarker2", "Apache FreeMarker2"],
|
|
207121
207124
|
loader: () => {
|
|
207122
|
-
return __vitePreload(() => import("./freemarker2-
|
|
207125
|
+
return __vitePreload(() => import("./freemarker2-DgtqmvCd.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
|
|
207123
207126
|
}
|
|
207124
207127
|
});
|
|
207125
207128
|
registerLanguage({
|
|
207126
207129
|
id: "freemarker2.tag-angle.interpolation-dollar",
|
|
207127
207130
|
aliases: ["FreeMarker2 (Angle/Dollar)", "Apache FreeMarker2 (Angle/Dollar)"],
|
|
207128
207131
|
loader: () => {
|
|
207129
|
-
return __vitePreload(() => import("./freemarker2-
|
|
207132
|
+
return __vitePreload(() => import("./freemarker2-DgtqmvCd.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
|
|
207130
207133
|
}
|
|
207131
207134
|
});
|
|
207132
207135
|
registerLanguage({
|
|
207133
207136
|
id: "freemarker2.tag-bracket.interpolation-dollar",
|
|
207134
207137
|
aliases: ["FreeMarker2 (Bracket/Dollar)", "Apache FreeMarker2 (Bracket/Dollar)"],
|
|
207135
207138
|
loader: () => {
|
|
207136
|
-
return __vitePreload(() => import("./freemarker2-
|
|
207139
|
+
return __vitePreload(() => import("./freemarker2-DgtqmvCd.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
|
|
207137
207140
|
}
|
|
207138
207141
|
});
|
|
207139
207142
|
registerLanguage({
|
|
207140
207143
|
id: "freemarker2.tag-angle.interpolation-bracket",
|
|
207141
207144
|
aliases: ["FreeMarker2 (Angle/Bracket)", "Apache FreeMarker2 (Angle/Bracket)"],
|
|
207142
207145
|
loader: () => {
|
|
207143
|
-
return __vitePreload(() => import("./freemarker2-
|
|
207146
|
+
return __vitePreload(() => import("./freemarker2-DgtqmvCd.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
|
|
207144
207147
|
}
|
|
207145
207148
|
});
|
|
207146
207149
|
registerLanguage({
|
|
207147
207150
|
id: "freemarker2.tag-bracket.interpolation-bracket",
|
|
207148
207151
|
aliases: ["FreeMarker2 (Bracket/Bracket)", "Apache FreeMarker2 (Bracket/Bracket)"],
|
|
207149
207152
|
loader: () => {
|
|
207150
|
-
return __vitePreload(() => import("./freemarker2-
|
|
207153
|
+
return __vitePreload(() => import("./freemarker2-DgtqmvCd.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
|
|
207151
207154
|
}
|
|
207152
207155
|
});
|
|
207153
207156
|
registerLanguage({
|
|
207154
207157
|
id: "freemarker2.tag-auto.interpolation-dollar",
|
|
207155
207158
|
aliases: ["FreeMarker2 (Auto/Dollar)", "Apache FreeMarker2 (Auto/Dollar)"],
|
|
207156
207159
|
loader: () => {
|
|
207157
|
-
return __vitePreload(() => import("./freemarker2-
|
|
207160
|
+
return __vitePreload(() => import("./freemarker2-DgtqmvCd.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
|
|
207158
207161
|
}
|
|
207159
207162
|
});
|
|
207160
207163
|
registerLanguage({
|
|
207161
207164
|
id: "freemarker2.tag-auto.interpolation-bracket",
|
|
207162
207165
|
aliases: ["FreeMarker2 (Auto/Bracket)", "Apache FreeMarker2 (Auto/Bracket)"],
|
|
207163
207166
|
loader: () => {
|
|
207164
|
-
return __vitePreload(() => import("./freemarker2-
|
|
207167
|
+
return __vitePreload(() => import("./freemarker2-DgtqmvCd.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
|
|
207165
207168
|
}
|
|
207166
207169
|
});
|
|
207167
207170
|
registerLanguage({
|
|
@@ -207182,7 +207185,7 @@ registerLanguage({
|
|
|
207182
207185
|
extensions: [".handlebars", ".hbs"],
|
|
207183
207186
|
aliases: ["Handlebars", "handlebars", "hbs"],
|
|
207184
207187
|
mimetypes: ["text/x-handlebars-template"],
|
|
207185
|
-
loader: () => __vitePreload(() => import("./handlebars-
|
|
207188
|
+
loader: () => __vitePreload(() => import("./handlebars-YEqC-zOn.js"), true ? [] : void 0, import.meta.url)
|
|
207186
207189
|
});
|
|
207187
207190
|
registerLanguage({
|
|
207188
207191
|
id: "hcl",
|
|
@@ -207195,7 +207198,7 @@ registerLanguage({
|
|
|
207195
207198
|
extensions: [".html", ".htm", ".shtml", ".xhtml", ".mdoc", ".jsp", ".asp", ".aspx", ".jshtm"],
|
|
207196
207199
|
aliases: ["HTML", "htm", "html", "xhtml"],
|
|
207197
207200
|
mimetypes: ["text/html", "text/x-jshtm", "text/template", "text/ng-template"],
|
|
207198
|
-
loader: () => __vitePreload(() => import("./html-
|
|
207201
|
+
loader: () => __vitePreload(() => import("./html-CwvkGxeB.js"), true ? [] : void 0, import.meta.url)
|
|
207199
207202
|
});
|
|
207200
207203
|
registerLanguage({
|
|
207201
207204
|
id: "ini",
|
|
@@ -207218,7 +207221,7 @@ registerLanguage({
|
|
|
207218
207221
|
filenames: ["jakefile"],
|
|
207219
207222
|
aliases: ["JavaScript", "javascript", "js"],
|
|
207220
207223
|
mimetypes: ["text/javascript"],
|
|
207221
|
-
loader: () => __vitePreload(() => import("./javascript-
|
|
207224
|
+
loader: () => __vitePreload(() => import("./javascript-Dy9cX9Fu.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
|
|
207222
207225
|
});
|
|
207223
207226
|
registerLanguage({
|
|
207224
207227
|
id: "julia",
|
|
@@ -207257,7 +207260,7 @@ registerLanguage({
|
|
|
207257
207260
|
extensions: [".liquid", ".html.liquid"],
|
|
207258
207261
|
aliases: ["Liquid", "liquid"],
|
|
207259
207262
|
mimetypes: ["application/liquid"],
|
|
207260
|
-
loader: () => __vitePreload(() => import("./liquid-
|
|
207263
|
+
loader: () => __vitePreload(() => import("./liquid-BJdFPWrv.js"), true ? [] : void 0, import.meta.url)
|
|
207261
207264
|
});
|
|
207262
207265
|
registerLanguage({
|
|
207263
207266
|
id: "m3",
|
|
@@ -207275,7 +207278,7 @@ registerLanguage({
|
|
|
207275
207278
|
id: "mdx",
|
|
207276
207279
|
extensions: [".mdx"],
|
|
207277
207280
|
aliases: ["MDX", "mdx"],
|
|
207278
|
-
loader: () => __vitePreload(() => import("./mdx-
|
|
207281
|
+
loader: () => __vitePreload(() => import("./mdx-kg2kuPRK.js"), true ? [] : void 0, import.meta.url)
|
|
207279
207282
|
});
|
|
207280
207283
|
registerLanguage({
|
|
207281
207284
|
id: "mips",
|
|
@@ -207374,7 +207377,7 @@ registerLanguage({
|
|
|
207374
207377
|
extensions: [".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi"],
|
|
207375
207378
|
aliases: ["Python", "py"],
|
|
207376
207379
|
firstLine: "^#!/.*\\bpython[0-9.-]*\\b",
|
|
207377
|
-
loader: () => __vitePreload(() => import("./python-
|
|
207380
|
+
loader: () => __vitePreload(() => import("./python-aIrmxkmH.js"), true ? [] : void 0, import.meta.url)
|
|
207378
207381
|
});
|
|
207379
207382
|
registerLanguage({
|
|
207380
207383
|
id: "qsharp",
|
|
@@ -207393,7 +207396,7 @@ registerLanguage({
|
|
|
207393
207396
|
extensions: [".cshtml"],
|
|
207394
207397
|
aliases: ["Razor", "razor"],
|
|
207395
207398
|
mimetypes: ["text/x-cshtml"],
|
|
207396
|
-
loader: () => __vitePreload(() => import("./razor-
|
|
207399
|
+
loader: () => __vitePreload(() => import("./razor-DdymcPOc.js"), true ? [] : void 0, import.meta.url)
|
|
207397
207400
|
});
|
|
207398
207401
|
registerLanguage({
|
|
207399
207402
|
id: "redis",
|
|
@@ -207526,7 +207529,7 @@ registerLanguage({
|
|
|
207526
207529
|
aliases: ["TypeScript", "ts", "typescript"],
|
|
207527
207530
|
mimetypes: ["text/typescript"],
|
|
207528
207531
|
loader: () => {
|
|
207529
|
-
return __vitePreload(() => import("./typescript-
|
|
207532
|
+
return __vitePreload(() => import("./typescript-Cq3g-z7h.js"), true ? [] : void 0, import.meta.url);
|
|
207530
207533
|
}
|
|
207531
207534
|
});
|
|
207532
207535
|
registerLanguage({
|
|
@@ -207571,14 +207574,14 @@ registerLanguage({
|
|
|
207571
207574
|
firstLine: "(\\<\\?xml.*)|(\\<svg)|(\\<\\!doctype\\s+svg)",
|
|
207572
207575
|
aliases: ["XML", "xml"],
|
|
207573
207576
|
mimetypes: ["text/xml", "application/xml", "application/xaml+xml", "application/xml-dtd"],
|
|
207574
|
-
loader: () => __vitePreload(() => import("./xml-
|
|
207577
|
+
loader: () => __vitePreload(() => import("./xml-BuBP5jOc.js"), true ? [] : void 0, import.meta.url)
|
|
207575
207578
|
});
|
|
207576
207579
|
registerLanguage({
|
|
207577
207580
|
id: "yaml",
|
|
207578
207581
|
extensions: [".yaml", ".yml"],
|
|
207579
207582
|
aliases: ["YAML", "yaml", "YML", "yml"],
|
|
207580
207583
|
mimetypes: ["application/x-yaml", "text/x-yaml"],
|
|
207581
|
-
loader: () => __vitePreload(() => import("./yaml-
|
|
207584
|
+
loader: () => __vitePreload(() => import("./yaml-B-3hWmol.js"), true ? [] : void 0, import.meta.url)
|
|
207582
207585
|
});
|
|
207583
207586
|
var __defProp = Object.defineProperty;
|
|
207584
207587
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -211779,15 +211782,25 @@ function App() {
|
|
|
211779
211782
|
))
|
|
211780
211783
|
] })
|
|
211781
211784
|
] }),
|
|
211782
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
211783
|
-
|
|
211784
|
-
|
|
211785
|
-
|
|
211786
|
-
|
|
211787
|
-
|
|
211788
|
-
|
|
211789
|
-
|
|
211790
|
-
|
|
211785
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "main-content", children: [
|
|
211786
|
+
visiblePaneIds.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "multi-split-empty", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "All panes hidden. Use the titlebar buttons to show a pane." }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
211787
|
+
PaneLayout,
|
|
211788
|
+
{
|
|
211789
|
+
layout: layout2,
|
|
211790
|
+
panes: panes.filter((p) => visiblePaneIds.includes(p.id)),
|
|
211791
|
+
onLayoutChange: setLayout,
|
|
211792
|
+
onToggle: togglePane
|
|
211793
|
+
}
|
|
211794
|
+
),
|
|
211795
|
+
panes.filter((p) => !visiblePaneIds.includes(p.id)).map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
|
|
211796
|
+
position: "absolute",
|
|
211797
|
+
width: "1px",
|
|
211798
|
+
height: "1px",
|
|
211799
|
+
overflow: "hidden",
|
|
211800
|
+
opacity: 0,
|
|
211801
|
+
pointerEvents: "none"
|
|
211802
|
+
}, children: p.content }, p.id))
|
|
211803
|
+
] }),
|
|
211791
211804
|
/* @__PURE__ */ jsxRuntimeExports.jsx(StatusBar, { wsStatus, cwd: cwd2, onChangeCwd: handleChangeCwd }),
|
|
211792
211805
|
/* @__PURE__ */ jsxRuntimeExports.jsx(SettingsDialog, { open: showSettings, onClose: () => setShowSettings(false) }),
|
|
211793
211806
|
showAgentPicker && agents.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { conf as conf$1, language as language$1 } from "./typescript-
|
|
2
|
-
import "./index-
|
|
1
|
+
import { conf as conf$1, language as language$1 } from "./typescript-Cq3g-z7h.js";
|
|
2
|
+
import "./index-CpDga7fF.js";
|
|
3
3
|
const conf = conf$1;
|
|
4
4
|
const language = {
|
|
5
5
|
// Set defaultToken to invalid to see what you do not tokenize yet
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as createWebWorker, l as languages, e as editor } from "./index-
|
|
2
|
-
import { f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter, H as HoverAdapter, b as DocumentSymbolAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, S as SelectionRangeAdapter, e as DiagnosticsAdapter } from "./lspLanguageFeatures-
|
|
3
|
-
import { a, D, h, R, c, i, j, t, k } from "./lspLanguageFeatures-
|
|
1
|
+
import { c as createWebWorker, l as languages, e as editor } from "./index-CpDga7fF.js";
|
|
2
|
+
import { f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter, H as HoverAdapter, b as DocumentSymbolAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, S as SelectionRangeAdapter, e as DiagnosticsAdapter } from "./lspLanguageFeatures-BMM3SRX4.js";
|
|
3
|
+
import { a, D, h, R, c, i, j, t, k } from "./lspLanguageFeatures-BMM3SRX4.js";
|
|
4
4
|
const STOP_WHEN_IDLE_FOR = 2 * 60 * 1e3;
|
|
5
5
|
class WorkerManager {
|
|
6
6
|
constructor(defaults) {
|
package/out/renderer/assets/{lspLanguageFeatures-DzxH499X.js → lspLanguageFeatures-BMM3SRX4.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as Range$1, l as languages, e as editor, U as Uri, M as MarkerSeverity } from "./index-
|
|
1
|
+
import { R as Range$1, l as languages, e as editor, U as Uri, M as MarkerSeverity } from "./index-CpDga7fF.js";
|
|
2
2
|
var DocumentUri;
|
|
3
3
|
(function(DocumentUri2) {
|
|
4
4
|
function is(value) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as createWebWorker, e as editor, U as Uri, a as MarkerTag, M as MarkerSeverity, l as languages, t as typescriptDefaults, R as Range } from "./index-
|
|
1
|
+
import { c as createWebWorker, e as editor, U as Uri, a as MarkerTag, M as MarkerSeverity, l as languages, t as typescriptDefaults, R as Range } from "./index-CpDga7fF.js";
|
|
2
2
|
class WorkerManager {
|
|
3
3
|
constructor(_modeId, _defaults) {
|
|
4
4
|
this._modeId = _modeId;
|
package/out/renderer/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>ctlsurf-worker</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-CpDga7fF.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="./assets/index-DK9wLFFm.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phenx-inc/ctlsurf",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Agent-agnostic terminal and desktop app for ctlsurf — run Claude Code, Codex, or any coding agent with live session logging and remote control",
|
|
5
5
|
"main": "out/main/index.js",
|
|
6
6
|
"bin": {
|
package/src/main/bridge.ts
CHANGED
package/src/renderer/App.tsx
CHANGED
|
@@ -274,6 +274,18 @@ export default function App() {
|
|
|
274
274
|
onToggle={togglePane}
|
|
275
275
|
/>
|
|
276
276
|
)}
|
|
277
|
+
{panes.filter(p => !visiblePaneIds.includes(p.id)).map(p => (
|
|
278
|
+
<div key={p.id} style={{
|
|
279
|
+
position: 'absolute',
|
|
280
|
+
width: '1px',
|
|
281
|
+
height: '1px',
|
|
282
|
+
overflow: 'hidden',
|
|
283
|
+
opacity: 0,
|
|
284
|
+
pointerEvents: 'none',
|
|
285
|
+
}}>
|
|
286
|
+
{p.content}
|
|
287
|
+
</div>
|
|
288
|
+
))}
|
|
277
289
|
</div>
|
|
278
290
|
|
|
279
291
|
<StatusBar wsStatus={wsStatus} cwd={cwd} onChangeCwd={handleChangeCwd} />
|
|
@@ -55,6 +55,7 @@ export function TerminalPanel({ agent, onSpawn, onExit }: TerminalPanelProps) {
|
|
|
55
55
|
brightWhite: '#c0caf5'
|
|
56
56
|
},
|
|
57
57
|
scrollback: 10000,
|
|
58
|
+
scrollOnUserInput: true,
|
|
58
59
|
allowTransparency: false
|
|
59
60
|
})
|
|
60
61
|
|
|
@@ -84,13 +85,14 @@ export function TerminalPanel({ agent, onSpawn, onExit }: TerminalPanelProps) {
|
|
|
84
85
|
onExit()
|
|
85
86
|
})
|
|
86
87
|
|
|
87
|
-
// Resize handling
|
|
88
|
+
// Resize handling — scroll to bottom after fit to prevent jumping to top
|
|
88
89
|
let resizeTimeout: ReturnType<typeof setTimeout>
|
|
89
90
|
const handleResize = () => {
|
|
90
91
|
clearTimeout(resizeTimeout)
|
|
91
92
|
resizeTimeout = setTimeout(() => {
|
|
92
93
|
if (fitAddonRef.current && terminalRef.current) {
|
|
93
94
|
fitAddonRef.current.fit()
|
|
95
|
+
terminalRef.current.scrollToBottom()
|
|
94
96
|
const { cols, rows } = terminalRef.current
|
|
95
97
|
window.worker.resizePty(cols, rows)
|
|
96
98
|
}
|
|
@@ -127,6 +129,7 @@ export function TerminalPanel({ agent, onSpawn, onExit }: TerminalPanelProps) {
|
|
|
127
129
|
// Send initial resize after spawn
|
|
128
130
|
if (fitAddonRef.current && terminalRef.current) {
|
|
129
131
|
fitAddonRef.current.fit()
|
|
132
|
+
terminalRef.current.scrollToBottom()
|
|
130
133
|
const { cols, rows } = terminalRef.current
|
|
131
134
|
window.worker.resizePty(cols, rows)
|
|
132
135
|
}
|