@office-kit/pptx-dev 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -40
- package/dist/cli.mjs +282 -52
- package/dist/cli.mjs.map +1 -1
- package/dist/terminal-client.css +285 -0
- package/dist/terminal-client.js +53 -0
- package/package.json +9 -4
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/page.ts","../src/chat.ts","../src/server.ts","../src/cli.ts"],"sourcesContent":["export const page = `<!doctype html>\n<html lang=\"en\">\n<meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width\">\n<title>Office Kit — PowerPoint preview</title>\n<style>\n*{box-sizing:border-box}\nbody{margin:0;height:100dvh;overflow:hidden;background:#e9ecf1;color:#202735;font:13px system-ui;display:grid;grid-template-rows:60px minmax(0,1fr) 42px}\nbutton,a,select{font:inherit;color:inherit}button,select,.download{border:1px solid #d4d9e2;border-radius:6px;background:#fff;padding:7px 12px;text-decoration:none;cursor:pointer}\nbutton:hover:not(:disabled),.download:hover{background:#f0f3f9}button:disabled{opacity:.4;cursor:default}\n:focus-visible{outline:2px solid #4967dd;outline-offset:3px}\nheader{display:flex;align-items:center;gap:16px;padding:0 20px;background:#fff;border-bottom:1px solid #d4d9e2;min-width:0}\n.brand{font-weight:700;font-size:17px;white-space:nowrap}.badge{font-size:11px;color:#616b7c;background:#f1f3f7;border-radius:4px;padding:3px 6px}\n#status{flex:1;color:#667085;min-width:0}#present{background:#293c73;color:#fff;border-color:#293c73}\n.workspace{display:grid;grid-template-columns:190px minmax(0,1fr) 340px;min-height:0}\n.filmstrip{background:#f7f8fa;border-right:1px solid #d4d9e2;overflow:auto;overscroll-behavior:contain;padding:16px 12px}\n.filmstrip h2{margin:0 0 12px 26px;text-transform:uppercase;letter-spacing:.1em;font-size:10px;color:#7c8596;font-weight:600}\n#thumbnails{display:flex;flex-direction:column;gap:12px;margin:0;padding:0;list-style:none}\n.thumbnail{display:flex;align-items:flex-start;gap:8px;width:100%;padding:4px 3px;border:0;background:transparent;border-radius:5px;text-align:left}\n.slide-number{width:18px;flex:none;text-align:right;font-size:11px;color:#737d8e;padding-top:5px}\n.thumbnail img{display:block;min-width:0;width:calc(100% - 26px);background:white;box-shadow:0 1px 4px #19212d18;border:2px solid transparent;border-radius:3px;aspect-ratio:var(--slide-ratio,16/9);object-fit:contain}\n.thumbnail[aria-current=\"true\"]{background:#e7edff}.thumbnail[aria-current=\"true\"] img{border-color:#4967dd}.thumbnail[aria-current=\"true\"] .slide-number{color:#3654c1;font-weight:700}\nmain{min-width:0;min-height:0;display:flex;flex-direction:column}\n#error{flex:none;max-height:30%;overflow:auto;background:#fff0ef;color:#922e25;margin:0;padding:16px 20px;white-space:pre-wrap;border-bottom:1px solid #e8bbb7}\n#error[hidden]{display:none}\n#stage{flex:1;min-height:0;overflow:auto;display:flex;padding:32px;overscroll-behavior:contain}\n#slide{position:relative;flex:none;margin:auto;background:white;box-shadow:0 3px 24px #19212d20;overflow:hidden}\n#slide{user-select:text;-webkit-user-select:text}\n#empty{margin:auto;color:#737d8e}\nfooter{display:flex;align-items:center;gap:14px;padding:0 16px;background:#fff;border-top:1px solid #d4d9e2;font-size:12px}\n#count{min-width:90px}footer .hint{flex:1;color:#737d8e}footer button{padding:3px 10px}footer select{padding:3px 8px}\n#presentation-controls{display:none}\nbody.presenting{grid-template-rows:minmax(0,1fr);background:#111}\n.presenting header,.presenting footer,.presenting .filmstrip,.presenting #chat{display:none}\n.presenting .workspace{grid-template-columns:minmax(0,1fr)}.presenting #stage{padding:0;background:#111}.presenting #slide{box-shadow:none}\n.presenting #presentation-controls{display:flex;position:fixed;bottom:16px;left:50%;transform:translateX(-50%);align-items:center;gap:12px;background:#202735e8;color:white;padding:6px;border-radius:8px;opacity:0;transition:opacity .15s}\n.presenting #presentation-controls:hover,.presenting #presentation-controls:focus-within{opacity:1}\n#presentation-controls button{background:transparent;color:white;border-color:#5d6575}\n@media(max-width:700px){.workspace{grid-template-columns:140px minmax(0,1fr)}.filmstrip{padding:12px 5px}header{padding:0 12px;gap:10px}.badge,footer .hint{display:none}#stage{padding:16px}footer{gap:8px}#status{font-size:11px}.download{padding:7px 8px}}\n#chat{min-height:0;display:flex;flex-direction:column;background:#fff;border-left:1px solid #d4d9e2}\n.chat-heading{display:flex;align-items:center;gap:8px;padding:16px;border-bottom:1px solid #e6e9ef}.chat-heading strong{flex:1}.chat-heading button{padding:5px 8px}\n#messages{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:16px;overscroll-behavior:contain}\n.chat-message{white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.6}.chat-message.user{background:#eef2ff;border-radius:8px;padding:10px 12px}.chat-message small{display:block;color:#697386;font-size:11px;margin-bottom:4px}.chat-intro{color:#737d8e;line-height:1.7}\n#chat-form{padding:14px;border-top:1px solid #e6e9ef}#chat-context{font-size:11px;color:#4967dd;margin-bottom:8px}#chat-input{font:inherit;resize:vertical;min-height:90px;max-height:200px;width:100%;padding:10px;border:1px solid #d4d9e2;border-radius:8px} .chat-actions{display:flex;gap:8px;margin-top:8px;align-items:center}.chat-actions select{min-width:0;flex:1}#chat-send{background:#293c73;color:white}#chat-status{padding:0 14px 12px;color:#667085;font-size:11px;white-space:pre-wrap;max-height:90px;overflow:auto}\nbody.chat-hidden .workspace{grid-template-columns:190px minmax(0,1fr)}body.chat-hidden #chat{display:none}\nbody.presenting .workspace{grid-template-columns:minmax(0,1fr)}\n@media(max-width:1000px){.workspace{grid-template-columns:120px minmax(0,1fr) 300px}.filmstrip{padding:12px 5px}#stage{padding:16px}}\n@media(max-width:700px){.workspace{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(160px,1fr) minmax(200px,1fr)}.filmstrip{display:none}#chat{border-top:1px solid #d4d9e2}.chat-heading{padding:8px 12px}#chat-form{padding:8px 12px}#chat-input{min-height:50px}body.chat-hidden .workspace,.presenting .workspace{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr)}.brand{font-size:14px}header{gap:6px}.download{font-size:11px}}\n@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}\n</style>\n<header><span class=\"brand\">Office Kit</span><span class=\"badge\">Preview</span><span id=\"status\" role=\"status\">Building…</span><button id=\"toggle-chat\" aria-expanded=\"true\" aria-controls=\"chat\">Chat</button><button id=\"present\" disabled>Present</button><a class=\"download\" href=\"/deck.pptx\">Download PPTX</a></header>\n<div class=\"workspace\">\n<nav class=\"filmstrip\" aria-label=\"Slides\"><h2>Slides</h2><ol id=\"thumbnails\"></ol></nav>\n<main aria-label=\"Slide viewer\"><pre id=\"error\" role=\"alert\" hidden></pre><div id=\"stage\" tabindex=\"-1\"><div id=\"empty\">Waiting for slides…</div><div id=\"slide\" hidden></div></div></main>\n<aside id=\"chat\" aria-label=\"Slide chat\">\n<div class=\"chat-heading\"><strong>Edit with AI</strong><button id=\"chat-reset\">New chat</button></div>\n<div id=\"messages\" role=\"log\" aria-label=\"Conversation\"><p class=\"chat-intro\">Describe what you want to change. The current slide is included automatically. You can also ask about other slides or the whole deck.<br><br>Uses your locally installed Claude Code or Codex. Sign in through its CLI first. Sending a message allows it to edit this project.</p></div>\n<form id=\"chat-form\"><div id=\"chat-context\">No slide selected</div><label for=\"chat-input\" class=\"chat-intro\">What would you like to change?</label><textarea id=\"chat-input\" maxlength=\"16000\" placeholder=\"Make this headline shorter…\" required></textarea><div class=\"chat-actions\"><select id=\"chat-provider\" aria-label=\"AI provider\"><option value=\"claude\">Claude Code</option><option value=\"codex\">Codex</option></select><button id=\"chat-stop\" type=\"button\" hidden>Stop</button><button id=\"chat-send\" type=\"submit\">Send</button></div></form>\n<div id=\"chat-status\" role=\"status\">Ready</div>\n</aside>\n</div>\n<footer><span id=\"count\" aria-live=\"polite\">No slides</span><span class=\"hint\">Changes appear automatically · Text can be selected and copied</span><button id=\"prev\" aria-label=\"Previous slide\" disabled>‹</button><button id=\"next\" aria-label=\"Next slide\" disabled>›</button><label for=\"zoom\">Zoom</label><select id=\"zoom\"><option value=\"fit\">Fit</option><option value=\"0.5\">50%</option><option value=\"0.75\">75%</option><option value=\"1\">100%</option><option value=\"1.25\">125%</option><option value=\"1.5\">150%</option><option value=\"2\">200%</option></select></footer>\n<div id=\"presentation-controls\"><button id=\"present-prev\" aria-label=\"Previous slide\">‹</button><span id=\"present-count\"></span><button id=\"present-next\" aria-label=\"Next slide\">›</button><button id=\"exit-present\">Exit · Esc</button></div>\n<script>\nlet state={slides:[],error:null,aspectRatio:16/9},index=0,urls=[],presenting=false;\nlet displayedSvg;\nconst byId=id=>document.getElementById(id);\nconst stage=byId('stage'),slide=byId('slide'),thumbnails=byId('thumbnails');\n// The slide lives in a shadow root rather than a sandboxed iframe so its text\n// (XHTML inside <foreignObject>) can be selected and copied while the deck's\n// SVG stays out of the viewer's own DOM and CSS. Keyboard events still reach the\n// document, so arrow-key navigation keeps working after clicking into a slide.\nconst canvas=slide.attachShadow({mode:'open'});\nfunction resize(){\n if(!state.slides.length)return;\n const style=getComputedStyle(stage);\n const width=Math.max(1,stage.clientWidth-parseFloat(style.paddingLeft)-parseFloat(style.paddingRight));\n const height=Math.max(1,stage.clientHeight-parseFloat(style.paddingTop)-parseFloat(style.paddingBottom));\n const ratio=state.aspectRatio;\n const zoom=byId('zoom').value;\n const slideWidth=presenting||zoom==='fit'?Math.min(width,height*ratio):1280*Number(zoom);\n slide.style.width=slideWidth+'px';slide.style.height=slideWidth/ratio+'px';\n}\nfunction selectSlide(next,focusThumbnail=false,reveal=true){\n index=Math.max(0,Math.min(next,state.slides.length-1));\n const count=state.slides.length?'Slide '+(index+1)+' of '+state.slides.length:'No slides';\n byId('chat-context').textContent=state.slides.length?'Context: '+count:'Context: whole project';\n byId('count').textContent=count;byId('present-count').textContent=count;\n for(const id of ['prev','present-prev'])byId(id).disabled=index===0;\n for(const id of ['next','present-next'])byId(id).disabled=index>=state.slides.length-1;\n byId('present').disabled=!state.slides.length;\n byId('zoom').disabled=!state.slides.length;\n slide.hidden=!state.slides.length;byId('empty').hidden=!!state.slides.length;\n const svg=state.slides[index];\n if(svg!==displayedSvg){\n displayedSvg=svg;\n canvas.innerHTML=svg?'<style>svg{display:block;width:100%;height:100%}</style>'+svg:'';\n }\n slide.setAttribute('aria-label','Slide '+(index+1));\n for(const [position,item] of Array.from(thumbnails.children).entries()){\n const button=item.firstElementChild,selected=position===index;\n button.setAttribute('aria-current',String(selected));button.tabIndex=selected?0:-1;\n if(selected){if(reveal)button.scrollIntoView({block:'nearest'});if(focusThumbnail)button.focus({preventScroll:true});}\n }\n resize();\n}\nfunction update(updated){\n const focusedThumbnail=thumbnails.contains(document.activeElement);\n const previous=state;\n state=updated;\n byId('status').textContent=state.error?'Build failed · showing last successful output':state.building?'Updating…':state.slides.length+' slides · Live';\n byId('error').textContent=state.error||'';byId('error').hidden=!state.error;\n document.documentElement.style.setProperty('--slide-ratio',String(state.aspectRatio));\n for(let i=state.slides.length;i<urls.length;i++){\n URL.revokeObjectURL(urls[i]);thumbnails.lastElementChild.remove();\n }\n urls.length=state.slides.length;\n state.slides.forEach((svg,i)=>{\n if(svg===previous.slides[i])return;\n const oldUrl=urls[i];\n urls[i]=URL.createObjectURL(new Blob([svg],{type:'image/svg+xml'}));\n let item=thumbnails.children[i];\n if(!item){\n item=document.createElement('li');\n const button=document.createElement('button'),number=document.createElement('span'),image=document.createElement('img');\n button.className='thumbnail';button.setAttribute('aria-label','Slide '+(i+1));button.onclick=()=>selectSlide(i,true);\n number.className='slide-number';number.textContent=String(i+1);\n image.alt='';image.draggable=false;image.loading='lazy';\n button.append(number,image);item.append(button);thumbnails.append(item);\n }\n item.querySelector('img').src=urls[i];\n if(oldUrl)URL.revokeObjectURL(oldUrl);\n });\n selectSlide(index,focusedThumbnail,false);\n if(!state.slides.length&&presenting)void exitPresentation();\n}\nfunction setPresenting(value){\n presenting=value;document.body.classList.toggle('presenting',value);resize();\n if(value)stage.focus();else{\n byId('present').focus();\n thumbnails.children[index]?.firstElementChild.scrollIntoView({block:'nearest'});\n }\n}\nasync function exitPresentation(){\n setPresenting(false);\n if(document.fullscreenElement)await document.exitFullscreen();\n}\nbyId('present').onclick=async()=>{\n setPresenting(true);\n try{await document.documentElement.requestFullscreen();}\n catch{byId('exit-present').textContent='Exit view · Esc';}\n};\nbyId('exit-present').onclick=exitPresentation;\ndocument.addEventListener('fullscreenchange',()=>{if(!document.fullscreenElement&&presenting)setPresenting(false);});\nfor(const id of ['prev','present-prev'])byId(id).onclick=()=>selectSlide(index-1);\nfor(const id of ['next','present-next'])byId(id).onclick=()=>selectSlide(index+1);\nbyId('zoom').onchange=resize;\nstage.onclick=()=>{if(presenting&&!getSelection().toString())selectSlide(index+1);};\ndocument.addEventListener('keydown',event=>{\n if(event.key==='Escape'&&presenting){event.preventDefault();void exitPresentation();return;}\n if(event.altKey||event.ctrlKey||event.metaKey||event.target.closest('select,input,textarea,[contenteditable]'))return;\n let next=index;\n if(['ArrowLeft','ArrowUp','PageUp'].includes(event.key))next--;\n else if(['ArrowRight','ArrowDown','PageDown'].includes(event.key))next++;\n else if(event.key==='Home')next=0;\n else if(event.key==='End')next=state.slides.length-1;\n else if(event.key===' '&&presenting&&!event.target.closest('button,a'))next+=event.shiftKey?-1:1;\n else return;\n event.preventDefault();selectSlide(next,thumbnails.contains(document.activeElement));\n});\nnew ResizeObserver(resize).observe(stage);\nlet refreshId=0;\nasync function refresh(){\n const id=++refreshId;\n try{const response=await fetch('/state'+(state.revision===undefined?'':'?since='+state.revision));if(!response.ok)throw new Error('Preview unavailable');const updated=await response.json();if(id===refreshId){if(updated.changes){updated.slides=state.slides.slice(0,updated.count);updated.slides.length=updated.count;for(const [position,svg] of Object.entries(updated.changes))updated.slides[Number(position)]=svg;}update(updated);}}\n catch{if(id===refreshId)byId('status').textContent='Reconnecting…';}\n}\nlet chatRequest=0,chatBusy=false,lastMessages='';\nfunction renderChat(chat){\n chatBusy=chat.running;\n byId('chat-send').disabled=chat.running;\n byId('chat-reset').disabled=chat.running;\n byId('chat-provider').disabled=chat.running;\n byId('chat-stop').hidden=!chat.running;\n byId('chat-status').textContent=chat.status;\n if(chat.messages.length)byId('chat-provider').value=chat.provider;\n const serialized=JSON.stringify(chat.messages);\n if(serialized===lastMessages)return;\n lastMessages=serialized;\n const log=byId('messages'),atBottom=log.scrollHeight-log.scrollTop-log.clientHeight<60;\n log.replaceChildren();\n if(!chat.messages.length){const intro=document.createElement('p');intro.className='chat-intro';intro.textContent='Ask for a focused slide edit or a change across the deck. Uses your locally installed CLI and its login. Sending allows edits to this project.';log.append(intro);}\n for(const message of chat.messages){\n const item=document.createElement('div'),label=document.createElement('small'),body=document.createElement('div');\n item.className='chat-message '+message.role;\n label.textContent=message.role==='user'?'You · '+(message.context?.slide?'Slide '+message.context.slide:'Whole project'):'Assistant';\n body.textContent=message.text||(chat.running?'Working…':'No response');item.append(label,body);log.append(item);\n }\n if(atBottom)log.scrollTop=log.scrollHeight;\n}\nasync function refreshChat(){\n const id=++chatRequest;\n try{const response=await fetch('/chat');if(!response.ok)throw new Error('Chat unavailable');const chat=await response.json();if(id===chatRequest)renderChat(chat);}\n catch(error){if(id===chatRequest)byId('chat-status').textContent=error.message;}\n}\nasync function chatAction(path,payload){\n ++chatRequest;\n const response=await fetch(path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});\n const result=await response.json();if(!response.ok)throw new Error(result.error||'Chat request failed');\n await refreshChat();\n}\nbyId('chat-form').onsubmit=async event=>{\n event.preventDefault();if(chatBusy)return;\n const input=byId('chat-input'),message=input.value.trim();if(!message)return;\n chatBusy=true;byId('chat-send').disabled=true;\n try{await chatAction('/chat',{message,provider:byId('chat-provider').value,slide:state.slides.length?index:null,revision:state.revision??0});if(input.value.trim()===message)input.value='';}\n catch(error){byId('chat-status').textContent=error.message;chatBusy=false;byId('chat-send').disabled=false;}\n};\nbyId('chat-input').onkeydown=event=>{if(event.key==='Enter'&&(event.metaKey||event.ctrlKey)&&!event.isComposing){event.preventDefault();byId('chat-form').requestSubmit();}};\nfor(const action of ['stop','reset'])byId('chat-'+action).onclick=()=>chatAction('/chat/'+action,{}).catch(error=>{byId('chat-status').textContent=error.message;});\nbyId('toggle-chat').onclick=()=>{const hidden=document.body.classList.toggle('chat-hidden');byId('toggle-chat').setAttribute('aria-expanded',String(!hidden));resize();};\nvoid refreshChat();\nconst events=new EventSource('/events');events.onmessage=event=>{if(event.data==='chat'){void refreshChat();return;}if(event.data==='ready'){delete state.revision;void refreshChat();}void refresh();};events.onerror=()=>{byId('status').textContent='Reconnecting…'};refresh();\n</script></html>`;\n","import { spawn, type ChildProcess } from 'node:child_process';\nimport { dirname, resolve } from 'node:path';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nexport type Provider = 'claude' | 'codex';\nexport interface FocusContext {\n slide: number | null;\n count: number;\n revision: number;\n text: string;\n entry: string;\n files: string[];\n buildError: string | null;\n}\ninterface Message {\n role: 'user' | 'assistant';\n text: string;\n context?: FocusContext;\n}\n\n/** One shared conversation and one editing process per dev server. */\nexport function createChat(entry: string, notify: () => void) {\n let messages: Message[] = [];\n let provider: Provider = 'claude';\n let child: ChildProcess | undefined;\n let status = 'Ready';\n let stopping = false;\n let killTimer: ReturnType<typeof setTimeout> | undefined;\n let completion: Promise<void> = Promise.resolve();\n const snapshot = () => ({ messages, provider, running: !!child, status });\n function stop() {\n if (!child || stopping) return;\n stopping = true;\n status = 'Stopping…';\n const processToStop = child;\n const kill = (signal: NodeJS.Signals) => {\n try {\n if (process.platform !== 'win32' && processToStop.pid)\n process.kill(-processToStop.pid, signal);\n else processToStop.kill(signal);\n } catch {\n /* Already exited. */\n }\n };\n kill('SIGTERM');\n killTimer = setTimeout(() => kill('SIGKILL'), 2000);\n notify();\n }\n function start(text: string, selected: Provider, context: FocusContext) {\n provider = selected;\n const user: Message = { role: 'user', text, context };\n // Keep recent conversational intent, including each turn's original focus.\n const history = messages.slice(-12).map((message) => ({\n ...message,\n text: message.text.slice(-12000),\n }));\n const prompt = `You are editing a local PowerPoint TSX project in ${dirname(resolve(entry))}.\nRead the project's authoring instructions. Make focused patches to the TSX source, preserving unrelated work.\nThe preview rebuilds automatically on save; do not start another dev server.\nThe attached focus is context, NOT a restriction: use it for references such as \"this slide\". For other slides or deck-wide requests, find the relevant source or shared theme instead. Do not rewrite the whole deck for a local edit.\nSlide numbers are 1-based and refer to the last successful preview at send time. Source may have changed since then; verify it before editing. Files are dependency candidates, not an exact slide-to-source mapping.\nRespond in the user's language, briefly describing edits or answering their question. If permissions prevent an action, explain that instead of claiming success.\nRecent conversation (JSON): ${JSON.stringify(history)}\nCurrent request and preview context (JSON): ${JSON.stringify(user)}`;\n messages.push(user);\n const answer: Message = { role: 'assistant', text: '' };\n messages.push(answer);\n if (messages.length > 100) messages = messages.slice(-100);\n status = 'Working…';\n stopping = false;\n const args =\n selected === 'codex'\n ? ['exec', '--json', '--sandbox', 'workspace-write', '--skip-git-repo-check', '-']\n : [\n '--print',\n '--verbose',\n '--output-format',\n 'stream-json',\n '--permission-mode',\n 'acceptEdits',\n '--tools',\n 'Read,Edit,Write,Glob,Grep',\n ];\n child = spawn(selected, args, {\n cwd: dirname(resolve(entry)),\n stdio: ['pipe', 'pipe', 'pipe'],\n detached: process.platform !== 'win32',\n shell: false,\n });\n const running = child;\n let buffer = '',\n stderr = '',\n failure = '',\n sawResult = false;\n function append(value: string) {\n answer.text = (answer.text + (answer.text ? '\\n\\n' : '') + value).slice(-100000);\n notify();\n }\n function consume(line: string) {\n if (!line.trim()) return;\n try {\n const event = JSON.parse(line);\n if (selected === 'codex') {\n if (event.type === 'item.completed' && event.item?.type === 'agent_message')\n append(event.item.text ?? '');\n if (event.type === 'item.started') {\n status = 'Editing / inspecting…';\n notify();\n }\n if (event.type === 'turn.completed') sawResult = true;\n if (event.type === 'turn.failed' || event.type === 'error')\n failure = event.error?.message ?? event.message ?? 'Agent failed';\n } else {\n if (event.type === 'assistant') {\n for (const block of event.message?.content ?? []) {\n if (block.type === 'text') append(block.text);\n if (block.type === 'tool_use') {\n status = 'Using ' + block.name + '…';\n notify();\n }\n }\n }\n if (event.type === 'result') {\n sawResult = true;\n if (event.is_error)\n failure = event.result || event.errors?.join('\\n') || 'Agent failed';\n else if (!answer.text && event.result) append(event.result);\n if (event.permission_denials?.length)\n failure = 'Some actions were denied by CLI permissions. ' + (event.result ?? '');\n }\n }\n } catch {\n failure = 'Unexpected CLI output. Update your CLI and try again.';\n }\n }\n running.stdout!.setEncoding('utf8');\n running.stdout!.on('data', (chunk: string) => {\n buffer += chunk;\n let end: number;\n while ((end = buffer.indexOf('\\n')) !== -1) {\n consume(buffer.slice(0, end));\n buffer = buffer.slice(end + 1);\n }\n if (buffer.length > 2_000_000) {\n failure = 'CLI output exceeded the limit.';\n stop();\n }\n });\n running.stderr!.setEncoding('utf8');\n running.stderr!.on('data', (chunk: string) => {\n stderr = (stderr + chunk).slice(-8000);\n });\n running.stdin!.on('error', () => {\n /* Early process exit is handled below. */\n });\n running.stdin!.end(prompt);\n completion = new Promise<void>((done) => {\n running.on('error', (error) => {\n failure = `Could not run ${selected}. Install it and log in from your terminal first. ${error.message}`;\n });\n running.on('close', (code) => {\n if (buffer.trim()) consume(buffer);\n clearTimeout(killTimer);\n status = stopping\n ? 'Stopped. Saved edits remain in the preview.'\n : failure ||\n (code !== 0\n ? stderr || `${selected} exited with code ${code}`\n : !sawResult\n ? 'CLI exited without a completed result.'\n : 'Done');\n if (status !== 'Done') append(status);\n child = undefined;\n notify();\n done();\n });\n });\n notify();\n }\n async function handle(\n request: IncomingMessage,\n response: ServerResponse,\n focus: (index: number | null, revision: number) => FocusContext,\n ) {\n const json = (code: number, value: unknown) => {\n response.writeHead(code, { 'Content-Type': 'application/json' });\n response.end(JSON.stringify(value));\n };\n if (request.method === 'GET' && request.url === '/chat') {\n json(200, snapshot());\n return;\n }\n if (request.method !== 'POST') {\n json(405, { error: 'Method not allowed' });\n return;\n }\n // Same-origin JSON prevents other sites from invoking local editing tools.\n if (\n request.headers.origin !== `http://${request.headers.host}` ||\n request.headers['content-type']?.split(';')[0] !== 'application/json'\n ) {\n json(403, { error: 'Same-origin JSON required' });\n return;\n }\n try {\n let body = '';\n request.setEncoding('utf8');\n for await (const chunk of request) {\n body += chunk;\n if (Buffer.byteLength(body) > 32000) {\n json(413, { error: 'Message too large' });\n return;\n }\n }\n const value = JSON.parse(body);\n if (request.url === '/chat/stop') {\n stop();\n json(200, snapshot());\n return;\n }\n if (child) {\n json(409, { error: 'An edit is already running' });\n return;\n }\n if (request.url === '/chat/reset') {\n messages = [];\n status = 'Ready';\n notify();\n json(200, snapshot());\n return;\n }\n if (request.url !== '/chat') {\n json(404, { error: 'Not found' });\n return;\n }\n if (\n typeof value.message !== 'string' ||\n !value.message.trim() ||\n value.message.length > 16000 ||\n !['claude', 'codex'].includes(value.provider) ||\n !(value.slide === null || (Number.isInteger(value.slide) && value.slide >= 0)) ||\n !Number.isInteger(value.revision)\n ) {\n json(400, { error: 'Invalid chat request' });\n return;\n }\n const context = focus(value.slide, value.revision);\n start(value.message.trim(), value.provider, context);\n json(202, snapshot());\n } catch (cause) {\n json(400, { error: cause instanceof Error ? cause.message : 'Invalid request' });\n }\n }\n return {\n handle,\n async close() {\n stop();\n await completion;\n },\n };\n}\n","import { createServer, type ServerResponse } from 'node:http';\nimport { watch } from 'node:fs';\nimport { dirname, resolve, sep } from 'node:path';\nimport type { BuildResult } from './build.ts';\nimport { createDeckBuilder } from './build-runner.ts';\nimport { page } from './page.ts';\nimport { createChat } from './chat.ts';\n\nexport async function serveDeck(entry: string, port = 4173) {\n let latest: BuildResult | undefined;\n let error: string | null = null;\n let building = false;\n let revision = 0;\n let generation = 0;\n let patch: Record<number, string> = {};\n let pending = false;\n let closed = false;\n const clients = new Set<ServerResponse>();\n const chat = createChat(entry, () => {\n for (const client of clients) client.write('data: chat\\n\\n');\n });\n const server = createServer((request, response) => {\n const host = request.headers.host;\n if (host !== `127.0.0.1:${actualPort}` && host !== `localhost:${actualPort}`) {\n response.writeHead(403).end();\n return;\n }\n response.setHeader('Cache-Control', 'no-store');\n if (request.url === '/chat' || request.url?.startsWith('/chat/')) {\n void chat.handle(request, response, (index, viewedRevision) => {\n if (viewedRevision !== revision)\n throw new Error('Preview changed. Review the current slide and send again.');\n if (index !== null && (!latest || index >= latest.slides.length))\n throw new Error('Slide no longer exists.');\n return {\n slide: index === null ? null : index + 1,\n count: latest?.slides.length ?? 0,\n revision,\n text: index === null ? '' : (latest?.slideTexts[index] ?? '').slice(0, 12000),\n entry: resolve(entry),\n files: latest?.dependencies ?? [resolve(entry)],\n buildError: error,\n };\n });\n } else if (request.method !== 'GET') {\n response.writeHead(405).end();\n } else if (request.url === '/events') {\n response.writeHead(200, { 'Content-Type': 'text/event-stream' });\n response.write('data: ready\\n\\n');\n clients.add(response);\n request.on('close', () => clients.delete(response));\n } else if (request.url === '/state' || request.url?.startsWith('/state?')) {\n const since = new URL(request.url, 'http://localhost').searchParams.get('since');\n const incremental =\n since !== null && (since === String(revision) || since === String(revision - 1));\n response.writeHead(200, { 'Content-Type': 'application/json' });\n response.end(\n JSON.stringify({\n revision,\n building,\n ...(incremental\n ? {\n changes: since === String(revision) ? {} : patch,\n count: latest?.slides.length ?? 0,\n }\n : { slides: latest?.slides ?? [] }),\n aspectRatio: latest?.aspectRatio ?? 16 / 9,\n error,\n diagnostics: latest?.diagnostics ?? [],\n }),\n );\n } else if (request.url === '/deck.pptx' && latest) {\n response.writeHead(200, {\n 'Content-Type': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'Content-Disposition': 'attachment; filename=\"deck.pptx\"',\n });\n response.end(latest.bytes);\n } else if (request.url === '/') {\n response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n response.end(page);\n } else {\n response.writeHead(404).end();\n }\n });\n async function rebuild() {\n if (closed) return;\n if (building) {\n pending = true;\n return;\n }\n building = true;\n const started = generation;\n for (const client of clients) client.write('data: building\\n\\n');\n try {\n const result = await builder.build();\n if (started === generation && !closed) {\n patch = {};\n result.slides.forEach((svg, index) => {\n if (svg !== latest?.slides[index]) patch[index] = svg;\n });\n latest = result;\n revision++;\n error = null;\n }\n } catch (cause) {\n if (started === generation)\n error = cause instanceof Error ? (cause.stack ?? cause.message) : String(cause);\n } finally {\n building = false;\n for (const client of clients) client.write('data: updated\\n\\n');\n if (pending && !timer) {\n pending = false;\n void rebuild();\n }\n }\n }\n let timer: ReturnType<typeof setTimeout> | undefined;\n const root = dirname(resolve(entry));\n const watcher = watch(root, { recursive: true }, (_, filename) => {\n if (\n !filename ||\n filename\n .split(sep)\n .some((part) => ['node_modules', '.git', 'dist', '.office-kit'].includes(part))\n )\n return;\n if (!/\\.([cm]?[jt]sx?|json|pptx|png|jpe?g|gif|bmp|tiff?|emf|wmf|svg)$/i.test(filename)) return;\n generation++;\n void builder.cancel();\n clearTimeout(timer);\n timer = setTimeout(() => {\n timer = undefined;\n pending = false;\n void rebuild();\n }, 30);\n });\n const builder = createDeckBuilder(entry);\n let actualPort = port;\n try {\n await new Promise<void>((resolveListen, reject) => {\n server.once('error', reject);\n server.listen(port, '127.0.0.1', () => {\n server.off('error', reject);\n resolveListen();\n });\n });\n } catch (cause) {\n watcher.close();\n await builder.close();\n throw cause;\n }\n const address = server.address();\n if (address && typeof address !== 'string') actualPort = address.port;\n await rebuild();\n return {\n url: `http://127.0.0.1:${actualPort}`,\n async close() {\n closed = true;\n clearTimeout(timer);\n watcher.close();\n await chat.close();\n await builder.close();\n for (const client of clients) client.end();\n await new Promise<void>((done, reject) =>\n server.close((cause) => (cause ? reject(cause) : done())),\n );\n },\n };\n}\n","#!/usr/bin/env node\nimport { resolve } from 'node:path';\nimport { exportDeck } from './index.ts';\nimport { serveDeck } from './server.ts';\nimport { initProject } from './init.ts';\nimport { inspectTemplate } from './inspect.ts';\n\nconst usage = `Usage: office-pptx init <new-directory>\n office-pptx dev <deck.tsx> [--port 4173]\n office-pptx build <deck.tsx> [--out deck.pptx]\n office-pptx inspect <template.pptx>`;\nconst [command, entry, ...args] = process.argv.slice(2);\ntry {\n if (command === '--help' || command === '-h') {\n console.log(usage);\n } else {\n if (!entry) throw new Error(usage);\n const option = command === 'build' ? '--out' : command === 'dev' ? '--port' : undefined;\n if (args.length && (args.length !== 2 || !option || args[0] !== option || !args[1])) {\n throw new Error(`Unexpected arguments: ${args.join(' ')}\\n${usage}`);\n }\n if (command === 'init') {\n const directory = await initProject(entry);\n console.log(`Created ${directory}\\nRun npm install, then npm run dev inside that directory.`);\n } else if (command === 'inspect') {\n console.log(JSON.stringify(await inspectTemplate(entry), null, 2));\n } else if (command === 'build') {\n const output = resolve(args[1] ?? 'deck.pptx');\n const result = await exportDeck(entry, output);\n console.log(`Wrote ${output} (${result.slides.length} slides)`);\n } else if (command === 'dev') {\n const port = args[1] === undefined ? 4173 : Number(args[1]);\n if (!Number.isInteger(port) || port < 0 || port > 65535)\n throw new Error('Port must be an integer between 0 and 65535.');\n const server = await serveDeck(entry, port);\n console.log(`Preview: ${server.url}`);\n const stop = () => {\n void server.close().then(() => process.exit(0));\n };\n process.once('SIGINT', stop);\n process.once('SIGTERM', stop);\n } else {\n throw new Error(usage);\n }\n }\n} catch (cause) {\n console.error(cause instanceof Error ? cause.message : cause);\n process.exitCode = 1;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/page.ts","../src/terminal.ts","../src/chat.ts","../src/server.ts","../src/cli.ts"],"sourcesContent":["export const page = `<!doctype html>\n<html lang=\"en\">\n<meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width\">\n<title>Office Kit — PowerPoint preview</title>\n<link rel=\"stylesheet\" href=\"/terminal.css\">\n<style>\n*{box-sizing:border-box}\nbody{margin:0;height:100dvh;overflow:hidden;background:#e9ecf1;color:#202735;font:13px system-ui;display:grid;grid-template-rows:60px minmax(0,1fr) 42px}\nbutton,a,select{font:inherit;color:inherit}button,select,.download{border:1px solid #d4d9e2;border-radius:6px;background:#fff;padding:7px 12px;text-decoration:none;cursor:pointer}\nbutton:hover:not(:disabled),.download:hover{background:#f0f3f9}button:disabled{opacity:.4;cursor:default}\n:focus-visible{outline:2px solid #4967dd;outline-offset:3px}\nheader{display:flex;align-items:center;gap:16px;padding:0 20px;background:#fff;border-bottom:1px solid #d4d9e2;min-width:0}\n.brand{font-weight:700;font-size:17px;white-space:nowrap}.badge{font-size:11px;color:#616b7c;background:#f1f3f7;border-radius:4px;padding:3px 6px}\n#status{flex:1;color:#667085;min-width:0}#present{background:#293c73;color:#fff;border-color:#293c73}\n.workspace{--filmstrip-width:190px;--chat-width:38vw;display:grid;grid-template-columns:var(--filmstrip-width) minmax(0,1fr) clamp(280px,var(--chat-width),calc(100vw - var(--filmstrip-width) - 240px));min-height:0}\n.filmstrip{background:#f7f8fa;border-right:1px solid #d4d9e2;overflow:auto;overscroll-behavior:contain;padding:16px 12px}\n.filmstrip h2{margin:0 0 12px 26px;text-transform:uppercase;letter-spacing:.1em;font-size:10px;color:#7c8596;font-weight:600}\n#thumbnails{display:flex;flex-direction:column;gap:12px;margin:0;padding:0;list-style:none}\n.thumbnail{display:flex;align-items:flex-start;gap:8px;width:100%;padding:4px 3px;border:0;background:transparent;border-radius:5px;text-align:left}\n.slide-number{width:18px;flex:none;text-align:right;font-size:11px;color:#737d8e;padding-top:5px}\n.thumbnail img{display:block;min-width:0;width:calc(100% - 26px);background:white;box-shadow:0 1px 4px #19212d18;border:2px solid transparent;border-radius:3px;aspect-ratio:var(--slide-ratio,16/9);object-fit:contain}\n.thumbnail[aria-current=\"true\"]{background:#e7edff}.thumbnail[aria-current=\"true\"] img{border-color:#4967dd}.thumbnail[aria-current=\"true\"] .slide-number{color:#3654c1;font-weight:700}\nmain{min-width:0;min-height:0;display:flex;flex-direction:column}\n#error{flex:none;max-height:30%;overflow:auto;background:#fff0ef;color:#922e25;margin:0;padding:16px 20px;white-space:pre-wrap;border-bottom:1px solid #e8bbb7}\n#error[hidden]{display:none}\n#stage{flex:1;min-height:0;overflow:auto;display:flex;padding:32px;overscroll-behavior:contain}\n#slide{position:relative;flex:none;margin:auto;background:white;box-shadow:0 3px 24px #19212d20;overflow:hidden}\n#slide{user-select:text;-webkit-user-select:text}\n#empty{margin:auto;color:#737d8e}\nfooter{display:flex;align-items:center;gap:14px;padding:0 16px;background:#fff;border-top:1px solid #d4d9e2;font-size:12px}\n#count{min-width:90px}footer .hint{flex:1;color:#737d8e}footer button{padding:3px 10px}footer select{padding:3px 8px}\n#presentation-controls{display:none}\nbody.presenting{grid-template-rows:minmax(0,1fr);background:#111}\n.presenting header,.presenting footer,.presenting .filmstrip,.presenting #chat{display:none}\n.presenting .workspace{grid-template-columns:minmax(0,1fr)}.presenting #stage{padding:0;background:#111}.presenting #slide{box-shadow:none}\n.presenting #presentation-controls{display:flex;position:fixed;bottom:16px;left:50%;transform:translateX(-50%);align-items:center;gap:12px;background:#202735e8;color:white;padding:6px;border-radius:8px;opacity:0;transition:opacity .15s}\n.presenting #presentation-controls:hover,.presenting #presentation-controls:focus-within{opacity:1}\n#presentation-controls button{background:transparent;color:white;border-color:#5d6575}\n@media(max-width:700px){.workspace{grid-template-columns:140px minmax(0,1fr)}.filmstrip{padding:12px 5px}header{padding:0 12px;gap:10px}.badge,footer .hint{display:none}#stage{padding:16px}footer{gap:8px}#status{font-size:11px}.download{padding:7px 8px}}\n#chat{position:relative;min-width:0;min-height:0;display:flex;flex-direction:column;background:#fff;border-left:1px solid #d4d9e2}\n#chat-resizer{position:absolute;left:-5px;top:0;bottom:0;width:10px;z-index:3;cursor:col-resize;touch-action:none}#chat-resizer::after{content:\"\";position:absolute;left:4px;top:0;bottom:0;width:2px}#chat-resizer:hover::after,#chat-resizer:focus-visible::after,.resizing-chat #chat-resizer::after{background:#4967dd}body.resizing-chat,body.resizing-chat *{cursor:col-resize!important;user-select:none!important}\n.chat-heading{display:flex;align-items:center;gap:8px;padding:16px;border-bottom:1px solid #e6e9ef}.chat-heading strong{flex:1}.chat-heading button{padding:5px 8px}\n#messages{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:16px;overscroll-behavior:contain}\n.chat-message{white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.6}.chat-message.user{background:#eef2ff;border-radius:8px;padding:10px 12px}.chat-message small{display:block;color:#697386;font-size:11px;margin-bottom:4px}.chat-intro{color:#737d8e;line-height:1.7}\n#chat-form{padding:14px;border-top:1px solid #e6e9ef}#chat-context{font-size:11px;color:#4967dd;margin-bottom:8px}#chat-input{font:inherit;resize:vertical;min-height:90px;max-height:200px;width:100%;padding:10px;border:1px solid #d4d9e2;border-radius:8px} .chat-actions{display:flex;gap:8px;margin-top:8px;align-items:center}.chat-actions select{min-width:0;flex:1}#chat-send{background:#293c73;color:white}#chat-status{padding:0 14px 12px;color:#667085;font-size:11px;white-space:pre-wrap;max-height:90px;overflow:auto}\nbody.chat-hidden .workspace{grid-template-columns:190px minmax(0,1fr)}body.chat-hidden #chat{display:none}\nbody.presenting .workspace{grid-template-columns:minmax(0,1fr)}\n@media(max-width:1000px){.workspace{--filmstrip-width:120px;--chat-width:380px}.filmstrip{padding:12px 5px}#stage{padding:16px}}\n@media(max-width:700px){#chat-resizer{display:none}.workspace{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(160px,1fr) minmax(200px,1fr)}.filmstrip{display:none}#chat{border-top:1px solid #d4d9e2}.chat-heading{padding:8px 12px}#chat-form{padding:8px 12px}#chat-input{min-height:50px}body.chat-hidden .workspace,.presenting .workspace{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr)}.brand{font-size:14px}header{gap:6px}.download{font-size:11px}}\n@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}\n\n[hidden]{display:none!important}\n.chat-heading{padding:10px 14px}.chat-heading select{flex:1;min-width:0;font-weight:600;border:0;padding-left:0}\n#chat-context{margin:0;padding:8px 14px;border-bottom:1px solid #e6e9ef;color:#667085;font-size:11px}\n#claude-panel,#codex-panel{display:flex;flex-direction:column;flex:1;min-height:0}\n#claude-panel{background:#171b24;color:#e4e8f0}\n.terminal-toolbar{display:flex;align-items:center;gap:8px;padding:8px 12px;font-size:11px;min-height:40px}.terminal-toolbar span{flex:1;overflow-wrap:anywhere}.terminal-toolbar button{font-size:11px;padding:5px 8px;background:#262d3b;border-color:#45516a;color:#e4e8f0}\n.terminal-toolbar button:hover:not(:disabled){background:#35415a}\n#terminal{flex:1;min-height:0;overflow:hidden;padding:4px 8px 8px}.xterm{height:100%}\n#chat-input{min-height:70px;resize:vertical}.chat-shortcut{flex:1;color:#9098a7;font-size:10px}\n.markdown{white-space:normal;line-height:1.65}.markdown>:first-child{margin-top:0}.markdown>:last-child{margin-bottom:0}.markdown p{margin:0 0 .8em}.markdown pre{overflow:auto;padding:10px;background:#f3f5f8;border:1px solid #e6e9ef;border-radius:6px;white-space:pre}.markdown code{font-size:.9em;background:#f3f5f8;border-radius:3px;padding:1px 4px}.markdown pre code{padding:0}.markdown ul,.markdown ol{padding-left:22px}.markdown blockquote{margin-left:0;padding-left:12px;border-left:3px solid #d4d9e2;color:#667085}.markdown table{display:block;overflow:auto;border-collapse:collapse}.markdown th,.markdown td{padding:6px 8px;border:1px solid #d4d9e2}.markdown h1,.markdown h2,.markdown h3{font-size:1.1em}.markdown a{color:#3654c1;text-decoration:underline}\n</style>\n<header><span class=\"brand\">Office Kit</span><span class=\"badge\">Preview</span><span id=\"status\" role=\"status\">Building…</span><button id=\"toggle-chat\" aria-expanded=\"true\" aria-controls=\"chat\">Chat</button><button id=\"present\" disabled>Present</button><a class=\"download\" href=\"/deck.pptx\">Download PPTX</a></header>\n<div class=\"workspace\">\n<nav class=\"filmstrip\" aria-label=\"Slides\"><h2>Slides</h2><ol id=\"thumbnails\"></ol></nav>\n<main aria-label=\"Slide viewer\"><pre id=\"error\" role=\"alert\" hidden></pre><div id=\"stage\" tabindex=\"-1\"><div id=\"empty\">Waiting for slides…</div><div id=\"slide\" hidden></div></div></main>\n<aside id=\"chat\" aria-label=\"Slide chat\">\n<div id=\"chat-resizer\" role=\"separator\" tabindex=\"0\" aria-label=\"Chat width\" aria-orientation=\"vertical\" aria-controls=\"chat\" title=\"Drag to resize · Double-click to reset\"></div>\n<div class=\"chat-heading\"><select id=\"chat-provider\" aria-label=\"AI provider\"><option value=\"claude\">Claude Code</option><option value=\"codex\">Codex</option></select><button id=\"chat-reset\" hidden>New chat</button></div>\n<div id=\"chat-context\">No slide selected</div>\n<section id=\"claude-panel\" aria-label=\"Claude Code terminal\"><div class=\"terminal-toolbar\"><span id=\"terminal-status\" role=\"status\">Start Claude Code to edit your slides.</span><button id=\"terminal-start\">Start</button><button id=\"terminal-stop\" hidden>End session</button></div><div id=\"terminal\"></div></section>\n<div id=\"codex-panel\" hidden>\n<div id=\"messages\" role=\"log\" aria-label=\"Conversation\"><p class=\"chat-intro\">Describe what you want to change. The current slide is included automatically. You can also ask about other slides or the whole deck.<br><br>Uses your locally installed Codex. Sign in through its CLI first. Sending a message allows it to edit this project.</p></div>\n<form id=\"chat-form\"><textarea id=\"chat-input\" aria-label=\"Message\" maxlength=\"16000\" placeholder=\"Ask for a change…\" required></textarea><div class=\"chat-actions\"><span class=\"chat-shortcut\">⌘ / Ctrl + Enter</span><button id=\"chat-stop\" type=\"button\" hidden>Stop</button><button id=\"chat-send\" type=\"submit\">Send</button></div></form>\n<div id=\"chat-status\" role=\"status\">Ready</div>\n</div>\n</aside>\n</div>\n<footer><span id=\"count\" aria-live=\"polite\">No slides</span><span class=\"hint\">Changes appear automatically · Text can be selected and copied</span><button id=\"prev\" aria-label=\"Previous slide\" disabled>‹</button><button id=\"next\" aria-label=\"Next slide\" disabled>›</button><label for=\"zoom\">Zoom</label><select id=\"zoom\"><option value=\"fit\">Fit</option><option value=\"0.5\">50%</option><option value=\"0.75\">75%</option><option value=\"1\">100%</option><option value=\"1.25\">125%</option><option value=\"1.5\">150%</option><option value=\"2\">200%</option></select></footer>\n<div id=\"presentation-controls\"><button id=\"present-prev\" aria-label=\"Previous slide\">‹</button><span id=\"present-count\"></span><button id=\"present-next\" aria-label=\"Next slide\">›</button><button id=\"exit-present\">Exit · Esc</button></div>\n<script>\nlet state={slides:[],error:null,aspectRatio:16/9},index=0,urls=[],presenting=false;\nlet displayedSvg;\nconst byId=id=>document.getElementById(id);\nconst stage=byId('stage'),slide=byId('slide'),thumbnails=byId('thumbnails');\nconst workspace=document.querySelector('.workspace'),chatResizer=byId('chat-resizer');\nconst chatWidthKey='office-kit-chat-width',minChatWidth=280,minStageWidth=240;\nfunction chatWidthLimits(){return {min:minChatWidth,max:Math.max(minChatWidth,workspace.clientWidth-document.querySelector('.filmstrip').getBoundingClientRect().width-minStageWidth)};}\nfunction updateChatWidthAria(){\n const {min,max}=chatWidthLimits();\n chatResizer.setAttribute('aria-valuemin',String(min));chatResizer.setAttribute('aria-valuemax',String(Math.round(max)));\n const width=Math.round(byId('chat').getBoundingClientRect().width);\n chatResizer.setAttribute('aria-valuenow',String(width));chatResizer.setAttribute('aria-valuetext',width+' pixels');\n}\nfunction setChatWidth(width){const {min,max}=chatWidthLimits();workspace.style.setProperty('--chat-width',Math.min(max,Math.max(min,width))+'px');}\nfunction saveChatWidth(){try{const width=workspace.style.getPropertyValue('--chat-width');if(width)localStorage.setItem(chatWidthKey,String(parseFloat(width)));else localStorage.removeItem(chatWidthKey);}catch(error){console.warn('Could not save chat width',error);}}\ntry{const saved=Number(localStorage.getItem(chatWidthKey));if(Number.isFinite(saved)&&saved>=minChatWidth)workspace.style.setProperty('--chat-width',saved+'px');}catch(error){console.warn('Could not restore chat width',error);}\nlet chatDrag=null;\nchatResizer.onpointerdown=event=>{\n if(event.button!==0||!event.isPrimary)return;\n event.preventDefault();chatResizer.focus();chatDrag={id:event.pointerId,x:event.clientX,width:byId('chat').getBoundingClientRect().width};\n chatResizer.setPointerCapture(event.pointerId);document.body.classList.add('resizing-chat');\n};\nchatResizer.onpointermove=event=>{if(chatDrag?.id===event.pointerId)setChatWidth(chatDrag.width+chatDrag.x-event.clientX);};\nfunction finishChatResize(){if(!chatDrag)return;chatDrag=null;document.body.classList.remove('resizing-chat');saveChatWidth();}\nchatResizer.onpointerup=finishChatResize;chatResizer.onpointercancel=finishChatResize;chatResizer.onlostpointercapture=finishChatResize;\nchatResizer.ondblclick=()=>{workspace.style.removeProperty('--chat-width');saveChatWidth();};\nchatResizer.onkeydown=event=>{\n const {min,max}=chatWidthLimits(),width=byId('chat').getBoundingClientRect().width,step=event.shiftKey?50:10;\n let next;\n if(event.key==='ArrowLeft')next=width+step;else if(event.key==='ArrowRight')next=width-step;else if(event.key==='Home')next=min;else if(event.key==='End')next=max;else return;\n event.preventDefault();event.stopPropagation();setChatWidth(next);saveChatWidth();\n};\nnew ResizeObserver(updateChatWidthAria).observe(byId('chat'));\n\n// The slide lives in a shadow root rather than a sandboxed iframe so its text\n// (XHTML inside <foreignObject>) can be selected and copied while the deck's\n// SVG stays out of the viewer's own DOM and CSS. Keyboard events still reach the\n// document, so arrow-key navigation keeps working after clicking into a slide.\nconst canvas=slide.attachShadow({mode:'open'});\nfunction resize(){\n if(!state.slides.length)return;\n const style=getComputedStyle(stage);\n const width=Math.max(1,stage.clientWidth-parseFloat(style.paddingLeft)-parseFloat(style.paddingRight));\n const height=Math.max(1,stage.clientHeight-parseFloat(style.paddingTop)-parseFloat(style.paddingBottom));\n const ratio=state.aspectRatio;\n const zoom=byId('zoom').value;\n const slideWidth=presenting||zoom==='fit'?Math.min(width,height*ratio):1280*Number(zoom);\n slide.style.width=slideWidth+'px';slide.style.height=slideWidth/ratio+'px';\n}\nfunction selectSlide(next,focusThumbnail=false,reveal=true){\n index=Math.max(0,Math.min(next,state.slides.length-1));\n const count=state.slides.length?'Slide '+(index+1)+' of '+state.slides.length:'No slides';\n byId('chat-context').textContent=state.slides.length?count:'Whole project';\n byId('chat-context').dataset.focus=JSON.stringify({slide:state.slides.length?index:null,revision:state.revision??0});\n byId('count').textContent=count;byId('present-count').textContent=count;\n for(const id of ['prev','present-prev'])byId(id).disabled=index===0;\n for(const id of ['next','present-next'])byId(id).disabled=index>=state.slides.length-1;\n byId('present').disabled=!state.slides.length;\n byId('zoom').disabled=!state.slides.length;\n slide.hidden=!state.slides.length;byId('empty').hidden=!!state.slides.length;\n const svg=state.slides[index];\n if(svg!==displayedSvg){\n displayedSvg=svg;\n canvas.innerHTML=svg?'<style>svg{display:block;width:100%;height:100%}</style>'+svg:'';\n }\n slide.setAttribute('aria-label','Slide '+(index+1));\n for(const [position,item] of Array.from(thumbnails.children).entries()){\n const button=item.firstElementChild,selected=position===index;\n button.setAttribute('aria-current',String(selected));button.tabIndex=selected?0:-1;\n if(selected){if(reveal)button.scrollIntoView({block:'nearest'});if(focusThumbnail)button.focus({preventScroll:true});}\n }\n resize();\n}\nfunction update(updated){\n const focusedThumbnail=thumbnails.contains(document.activeElement);\n const previous=state;\n state=updated;\n byId('status').textContent=state.error?'Build failed · showing last successful output':state.building?'Updating…':state.slides.length+' slides · Live';\n byId('error').textContent=state.error||'';byId('error').hidden=!state.error;\n document.documentElement.style.setProperty('--slide-ratio',String(state.aspectRatio));\n for(let i=state.slides.length;i<urls.length;i++){\n URL.revokeObjectURL(urls[i]);thumbnails.lastElementChild.remove();\n }\n urls.length=state.slides.length;\n state.slides.forEach((svg,i)=>{\n if(svg===previous.slides[i])return;\n const oldUrl=urls[i];\n urls[i]=URL.createObjectURL(new Blob([svg],{type:'image/svg+xml'}));\n let item=thumbnails.children[i];\n if(!item){\n item=document.createElement('li');\n const button=document.createElement('button'),number=document.createElement('span'),image=document.createElement('img');\n button.className='thumbnail';button.setAttribute('aria-label','Slide '+(i+1));button.onclick=()=>selectSlide(i,true);\n number.className='slide-number';number.textContent=String(i+1);\n image.alt='';image.draggable=false;image.loading='lazy';\n button.append(number,image);item.append(button);thumbnails.append(item);\n }\n item.querySelector('img').src=urls[i];\n if(oldUrl)URL.revokeObjectURL(oldUrl);\n });\n selectSlide(index,focusedThumbnail,false);\n if(!state.slides.length&&presenting)void exitPresentation();\n}\nfunction setPresenting(value){\n presenting=value;document.body.classList.toggle('presenting',value);resize();\n if(value)stage.focus();else{\n byId('present').focus();\n thumbnails.children[index]?.firstElementChild.scrollIntoView({block:'nearest'});\n }\n}\nasync function exitPresentation(){\n setPresenting(false);\n if(document.fullscreenElement)await document.exitFullscreen();\n}\nbyId('present').onclick=async()=>{\n setPresenting(true);\n try{await document.documentElement.requestFullscreen();}\n catch{byId('exit-present').textContent='Exit view · Esc';}\n};\nbyId('exit-present').onclick=exitPresentation;\ndocument.addEventListener('fullscreenchange',()=>{if(!document.fullscreenElement&&presenting)setPresenting(false);});\nfor(const id of ['prev','present-prev'])byId(id).onclick=()=>selectSlide(index-1);\nfor(const id of ['next','present-next'])byId(id).onclick=()=>selectSlide(index+1);\nbyId('zoom').onchange=resize;\nstage.onclick=()=>{if(presenting&&!getSelection().toString())selectSlide(index+1);};\ndocument.addEventListener('keydown',event=>{\n if(event.key==='Escape'&&presenting){event.preventDefault();void exitPresentation();return;}\n if(event.altKey||event.ctrlKey||event.metaKey||event.target.closest('select,input,textarea,[contenteditable]'))return;\n let next=index;\n if(['ArrowLeft','ArrowUp','PageUp'].includes(event.key))next--;\n else if(['ArrowRight','ArrowDown','PageDown'].includes(event.key))next++;\n else if(event.key==='Home')next=0;\n else if(event.key==='End')next=state.slides.length-1;\n else if(event.key===' '&&presenting&&!event.target.closest('button,a'))next+=event.shiftKey?-1:1;\n else return;\n event.preventDefault();selectSlide(next,thumbnails.contains(document.activeElement));\n});\nnew ResizeObserver(resize).observe(stage);\nlet refreshId=0;\nasync function refresh(){\n const id=++refreshId;\n try{const response=await fetch('/state'+(state.revision===undefined?'':'?since='+state.revision));if(!response.ok)throw new Error('Preview unavailable');const updated=await response.json();if(id===refreshId){if(updated.changes){updated.slides=state.slides.slice(0,updated.count);updated.slides.length=updated.count;for(const [position,svg] of Object.entries(updated.changes))updated.slides[Number(position)]=svg;}update(updated);}}\n catch{if(id===refreshId)byId('status').textContent='Reconnecting…';}\n}\nlet chatRequest=0,chatBusy=false,lastMessages='';\nfunction renderChat(chat){\n chatBusy=chat.running;\n byId('chat-send').disabled=chat.running;\n byId('chat-reset').disabled=chat.running;\n if(byId('chat-provider').value==='codex')byId('chat-provider').disabled=chat.running;\n byId('chat-stop').hidden=!chat.running;\n byId('chat-status').textContent=chat.status;\n\n const serialized=JSON.stringify(chat.messages);\n if(serialized===lastMessages)return;\n lastMessages=serialized;\n const log=byId('messages'),atBottom=log.scrollHeight-log.scrollTop-log.clientHeight<60;\n log.replaceChildren();\n if(!chat.messages.length){const intro=document.createElement('p');intro.className='chat-intro';intro.textContent='Ask for a focused slide edit or a change across the deck. Uses your locally installed CLI and its login. Sending allows edits to this project.';log.append(intro);}\n for(const message of chat.messages){\n const item=document.createElement('div'),label=document.createElement('small'),body=document.createElement('div');\n item.className='chat-message '+message.role;\n label.textContent=message.role==='user'?'You · '+(message.context?.slide?'Slide '+message.context.slide:'Whole project'):'Assistant';\n if(message.role==='assistant'&&message.html){body.className='markdown';body.innerHTML=message.html;}else body.textContent=message.text||(chat.running?'Working…':'No response');item.append(label,body);log.append(item);\n }\n if(atBottom)log.scrollTop=log.scrollHeight;\n}\nasync function refreshChat(){\n const id=++chatRequest;\n try{const response=await fetch('/chat');if(!response.ok)throw new Error('Chat unavailable');const chat=await response.json();if(id===chatRequest)renderChat(chat);}\n catch(error){if(id===chatRequest)byId('chat-status').textContent=error.message;}\n}\nasync function chatAction(path,payload){\n ++chatRequest;\n const response=await fetch(path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});\n const result=await response.json();if(!response.ok)throw new Error(result.error||'Chat request failed');\n await refreshChat();\n}\nbyId('chat-form').onsubmit=async event=>{\n event.preventDefault();if(chatBusy)return;\n const input=byId('chat-input'),message=input.value.trim();if(!message)return;\n chatBusy=true;byId('chat-send').disabled=true;\n try{await chatAction('/chat',{message,provider:byId('chat-provider').value,slide:state.slides.length?index:null,revision:state.revision??0});if(input.value.trim()===message)input.value='';}\n catch(error){byId('chat-status').textContent=error.message;chatBusy=false;byId('chat-send').disabled=false;}\n};\nbyId('chat-input').onkeydown=event=>{if(event.key==='Enter'&&(event.metaKey||event.ctrlKey)&&!event.isComposing){event.preventDefault();byId('chat-form').requestSubmit();}};\nfor(const action of ['stop','reset'])byId('chat-'+action).onclick=()=>chatAction('/chat/'+action,{}).catch(error=>{byId('chat-status').textContent=error.message;});\nbyId('toggle-chat').onclick=()=>{const hidden=document.body.classList.toggle('chat-hidden');byId('toggle-chat').setAttribute('aria-expanded',String(!hidden));resize();};\nbyId('chat-provider').onchange=()=>{const claude=byId('chat-provider').value==='claude';byId('claude-panel').hidden=!claude;byId('codex-panel').hidden=claude;byId('chat-reset').hidden=claude;};\nvoid refreshChat();\nconst events=new EventSource('/events');events.onmessage=event=>{if(event.data==='chat'){void refreshChat();return;}if(event.data==='ready'){delete state.revision;void refreshChat();}void refresh();};events.onerror=()=>{byId('status').textContent='Reconnecting…'};refresh();\n</script><script type=\"module\" src=\"/terminal.js\"></script></html>`;\n","import { randomBytes } from 'node:crypto';\nimport { dirname, resolve, join } from 'node:path';\nimport { createRequire } from 'node:module';\nimport { stat, chmod } from 'node:fs/promises';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { IPty } from 'node-pty';\nimport type { FocusContext } from './chat.ts';\n\nconst OUTPUT_LIMIT = 2_000_000;\nconst REQUEST_LIMIT = 128_000;\n\n/** A real interactive Claude session, shared across reloads of its owning tab. */\nexport function createTerminal(entry: string, busy = () => false) {\n let process: IPty | undefined;\n let owner = '';\n let output = '';\n let context: FocusContext | undefined;\n let status = 'Start Claude Code to edit your slides.';\n let completion = Promise.resolve();\n let closing = false;\n let stopping = false;\n let starting = false;\n const token = randomBytes(32).toString('hex');\n const clients = new Set<ServerResponse>();\n const snapshot = () => ({ running: !!process, owner, status });\n const emit = (event: string, value: unknown) => {\n for (const client of clients) {\n if (!client.write(`event: ${event}\\ndata: ${JSON.stringify(value)}\\n\\n`)) {\n clients.delete(client);\n client.end();\n }\n }\n };\n async function stop() {\n if (!process || stopping) return completion;\n stopping = true;\n status = 'Stopping…';\n emit('state', snapshot());\n const running = process;\n running.kill();\n const timer = setTimeout(() => {\n if (process === running) running.kill('SIGKILL');\n }, 2000);\n await completion;\n clearTimeout(timer);\n }\n async function handle(\n request: IncomingMessage,\n response: ServerResponse,\n focus: (index: number | null, revision: number) => FocusContext,\n ) {\n const json = (code: number, value: unknown) => {\n response.writeHead(code, { 'Content-Type': 'application/json' });\n response.end(JSON.stringify(value));\n };\n if (request.url === '/terminal/context') {\n if (request.method !== 'POST' || request.headers.authorization !== `Bearer ${token}`) {\n json(403, { error: 'Invalid hook credentials' });\n return;\n }\n request.resume();\n json(200, {\n hookSpecificOutput: {\n hookEventName: 'UserPromptSubmit',\n additionalContext: `You are editing the local PowerPoint TSX project at ${resolve(entry)}.\nRead the project's authoring instructions. Make focused source patches and preserve unrelated work.\nThe preview rebuilds on save; do not start another dev server.\nThe focused slide is context, NOT a restriction. For \"this slide\", edit only its relevant source. For other slides or deck-wide requests, locate the relevant source or shared theme.\nSlide numbers are 1-based. Verify source before editing; dependency paths are candidates, not an exact slide-to-file mapping.\nPreview context captured with the latest terminal input: ${JSON.stringify(context)}`,\n },\n });\n return;\n }\n const origin = `http://${request.headers.host}`;\n if (\n request.headers['sec-fetch-site'] === 'cross-site' ||\n (request.headers.origin && request.headers.origin !== origin)\n ) {\n json(403, { error: 'Same-origin requests only' });\n return;\n }\n if (request.method === 'GET' && request.url === '/terminal/events') {\n response.writeHead(200, { 'Content-Type': 'text/event-stream' });\n response.write(`event: replay\\ndata: ${JSON.stringify(output)}\\n\\n`);\n response.write(`event: state\\ndata: ${JSON.stringify(snapshot())}\\n\\n`);\n clients.add(response);\n request.on('close', () => clients.delete(response));\n return;\n }\n if (\n request.method !== 'POST' ||\n request.headers.origin !== origin ||\n request.headers['content-type'] !== 'application/json'\n ) {\n json(403, { error: 'Same-origin JSON requests only' });\n return;\n }\n try {\n let body = '';\n request.setEncoding('utf8');\n for await (const chunk of request) {\n body += chunk;\n if (Buffer.byteLength(body) > REQUEST_LIMIT) throw new Error('Request too large');\n }\n const value = JSON.parse(body);\n if (!value || typeof value.client !== 'string' || !/^[\\w-]{1,64}$/.test(value.client))\n throw new Error('Invalid terminal client');\n if (closing || starting || stopping || (process && owner !== value.client)) {\n json(409, {\n error: stopping\n ? 'Claude Code is stopping'\n : 'Claude Code is open in another tab. Use that tab to control it.',\n });\n return;\n }\n const size = () => {\n if (\n !Number.isInteger(value.cols) ||\n value.cols < 20 ||\n value.cols > 500 ||\n !Number.isInteger(value.rows) ||\n value.rows < 5 ||\n value.rows > 300\n )\n throw new Error('Invalid terminal dimensions');\n };\n const updateFocus = () => {\n if (\n !(value.slide === null || (Number.isInteger(value.slide) && value.slide >= 0)) ||\n !Number.isInteger(value.revision)\n )\n throw new Error('Invalid preview context');\n context = focus(value.slide, value.revision);\n };\n if (request.url === '/terminal/start') {\n if (busy()) throw new Error('A Codex edit is already running');\n size();\n updateFocus();\n if (!process) {\n starting = true;\n try {\n // Lazy loading leaves preview/export usable if a native PTY cannot load.\n // node-pty 1.1.0 ships its macOS spawn helper without executable bits.\n // Restore the packaged helper's mode before its first launch.\n if (globalThis.process.platform === 'darwin') {\n const root = dirname(createRequire(import.meta.url).resolve('node-pty/package.json'));\n const helper = join(\n root,\n 'prebuilds',\n `darwin-${globalThis.process.arch}`,\n 'spawn-helper',\n );\n const info = await stat(helper).catch((error: NodeJS.ErrnoException) => {\n if (error.code === 'ENOENT') return undefined;\n throw error;\n });\n if (info && !(info.mode & 0o100)) await chmod(helper, info.mode | 0o100);\n }\n const pty = await import('node-pty');\n if (process || closing) throw new Error('Terminal state changed. Try again.');\n const settings = {\n hooks: {\n UserPromptSubmit: [\n {\n hooks: [\n {\n type: 'http',\n url: `${origin}/terminal/context`,\n headers: { Authorization: `Bearer ${token}` },\n timeout: 5,\n },\n ],\n },\n ],\n },\n };\n const env = { ...globalThis.process.env };\n // This is an independent interactive session, even if the preview was started by Claude.\n delete env.CLAUDECODE;\n process = pty.spawn('claude', ['--settings', JSON.stringify(settings)], {\n cwd: dirname(resolve(entry)),\n name: 'xterm-256color',\n env,\n cols: value.cols,\n rows: value.rows,\n });\n } finally {\n starting = false;\n }\n owner = value.client;\n output = '';\n status = 'Claude Code';\n emit('replay', '');\n process.onData((data) => {\n output = (output + data).slice(-OUTPUT_LIMIT);\n emit('output', data);\n });\n completion = new Promise<void>((done) =>\n process!.onExit(({ exitCode }) => {\n process = undefined;\n stopping = false;\n status = `Claude Code exited (${exitCode}). Start to open a new session.`;\n emit('state', snapshot());\n done();\n }),\n );\n }\n } else if (request.url === '/terminal/input') {\n if (!process) throw new Error('Start Claude Code first');\n if (typeof value.data !== 'string' || value.data.length > 32000)\n throw new Error('Invalid terminal input');\n updateFocus();\n process.write(value.data);\n } else if (request.url === '/terminal/resize') {\n size();\n process?.resize(value.cols, value.rows);\n } else if (request.url === '/terminal/stop') {\n await stop();\n } else {\n json(404, { error: 'Not found' });\n return;\n }\n emit('state', snapshot());\n json(200, snapshot());\n } catch (cause) {\n json(400, { error: cause instanceof Error ? cause.message : 'Invalid terminal request' });\n }\n }\n return {\n handle,\n isRunning: () => starting || !!process,\n async close() {\n closing = true;\n await stop();\n for (const client of clients) client.end();\n clients.clear();\n },\n };\n}\n","import MarkdownIt from 'markdown-it';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { dirname, resolve } from 'node:path';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nexport type Provider = 'codex';\nconst markdown = new MarkdownIt({ html: false, linkify: false });\n// Do not fetch remote images from model output.\nmarkdown.renderer.rules.image = (tokens, index) =>\n markdown.utils.escapeHtml(tokens[index]?.content ?? '');\nexport interface FocusContext {\n slide: number | null;\n count: number;\n revision: number;\n text: string;\n entry: string;\n files: string[];\n buildError: string | null;\n}\ninterface Message {\n role: 'user' | 'assistant';\n text: string;\n context?: FocusContext;\n}\n\n/** One shared conversation and one editing process per dev server. */\nexport function createChat(entry: string, notify: () => void, busy = () => false) {\n let messages: Message[] = [];\n let provider: Provider = 'codex';\n let child: ChildProcess | undefined;\n let status = 'Ready';\n let stopping = false;\n let killTimer: ReturnType<typeof setTimeout> | undefined;\n let completion: Promise<void> = Promise.resolve();\n const snapshot = () => ({\n messages: messages.map((message) => ({\n ...message,\n ...(message.role === 'assistant' ? { html: markdown.render(message.text) } : {}),\n })),\n provider,\n running: !!child,\n status,\n });\n function stop() {\n if (!child || stopping) return;\n stopping = true;\n status = 'Stopping…';\n const processToStop = child;\n const kill = (signal: NodeJS.Signals) => {\n try {\n if (process.platform !== 'win32' && processToStop.pid)\n process.kill(-processToStop.pid, signal);\n else processToStop.kill(signal);\n } catch {\n /* Already exited. */\n }\n };\n kill('SIGTERM');\n killTimer = setTimeout(() => kill('SIGKILL'), 2000);\n notify();\n }\n function start(text: string, selected: Provider, context: FocusContext) {\n provider = selected;\n const user: Message = { role: 'user', text, context };\n // Keep recent conversational intent, including each turn's original focus.\n const history = messages.slice(-12).map((message) => ({\n ...message,\n text: message.text.slice(-12000),\n }));\n const prompt = `You are editing a local PowerPoint TSX project in ${dirname(resolve(entry))}.\nRead the project's authoring instructions. Make focused patches to the TSX source, preserving unrelated work.\nThe preview rebuilds automatically on save; do not start another dev server.\nThe attached focus is context, NOT a restriction: use it for references such as \"this slide\". For other slides or deck-wide requests, find the relevant source or shared theme instead. Do not rewrite the whole deck for a local edit.\nSlide numbers are 1-based and refer to the last successful preview at send time. Source may have changed since then; verify it before editing. Files are dependency candidates, not an exact slide-to-source mapping.\nRespond in the user's language, briefly describing edits or answering their question. If permissions prevent an action, explain that instead of claiming success.\nRecent conversation (JSON): ${JSON.stringify(history)}\nCurrent request and preview context (JSON): ${JSON.stringify(user)}`;\n messages.push(user);\n const answer: Message = { role: 'assistant', text: '' };\n messages.push(answer);\n if (messages.length > 100) messages = messages.slice(-100);\n status = 'Working…';\n stopping = false;\n const args = ['exec', '--json', '--sandbox', 'workspace-write', '--skip-git-repo-check', '-'];\n child = spawn(selected, args, {\n cwd: dirname(resolve(entry)),\n stdio: ['pipe', 'pipe', 'pipe'],\n detached: process.platform !== 'win32',\n shell: false,\n });\n const running = child;\n let buffer = '',\n stderr = '',\n failure = '',\n sawResult = false;\n function append(value: string) {\n answer.text = (answer.text + (answer.text ? '\\n\\n' : '') + value).slice(-100000);\n notify();\n }\n function consume(line: string) {\n if (!line.trim()) return;\n try {\n const event = JSON.parse(line);\n if (event.type === 'item.completed' && event.item?.type === 'agent_message')\n append(event.item.text ?? '');\n if (event.type === 'item.started') {\n status = 'Editing / inspecting…';\n notify();\n }\n if (event.type === 'turn.completed') sawResult = true;\n if (event.type === 'turn.failed' || event.type === 'error')\n failure = event.error?.message ?? event.message ?? 'Agent failed';\n } catch {\n failure = 'Unexpected CLI output. Update your CLI and try again.';\n }\n }\n running.stdout!.setEncoding('utf8');\n running.stdout!.on('data', (chunk: string) => {\n buffer += chunk;\n let end: number;\n while ((end = buffer.indexOf('\\n')) !== -1) {\n consume(buffer.slice(0, end));\n buffer = buffer.slice(end + 1);\n }\n if (buffer.length > 2_000_000) {\n failure = 'CLI output exceeded the limit.';\n stop();\n }\n });\n running.stderr!.setEncoding('utf8');\n running.stderr!.on('data', (chunk: string) => {\n stderr = (stderr + chunk).slice(-8000);\n });\n running.stdin!.on('error', () => {\n /* Early process exit is handled below. */\n });\n running.stdin!.end(prompt);\n completion = new Promise<void>((done) => {\n running.on('error', (error) => {\n failure = `Could not run ${selected}. Install it and log in from your terminal first. ${error.message}`;\n });\n running.on('close', (code) => {\n if (buffer.trim()) consume(buffer);\n clearTimeout(killTimer);\n status = stopping\n ? 'Stopped. Saved edits remain in the preview.'\n : failure ||\n (code !== 0\n ? stderr || `${selected} exited with code ${code}`\n : !sawResult\n ? 'CLI exited without a completed result.'\n : 'Done');\n if (status !== 'Done') append(status);\n child = undefined;\n notify();\n done();\n });\n });\n notify();\n }\n async function handle(\n request: IncomingMessage,\n response: ServerResponse,\n focus: (index: number | null, revision: number) => FocusContext,\n ) {\n const json = (code: number, value: unknown) => {\n response.writeHead(code, { 'Content-Type': 'application/json' });\n response.end(JSON.stringify(value));\n };\n if (request.method === 'GET' && request.url === '/chat') {\n json(200, snapshot());\n return;\n }\n if (request.method !== 'POST') {\n json(405, { error: 'Method not allowed' });\n return;\n }\n // Same-origin JSON prevents other sites from invoking local editing tools.\n if (\n request.headers.origin !== `http://${request.headers.host}` ||\n request.headers['content-type']?.split(';')[0] !== 'application/json'\n ) {\n json(403, { error: 'Same-origin JSON required' });\n return;\n }\n try {\n let body = '';\n request.setEncoding('utf8');\n for await (const chunk of request) {\n body += chunk;\n if (Buffer.byteLength(body) > 32000) {\n json(413, { error: 'Message too large' });\n return;\n }\n }\n const value = JSON.parse(body);\n if (request.url === '/chat/stop') {\n stop();\n json(200, snapshot());\n return;\n }\n if (child) {\n json(409, { error: 'An edit is already running' });\n return;\n }\n if (request.url === '/chat/reset') {\n messages = [];\n status = 'Ready';\n notify();\n json(200, snapshot());\n return;\n }\n if (request.url !== '/chat') {\n json(404, { error: 'Not found' });\n return;\n }\n if (\n typeof value.message !== 'string' ||\n !value.message.trim() ||\n value.message.length > 16000 ||\n value.provider !== 'codex' ||\n !(value.slide === null || (Number.isInteger(value.slide) && value.slide >= 0)) ||\n !Number.isInteger(value.revision)\n ) {\n json(400, { error: 'Invalid chat request' });\n return;\n }\n if (busy()) {\n json(409, { error: 'End the Claude Code session before starting a Codex edit' });\n return;\n }\n const context = focus(value.slide, value.revision);\n start(value.message.trim(), value.provider, context);\n json(202, snapshot());\n } catch (cause) {\n json(400, { error: cause instanceof Error ? cause.message : 'Invalid request' });\n }\n }\n return {\n handle,\n isRunning: () => !!child,\n async close() {\n stop();\n await completion;\n },\n };\n}\n","import { createServer, type ServerResponse } from 'node:http';\nimport { watch } from 'node:fs';\nimport { dirname, resolve, sep } from 'node:path';\nimport type { BuildResult } from './build.ts';\nimport { createDeckBuilder } from './build-runner.ts';\nimport { page } from './page.ts';\nimport { readFile } from 'node:fs/promises';\nimport { createTerminal } from './terminal.ts';\nimport { createChat } from './chat.ts';\n\nexport async function serveDeck(entry: string, port = 4173) {\n let latest: BuildResult | undefined;\n let error: string | null = null;\n let building = false;\n let revision = 0;\n let generation = 0;\n let patch: Record<number, string> = {};\n let pending = false;\n let closed = false;\n const clients = new Set<ServerResponse>();\n const chat = createChat(\n entry,\n () => {\n for (const client of clients) client.write('data: chat\\n\\n');\n },\n () => terminal.isRunning(),\n );\n const terminal = createTerminal(entry, () => chat.isRunning());\n const server = createServer((request, response) => {\n const host = request.headers.host;\n if (host !== `127.0.0.1:${actualPort}` && host !== `localhost:${actualPort}`) {\n response.writeHead(403).end();\n return;\n }\n response.setHeader('Cache-Control', 'no-store');\n if (\n request.url === '/chat' ||\n request.url?.startsWith('/chat/') ||\n request.url?.startsWith('/terminal/')\n ) {\n const handler = request.url.startsWith('/terminal/') ? terminal : chat;\n void handler.handle(request, response, (index, viewedRevision) => {\n if (viewedRevision !== revision)\n throw new Error('Preview changed. Review the current slide and send again.');\n if (index !== null && (!latest || index >= latest.slides.length))\n throw new Error('Slide no longer exists.');\n return {\n slide: index === null ? null : index + 1,\n count: latest?.slides.length ?? 0,\n revision,\n text: index === null ? '' : (latest?.slideTexts[index] ?? '').slice(0, 12000),\n entry: resolve(entry),\n files: latest?.dependencies ?? [resolve(entry)],\n buildError: error,\n };\n });\n } else if (\n request.method === 'GET' &&\n ['/terminal.js', '/terminal.css'].includes(request.url ?? '')\n ) {\n const asset = request.url === '/terminal.js' ? 'terminal-client.js' : 'terminal-client.css';\n void readFile(new URL(asset, import.meta.url)).then(\n (bytes) => {\n response.writeHead(200, {\n 'Content-Type': asset.endsWith('.css') ? 'text/css' : 'text/javascript',\n });\n response.end(bytes);\n },\n () => response.writeHead(500).end('Terminal assets unavailable. Rebuild pptx-dev.'),\n );\n } else if (request.method !== 'GET') {\n response.writeHead(405).end();\n } else if (request.url === '/events') {\n response.writeHead(200, { 'Content-Type': 'text/event-stream' });\n response.write('data: ready\\n\\n');\n clients.add(response);\n request.on('close', () => clients.delete(response));\n } else if (request.url === '/state' || request.url?.startsWith('/state?')) {\n const since = new URL(request.url, 'http://localhost').searchParams.get('since');\n const incremental =\n since !== null && (since === String(revision) || since === String(revision - 1));\n response.writeHead(200, { 'Content-Type': 'application/json' });\n response.end(\n JSON.stringify({\n revision,\n building,\n ...(incremental\n ? {\n changes: since === String(revision) ? {} : patch,\n count: latest?.slides.length ?? 0,\n }\n : { slides: latest?.slides ?? [] }),\n aspectRatio: latest?.aspectRatio ?? 16 / 9,\n error,\n diagnostics: latest?.diagnostics ?? [],\n }),\n );\n } else if (request.url === '/deck.pptx' && latest) {\n response.writeHead(200, {\n 'Content-Type': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'Content-Disposition': 'attachment; filename=\"deck.pptx\"',\n });\n response.end(latest.bytes);\n } else if (request.url === '/') {\n response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n response.end(page);\n } else {\n response.writeHead(404).end();\n }\n });\n async function rebuild() {\n if (closed) return;\n if (building) {\n pending = true;\n return;\n }\n building = true;\n const started = generation;\n for (const client of clients) client.write('data: building\\n\\n');\n try {\n const result = await builder.build();\n if (started === generation && !closed) {\n patch = {};\n result.slides.forEach((svg, index) => {\n if (svg !== latest?.slides[index]) patch[index] = svg;\n });\n latest = result;\n revision++;\n error = null;\n }\n } catch (cause) {\n if (started === generation)\n error = cause instanceof Error ? (cause.stack ?? cause.message) : String(cause);\n } finally {\n building = false;\n for (const client of clients) client.write('data: updated\\n\\n');\n if (pending && !timer) {\n pending = false;\n void rebuild();\n }\n }\n }\n let timer: ReturnType<typeof setTimeout> | undefined;\n const root = dirname(resolve(entry));\n const watcher = watch(root, { recursive: true }, (_, filename) => {\n if (\n !filename ||\n filename\n .split(sep)\n .some((part) => ['node_modules', '.git', 'dist', '.office-kit'].includes(part))\n )\n return;\n if (!/\\.([cm]?[jt]sx?|json|pptx|png|jpe?g|gif|bmp|tiff?|emf|wmf|svg)$/i.test(filename)) return;\n generation++;\n void builder.cancel();\n clearTimeout(timer);\n timer = setTimeout(() => {\n timer = undefined;\n pending = false;\n void rebuild();\n }, 30);\n });\n const builder = createDeckBuilder(entry);\n let actualPort = port;\n try {\n await new Promise<void>((resolveListen, reject) => {\n server.once('error', reject);\n server.listen(port, '127.0.0.1', () => {\n server.off('error', reject);\n resolveListen();\n });\n });\n } catch (cause) {\n watcher.close();\n await builder.close();\n throw cause;\n }\n const address = server.address();\n if (address && typeof address !== 'string') actualPort = address.port;\n await rebuild();\n return {\n url: `http://127.0.0.1:${actualPort}`,\n async close() {\n closed = true;\n clearTimeout(timer);\n watcher.close();\n await terminal.close();\n await chat.close();\n await builder.close();\n for (const client of clients) client.end();\n await new Promise<void>((done, reject) =>\n server.close((cause) => (cause ? reject(cause) : done())),\n );\n },\n };\n}\n","#!/usr/bin/env node\nimport { resolve } from 'node:path';\nimport { exportDeck } from './index.ts';\nimport { serveDeck } from './server.ts';\nimport { initProject } from './init.ts';\nimport { inspectTemplate } from './inspect.ts';\n\nconst usage = `Usage: office-pptx init <new-directory>\n office-pptx dev <deck.tsx> [--port 4173]\n office-pptx build <deck.tsx> [--out deck.pptx]\n office-pptx inspect <template.pptx>`;\nconst [command, entry, ...args] = process.argv.slice(2);\ntry {\n if (command === '--help' || command === '-h') {\n console.log(usage);\n } else {\n if (!entry) throw new Error(usage);\n const option = command === 'build' ? '--out' : command === 'dev' ? '--port' : undefined;\n if (args.length && (args.length !== 2 || !option || args[0] !== option || !args[1])) {\n throw new Error(`Unexpected arguments: ${args.join(' ')}\\n${usage}`);\n }\n if (command === 'init') {\n const directory = await initProject(entry);\n console.log(`Created ${directory}\\nRun npm install, then npm run dev inside that directory.`);\n } else if (command === 'inspect') {\n console.log(JSON.stringify(await inspectTemplate(entry), null, 2));\n } else if (command === 'build') {\n const output = resolve(args[1] ?? 'deck.pptx');\n const result = await exportDeck(entry, output);\n console.log(`Wrote ${output} (${result.slides.length} slides)`);\n } else if (command === 'dev') {\n const port = args[1] === undefined ? 4173 : Number(args[1]);\n if (!Number.isInteger(port) || port < 0 || port > 65535)\n throw new Error('Port must be an integer between 0 and 65535.');\n const server = await serveDeck(entry, port);\n console.log(`Preview: ${server.url}`);\n const stop = () => {\n void server.close().then(() => process.exit(0));\n };\n process.once('SIGINT', stop);\n process.once('SIGTERM', stop);\n } else {\n throw new Error(usage);\n }\n }\n} catch (cause) {\n console.error(cause instanceof Error ? cause.message : cause);\n process.exitCode = 1;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
3
|
+
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
|
4
|
+
* https://github.com/chjj/term.js
|
|
5
|
+
* @license MIT
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in
|
|
15
|
+
* all copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
+
* THE SOFTWARE.
|
|
24
|
+
*
|
|
25
|
+
* Originally forked from (with the author's permission):
|
|
26
|
+
* Fabrice Bellard's javascript vt100 for jslinux:
|
|
27
|
+
* http://bellard.org/jslinux/
|
|
28
|
+
* Copyright (c) 2011 Fabrice Bellard
|
|
29
|
+
* The original design remains. The terminal itself
|
|
30
|
+
* has been extended to include xterm CSI codes, among
|
|
31
|
+
* other features.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Default styles for xterm.js
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
.xterm {
|
|
39
|
+
cursor: text;
|
|
40
|
+
position: relative;
|
|
41
|
+
user-select: none;
|
|
42
|
+
-ms-user-select: none;
|
|
43
|
+
-webkit-user-select: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.xterm.focus,
|
|
47
|
+
.xterm:focus {
|
|
48
|
+
outline: none;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.xterm .xterm-helpers {
|
|
52
|
+
position: absolute;
|
|
53
|
+
top: 0;
|
|
54
|
+
/**
|
|
55
|
+
* The z-index of the helpers must be higher than the canvases in order for
|
|
56
|
+
* IMEs to appear on top.
|
|
57
|
+
*/
|
|
58
|
+
z-index: 5;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.xterm .xterm-helper-textarea {
|
|
62
|
+
padding: 0;
|
|
63
|
+
border: 0;
|
|
64
|
+
margin: 0;
|
|
65
|
+
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
|
|
66
|
+
position: absolute;
|
|
67
|
+
opacity: 0;
|
|
68
|
+
left: -9999em;
|
|
69
|
+
top: 0;
|
|
70
|
+
width: 0;
|
|
71
|
+
height: 0;
|
|
72
|
+
z-index: -5;
|
|
73
|
+
/** Prevent wrapping so the IME appears against the textarea at the correct position */
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
resize: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.xterm .composition-view {
|
|
80
|
+
/* TODO: Composition position got messed up somewhere */
|
|
81
|
+
background: #000;
|
|
82
|
+
color: #FFF;
|
|
83
|
+
display: none;
|
|
84
|
+
position: absolute;
|
|
85
|
+
white-space: nowrap;
|
|
86
|
+
z-index: 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.xterm .composition-view.active {
|
|
90
|
+
display: block;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.xterm .xterm-viewport {
|
|
94
|
+
/* On OS X this is required in order for the scroll bar to appear fully opaque */
|
|
95
|
+
background-color: #000;
|
|
96
|
+
overflow-y: scroll;
|
|
97
|
+
cursor: default;
|
|
98
|
+
position: absolute;
|
|
99
|
+
right: 0;
|
|
100
|
+
left: 0;
|
|
101
|
+
top: 0;
|
|
102
|
+
bottom: 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.xterm .xterm-screen {
|
|
106
|
+
position: relative;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.xterm .xterm-screen canvas {
|
|
110
|
+
position: absolute;
|
|
111
|
+
left: 0;
|
|
112
|
+
top: 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.xterm-char-measure-element {
|
|
116
|
+
display: inline-block;
|
|
117
|
+
visibility: hidden;
|
|
118
|
+
position: absolute;
|
|
119
|
+
top: 0;
|
|
120
|
+
left: -9999em;
|
|
121
|
+
line-height: normal;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.xterm.enable-mouse-events {
|
|
125
|
+
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
|
|
126
|
+
cursor: default;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.xterm.xterm-cursor-pointer,
|
|
130
|
+
.xterm .xterm-cursor-pointer {
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.xterm.column-select.focus {
|
|
135
|
+
/* Column selection mode */
|
|
136
|
+
cursor: crosshair;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.xterm .xterm-accessibility:not(.debug),
|
|
140
|
+
.xterm .xterm-message {
|
|
141
|
+
position: absolute;
|
|
142
|
+
left: 0;
|
|
143
|
+
top: 0;
|
|
144
|
+
bottom: 0;
|
|
145
|
+
right: 0;
|
|
146
|
+
z-index: 10;
|
|
147
|
+
color: transparent;
|
|
148
|
+
pointer-events: none;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.xterm .xterm-accessibility-tree:not(.debug) *::selection {
|
|
152
|
+
color: transparent;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.xterm .xterm-accessibility-tree {
|
|
156
|
+
font-family: monospace;
|
|
157
|
+
user-select: text;
|
|
158
|
+
white-space: pre;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.xterm .xterm-accessibility-tree > div {
|
|
162
|
+
transform-origin: left;
|
|
163
|
+
width: fit-content;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.xterm .live-region {
|
|
167
|
+
position: absolute;
|
|
168
|
+
left: -9999px;
|
|
169
|
+
width: 1px;
|
|
170
|
+
height: 1px;
|
|
171
|
+
overflow: hidden;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.xterm-dim {
|
|
175
|
+
/* Dim should not apply to background, so the opacity of the foreground color is applied
|
|
176
|
+
* explicitly in the generated class and reset to 1 here */
|
|
177
|
+
opacity: 1 !important;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.xterm-underline-1 { text-decoration: underline; }
|
|
181
|
+
.xterm-underline-2 { text-decoration: double underline; }
|
|
182
|
+
.xterm-underline-3 { text-decoration: wavy underline; }
|
|
183
|
+
.xterm-underline-4 { text-decoration: dotted underline; }
|
|
184
|
+
.xterm-underline-5 { text-decoration: dashed underline; }
|
|
185
|
+
|
|
186
|
+
.xterm-overline {
|
|
187
|
+
text-decoration: overline;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.xterm-overline.xterm-underline-1 { text-decoration: overline underline; }
|
|
191
|
+
.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }
|
|
192
|
+
.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }
|
|
193
|
+
.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }
|
|
194
|
+
.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }
|
|
195
|
+
|
|
196
|
+
.xterm-strikethrough {
|
|
197
|
+
text-decoration: line-through;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.xterm-screen .xterm-decoration-container .xterm-decoration {
|
|
201
|
+
z-index: 6;
|
|
202
|
+
position: absolute;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {
|
|
206
|
+
z-index: 7;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.xterm-decoration-overview-ruler {
|
|
210
|
+
z-index: 8;
|
|
211
|
+
position: absolute;
|
|
212
|
+
top: 0;
|
|
213
|
+
right: 0;
|
|
214
|
+
pointer-events: none;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.xterm-decoration-top {
|
|
218
|
+
z-index: 2;
|
|
219
|
+
position: relative;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
/* Derived from vs/base/browser/ui/scrollbar/media/scrollbar.css */
|
|
225
|
+
|
|
226
|
+
/* xterm.js customization: Override xterm's cursor style */
|
|
227
|
+
.xterm .xterm-scrollable-element > .scrollbar {
|
|
228
|
+
cursor: default;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* Arrows */
|
|
232
|
+
.xterm .xterm-scrollable-element > .scrollbar > .scra {
|
|
233
|
+
cursor: pointer;
|
|
234
|
+
font-size: 11px !important;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.xterm .xterm-scrollable-element > .visible {
|
|
238
|
+
opacity: 1;
|
|
239
|
+
|
|
240
|
+
/* Background rule added for IE9 - to allow clicks on dom node */
|
|
241
|
+
background:rgba(0,0,0,0);
|
|
242
|
+
|
|
243
|
+
transition: opacity 100ms linear;
|
|
244
|
+
/* In front of peek view */
|
|
245
|
+
z-index: 11;
|
|
246
|
+
}
|
|
247
|
+
.xterm .xterm-scrollable-element > .invisible {
|
|
248
|
+
opacity: 0;
|
|
249
|
+
pointer-events: none;
|
|
250
|
+
}
|
|
251
|
+
.xterm .xterm-scrollable-element > .invisible.fade {
|
|
252
|
+
transition: opacity 800ms linear;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Scrollable Content Inset Shadow */
|
|
256
|
+
.xterm .xterm-scrollable-element > .shadow {
|
|
257
|
+
position: absolute;
|
|
258
|
+
display: none;
|
|
259
|
+
}
|
|
260
|
+
.xterm .xterm-scrollable-element > .shadow.top {
|
|
261
|
+
display: block;
|
|
262
|
+
top: 0;
|
|
263
|
+
left: 3px;
|
|
264
|
+
height: 3px;
|
|
265
|
+
width: 100%;
|
|
266
|
+
box-shadow: var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset;
|
|
267
|
+
}
|
|
268
|
+
.xterm .xterm-scrollable-element > .shadow.left {
|
|
269
|
+
display: block;
|
|
270
|
+
top: 3px;
|
|
271
|
+
left: 0;
|
|
272
|
+
height: 100%;
|
|
273
|
+
width: 3px;
|
|
274
|
+
box-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset;
|
|
275
|
+
}
|
|
276
|
+
.xterm .xterm-scrollable-element > .shadow.top-left-corner {
|
|
277
|
+
display: block;
|
|
278
|
+
top: 0;
|
|
279
|
+
left: 0;
|
|
280
|
+
height: 3px;
|
|
281
|
+
width: 3px;
|
|
282
|
+
}
|
|
283
|
+
.xterm .xterm-scrollable-element > .shadow.top.left {
|
|
284
|
+
box-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset;
|
|
285
|
+
}
|