jiranimo 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,36 @@
1
+ const API_BASE="";let ws=null,tasks=[],serverEpoch=0,revision=0;async function fetchSync(){try{const e=await fetch("/api/sync");if(!e.ok)throw new Error(`Sync failed: ${e.status}`);const t=await e.json();serverEpoch=t.serverEpoch||0,revision=t.revision||0,tasks=Array.isArray(t.tasks)?t.tasks:[],render(),refreshOpenConvLog()}catch(e){console.error("Failed to sync dashboard state:",e)}}function connectWs(){const e=location.protocol==="https:"?"wss:":"ws:";ws=new WebSocket(`${e}//${location.host}/ws`),ws.onopen=async()=>{document.getElementById("ws-status").textContent="Connected",document.getElementById("ws-status").className="connected",await fetchSync()},ws.onclose=()=>{document.getElementById("ws-status").textContent="Disconnected",document.getElementById("ws-status").className="disconnected",setTimeout(connectWs,3e3)},ws.onmessage=async t=>{const n=JSON.parse(t.data);if(n.type==="sync-needed"){const s=Number(n.serverEpoch||0),l=Number(n.revision||0);(s>serverEpoch||l>revision)&&await fetchSync()}}}function render(){const e=tasks.filter(o=>o.status==="queued"),t=tasks.filter(o=>o.status==="in-progress"),n=tasks.filter(o=>o.status==="interrupted"),s=tasks.filter(o=>o.status==="completed"),l=tasks.filter(o=>o.status==="failed");document.getElementById("queued-tasks").innerHTML=e.map(taskCard).join(""),document.getElementById("in-progress-tasks").innerHTML=t.map(taskCard).join(""),document.getElementById("interrupted-tasks").innerHTML=n.map(taskCard).join(""),document.getElementById("completed-tasks").innerHTML=s.map(taskCard).join(""),document.getElementById("failed-tasks").innerHTML=l.map(taskCard).join(""),document.getElementById("task-counts").textContent=`${e.length} queued | ${t.length} running | ${n.length} interrupted | ${s.length} done | ${l.length} failed | epoch ${serverEpoch} rev ${revision}`}function taskCard(e){let t=`<span>${e.priority}</span><span>${e.issueType}</span>`;e.prUrl&&(t+=`<a href="${e.prUrl}" target="_blank">View PR</a>`),e.claudeCostUsd&&(t+=`<span>$${e.claudeCostUsd.toFixed(2)}</span>`),e.resumeMode&&(t+=`<span>Resume: ${escapeHtml(e.resumeMode)}</span>`),e.resumeAfter&&(t+=`<span>Auto resume: ${new Date(e.resumeAfter).toLocaleTimeString()}</span>`);let n="";if(e.status==="failed"&&e.errorMessage&&(n=`<div class="error-msg">${escapeHtml(e.errorMessage)}</div>`,n+=`<button class="btn" onclick="retryTask('${e.key}')">Retry</button>`),e.status==="interrupted"){const o=e.resumeReason?`<div class="error-msg">Interrupted: ${escapeHtml(e.resumeReason)}</div>`:"";n+=o,e.recoveryState==="resume-pending"&&(n+=`<button class="btn" onclick="cancelResume('${e.key}')">Cancel Resume</button>`),e.recoveryState==="resume-cancelled"&&(n+=`<button class="btn" onclick="resumeTask('${e.key}')">Resume Now</button>`)}return["in-progress","interrupted","completed","failed"].includes(e.status)&&(n+=`<button class="btn btn-log" onclick="openConvLog('${e.key}', '${escapeHtml(e.key)} \u2014 ${escapeHtml(e.summary).replace(/'/g,"&#39;").replace(/"/g,"&quot;")}')">View Log</button>`),e.prUrl&&(e.status==="completed"||e.status==="failed")&&(n+=`<button class="btn" onclick="fixComments('${e.key}')">Fix comments</button>`),`
2
+ <div class="task-card status-${e.status}">
3
+ <div class="task-key">${escapeHtml(e.key)}</div>
4
+ <div class="task-summary">${escapeHtml(e.summary)}</div>
5
+ <div class="task-meta">${t}</div>
6
+ ${n}
7
+ </div>
8
+ `}let currentLogKey=null,currentLogTitle=null,currentFullLogText=null,currentCompactLogText=null,currentLogTab="compact",isRefreshingOpenLog=!1;async function openConvLog(e,t){const n=document.getElementById("conv-modal"),s=document.getElementById("conv-modal-body");document.getElementById("conv-modal-title").textContent=t,s.innerHTML='<div class="conv-loading">Loading conversation\u2026</div>',n.style.display="flex",document.body.style.overflow="hidden",currentLogKey=e,currentLogTitle=t,currentFullLogText=null,currentCompactLogText=null,currentLogTab="compact",await loadConvLog(e,{showLoading:!1,preserveTab:!1})}async function loadConvLog(e,t={}){const{showLoading:n=!1,preserveTab:s=!0}=t,l=document.getElementById("conv-modal-body");n&&(l.innerHTML='<div class="conv-loading">Loading conversation\u2026</div>');const[o,a]=await Promise.allSettled([fetch(`/api/tasks/${e}/logs`),fetch(`/api/tasks/${e}/compact-log`)]);if(currentLogKey!==e)return;o.status==="fulfilled"&&o.value.ok&&(currentFullLogText=await o.value.text()),a.status==="fulfilled"&&a.value.ok&&(currentCompactLogText=(await a.value.json()).compactLog??null);const c=document.getElementById("log-tabs");if(currentCompactLogText){c.style.display="flex";const i=s&&currentLogTab==="full"?"full":"compact";setActiveTab(i),showLogTab(i)}else c.style.display="none",currentLogTab="full",showLogTab("full")}async function refreshOpenConvLog(){const e=document.getElementById("conv-modal");if(!currentLogKey||!e||e.style.display==="none"||isRefreshingOpenLog)return;const t=tasks.find(s=>s.key===currentLogKey);if(!(!t||!(!currentCompactLogText||t.status==="in-progress"))){isRefreshingOpenLog=!0;try{await loadConvLog(currentLogKey,{preserveTab:!0})}finally{isRefreshingOpenLog=!1}}}function setActiveTab(e){document.getElementById("tab-compact").classList.toggle("active",e==="compact"),document.getElementById("tab-full").classList.toggle("active",e==="full")}function showLogTab(e){const t=document.getElementById("conv-modal-body");currentLogTab=e,setActiveTab(e),e==="compact"?currentCompactLogText?t.innerHTML=renderCompactLog(currentCompactLogText):t.innerHTML='<div class="conv-loading">Compact log not available.</div>':currentFullLogText?(t.innerHTML=renderConvLog(currentFullLogText),t.scrollTop=t.scrollHeight):t.innerHTML='<div class="conv-loading">No logs available for this task yet.</div>'}function switchLogTab(e){showLogTab(e)}async function copyLogToClipboard(){const e=document.getElementById("btn-copy-log");let t="";if(currentLogTab==="compact"&&currentCompactLogText)t=currentCompactLogText;else if(currentLogTab==="full"&&currentFullLogText){const n=document.getElementById("conv-modal-body");t=n.innerText||n.textContent||""}if(t)try{await navigator.clipboard.writeText(t);const n=e.textContent;e.textContent="Copied!",setTimeout(()=>{e.textContent=n},1500)}catch{const n=document.createElement("textarea");n.value=t,n.style.position="fixed",n.style.opacity="0",document.body.appendChild(n),n.select(),document.execCommand("copy"),document.body.removeChild(n);const s=e.textContent;e.textContent="Copied!",setTimeout(()=>{e.textContent=s},1500)}}function closeConvModal(e){e&&e.target!==document.getElementById("conv-modal")&&!e.target.classList.contains("modal-close")||(document.getElementById("conv-modal").style.display="none",document.body.style.overflow="",currentLogKey=null,currentLogTitle=null)}document.addEventListener("keydown",e=>{if(e.key==="Escape"){const t=document.getElementById("conv-modal");t&&t.style.display!=="none"&&closeConvModal({target:t})}});function renderConvLog(e){const t=e.split(`
9
+ `).filter(s=>s.trim());if(!t.length)return'<div class="conv-loading">Log is empty.</div>';const n=[];for(const s of t){let l;try{l=JSON.parse(s)}catch{continue}switch(l.type){case"system":{const o=l.session_id||l.sessionId||"";n.push(convEntry("system","System",`Session started${o?` \xB7 ${o}`:""}`));break}case"assistant":{const a=(l.message||l).content||[];for(const c of a)if(c.type==="text"&&c.text)n.push(convEntry("assistant","Claude",c.text));else if(c.type==="tool_use"){const i=c.input!=null?JSON.stringify(c.input,null,2):"";n.push(convEntry("tool-use",`Tool: ${c.name}`,i))}break}case"user":{const o=l.message||l,a=Array.isArray(o.content)?o.content:[];for(const c of a)if(c.type==="tool_result"){const i=Array.isArray(c.content)?c.content.map(r=>r.text||"").join(`
10
+ `):typeof c.content=="string"?c.content:"";n.push(convEntry("tool-result","Tool Result",i))}else c.type==="text"&&c.text&&n.push(convEntry("user","User",c.text));break}case"result":{const o=l.subtype||"unknown",a=typeof l.cost_usd=="number"?` \xB7 $${l.cost_usd.toFixed(4)}`:"",c=l.result?`
11
+
12
+ ${l.result}`:"",i=o==="success"?"conv-result-success":o==="error_during_execution"?"conv-result-failure":"";n.push(convEntry("result",`Result: ${o}${a}`,c.trim(),i));break}default:break}}return n.length?n.join(""):'<div class="conv-loading">No conversation entries found in log.</div>'}function renderCompactLog(e){const t=e.split(`
13
+ `).map(a=>a.trim()).filter(Boolean);if(!t.length)return'<div class="conv-loading">Compact log is empty.</div>';const n=[],s=[];let l=null;for(const a of t){const c=a.replace(/^[-*]\s*/,"").trim();if(c){if(/^\*{0,2}Outcome[:*]/i.test(c)){l=c;continue}/^[-*]\s/.test(a)?n.push(c):s.push(c)}}const o=['<div class="compact-log-shell">'];return s.length&&o.push(`
14
+ <div class="compact-log-intro">
15
+ ${s.map(a=>`<p>${renderCompactInline(a)}</p>`).join("")}
16
+ </div>
17
+ `),n.length&&o.push(`
18
+ <ul class="compact-log-list">
19
+ ${n.map(a=>`
20
+ <li class="compact-log-item">
21
+ <span class="compact-log-marker"></span>
22
+ <div class="compact-log-item-text">${renderCompactInline(a)}</div>
23
+ </li>
24
+ `).join("")}
25
+ </ul>
26
+ `),l&&o.push(`
27
+ <div class="compact-log-outcome">
28
+ <div class="compact-log-outcome-label">Outcome</div>
29
+ <div class="compact-log-outcome-text">${renderCompactInline(stripOutcomeLabel(l))}</div>
30
+ </div>
31
+ `),o.push("</div>"),o.join("")}function stripOutcomeLabel(e){return e.replace(/^\*{0,2}Outcome\*{0,2}:\s*/i,"").trim()}function renderCompactInline(e){return escapeHtml(e).replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/`([^`]+)`/g,"<code>$1</code>")}function convEntry(e,t,n,s=""){return`
32
+ <div class="conv-entry conv-${e}${s?" "+s:""}">
33
+ <div class="conv-role">${escapeHtml(t)}</div>
34
+ <div class="conv-text">${escapeHtml(n)}</div>
35
+ </div>
36
+ `}async function retryTask(e){try{await fetch(`/api/tasks/${e}/retry`,{method:"POST"})}catch(t){console.error("Failed to retry task:",t)}}async function fixComments(e){try{const t=await fetch(`/api/tasks/${e}/fix-comments`,{method:"POST"});if(!t.ok){let n=`Failed to fix comments: ${t.status}`;try{const s=await t.json();s?.error&&(n=s.error)}catch{}throw new Error(n)}await fetchSync()}catch(t){console.error("Failed to fix comments:",t),alert(t.message||"Failed to fix comments")}}async function cancelResume(e){try{await fetch(`/api/tasks/${e}/cancel-resume`,{method:"POST"})}catch(t){console.error("Failed to cancel resume:",t)}}async function resumeTask(e){try{await fetch(`/api/tasks/${e}/resume`,{method:"POST"})}catch(t){console.error("Failed to resume task:",t)}}function escapeHtml(e){const t=document.createElement("div");return t.textContent=e==null?"":String(e),t.innerHTML}fetchSync(),connectWs();
@@ -0,0 +1 @@
1
+ <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Jiranimo Dashboard</title><link rel="icon" href="logo-mark.svg?v=2" type="image/svg+xml"><link rel="shortcut icon" href="logo-mark.svg?v=2" type="image/svg+xml"><link rel="stylesheet" href="style.css"></head><body><header><div class="brand"><img src="logo-mark.svg?v=2" alt="Jiranimo logo" class="brand-mark"><h1>Jiranimo</h1></div><div id="status-bar"><span id="ws-status" class="disconnected">Disconnected</span><span id="task-counts"></span></div></header><main><section id="queued-section"><h2>Queued</h2><div id="queued-tasks" class="task-list"></div></section><section id="in-progress-section"><h2>In Progress</h2><div id="in-progress-tasks" class="task-list"></div></section><section id="interrupted-section"><h2>Interrupted</h2><div id="interrupted-tasks" class="task-list"></div></section><section id="completed-section"><h2>Completed</h2><div id="completed-tasks" class="task-list"></div></section><section id="failed-section"><h2>Failed</h2><div id="failed-tasks" class="task-list"></div></section></main><!-- Conversation Log Modal --><div id="conv-modal" class="modal-overlay" style="display:none" onclick="closeConvModal(event)"><div class="modal"><div class="modal-header"><span id="conv-modal-title">Conversation Log</span><div class="modal-header-actions"><div class="log-tabs" id="log-tabs"><button class="log-tab active" id="tab-compact" onclick="switchLogTab('compact')">Compact</button><button class="log-tab" id="tab-full" onclick="switchLogTab('full')">Full</button></div><button class="btn-copy" id="btn-copy-log" onclick="copyLogToClipboard()" title="Copy to clipboard">Copy</button><button class="modal-close" onclick="closeConvModal()">✕</button></div></div><div id="conv-modal-body" class="modal-body"></div></div></div><script src="app.js"></script></body></html>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" fill="none"><defs><linearGradient id="bg" x1="18" y1="12" x2="112" y2="118" gradientUnits="userSpaceOnUse"><stop stop-color="#0C1733"/><stop offset="0.55" stop-color="#123C85"/><stop offset="1" stop-color="#1C88E5"/></linearGradient><radialGradient id="glow" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(93 24) rotate(137) scale(41 30)"><stop stop-color="#C7EBFF" stop-opacity="0.95"/><stop offset="1" stop-color="#C7EBFF" stop-opacity="0"/></radialGradient><linearGradient id="letter" x1="37" y1="26" x2="79" y2="98" gradientUnits="userSpaceOnUse"><stop stop-color="#F4FBFF"/><stop offset="0.62" stop-color="#BEE7FF"/><stop offset="1" stop-color="#6BC4FF"/></linearGradient><linearGradient id="shadow" x1="43" y1="34" x2="78" y2="99" gradientUnits="userSpaceOnUse"><stop stop-color="#0A1C42" stop-opacity="0.26"/><stop offset="1" stop-color="#0A1C42" stop-opacity="0"/></linearGradient><linearGradient id="spark" x1="89" y1="17" x2="108" y2="42" gradientUnits="userSpaceOnUse"><stop stop-color="#FFE89B"/><stop offset="1" stop-color="#FFC54D"/></linearGradient></defs><rect x="8" y="8" width="112" height="112" rx="28" fill="url(#bg)"/><rect x="8" y="8" width="112" height="112" rx="28" fill="url(#glow)"/><path d="M24 94C39.333 78.667 56.667 71 76 71C89.333 71 100.667 74.333 110 81V108H24V94Z" fill="#07152D" fill-opacity="0.28"/><g><path d="M76 28H88V73.5C88 82.667 85.25 89.792 79.75 94.875C74.292 99.917 66.917 102.438 57.625 102.438C49.417 102.438 42.625 100.708 37.25 97.25V85.438C43.292 90.146 49.875 92.5 57 92.5C62.792 92.5 67.313 90.896 70.563 87.688C73.813 84.438 75.438 79.625 75.438 73.25V28.563L76 28Z" fill="url(#shadow)"/><path d="M74 24H86V73C86 82.583 83.188 90.063 77.563 95.438C71.979 100.771 64.417 103.438 54.875 103.438C46.25 103.438 39.042 101.521 33.25 97.688V84.188C39.667 89.938 46.646 92.813 54.188 92.813C60.104 92.813 64.708 91.146 68 87.813C71.292 84.438 72.938 79.417 72.938 72.75V36.438H52.063V24H74Z" fill="url(#letter)"/><path d="M51.5 24H72.938V36.438H51.5V24Z" fill="#DFF4FF" fill-opacity="0.88"/><path d="M40.313 96.625C38.688 95.708 37 94.729 35.25 93.688V85.563C36.75 86.813 38.438 87.979 40.313 89.063V96.625Z" fill="#EAF8FF" fill-opacity="0.55"/></g><g transform="translate(83 16)"><path d="M12 0.8C12.455 0.8 12.856 1.102 12.981 1.54L14.544 6.998C14.765 7.77 15.369 8.374 16.14 8.595L21.599 10.157C22.036 10.283 22.338 10.683 22.338 11.138C22.338 11.594 22.036 11.994 21.599 12.119L16.14 13.682C15.369 13.903 14.765 14.507 14.544 15.279L12.981 20.737C12.856 21.175 12.455 21.477 12 21.477C11.545 21.477 11.144 21.175 11.019 20.737L9.456 15.279C9.235 14.507 8.631 13.903 7.86 13.682L2.401 12.119C1.964 11.994 1.662 11.594 1.662 11.138C1.662 10.683 1.964 10.283 2.401 10.157L7.86 8.595C8.631 8.374 9.235 7.77 9.456 6.998L11.019 1.54C11.144 1.102 11.545 0.8 12 0.8Z" fill="url(#spark)"/><path d="M12 4.25L12.957 7.593C13.251 8.621 14.057 9.427 15.085 9.721L18.428 10.678L15.085 11.635C14.057 11.929 13.251 12.735 12.957 13.763L12 17.106L11.043 13.763C10.749 12.735 9.943 11.929 8.915 11.635L5.572 10.678L8.915 9.721C9.943 9.427 10.749 8.621 11.043 7.593L12 4.25Z" fill="#FFF7D7"/></g><g transform="translate(75 27) scale(0.56)"><path d="M12 0.8C12.455 0.8 12.856 1.102 12.981 1.54L14.544 6.998C14.765 7.77 15.369 8.374 16.14 8.595L21.599 10.157C22.036 10.283 22.338 10.683 22.338 11.138C22.338 11.594 22.036 11.994 21.599 12.119L16.14 13.682C15.369 13.903 14.765 14.507 14.544 15.279L12.981 20.737C12.856 21.175 12.455 21.477 12 21.477C11.545 21.477 11.144 21.175 11.019 20.737L9.456 15.279C9.235 14.507 8.631 13.903 7.86 13.682L2.401 12.119C1.964 11.994 1.662 11.594 1.662 11.138C1.662 10.683 1.964 10.283 2.401 10.157L7.86 8.595C8.631 8.374 9.235 7.77 9.456 6.998L11.019 1.54C11.144 1.102 11.545 0.8 12 0.8Z" fill="#9EE7FF"/></g></svg>
@@ -0,0 +1 @@
1
+ *{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background:#0d1117;color:#c9d1d9;min-height:100vh}header{display:flex;justify-content:space-between;align-items:center;padding:16px 24px;background:#161b22;border-bottom:1px solid #30363d}.brand{display:flex;align-items:center;gap:12px}.brand-mark{width:36px;height:36px;display:block;border-radius:10px;box-shadow:0 10px 22px #050c1859}header h1{font-size:20px;color:#58a6ff;letter-spacing:-.02em}#status-bar{display:flex;gap:16px;align-items:center;font-size:13px}#ws-status{padding:2px 8px;border-radius:12px;font-size:12px}#ws-status.connected{background:#238636;color:#fff}#ws-status.disconnected{background:#da3633;color:#fff}main{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px;padding:24px}section h2{font-size:14px;text-transform:uppercase;letter-spacing:.5px;color:#8b949e;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid #21262d}.task-list{display:flex;flex-direction:column;gap:8px}.task-card{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:12px 16px}.task-card .task-key{font-size:12px;font-weight:600;color:#58a6ff}.task-card .task-summary{font-size:14px;margin:4px 0}.task-card .task-meta{font-size:12px;color:#8b949e;display:flex;gap:12px;margin-top:8px}.task-card .task-meta a{color:#58a6ff;text-decoration:none}.task-card .task-meta a:hover{text-decoration:underline}.task-card.status-queued{border-left:3px solid #d29922}.task-card.status-in-progress{border-left:3px solid #58a6ff}.task-card.status-interrupted{border-left:3px solid #db6d28}.task-card.status-completed{border-left:3px solid #238636}.task-card.status-failed{border-left:3px solid #da3633}.task-card .error-msg{font-size:12px;color:#f85149;margin-top:8px;padding:8px;background:#1c0e0e;border-radius:4px;white-space:pre-wrap;max-height:100px;overflow-y:auto}.btn{display:inline-block;padding:4px 12px;font-size:12px;border-radius:6px;border:1px solid #30363d;background:#21262d;color:#c9d1d9;cursor:pointer;margin-top:8px}.btn+.btn{margin-left:8px}.btn:hover{background:#30363d}.modal-overlay{position:fixed;inset:0;background:#000000b3;display:flex;align-items:center;justify-content:center;z-index:1000;padding:24px}.modal{background:#161b22;border:1px solid #30363d;border-radius:12px;width:100%;max-width:800px;max-height:85vh;display:flex;flex-direction:column;box-shadow:0 24px 64px #0009}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #30363d;font-weight:600;font-size:15px;color:#e6edf3;flex-shrink:0;gap:12px}.modal-header-actions{display:flex;align-items:center;gap:8px;flex-shrink:0}.log-tabs{display:flex;gap:2px;background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:2px}.log-tab{padding:3px 12px;font-size:12px;border-radius:4px;border:none;background:transparent;color:#8b949e;cursor:pointer;font-weight:500}.log-tab.active{background:#21262d;color:#e6edf3}.log-tab:hover:not(.active){color:#c9d1d9}.btn-copy{padding:3px 12px;font-size:12px;border-radius:6px;border:1px solid #30363d;background:#21262d;color:#c9d1d9;cursor:pointer}.btn-copy:hover{background:#30363d}.modal-close{background:none;border:none;color:#8b949e;cursor:pointer;font-size:16px;padding:4px 8px;border-radius:6px;line-height:1}.modal-close:hover{background:#21262d;color:#e6edf3}.modal-body{overflow-y:auto;padding:16px 20px;display:flex;flex-direction:column;gap:12px;flex:1}.conv-entry{border-radius:8px;padding:10px 14px;font-size:13px;line-height:1.6}.conv-entry .conv-role{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;margin-bottom:6px}.conv-entry .conv-text{white-space:pre-wrap;word-break:break-word}.conv-system{background:#1c2128;border-left:3px solid #6e7681;color:#8b949e}.conv-system .conv-role{color:#6e7681}.conv-user{background:#0d2033;border-left:3px solid #388bfd;color:#a5c8ff}.conv-user .conv-role{color:#58a6ff}.conv-assistant{background:#0d2a1a;border-left:3px solid #3fb950;color:#b3f0c0}.conv-assistant .conv-role{color:#3fb950}.conv-tool-use{background:#1e1a0e;border-left:3px solid #d29922;color:#e3b341}.conv-tool-use .conv-role{color:#d29922}.conv-tool-use .conv-text{font-family:SFMono-Regular,Consolas,monospace;font-size:12px}.conv-tool-result{background:#1a0e2a;border-left:3px solid #a371f7;color:#d2b0f7}.conv-tool-result .conv-role{color:#a371f7}.conv-tool-result .conv-text{font-family:SFMono-Regular,Consolas,monospace;font-size:12px}.conv-result{background:#111820;border-left:3px solid #58a6ff;color:#c9d1d9}.conv-result .conv-role{color:#58a6ff}.conv-result.conv-result-success{border-left-color:#3fb950}.conv-result.conv-result-failure{border-left-color:#da3633}.conv-loading{text-align:center;color:#8b949e;padding:32px;font-size:14px}.btn-log{border-color:#1f6feb;color:#58a6ff}.btn-log:hover{background:#0d2033;border-color:#388bfd}.conv-compact{padding:0;background:transparent;border:none}.compact-log-shell{background:radial-gradient(circle at top right,rgba(56,139,253,.14),transparent 34%),linear-gradient(180deg,#0f1622,#0c121b);border:1px solid #283445;border-radius:18px;padding:22px 24px;color:#dbe5f0;box-shadow:inset 0 1px #ffffff0a,0 18px 40px #00000047}.compact-log-intro{display:grid;gap:12px;margin-bottom:14px}.compact-log-intro p{margin:0;color:#c4d0de;font-size:15px;line-height:1.75}.compact-log-list{list-style:none;display:grid;gap:10px;padding:0;margin:0}.compact-log-item{display:grid;grid-template-columns:14px minmax(0,1fr);gap:14px;align-items:start;padding:0}.compact-log-marker{width:14px;height:14px;margin-top:6px;border-radius:999px;background:linear-gradient(180deg,#7cc0ff,#388bfd);box-shadow:0 0 0 4px #388bfd1f}.compact-log-item-text{color:#dbe5f0;font-family:Avenir Next,Segoe UI,sans-serif;font-size:15px;line-height:1.75;word-break:break-word}.compact-log-outcome{margin-top:18px;padding:18px 20px;border-radius:16px;background:linear-gradient(180deg,#0c1f16f2,#0c1814eb);border:1px solid rgba(63,185,80,.28)}.compact-log-outcome-label{font-size:11px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:#56d364;margin-bottom:8px}.compact-log-outcome-text{color:#d8ffe0;font-size:15px;line-height:1.75}.compact-log-shell code{font-family:SFMono-Regular,Consolas,monospace;font-size:.92em;color:#9ecbff;background:#0f1723f2;border:1px solid rgba(88,166,255,.22);border-radius:8px;padding:2px 7px}.compact-log-shell strong{color:#f0f6fc;font-weight:700}@media(max-width:640px){.compact-log-shell{padding:20px 18px;border-radius:16px}.compact-log-item{grid-template-columns:12px minmax(0,1fr);gap:12px}.compact-log-marker{width:12px;height:12px}}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "jiranimo",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Local automation server for the Jiranimo Jira extension",
6
+ "main": "./dist/index.js",
7
+ "bin": {
8
+ "jiranimo": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/Niryo/Jiranimo.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/Niryo/Jiranimo/issues"
27
+ },
28
+ "homepage": "https://github.com/Niryo/Jiranimo#readme",
29
+ "scripts": {
30
+ "start": "tsx src/index.ts",
31
+ "dev": "tsx watch src/index.ts",
32
+ "build": "node ../scripts/build-server-package.mjs",
33
+ "prepack": "npm run build",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest",
36
+ "test:integration": "vitest run --config vitest.integration.config.ts",
37
+ "test:e2e": "vitest run --config vitest.e2e.config.ts",
38
+ "test:e2e:cleanup": "tsx test/e2e/cleanup-jira.ts",
39
+ "test:coverage": "vitest run --coverage",
40
+ "test:all": "tsx test/run-tests.ts"
41
+ },
42
+ "license": "MIT",
43
+ "dependencies": {
44
+ "@anthropic-ai/sdk": "^0.78.0",
45
+ "@modelcontextprotocol/sdk": "^1.27.1",
46
+ "express": "^5.2.1",
47
+ "ws": "^8.19.0",
48
+ "zod": "^4.3.6"
49
+ },
50
+ "devDependencies": {
51
+ "@types/express": "^5.0.6",
52
+ "@types/node": "^25.5.0",
53
+ "@types/supertest": "^7.2.0",
54
+ "@types/ws": "^8.18.1",
55
+ "@vitest/coverage-v8": "^4.1.0",
56
+ "dotenv": "^17.3.1",
57
+ "playwright": "^1.58.2",
58
+ "supertest": "^7.2.2",
59
+ "tsx": "^4.21.0",
60
+ "typescript": "^6.0.2",
61
+ "vitest": "^4.1.0"
62
+ }
63
+ }