@loicngr/kobo 1.1.1 → 1.3.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.
- package/dist/mcp-server/kobo-tasks-handlers.js +147 -0
- package/dist/mcp-server/kobo-tasks-server.js +236 -29
- package/dist/server/db/migrations.js +61 -10
- package/dist/server/db/schema.js +1 -0
- package/dist/server/index.js +10 -4
- package/dist/server/routes/images.js +57 -0
- package/dist/server/routes/workspaces.js +80 -19
- package/dist/server/services/agent-manager.js +91 -5
- package/dist/server/services/image-service.js +73 -0
- package/dist/server/services/pr-watcher-service.js +61 -0
- package/dist/server/services/settings-service.js +75 -10
- package/dist/server/services/workspace-service.js +13 -0
- package/dist/server/utils/git-ops.js +39 -0
- package/package.json +3 -1
- package/src/client/dist/spa/assets/{ActivityFeed-CufaRX1M.js → ActivityFeed-Bie-lcn7.js} +7 -7
- package/src/client/dist/spa/assets/ActivityFeed-D88GOO2z.css +1 -0
- package/src/client/dist/spa/assets/CreatePage-OC-fnNGP.js +2 -0
- package/src/client/dist/spa/assets/MainLayout-91cUoVYa.css +1 -0
- package/src/client/dist/spa/assets/MainLayout-BIQNJixM.js +1 -0
- package/src/client/dist/spa/assets/QBadge-DbE3eSf1.js +1 -0
- package/src/client/dist/spa/assets/QDialog-Cd_4PvgW.js +1 -0
- package/src/client/dist/spa/assets/QExpansionItem-pMQDDRMv.js +1 -0
- package/src/client/dist/spa/assets/QPage-lhV4XbI2.js +1 -0
- package/src/client/dist/spa/assets/{QSpinnerDots-DcaNq8uL.js → QSpinnerDots-ByNZaBWw.js} +1 -1
- package/src/client/dist/spa/assets/QTooltip-6GSFtFKP.js +1 -0
- package/src/client/dist/spa/assets/SettingsPage-BPH70mno.css +1 -0
- package/src/client/dist/spa/assets/SettingsPage-s2WJBreM.js +1 -0
- package/src/client/dist/spa/assets/WorkspacePage-Dhkuuhf8.css +1 -0
- package/src/client/dist/spa/assets/WorkspacePage-XT26aCJE.js +2 -0
- package/src/client/dist/spa/assets/_plugin-vue_export-helper-B6FaNy4R.js +1 -0
- package/src/client/dist/spa/assets/index-BoQWbZtE.js +5 -0
- package/src/client/dist/spa/assets/{nodes-DeIen-kp.js → nodes-CXdiSdC2.js} +1 -1
- package/src/client/dist/spa/assets/use-checkbox-Z9pfihkw.js +1 -0
- package/src/client/dist/spa/assets/use-quasar-CtCe3LQU.js +1 -0
- package/src/client/dist/spa/index.html +2 -2
- package/src/mcp-server/README.md +179 -0
- package/src/mcp-server/kobo-tasks-handlers.ts +238 -0
- package/src/mcp-server/kobo-tasks-server.ts +263 -29
- package/src/client/dist/spa/assets/ActivityFeed-DBNn62g_.css +0 -1
- package/src/client/dist/spa/assets/CreatePage-Cyl-TRHT.js +0 -2
- package/src/client/dist/spa/assets/MainLayout-D_vxGAPn.css +0 -1
- package/src/client/dist/spa/assets/MainLayout-Dzy0I8lB.js +0 -1
- package/src/client/dist/spa/assets/QBadge-Cb92Ia8-.js +0 -1
- package/src/client/dist/spa/assets/QDialog-CMC1Ph52.js +0 -1
- package/src/client/dist/spa/assets/QExpansionItem-DDjku8zz.js +0 -1
- package/src/client/dist/spa/assets/QPage-DaNo_vcd.js +0 -1
- package/src/client/dist/spa/assets/QTabPanels-BKHAAJ2p.js +0 -1
- package/src/client/dist/spa/assets/QTooltip-637ruGFc.js +0 -1
- package/src/client/dist/spa/assets/SettingsPage-B9VYIQs-.css +0 -1
- package/src/client/dist/spa/assets/SettingsPage-Blw7Qk7m.js +0 -1
- package/src/client/dist/spa/assets/WorkspacePage-DlnwomOE.js +0 -2
- package/src/client/dist/spa/assets/WorkspacePage-HtatyhXN.css +0 -1
- package/src/client/dist/spa/assets/_plugin-vue_export-helper-CHpmshS7.js +0 -1
- package/src/client/dist/spa/assets/index-DJkEmbBM.js +0 -5
- package/src/client/dist/spa/assets/use-quasar-Dq-Vjx_2.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{B as e,C as t,Ct as n,E as r,Et as i,F as a,H as o,I as s,It as c,J as l,K as u,L as d,N as f,Nt as p,Ot as m,Q as h,Rt as g,T as _,V as v,Vt as y,W as b,Wt as x,Z as S,b as C,bt as w,et as T,f as E,kt as D,o as O,q as k,st as A,ut as j,wt as M,x as N,xt as P,y as F,yt as I,z as ee}from"./nodes-CXdiSdC2.js";import{l as te,m as ne,u as L}from"./index-BoQWbZtE.js";import{h as R,k as re,m as z}from"./_plugin-vue_export-helper-B6FaNy4R.js";import{C as B,S as V,c as H,u as U,v as ie,y as W}from"./QDialog-Cd_4PvgW.js";var G={left:!0,right:!0,up:!0,down:!0,horizontal:!0,vertical:!0},ae=Object.keys(G);G.all=!0;function K(e){let t={};for(let n of ae)e[n]===!0&&(t[n]=!0);return Object.keys(t).length===0?G:(t.horizontal===!0?t.left=t.right=!0:t.left===!0&&t.right===!0&&(t.horizontal=!0),t.vertical===!0?t.up=t.down=!0:t.up===!0&&t.down===!0&&(t.vertical=!0),t.horizontal===!0&&t.vertical===!0&&(t.all=!0),t)}var q=[`INPUT`,`TEXTAREA`];function J(e,t){return t.event===void 0&&e.target!==void 0&&e.target.draggable!==!0&&typeof t.handler==`function`&&q.includes(e.target.nodeName.toUpperCase())===!1&&(e.qClonedBy===void 0||e.qClonedBy.indexOf(t.uid)===-1)}var oe=0,se=[`click`,`keydown`],Y={icon:String,label:[Number,String],alert:[Boolean,String],alertIcon:String,name:{type:[Number,String],default:()=>`t_${oe++}`},noCaps:Boolean,tabindex:[String,Number],disable:Boolean,contentClass:String,ripple:{type:[Boolean,Object],default:!0}};function ce(e,t,n,r){let o=P(ne,L);if(o===L)return console.error(`QTab/QRouteTab component needs to be child of QTabs`),L;let{proxy:s}=I(),c=y(null),l=y(null),d=y(null),p=j(()=>e.disable===!0||e.ripple===!1?!1:Object.assign({keyCodes:[13,32],early:!0},e.ripple===!0?{}:e.ripple)),m=j(()=>o.currentModel.value===e.name),h=j(()=>`q-tab relative-position self-stretch flex flex-center text-center`+(m.value===!0?` q-tab--active`+(o.tabProps.value.activeClass?` `+o.tabProps.value.activeClass:``)+(o.tabProps.value.activeColor?` text-${o.tabProps.value.activeColor}`:``)+(o.tabProps.value.activeBgColor?` bg-${o.tabProps.value.activeBgColor}`:``):` q-tab--inactive`)+(e.icon&&e.label&&o.tabProps.value.inlineLabel===!1?` q-tab--full`:``)+(e.noCaps===!0||o.tabProps.value.noCaps===!0?` q-tab--no-caps`:``)+(e.disable===!0?` disabled`:` q-focusable q-hoverable cursor-pointer`)+(r===void 0?``:r.linkClass.value)),_=j(()=>`q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable `+(o.tabProps.value.inlineLabel===!0?`row no-wrap q-tab__content--inline`:`column`)+(e.contentClass===void 0?``:` ${e.contentClass}`)),v=j(()=>e.disable===!0||o.hasFocus.value===!0||m.value===!1&&o.hasActiveTab.value===!0?-1:e.tabindex||0);function b(t,i){if(i!==!0&&t?.qAvoidFocus!==!0&&c.value?.focus(),e.disable===!0){r?.hasRouterLink.value===!0&&u(t);return}if(r===void 0){o.updateModel({name:e.name}),n(`click`,t);return}if(r.hasRouterLink.value===!0){let i=(n={})=>{let i,a=n.to===void 0||te(n.to,e.to)===!0?o.avoidRouteWatcher=W():null;return r.navigateToRouterLink(t,{...n,returnRouterError:!0}).catch(e=>{i=e}).then(t=>{if(a===o.avoidRouteWatcher&&(o.avoidRouteWatcher=!1,i===void 0&&(t===void 0||t.message?.startsWith(`Avoided redundant navigation`)===!0)&&o.updateModel({name:e.name})),n.returnRouterError===!0)return i===void 0?t:Promise.reject(i)})};n(`click`,t,i),t.defaultPrevented!==!0&&i();return}n(`click`,t)}function x(e){f(e,[13,32])?b(e,!0):a(e)!==!0&&e.keyCode>=35&&e.keyCode<=40&&e.altKey!==!0&&e.metaKey!==!0&&o.onKbdNavigate(e.keyCode,s.$el)===!0&&u(e),n(`keydown`,e)}function S(){let n=o.tabProps.value.narrowIndicator,r=[],i=w(`div`,{ref:d,class:[`q-tab__indicator`,o.tabProps.value.indicatorClass]});e.icon!==void 0&&r.push(w(F,{class:`q-tab__icon`,name:e.icon})),e.label!==void 0&&r.push(w(`div`,{class:`q-tab__label`},e.label)),e.alert!==!1&&r.push(e.alertIcon===void 0?w(`div`,{class:`q-tab__alert`+(e.alert===!0?``:` text-${e.alert}`)}):w(F,{class:`q-tab__alert-icon`,color:e.alert===!0?void 0:e.alert,name:e.alertIcon})),n===!0&&r.push(i);let a=[w(`div`,{class:`q-focus-helper`,tabindex:-1,ref:c}),w(`div`,{class:_.value},N(t.default,r))];return n===!1&&a.push(i),a}let C={name:j(()=>e.name),rootRef:l,tabIndicatorRef:d,routeData:r};i(()=>{o.unregisterTab(C)}),D(()=>{o.registerTab(C)});function T(t,n){return g(w(t,{ref:l,class:h.value,tabindex:v.value,role:`tab`,"aria-selected":m.value===!0?`true`:`false`,"aria-disabled":e.disable===!0?`true`:void 0,onClick:b,onKeydown:x,...n},S()),[[E,p.value]])}return{renderTab:T,$tabs:o}}var le=k({name:`QTab`,props:Y,emits:se,setup(e,{slots:t,emit:n}){let{renderTab:r}=ce(e,t,n);return()=>r(`div`)}});function ue(){let e=y(!h.value);return e.value===!1&&D(()=>{e.value=!0}),{isHydrated:e}}var X=typeof ResizeObserver<`u`,de=X===!0?{}:{style:`display:block;position:absolute;top:0;left:0;right:0;bottom:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1;`,url:`about:blank`},fe=k({name:`QResizeObserver`,props:{debounce:{type:[String,Number],default:100}},emits:[`resize`],setup(t,{emit:r}){let a=null,o,s={width:-1,height:-1};function c(e){e===!0||t.debounce===0||t.debounce===`0`?l():a===null&&(a=setTimeout(l,t.debounce))}function l(){if(a!==null&&(clearTimeout(a),a=null),o){let{offsetWidth:e,offsetHeight:t}=o;(e!==s.width||t!==s.height)&&(s={width:e,height:t},r(`resize`,s))}}let{proxy:u}=I();if(u.trigger=c,X===!0){let e,t=r=>{o=u.$el.parentNode,o?(e=new ResizeObserver(c),e.observe(o),l()):r!==!0&&n(()=>{t(!0)})};return D(()=>{t()}),i(()=>{a!==null&&clearTimeout(a),e!==void 0&&(e.disconnect===void 0?o&&e.unobserve(o):e.disconnect())}),v}else{let{isHydrated:t}=ue(),r;function s(){a!==null&&(clearTimeout(a),a=null),r!==void 0&&(r.removeEventListener!==void 0&&r.removeEventListener(`resize`,c,e.passive),r=void 0)}function d(){s(),o?.contentDocument&&(r=o.contentDocument.defaultView,r.addEventListener(`resize`,c,e.passive),l())}return D(()=>{n(()=>{o=u.$el,o&&d()})}),i(s),()=>{if(t.value===!0)return w(`object`,{class:`q--avoid-card-border`,style:de.style,tabindex:-1,type:`text/html`,data:de.url,"aria-hidden":`true`,onLoad:d})}}}});function pe(e,t,n){let r=n===!0?[`left`,`right`]:[`top`,`bottom`];return`absolute-${t===!0?r[0]:r[1]}${e?` text-${e}`:``}`}var me=[`left`,`center`,`right`,`justify`],he=k({name:`QTabs`,props:{modelValue:[Number,String],align:{type:String,default:`center`,validator:e=>me.includes(e)},breakpoint:{type:[String,Number],default:600},vertical:Boolean,shrink:Boolean,stretch:Boolean,activeClass:String,activeColor:String,activeBgColor:String,indicatorColor:String,leftIcon:String,rightIcon:String,outsideArrows:Boolean,mobileArrows:Boolean,switchIndicator:Boolean,narrowIndicator:Boolean,inlineLabel:Boolean,noCaps:Boolean,dense:Boolean,contentClass:String,"onUpdate:modelValue":[Function,Array]},setup(e,{slots:n,emit:r}){let{proxy:a}=I(),{$q:o}=a,{registerTick:s}=R(),{registerTick:l}=R(),{registerTick:u}=R(),{registerTimeout:d,removeTimeout:f}=z(),{registerTimeout:h,removeTimeout:g}=z(),_=y(null),v=y(null),b=y(e.modelValue),x=y(!1),S=y(!0),C=y(!1),T=y(!1),E=[],D=y(0),O=y(!1),k=null,A=null,N,P=j(()=>({activeClass:e.activeClass,activeColor:e.activeColor,activeBgColor:e.activeBgColor,indicatorClass:pe(e.indicatorColor,e.switchIndicator,e.vertical),narrowIndicator:e.narrowIndicator,inlineLabel:e.inlineLabel,noCaps:e.noCaps})),ee=j(()=>{let e=D.value,t=b.value;for(let n=0;n<e;n++)if(E[n].name.value===t)return!0;return!1}),te=j(()=>`q-tabs__content--align-${x.value===!0?`left`:T.value===!0?`justify`:e.align}`),L=j(()=>`q-tabs row no-wrap items-center q-tabs--${x.value===!0?``:`not-`}scrollable q-tabs--${e.vertical===!0?`vertical`:`horizontal`} q-tabs__arrows--${e.outsideArrows===!0?`outside`:`inside`} q-tabs--mobile-with${e.mobileArrows===!0?``:`out`}-arrows`+(e.dense===!0?` q-tabs--dense`:``)+(e.shrink===!0?` col-shrink`:``)+(e.stretch===!0?` self-stretch`:``)),re=j(()=>`q-tabs__content scroll--mobile row no-wrap items-center self-stretch hide-scrollbar relative-position `+te.value+(e.contentClass===void 0?``:` ${e.contentClass}`)),B=j(()=>e.vertical===!0?{container:`height`,content:`offsetHeight`,scroll:`scrollHeight`}:{container:`width`,content:`offsetWidth`,scroll:`scrollWidth`}),V=j(()=>e.vertical!==!0&&o.lang.rtl===!0),H=j(()=>ie===!1&&V.value===!0);c(V,q),c(()=>e.modelValue,e=>{U({name:e,setCurrent:!0,skipEmit:!0})}),c(()=>e.outsideArrows,W);function U({name:t,setCurrent:n,skipEmit:i}){b.value!==t&&(i!==!0&&e[`onUpdate:modelValue`]!==void 0&&r(`update:modelValue`,t),(n===!0||e[`onUpdate:modelValue`]===void 0)&&(ae(b.value,t),b.value=t))}function W(){s(()=>{_.value&&G({width:_.value.offsetWidth,height:_.value.offsetHeight})})}function G(t){if(B.value===void 0||v.value===null)return;let n=t[B.value.container],r=Math.min(v.value[B.value.scroll],Array.prototype.reduce.call(v.value.children,(e,t)=>e+(t[B.value.content]||0),0)),i=n>0&&r>n;x.value=i,i===!0&&l(q),T.value=n<parseInt(e.breakpoint,10)}function ae(t,n){let r=t!=null&&t!==``?E.find(e=>e.name.value===t):null,i=n!=null&&n!==``?E.find(e=>e.name.value===n):null;if($===!0)$=!1;else if(r&&i){let t=r.tabIndicatorRef.value,n=i.tabIndicatorRef.value;k!==null&&(clearTimeout(k),k=null),t.style.transition=`none`,t.style.transform=`none`,n.style.transition=`none`,n.style.transform=`none`;let a=t.getBoundingClientRect(),o=n.getBoundingClientRect();n.style.transform=e.vertical===!0?`translate3d(0,${a.top-o.top}px,0) scale3d(1,${o.height?a.height/o.height:1},1)`:`translate3d(${a.left-o.left}px,0,0) scale3d(${o.width?a.width/o.width:1},1,1)`,u(()=>{k=setTimeout(()=>{k=null,n.style.transition=`transform .25s cubic-bezier(.4, 0, .2, 1)`,n.style.transform=`none`},70)})}i&&x.value===!0&&K(i.rootRef.value)}function K(t){let{left:n,width:r,top:i,height:a}=v.value.getBoundingClientRect(),o=t.getBoundingClientRect(),s=e.vertical===!0?o.top-i:o.left-n;if(s<0){v.value[e.vertical===!0?`scrollTop`:`scrollLeft`]+=Math.floor(s),q();return}s+=e.vertical===!0?o.height-a:o.width-r,s>0&&(v.value[e.vertical===!0?`scrollTop`:`scrollLeft`]+=Math.ceil(s),q())}function q(){let t=v.value;if(t===null)return;let n=t.getBoundingClientRect(),r=e.vertical===!0?t.scrollTop:Math.abs(t.scrollLeft);V.value===!0?(S.value=Math.ceil(r+n.width)<t.scrollWidth-1,C.value=r>0):(S.value=r>0,C.value=e.vertical===!0?Math.ceil(r+n.height)<t.scrollHeight:Math.ceil(r+n.width)<t.scrollWidth)}function J(e){A!==null&&clearInterval(A),A=setInterval(()=>{ue(e)===!0&&Y()},5)}function oe(){J(H.value===!0?2**53-1:0)}function se(){J(H.value===!0?0:2**53-1)}function Y(){A!==null&&(clearInterval(A),A=null)}function ce(t,n){let r=Array.prototype.filter.call(v.value.children,e=>e===n||e.matches&&e.matches(`.q-tab.q-focusable`)===!0),i=r.length;if(i===0)return;if(t===36)return K(r[0]),r[0].focus(),!0;if(t===35)return K(r[i-1]),r[i-1].focus(),!0;let a=t===(e.vertical===!0?38:37),o=t===(e.vertical===!0?40:39),s=a===!0?-1:o===!0?1:void 0;if(s!==void 0){let e=V.value===!0?-1:1,t=r.indexOf(n)+s*e;return t>=0&&t<i&&(K(r[t]),r[t].focus({preventScroll:!0})),!0}}let le=j(()=>H.value===!0?{get:e=>Math.abs(e.scrollLeft),set:(e,t)=>{e.scrollLeft=-t}}:e.vertical===!0?{get:e=>e.scrollTop,set:(e,t)=>{e.scrollTop=t}}:{get:e=>e.scrollLeft,set:(e,t)=>{e.scrollLeft=t}});function ue(e){let t=v.value,{get:n,set:r}=le.value,i=!1,a=n(t),o=e<a?-1:1;return a+=o*5,a<0?(i=!0,a=0):(o===-1&&a<=e||o===1&&a>=e)&&(i=!0,a=e),r(t,a),q(),i}function X(e,t){for(let n in e)if(e[n]!==t[n])return!1;return!0}function de(){let e=null,t={matchedLen:0,queryDiff:9999,hrefLen:0},n=E.filter(e=>e.routeData?.hasRouterLink.value===!0),{hash:r,query:i}=a.$route,o=Object.keys(i).length;for(let a of n){let n=a.routeData.exact.value===!0;if(a.routeData[n===!0?`linkIsExactActive`:`linkIsActive`].value!==!0)continue;let{hash:s,query:c,matched:l,href:u}=a.routeData.resolvedLink.value,d=Object.keys(c).length;if(n===!0){if(s!==r||d!==o||X(i,c)===!1)continue;e=a.name.value;break}if(s!==``&&s!==r||d!==0&&X(c,i)===!1)continue;let f={matchedLen:l.length,queryDiff:o-d,hrefLen:u.length-s.length};if(f.matchedLen>t.matchedLen){e=a.name.value,t=f;continue}else if(f.matchedLen!==t.matchedLen)continue;if(f.queryDiff<t.queryDiff)e=a.name.value,t=f;else if(f.queryDiff!==t.queryDiff)continue;f.hrefLen>t.hrefLen&&(e=a.name.value,t=f)}if(e===null&&E.some(e=>e.routeData===void 0&&e.name.value===b.value)===!0){$=!1;return}U({name:e,setCurrent:!0})}function me(e){if(f(),O.value!==!0&&_.value!==null&&e.target&&typeof e.target.closest==`function`){let t=e.target.closest(`.q-tab`);t&&_.value.contains(t)===!0&&(O.value=!0,x.value===!0&&K(t))}}function he(){d(()=>{O.value=!1},30)}function Z(){Q.avoidRouteWatcher===!1?h(de):g()}function ge(){if(N===void 0){let e=c(()=>a.$route.fullPath,Z);N=()=>{e(),N=void 0}}}function _e(e){E.push(e),D.value++,W(),e.routeData===void 0||a.$route===void 0?h(()=>{if(x.value===!0){let e=b.value,t=e!=null&&e!==``?E.find(t=>t.name.value===e):null;t&&K(t.rootRef.value)}}):(ge(),e.routeData.hasRouterLink.value===!0&&Z())}function ve(e){E.splice(E.indexOf(e),1),D.value--,W(),N!==void 0&&e.routeData!==void 0&&(E.every(e=>e.routeData===void 0)===!0&&N(),Z())}let Q={currentModel:b,tabProps:P,hasFocus:O,hasActiveTab:ee,registerTab:_e,unregisterTab:ve,verifyRouteModel:Z,updateModel:U,onKbdNavigate:ce,avoidRouteWatcher:!1};p(ne,Q);function ye(){k!==null&&clearTimeout(k),Y(),N?.()}let be,$;return i(ye),m(()=>{be=N!==void 0,ye()}),M(()=>{be===!0&&(ge(),$=!0,Z()),W()}),()=>w(`div`,{ref:_,class:L.value,role:`tablist`,onFocusin:me,onFocusout:he},[w(fe,{onResize:G}),w(`div`,{ref:v,class:re.value,onScroll:q},t(n.default)),w(F,{class:`q-tabs__arrow q-tabs__arrow--left absolute q-tab__icon`+(S.value===!0?``:` q-tabs__arrow--faded`),name:e.leftIcon||o.iconSet.tabs[e.vertical===!0?`up`:`left`],onMousedownPassive:oe,onTouchstartPassive:oe,onMouseupPassive:Y,onMouseleavePassive:Y,onTouchendPassive:Y}),w(F,{class:`q-tabs__arrow q-tabs__arrow--right absolute q-tab__icon`+(C.value===!0?``:` q-tabs__arrow--faded`),name:e.rightIcon||o.iconSet.tabs[e.vertical===!0?`down`:`right`],onMousedownPassive:se,onTouchstartPassive:se,onMouseupPassive:Y,onMouseleavePassive:Y,onTouchendPassive:Y})])}});function Z(e){let t=[.06,6,50];return typeof e==`string`&&e.length&&e.split(`:`).forEach((e,n)=>{let r=parseFloat(e);r&&(t[n]=r)}),t}var ge=l({name:`touch-swipe`,beforeMount(e,{value:t,arg:n,modifiers:r}){if(r.mouse!==!0&&S.has.touch!==!0)return;let i=r.mouseCapture===!0?`Capture`:``,a={handler:t,sensitivity:Z(n),direction:K(r),noop:v,mouseStart(e){J(e,a)&&ee(e)&&(s(a,`temp`,[[document,`mousemove`,`move`,`notPassive${i}`],[document,`mouseup`,`end`,`notPassiveCapture`]]),a.start(e,!0))},touchStart(e){if(J(e,a)){let t=e.target;s(a,`temp`,[[t,`touchmove`,`move`,`notPassiveCapture`],[t,`touchcancel`,`end`,`notPassiveCapture`],[t,`touchend`,`end`,`notPassiveCapture`]]),a.start(e)}},start(t,n){S.is.firefox===!0&&b(e,!0);let r=o(t);a.event={x:r.left,y:r.top,time:Date.now(),mouse:n===!0,dir:!1}},move(e){if(a.event===void 0)return;if(a.event.dir!==!1){u(e);return}let t=Date.now()-a.event.time;if(t===0)return;let n=o(e),r=n.left-a.event.x,i=Math.abs(r),s=n.top-a.event.y,c=Math.abs(s);if(a.event.mouse!==!0){if(i<a.sensitivity[1]&&c<a.sensitivity[1]){a.end(e);return}}else if(window.getSelection().toString()!==``){a.end(e);return}else if(i<a.sensitivity[2]&&c<a.sensitivity[2])return;let l=i/t,d=c/t;a.direction.vertical===!0&&i<c&&i<100&&d>a.sensitivity[0]&&(a.event.dir=s<0?`up`:`down`),a.direction.horizontal===!0&&i>c&&c<100&&l>a.sensitivity[0]&&(a.event.dir=r<0?`left`:`right`),a.direction.up===!0&&i<c&&s<0&&i<100&&d>a.sensitivity[0]&&(a.event.dir=`up`),a.direction.down===!0&&i<c&&s>0&&i<100&&d>a.sensitivity[0]&&(a.event.dir=`down`),a.direction.left===!0&&i>c&&r<0&&c<100&&l>a.sensitivity[0]&&(a.event.dir=`left`),a.direction.right===!0&&i>c&&r>0&&c<100&&l>a.sensitivity[0]&&(a.event.dir=`right`),a.event.dir===!1?a.end(e):(u(e),a.event.mouse===!0&&(document.body.classList.add(`no-pointer-events--children`),document.body.classList.add(`non-selectable`),re(),a.styleCleanup=e=>{a.styleCleanup=void 0,document.body.classList.remove(`non-selectable`);let t=()=>{document.body.classList.remove(`no-pointer-events--children`)};e===!0?setTimeout(t,50):t()}),a.handler({evt:e,touch:a.event.mouse!==!0,mouse:a.event.mouse,direction:a.event.dir,duration:t,distance:{x:i,y:c}}))},end(t){a.event!==void 0&&(d(a,`temp`),S.is.firefox===!0&&b(e,!1),a.styleCleanup?.(!0),t!==void 0&&a.event.dir!==!1&&u(t),a.event=void 0)}};e.__qtouchswipe=a,r.mouse===!0&&s(a,`main`,[[e,`mousedown`,`mouseStart`,`passive${r.mouseCapture===!0||r.mousecapture===!0?`Capture`:``}`]]),S.has.touch===!0&&s(a,`main`,[[e,`touchstart`,`touchStart`,`passive${r.capture===!0?`Capture`:``}`],[e,`touchmove`,`noop`,`notPassiveCapture`]])},updated(e,t){let n=e.__qtouchswipe;n!==void 0&&(t.oldValue!==t.value&&(typeof t.value!=`function`&&n.end(),n.handler=t.value),n.direction=K(t.modifiers))},beforeUnmount(e){let t=e.__qtouchswipe;t!==void 0&&(d(t,`main`),d(t,`temp`),S.is.firefox===!0&&b(e,!1),t.styleCleanup?.(),delete e.__qtouchswipe)}});function _e(){let e=Object.create(null);return{getCache:(t,n)=>e[t]===void 0?e[t]=typeof n==`function`?n():n:e[t],setCache(t,n){e[t]=n},hasCache(t){return Object.hasOwnProperty.call(e,t)},clearCache(t){t===void 0?e=Object.create(null):delete e[t]}}}var ve={name:{required:!0},disable:Boolean},Q={setup(e,{slots:n}){return()=>w(`div`,{class:`q-panel scroll`,role:`tabpanel`},t(n.default))}},ye={modelValue:{required:!0},animated:Boolean,infinite:Boolean,swipeable:Boolean,vertical:Boolean,transitionPrev:String,transitionNext:String,transitionDuration:{type:[String,Number],default:300},keepAlive:Boolean,keepAliveInclude:[String,Array,RegExp],keepAliveExclude:[String,Array,RegExp],keepAliveMax:Number},be=[`update:modelValue`,`beforeTransition`,`transition`];function $(){let{props:e,emit:n,proxy:r}=I(),{getCache:i}=_e(),{registerTimeout:a}=z(),o,s,l=y(null),u={value:null};function d(t){let n=e.vertical===!0?`up`:`left`;M((r.$q.lang.rtl===!0?-1:1)*(t.direction===n?1:-1))}let f=j(()=>[[ge,d,void 0,{horizontal:e.vertical!==!0,vertical:e.vertical,mouse:!0}]]),p=j(()=>e.transitionPrev||`slide-${e.vertical===!0?`down`:`right`}`),m=j(()=>e.transitionNext||`slide-${e.vertical===!0?`up`:`left`}`),h=j(()=>`--q-transition-duration: ${e.transitionDuration}ms`),g=j(()=>typeof e.modelValue==`string`||typeof e.modelValue==`number`?e.modelValue:String(e.modelValue)),_=j(()=>({include:e.keepAliveInclude,exclude:e.keepAliveExclude,max:e.keepAliveMax})),v=j(()=>e.keepAliveInclude!==void 0||e.keepAliveExclude!==void 0);c(()=>e.modelValue,(t,r)=>{let i=C(t)===!0?E(t):-1;s!==!0&&k(i===-1?0:i<E(r)?-1:1),u.value!==i&&(u.value=i,n(`beforeTransition`,t,r),a(()=>{n(`transition`,t,r)},e.transitionDuration))});function b(){M(1)}function x(){M(-1)}function S(e){n(`update:modelValue`,e)}function C(e){return e!=null&&e!==``}function E(e){return o.findIndex(t=>t.props.name===e&&t.props.disable!==``&&t.props.disable!==!0)}function D(){return o.filter(e=>e.props.disable!==``&&e.props.disable!==!0)}function k(t){let n=t!==0&&e.animated===!0&&u.value!==-1?`q-transition--`+(t===-1?p.value:m.value):null;l.value!==n&&(l.value=n)}function M(t,r=u.value){let i=r+t;for(;i!==-1&&i<o.length;){let e=o[i];if(e!==void 0&&e.props.disable!==``&&e.props.disable!==!0){k(t),s=!0,n(`update:modelValue`,e.props.name),setTimeout(()=>{s=!1});return}i+=t}e.infinite===!0&&o.length!==0&&r!==-1&&r!==o.length&&M(t,t===-1?o.length:-1)}function N(){let t=E(e.modelValue);return u.value!==t&&(u.value=t),!0}function P(){let t=C(e.modelValue)===!0&&N()&&o[u.value];return e.keepAlive===!0?[w(A,_.value,[w(v.value===!0?i(g.value,()=>({...Q,name:g.value})):Q,{key:g.value,style:h.value},()=>t)])]:[w(`div`,{class:`q-panel scroll`,style:h.value,key:g.value,role:`tabpanel`},[t])]}function F(){if(o.length!==0)return e.animated===!0?[w(T,{name:l.value},P)]:P()}function ee(e){return o=O(t(e.default,[])).filter(e=>e.props!==null&&e.props.slot===void 0&&C(e.props.name)===!0),o.length}function te(){return o}return Object.assign(r,{next:b,previous:x,goTo:S}),{panelIndex:u,panelDirectives:f,updatePanelsList:ee,updatePanelIndex:N,getPanelContent:F,getEnabledPanels:D,getPanels:te,isValidPanelName:C,keepAliveProps:_,needsUniqueKeepAliveWrapper:v,goToPanelByOffset:M,goToPanel:S,nextPanel:b,previousPanel:x}}var xe=k({name:`QTabPanel`,props:ve,setup(e,{slots:n}){return()=>w(`div`,{class:`q-tab-panel`,role:`tabpanel`},t(n.default))}}),Se=k({name:`QTabPanels`,props:{...ye,...B},emits:be,setup(e,{slots:t}){let n=V(e,I().proxy.$q),{updatePanelsList:r,getPanelContent:i,panelDirectives:a}=$(),o=j(()=>`q-tab-panels q-panel-parent`+(n.value===!0?` q-tab-panels--dark q-dark`:``));return()=>(r(t),C(`div`,{class:o.value},i(),`pan`,e.swipeable,()=>a.value))}});function Ce(e,t){let n=y(null),r=j(()=>e.disable===!0?null:w(`span`,{ref:n,class:`no-outline`,tabindex:-1}));function i(e){let r=t.value;e?.qAvoidFocus!==!0&&(e?.type.indexOf(`key`)===0?document.activeElement!==r&&r?.contains(document.activeElement)===!0&&r.focus():n.value!==null&&(e===void 0||r?.contains(e.target)===!0)&&n.value.focus())}return{refocusTargetEl:r,refocusTarget:i}}var we={xs:30,sm:35,md:40,lg:50,xl:60},Te={...B,...r,...U,modelValue:{required:!0,default:null},val:{},trueValue:{default:!0},falseValue:{default:!1},indeterminateValue:{default:null},checkedIcon:String,uncheckedIcon:String,indeterminateIcon:String,toggleOrder:{type:String,validator:e=>e===`tf`||e===`ft`},toggleIndeterminate:Boolean,label:String,leftLabel:Boolean,color:String,keepColor:Boolean,dense:Boolean,disable:Boolean,tabindex:[String,Number]},Ee=[`update:modelValue`];function De(e,n){let{props:r,slots:i,emit:a,proxy:o}=I(),{$q:s}=o,c=V(r,s),l=y(null),{refocusTargetEl:d,refocusTarget:f}=Ce(r,l),p=_(r,we),m=j(()=>r.val!==void 0&&Array.isArray(r.modelValue)),h=j(()=>{let e=x(r.val);return m.value===!0?r.modelValue.findIndex(t=>x(t)===e):-1}),g=j(()=>m.value===!0?h.value!==-1:x(r.modelValue)===x(r.trueValue)),v=j(()=>m.value===!0?h.value===-1:x(r.modelValue)===x(r.falseValue)),b=j(()=>g.value===!1&&v.value===!1),S=j(()=>r.disable===!0?-1:r.tabindex||0),C=j(()=>`q-${e} cursor-pointer no-outline row inline no-wrap items-center`+(r.disable===!0?` disabled`:``)+(c.value===!0?` q-${e}--dark`:``)+(r.dense===!0?` q-${e}--dense`:``)+(r.leftLabel===!0?` reverse`:``)),T=j(()=>`q-${e}__inner relative-position non-selectable q-${e}__inner--${g.value===!0?`truthy`:v.value===!0?`falsy`:`indet`}${r.color!==void 0&&(r.keepColor===!0||(e===`toggle`?g.value===!0:v.value!==!0))?` text-${r.color}`:``}`),E=H(j(()=>{let e={type:`checkbox`};return r.name!==void 0&&Object.assign(e,{".checked":g.value,"^checked":g.value===!0?`checked`:void 0,name:r.name,value:m.value===!0?r.val:r.trueValue}),e})),D=j(()=>{let t={tabindex:S.value,role:e===`toggle`?`switch`:`checkbox`,"aria-label":r.label,"aria-checked":b.value===!0?`mixed`:g.value===!0?`true`:`false`};return r.disable===!0&&(t[`aria-disabled`]=`true`),t});function O(e){e!==void 0&&(u(e),f(e)),r.disable!==!0&&a(`update:modelValue`,k(),e)}function k(){if(m.value===!0){if(g.value===!0){let e=r.modelValue.slice();return e.splice(h.value,1),e}return r.modelValue.concat([r.val])}if(g.value===!0){if(r.toggleOrder!==`ft`||r.toggleIndeterminate===!1)return r.falseValue}else if(v.value===!0){if(r.toggleOrder===`ft`||r.toggleIndeterminate===!1)return r.trueValue}else return r.toggleOrder===`ft`?r.falseValue:r.trueValue;return r.indeterminateValue}function A(e){(e.keyCode===13||e.keyCode===32)&&u(e)}function M(e){(e.keyCode===13||e.keyCode===32)&&O(e)}let P=n(g,b);return Object.assign(o,{toggle:O}),()=>{let n=P();r.disable!==!0&&E(n,`unshift`,` q-${e}__native absolute q-ma-none q-pa-none`);let a=[w(`div`,{class:T.value,style:p.value,"aria-hidden":`true`},n)];d.value!==null&&a.push(d.value);let o=r.label===void 0?t(i.default):N(i.default,[r.label]);return o!==void 0&&a.push(w(`div`,{class:`q-${e}__label q-anchor--skip`},o)),w(`div`,{ref:l,class:C.value,...D.value,onClick:O,onKeydown:A,onKeyup:M},a)}}export{xe as a,le as c,Se as i,K as l,Ee as n,he as o,Te as r,fe as s,De as t,J as u};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{xt as e}from"./nodes-CXdiSdC2.js";function t(){return e(`_q_`)}export{t};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!DOCTYPE html><html><head><title>Kōbō</title><meta charset=utf-8><meta name=description content="Kōbō — multi-workspace agent manager for Claude Code"><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width,height=device-height"> <script type="module" crossorigin src="/assets/index-
|
|
2
|
-
<link rel="modulepreload" crossorigin href="/assets/nodes-
|
|
1
|
+
<!DOCTYPE html><html><head><title>Kōbō</title><meta charset=utf-8><meta name=description content="Kōbō — multi-workspace agent manager for Claude Code"><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width,height=device-height"> <script type="module" crossorigin src="/assets/index-BoQWbZtE.js"></script>
|
|
2
|
+
<link rel="modulepreload" crossorigin href="/assets/nodes-CXdiSdC2.js">
|
|
3
3
|
<link rel="stylesheet" crossorigin href="/assets/index-BThMCiY7.css">
|
|
4
4
|
</head><body><div id=q-app></div></body></html>
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Kōbō Tasks MCP Server
|
|
2
|
+
|
|
3
|
+
Standalone MCP (Model Context Protocol) server spawned by Kōbō for each Claude Code agent running inside a workspace. Exposes workspace-scoped tools that the agent can invoke to interact with Kōbō state: tasks, settings, dev server, images, git, etc.
|
|
4
|
+
|
|
5
|
+
## How it runs
|
|
6
|
+
|
|
7
|
+
Kōbō's `agent-manager.ts` writes a `.mcp.json` file into each worktree and passes it to Claude Code via `--mcp-config`. Claude spawns this server as a child process with stdio transport and injects these environment variables:
|
|
8
|
+
|
|
9
|
+
| Env var | Purpose |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `KOBO_WORKSPACE_ID` | ID of the current workspace — scopes all queries. **Required**. |
|
|
12
|
+
| `KOBO_DB_PATH` | Absolute path to Kōbō's SQLite DB. **Required**. |
|
|
13
|
+
| `KOBO_SETTINGS_PATH` | Absolute path to Kōbō's `settings.json`. Optional — `get_settings` returns an error shape if absent. |
|
|
14
|
+
| `KOBO_BACKEND_URL` | Base URL of the running Kōbō HTTP backend. Default: `http://localhost:3000`. Used by tools that need runtime state (dev server, git info, workspace transitions). |
|
|
15
|
+
|
|
16
|
+
The server reads the DB directly for read-only queries, writes directly for task CRUD, and calls the backend HTTP API for anything that touches runtime processes (dev server) or state transitions requiring validation.
|
|
17
|
+
|
|
18
|
+
## Tools
|
|
19
|
+
|
|
20
|
+
### Tasks
|
|
21
|
+
|
|
22
|
+
#### `list_tasks`
|
|
23
|
+
List all tasks and acceptance criteria for the current workspace with their IDs and current status. Call this first to discover task IDs.
|
|
24
|
+
|
|
25
|
+
**Input:** none
|
|
26
|
+
**Output:** `TaskDto[]` — `{ id, title, status, is_acceptance_criterion }`
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
#### `mark_task_done`
|
|
31
|
+
Mark a task or acceptance criterion as done. Use when you have completed and validated the work.
|
|
32
|
+
|
|
33
|
+
**Input:**
|
|
34
|
+
- `task_id` (string, required) — ID from `list_tasks`
|
|
35
|
+
|
|
36
|
+
**Output:** `{ success: true, task: TaskDto }`
|
|
37
|
+
**Side effect:** emits `task:updated` WS event (via backend `notify-done`).
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
#### `create_task`
|
|
42
|
+
Create a new task or acceptance criterion for the current workspace. Appended at the end of the list.
|
|
43
|
+
|
|
44
|
+
**Input:**
|
|
45
|
+
- `title` (string, required)
|
|
46
|
+
- `is_acceptance_criterion` (boolean, optional) — default `false`
|
|
47
|
+
|
|
48
|
+
**Output:** `TaskDto`
|
|
49
|
+
**Side effect:** emits `task:updated` WS event.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
#### `update_task`
|
|
54
|
+
Update an existing task — change title, status, or `is_acceptance_criterion` flag. At least one field is required.
|
|
55
|
+
|
|
56
|
+
**Input:**
|
|
57
|
+
- `task_id` (string, required)
|
|
58
|
+
- `title` (string, optional)
|
|
59
|
+
- `status` (string, optional) — `pending | in_progress | done`
|
|
60
|
+
- `is_acceptance_criterion` (boolean, optional)
|
|
61
|
+
|
|
62
|
+
**Output:** `TaskDto`
|
|
63
|
+
**Side effect:** emits `task:updated` WS event.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
#### `delete_task`
|
|
68
|
+
Delete a task from the current workspace permanently.
|
|
69
|
+
|
|
70
|
+
**Input:**
|
|
71
|
+
- `task_id` (string, required)
|
|
72
|
+
|
|
73
|
+
**Output:** `{ success: true, task_id: string }`
|
|
74
|
+
**Side effect:** emits `task:updated` WS event.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### Workspace
|
|
79
|
+
|
|
80
|
+
#### `get_workspace_info`
|
|
81
|
+
Get all metadata about the current workspace in a single call: name, project path, branches, model, Notion URL, worktree path, status, timestamps.
|
|
82
|
+
|
|
83
|
+
**Input:** none
|
|
84
|
+
**Output:**
|
|
85
|
+
```ts
|
|
86
|
+
{
|
|
87
|
+
id, name, projectPath, sourceBranch, workingBranch,
|
|
88
|
+
worktreePath, status, model, notionUrl, notionPageId,
|
|
89
|
+
devServerStatus, createdAt, updatedAt
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
#### `set_workspace_status`
|
|
96
|
+
Update the current workspace status. Transitions are validated by the backend against the state machine.
|
|
97
|
+
|
|
98
|
+
**Input:**
|
|
99
|
+
- `status` (string, required) — e.g. `idle`, `completed`, `error`
|
|
100
|
+
|
|
101
|
+
**Output:** updated `Workspace`
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
#### `get_git_info`
|
|
106
|
+
Get git stats for the current workspace: commit count, files changed, insertions, deletions, and PR URL if one exists for the branch.
|
|
107
|
+
|
|
108
|
+
**Input:** none
|
|
109
|
+
**Output:** `{ commitCount, filesChanged, insertions, deletions, prUrl }`
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### Dev server
|
|
114
|
+
|
|
115
|
+
#### `get_dev_server_status`
|
|
116
|
+
Check whether the dev server is running for the current workspace. Reads `dev_server_status` from the DB.
|
|
117
|
+
|
|
118
|
+
**Input:** none
|
|
119
|
+
**Output:** `{ workspaceId, status }`
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
#### `start_dev_server`
|
|
124
|
+
Start the dev server configured for the current workspace (via backend).
|
|
125
|
+
|
|
126
|
+
**Input:** none
|
|
127
|
+
**Output:** `DevServerStatus`
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
#### `stop_dev_server`
|
|
132
|
+
Stop the dev server of the current workspace (via backend).
|
|
133
|
+
|
|
134
|
+
**Input:** none
|
|
135
|
+
**Output:** `DevServerStatus`
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
#### `get_dev_server_logs`
|
|
140
|
+
Fetch the last N lines of the dev server logs for the current workspace.
|
|
141
|
+
|
|
142
|
+
**Input:**
|
|
143
|
+
- `tail` (number, optional) — default `200`
|
|
144
|
+
|
|
145
|
+
**Output:** `{ logs: string[] }`
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### Settings
|
|
150
|
+
|
|
151
|
+
#### `get_settings`
|
|
152
|
+
Read Kōbō settings (global and/or per-project). Reads `KOBO_SETTINGS_PATH` directly from disk.
|
|
153
|
+
|
|
154
|
+
**Input:**
|
|
155
|
+
- `project_path` (string, optional) — if provided, returns the specific project entry alongside global
|
|
156
|
+
|
|
157
|
+
**Output (with `project_path`):** `{ global, project }`
|
|
158
|
+
**Output (without):** `{ global, projects }`
|
|
159
|
+
**Output (settings unavailable):** `{ global: null, project: null, error }`
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### Images
|
|
164
|
+
|
|
165
|
+
#### `list_workspace_images`
|
|
166
|
+
List all images uploaded to the current workspace via Kōbō's chat paste/upload flow. Reads `.ai/images/index.json` from the worktree.
|
|
167
|
+
|
|
168
|
+
**Input:** none
|
|
169
|
+
**Output:** `Array<{ uid, originalName, relativePath, createdAt }>`
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Implementation notes
|
|
174
|
+
|
|
175
|
+
- **Handlers** live in `kobo-tasks-handlers.ts` as pure functions taking the DB handle (and sometimes paths) as arguments. This keeps them unit-testable in isolation — see `src/__tests__/kobo-tasks-server.test.ts`.
|
|
176
|
+
- **Backend HTTP helper** `backendRequest()` in `kobo-tasks-server.ts` wraps fetch calls to `KOBO_BACKEND_URL` for tools needing runtime state. On non-2xx it throws — the top-level dispatcher catches and returns an `isError` content.
|
|
177
|
+
- **Notifications**: `mark_task_done` hits `POST /tasks/:id/notify-done`, while `create_task` / `update_task` / `delete_task` hit `POST /tasks/notify-updated`. Both cause the backend to emit a `task:updated` WS event so the Vue UI refreshes.
|
|
178
|
+
- **Workspace scoping**: every handler that touches tasks uses `WHERE workspace_id = ?` to prevent cross-workspace access, even if the LLM passes a task_id from another workspace.
|
|
179
|
+
- **Error handling**: the MCP dispatcher wraps every tool call in a `try/catch` and returns `{ isError: true, content: [{ type: 'text', text: 'Error: ...' }] }` on failure. Handlers should throw with descriptive messages.
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
1
3
|
import type Database from 'better-sqlite3'
|
|
4
|
+
import { nanoid } from 'nanoid'
|
|
5
|
+
|
|
6
|
+
export const VALID_TASK_STATUSES = ['pending', 'in_progress', 'done'] as const
|
|
7
|
+
export type TaskStatus = (typeof VALID_TASK_STATUSES)[number]
|
|
2
8
|
|
|
3
9
|
export interface TaskDto {
|
|
4
10
|
id: string
|
|
@@ -12,6 +18,11 @@ export interface MarkDoneResult {
|
|
|
12
18
|
task: TaskDto
|
|
13
19
|
}
|
|
14
20
|
|
|
21
|
+
export interface DevServerStatusDto {
|
|
22
|
+
workspaceId: string
|
|
23
|
+
status: string
|
|
24
|
+
}
|
|
25
|
+
|
|
15
26
|
interface TaskRow {
|
|
16
27
|
id: string
|
|
17
28
|
title: string
|
|
@@ -52,3 +63,230 @@ export function markTaskDoneHandler(db: Database.Database, workspaceId: string,
|
|
|
52
63
|
.get(taskId) as TaskRow
|
|
53
64
|
return { success: true, task: rowToDto(row) }
|
|
54
65
|
}
|
|
66
|
+
|
|
67
|
+
export function createTaskHandler(
|
|
68
|
+
db: Database.Database,
|
|
69
|
+
workspaceId: string,
|
|
70
|
+
data: { title: string; is_acceptance_criterion?: boolean },
|
|
71
|
+
): TaskDto {
|
|
72
|
+
if (!data.title?.trim()) {
|
|
73
|
+
throw new Error('title is required')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Verify workspace exists
|
|
77
|
+
const ws = db.prepare('SELECT id FROM workspaces WHERE id = ?').get(workspaceId) as { id: string } | undefined
|
|
78
|
+
if (!ws) {
|
|
79
|
+
throw new Error(`Workspace '${workspaceId}' not found`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const id = nanoid()
|
|
83
|
+
const now = new Date().toISOString()
|
|
84
|
+
const isAC = data.is_acceptance_criterion ? 1 : 0
|
|
85
|
+
|
|
86
|
+
// Append at the end: max(sort_order) + 1
|
|
87
|
+
const maxRow = db
|
|
88
|
+
.prepare('SELECT COALESCE(MAX(sort_order), -1) AS max FROM tasks WHERE workspace_id = ?')
|
|
89
|
+
.get(workspaceId) as { max: number }
|
|
90
|
+
const sortOrder = maxRow.max + 1
|
|
91
|
+
|
|
92
|
+
db.prepare(
|
|
93
|
+
'INSERT INTO tasks (id, workspace_id, title, status, is_acceptance_criterion, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
|
94
|
+
).run(id, workspaceId, data.title.trim(), 'pending', isAC, sortOrder, now, now)
|
|
95
|
+
|
|
96
|
+
const row = db.prepare('SELECT id, title, status, is_acceptance_criterion FROM tasks WHERE id = ?').get(id) as TaskRow
|
|
97
|
+
return rowToDto(row)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function updateTaskHandler(
|
|
101
|
+
db: Database.Database,
|
|
102
|
+
workspaceId: string,
|
|
103
|
+
taskId: string,
|
|
104
|
+
data: { title?: string; status?: string; is_acceptance_criterion?: boolean },
|
|
105
|
+
): TaskDto {
|
|
106
|
+
// Verify task belongs to workspace
|
|
107
|
+
const existing = db.prepare('SELECT id FROM tasks WHERE id = ? AND workspace_id = ?').get(taskId, workspaceId) as
|
|
108
|
+
| { id: string }
|
|
109
|
+
| undefined
|
|
110
|
+
if (!existing) {
|
|
111
|
+
throw new Error(`Task '${taskId}' not found in workspace '${workspaceId}'`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const sets: string[] = []
|
|
115
|
+
const values: unknown[] = []
|
|
116
|
+
|
|
117
|
+
if (data.title !== undefined) {
|
|
118
|
+
if (!data.title.trim()) throw new Error('title cannot be empty')
|
|
119
|
+
sets.push('title = ?')
|
|
120
|
+
values.push(data.title.trim())
|
|
121
|
+
}
|
|
122
|
+
if (data.status !== undefined) {
|
|
123
|
+
if (!(VALID_TASK_STATUSES as readonly string[]).includes(data.status)) {
|
|
124
|
+
throw new Error(`Invalid status '${data.status}'. Must be one of: ${VALID_TASK_STATUSES.join(', ')}`)
|
|
125
|
+
}
|
|
126
|
+
sets.push('status = ?')
|
|
127
|
+
values.push(data.status)
|
|
128
|
+
}
|
|
129
|
+
if (data.is_acceptance_criterion !== undefined) {
|
|
130
|
+
sets.push('is_acceptance_criterion = ?')
|
|
131
|
+
values.push(data.is_acceptance_criterion ? 1 : 0)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (sets.length === 0) {
|
|
135
|
+
throw new Error('No fields to update (provide title, status, or is_acceptance_criterion)')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
sets.push('updated_at = ?')
|
|
139
|
+
values.push(new Date().toISOString())
|
|
140
|
+
values.push(taskId)
|
|
141
|
+
|
|
142
|
+
db.prepare(`UPDATE tasks SET ${sets.join(', ')} WHERE id = ?`).run(...values)
|
|
143
|
+
|
|
144
|
+
const row = db
|
|
145
|
+
.prepare('SELECT id, title, status, is_acceptance_criterion FROM tasks WHERE id = ?')
|
|
146
|
+
.get(taskId) as TaskRow
|
|
147
|
+
return rowToDto(row)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function deleteTaskHandler(
|
|
151
|
+
db: Database.Database,
|
|
152
|
+
workspaceId: string,
|
|
153
|
+
taskId: string,
|
|
154
|
+
): { success: true; task_id: string } {
|
|
155
|
+
const result = db.prepare('DELETE FROM tasks WHERE id = ? AND workspace_id = ?').run(taskId, workspaceId)
|
|
156
|
+
if (result.changes === 0) {
|
|
157
|
+
throw new Error(`Task '${taskId}' not found in workspace '${workspaceId}'`)
|
|
158
|
+
}
|
|
159
|
+
return { success: true, task_id: taskId }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function getDevServerStatusHandler(db: Database.Database, workspaceId: string): DevServerStatusDto {
|
|
163
|
+
const row = db.prepare('SELECT dev_server_status FROM workspaces WHERE id = ?').get(workspaceId) as
|
|
164
|
+
| { dev_server_status: string }
|
|
165
|
+
| undefined
|
|
166
|
+
if (!row) {
|
|
167
|
+
throw new Error(`Workspace '${workspaceId}' not found`)
|
|
168
|
+
}
|
|
169
|
+
return { workspaceId, status: row.dev_server_status }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function getSettingsHandler(settingsPath: string | undefined, projectPath?: string): Record<string, unknown> {
|
|
173
|
+
// Shape is determined solely by whether projectPath was provided:
|
|
174
|
+
// - with projectPath → { global, project }
|
|
175
|
+
// - without → { global, projects }
|
|
176
|
+
// The `error` field is added on top when settings are unavailable.
|
|
177
|
+
if (!settingsPath || !fs.existsSync(settingsPath)) {
|
|
178
|
+
const base = projectPath ? { global: null, project: null } : { global: null, projects: [] }
|
|
179
|
+
return { ...base, error: 'Settings file not available' }
|
|
180
|
+
}
|
|
181
|
+
let parsed: Record<string, unknown>
|
|
182
|
+
try {
|
|
183
|
+
parsed = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))
|
|
184
|
+
} catch (err) {
|
|
185
|
+
throw new Error(`Failed to read settings: ${err instanceof Error ? err.message : String(err)}`)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const global = parsed.global ?? null
|
|
189
|
+
const projects = Array.isArray(parsed.projects) ? (parsed.projects as Array<Record<string, unknown>>) : []
|
|
190
|
+
|
|
191
|
+
if (projectPath) {
|
|
192
|
+
const project = projects.find((p) => p.path === projectPath) ?? null
|
|
193
|
+
return { global, project }
|
|
194
|
+
}
|
|
195
|
+
return { global, projects }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── Workspace info ─────────────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
export interface WorkspaceInfoDto {
|
|
201
|
+
id: string
|
|
202
|
+
name: string
|
|
203
|
+
projectPath: string
|
|
204
|
+
sourceBranch: string
|
|
205
|
+
workingBranch: string
|
|
206
|
+
worktreePath: string
|
|
207
|
+
status: string
|
|
208
|
+
model: string
|
|
209
|
+
notionUrl: string | null
|
|
210
|
+
notionPageId: string | null
|
|
211
|
+
devServerStatus: string
|
|
212
|
+
createdAt: string
|
|
213
|
+
updatedAt: string
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
interface WorkspaceRow {
|
|
217
|
+
id: string
|
|
218
|
+
name: string
|
|
219
|
+
project_path: string
|
|
220
|
+
source_branch: string
|
|
221
|
+
working_branch: string
|
|
222
|
+
status: string
|
|
223
|
+
notion_url: string | null
|
|
224
|
+
notion_page_id: string | null
|
|
225
|
+
model: string
|
|
226
|
+
dev_server_status: string
|
|
227
|
+
created_at: string
|
|
228
|
+
updated_at: string
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function getWorkspaceInfoHandler(db: Database.Database, workspaceId: string): WorkspaceInfoDto {
|
|
232
|
+
const row = db
|
|
233
|
+
.prepare(
|
|
234
|
+
'SELECT id, name, project_path, source_branch, working_branch, status, notion_url, notion_page_id, model, dev_server_status, created_at, updated_at FROM workspaces WHERE id = ?',
|
|
235
|
+
)
|
|
236
|
+
.get(workspaceId) as WorkspaceRow | undefined
|
|
237
|
+
|
|
238
|
+
if (!row) {
|
|
239
|
+
throw new Error(`Workspace '${workspaceId}' not found`)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
id: row.id,
|
|
244
|
+
name: row.name,
|
|
245
|
+
projectPath: row.project_path,
|
|
246
|
+
sourceBranch: row.source_branch,
|
|
247
|
+
workingBranch: row.working_branch,
|
|
248
|
+
worktreePath: path.join(row.project_path, '.worktrees', row.working_branch),
|
|
249
|
+
status: row.status,
|
|
250
|
+
model: row.model,
|
|
251
|
+
notionUrl: row.notion_url,
|
|
252
|
+
notionPageId: row.notion_page_id,
|
|
253
|
+
devServerStatus: row.dev_server_status,
|
|
254
|
+
createdAt: row.created_at,
|
|
255
|
+
updatedAt: row.updated_at,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ── Workspace images (read from .ai/images/index.json in worktree) ─────────────
|
|
260
|
+
|
|
261
|
+
export interface WorkspaceImageDto {
|
|
262
|
+
uid: string
|
|
263
|
+
originalName: string
|
|
264
|
+
relativePath: string
|
|
265
|
+
createdAt: string
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function listWorkspaceImagesHandler(worktreePath: string): WorkspaceImageDto[] {
|
|
269
|
+
const imagesDir = path.join(worktreePath, '.ai', 'images')
|
|
270
|
+
const indexPath = path.join(imagesDir, 'index.json')
|
|
271
|
+
if (!fs.existsSync(indexPath)) return []
|
|
272
|
+
|
|
273
|
+
let entries: Array<{ uid: string; originalName: string; createdAt: string }>
|
|
274
|
+
try {
|
|
275
|
+
entries = JSON.parse(fs.readFileSync(indexPath, 'utf-8'))
|
|
276
|
+
} catch {
|
|
277
|
+
return []
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Read directory once — imagesDir is guaranteed to exist because indexPath does
|
|
281
|
+
const files = fs.readdirSync(imagesDir)
|
|
282
|
+
|
|
283
|
+
return entries.map((e) => {
|
|
284
|
+
const match = files.find((f) => f.startsWith(`${e.uid}.`))
|
|
285
|
+
return {
|
|
286
|
+
uid: e.uid,
|
|
287
|
+
originalName: e.originalName,
|
|
288
|
+
relativePath: match ? path.join('.ai', 'images', match) : '',
|
|
289
|
+
createdAt: e.createdAt,
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
}
|