@luuthuong/devboard 0.0.0-stage → 1.0.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/.claude/skills/devboard/SKILL.md +111 -0
- package/bin/devboard.mjs +59 -0
- package/dist/assets/CardDetailView-BZE_MfjM.js +24 -0
- package/dist/assets/DiffPane-2WnoRvZ0.js +22 -0
- package/dist/assets/DiffPane-CpoDDQrA.css +1 -0
- package/dist/assets/KnowledgeView-C4WPFAUM.js +3 -0
- package/dist/assets/ListBoardView-DEE_eK4h.js +3 -0
- package/dist/assets/NewTaskDialog-D8cpc7Bc.js +1 -0
- package/dist/assets/OnboardingWizard-9ZkFPGvH.js +1 -0
- package/dist/assets/ProcessManagerView-DJOr44DY.js +1 -0
- package/dist/assets/ProposalCard-B8jqFtL8.js +1 -0
- package/dist/assets/SearchOverlay-qGxzmM_v.js +1 -0
- package/dist/assets/SettingsView-C3GkCbTz.js +1 -0
- package/dist/assets/SetupProjectView-CSx6ax5r.js +1 -0
- package/dist/assets/Surface-BQKeigoq.js +1 -0
- package/dist/assets/TaskDescriptionEditor-D0_b81zY.js +58 -0
- package/dist/assets/Terminal-BrP-ENHg.css +1 -0
- package/dist/assets/Terminal-yq90NOBX.js +80 -0
- package/dist/assets/deepEqual-BiJVGDfM.js +1 -0
- package/dist/assets/index-BtXUncAU.css +1 -0
- package/dist/assets/index-CQnL8JJ5.js +18 -0
- package/dist/assets/jsx-runtime-QmcfnBgR.js +1 -0
- package/dist/assets/plus-Cecrm8sZ.js +1 -0
- package/dist/assets/react-dom-DjtJUrx3.js +1 -0
- package/dist/assets/x-B6wVesqk.js +1 -0
- package/dist/index.html +22 -0
- package/package.json +81 -4
- package/server-dist/server/assets/remote-shell.sh +239 -0
- package/server-dist/server/board.js +486 -0
- package/server-dist/server/commands.js +390 -0
- package/server-dist/server/context.js +34 -0
- package/server-dist/server/coordination.js +442 -0
- package/server-dist/server/editor.js +101 -0
- package/server-dist/server/events.js +17 -0
- package/server-dist/server/fsBrowse.js +143 -0
- package/server-dist/server/git.js +258 -0
- package/server-dist/server/harness/config.js +90 -0
- package/server-dist/server/harness/entry.js +98 -0
- package/server-dist/server/harness/intents.js +268 -0
- package/server-dist/server/harness/kbIndex.js +291 -0
- package/server-dist/server/harness/kbStore.js +258 -0
- package/server-dist/server/harness/librarian.js +147 -0
- package/server-dist/server/harness/pack.js +182 -0
- package/server-dist/server/harness/proposals.js +113 -0
- package/server-dist/server/harness/repo.js +150 -0
- package/server-dist/server/harness/scrub.js +30 -0
- package/server-dist/server/harness/service.js +683 -0
- package/server-dist/server/harness/template/README.md +38 -0
- package/server-dist/server/harness/template/agents/code-reviewer.md +23 -0
- package/server-dist/server/harness/template/agents/security-reviewer.md +20 -0
- package/server-dist/server/harness/template/agents/test-writer.md +16 -0
- package/server-dist/server/harness/template/agents.yaml +41 -0
- package/server-dist/server/harness/template/harness.json +11 -0
- package/server-dist/server/harness/template/kb/team/README.md +4 -0
- package/server-dist/server/harness/template/skills/aidlc-design/SKILL.md +41 -0
- package/server-dist/server/harness/template/skills/aidlc-implement/SKILL.md +34 -0
- package/server-dist/server/harness/template/skills/aidlc-postmortem/SKILL.md +35 -0
- package/server-dist/server/harness/template/skills/aidlc-pr/SKILL.md +22 -0
- package/server-dist/server/harness/template/skills/aidlc-requirements/SKILL.md +40 -0
- package/server-dist/server/harness/template/skills/aidlc-units/SKILL.md +36 -0
- package/server-dist/server/harness/template/skills/project-kb/SKILL.md +41 -0
- package/server-dist/server/harness/usage.js +82 -0
- package/server-dist/server/hooks.js +222 -0
- package/server-dist/server/http.js +266 -0
- package/server-dist/server/index.js +77 -0
- package/server-dist/server/integrations.js +292 -0
- package/server-dist/server/pollers.js +332 -0
- package/server-dist/server/processes.js +123 -0
- package/server-dist/server/psmux.js +120 -0
- package/server-dist/server/pty.js +93 -0
- package/server-dist/server/remote.js +216 -0
- package/server-dist/server/sessionReaper.js +101 -0
- package/server-dist/server/sessions.js +315 -0
- package/server-dist/server/settings.js +125 -0
- package/server-dist/server/skill.js +29 -0
- package/server-dist/server/tmux.js +217 -0
- package/server-dist/server/transcripts.js +334 -0
- package/server-dist/server/util.js +286 -0
- package/server-dist/src/types/index.js +53 -0
- package/README.md +0 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t,t.exports),e=null),t.exports),s=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},c=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},l=(n,r,o)=>(o=n==null?{}:e(i(n)),c(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n)),u=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.for(`react.view_transition`),m=Symbol.iterator;function h(e){return typeof e!=`object`||!e?null:(e=m&&e[m]||e[`@@iterator`],typeof e==`function`?e:null)}var g={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},_=Object.assign,v={};function y(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||g}y.prototype.isReactComponent={},y.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},y.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function b(){}b.prototype=y.prototype;function x(e,t,n){this.props=e,this.context=t,this.refs=v,this.updater=n||g}var S=x.prototype=new b;S.constructor=x,_(S,y.prototype),S.isPureReactComponent=!0;var C=Array.isArray;function w(){}var T={H:null,A:null,T:null,S:null},E=Object.prototype.hasOwnProperty;function D(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function O(e,t){return D(e.type,t,e.props)}function k(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function A(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var j=/\/+/g;function M(e,t){return typeof e==`object`&&e&&e.key!=null?A(``+e.key):t.toString(36)}function N(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(w,w):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function P(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,P(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+M(e,0):a,C(o)?(i=``,c!=null&&(i=c.replace(j,`$&/`)+`/`),P(o,r,i,``,function(e){return e})):o!=null&&(k(o)&&(o=O(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(j,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(C(e))for(var u=0;u<e.length;u++)a=e[u],s=l+M(a,u),c+=P(a,r,i,s,o);else if(u=h(e),typeof u==`function`)for(e=u.call(e),u=0;!(a=e.next()).done;)a=a.value,s=l+M(a,u++),c+=P(a,r,i,s,o);else if(s===`object`){if(typeof e.then==`function`)return P(N(e),r,i,a,o);throw r=String(e),Error(`Objects are not valid as a React child (found: `+(r===`[object Object]`?`object with keys {`+Object.keys(e).join(`, `)+`}`:r)+`). If you meant to render a collection of children, use an array instead.`)}return c}function F(e,t,n){if(e==null)return e;var r=[],i=0;return P(e,r,``,``,function(e){return t.call(n,e,i++)}),r}function I(e){if(e._status===-1){var t=e._result,n=t();n.then(function(t){(e._status===0||e._status===-1)&&(e._status=1,e._result=t,n.status===void 0&&(n.status=`fulfilled`,n.value=t))},function(t){(e._status===0||e._status===-1)&&(e._status=2,e._result=t,n.status===void 0&&(n.status=`rejected`,n.reason=t))}),e._status===-1&&(e._status=0,e._result=n)}if(e._status===1)return e._result.default;throw e._result}var L=typeof reportError==`function`?reportError:function(e){if(typeof window==`object`&&typeof window.ErrorEvent==`function`){var t=new window.ErrorEvent(`error`,{bubbles:!0,cancelable:!0,message:typeof e==`object`&&e&&typeof e.message==`string`?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process==`object`&&typeof process.emit==`function`){process.emit(`uncaughtException`,e);return}console.error(e)};function R(e){var t=T.T,n={};n.types=t===null?null:t.types,T.T=n;try{var r=e(),i=T.S;i!==null&&i(n,r),typeof r==`object`&&r&&typeof r.then==`function`&&r.then(w,L)}catch(e){L(e)}finally{t!==null&&n.types!==null&&(t.types=n.types),T.T=t}}function z(e){var t=T.T;if(t!==null){var n=t.types;n===null?t.types=[e]:n.indexOf(e)===-1&&n.push(e)}else R(z.bind(null,e))}var B={map:F,forEach:function(e,t,n){F(e,function(){t.apply(this,arguments)},n)},count:function(e){var t=0;return F(e,function(){t++}),t},toArray:function(e){return F(e,function(e){return e})||[]},only:function(e){if(!k(e))throw Error(`React.Children.only expected to receive a single React element child.`);return e}};e.Activity=f,e.Children=B,e.Component=y,e.Fragment=r,e.Profiler=a,e.PureComponent=x,e.StrictMode=i,e.Suspense=l,e.ViewTransition=p,e.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=T,e.__COMPILER_RUNTIME={__proto__:null,c:function(e){return T.H.useMemoCache(e)}},e.addTransitionType=z,e.cache=function(e){return function(){return e.apply(null,arguments)}},e.cacheSignal=function(){return null},e.cloneElement=function(e,t,n){if(e==null)throw Error(`The argument must be a React element, but you passed `+e+`.`);var r=_({},e.props),i=e.key;if(t!=null)for(a in t.key!==void 0&&(i=``+t.key),t)!E.call(t,a)||a===`key`||a===`__self`||a===`__source`||a===`ref`&&t.ref===void 0||(r[a]=t[a]);var a=arguments.length-2;if(a===1)r.children=n;else if(1<a){for(var o=Array(a),s=0;s<a;s++)o[s]=arguments[s+2];r.children=o}return D(e.type,i,r)},e.createContext=function(e){return e={$$typeof:s,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null},e.Provider=e,e.Consumer={$$typeof:o,_context:e},e},e.createElement=function(e,t,n){var r,i={},a=null;if(t!=null)for(r in t.key!==void 0&&(a=``+t.key),t)E.call(t,r)&&r!==`key`&&r!==`__self`&&r!==`__source`&&(i[r]=t[r]);var o=arguments.length-2;if(o===1)i.children=n;else if(1<o){for(var s=Array(o),c=0;c<o;c++)s[c]=arguments[c+2];i.children=s}if(e&&e.defaultProps)for(r in o=e.defaultProps,o)i[r]===void 0&&(i[r]=o[r]);return D(e,a,i)},e.createRef=function(){return{current:null}},e.forwardRef=function(e){return{$$typeof:c,render:e}},e.isValidElement=k,e.lazy=function(e){return{$$typeof:d,_payload:{_status:-1,_result:e},_init:I}},e.memo=function(e,t){return{$$typeof:u,type:e,compare:t===void 0?null:t}},e.startTransition=R,e.unstable_useCacheRefresh=function(){return T.H.useCacheRefresh()},e.use=function(e){return T.H.use(e)},e.useActionState=function(e,t,n){return T.H.useActionState(e,t,n)},e.useCallback=function(e,t){return T.H.useCallback(e,t)},e.useContext=function(e){return T.H.useContext(e)},e.useDebugValue=function(){},e.useDeferredValue=function(e,t){return T.H.useDeferredValue(e,t)},e.useEffect=function(e,t){return T.H.useEffect(e,t)},e.useEffectEvent=function(e){return T.H.useEffectEvent(e)},e.useId=function(){return T.H.useId()},e.useImperativeHandle=function(e,t,n){return T.H.useImperativeHandle(e,t,n)},e.useInsertionEffect=function(e,t){return T.H.useInsertionEffect(e,t)},e.useLayoutEffect=function(e,t){return T.H.useLayoutEffect(e,t)},e.useMemo=function(e,t){return T.H.useMemo(e,t)},e.useOptimistic=function(e,t){return T.H.useOptimistic(e,t)},e.useReducer=function(e,t,n){return T.H.useReducer(e,t,n)},e.useRef=function(e){return T.H.useRef(e)},e.useState=function(e){return T.H.useState(e)},e.useSyncExternalStore=function(e,t,n){return T.H.useSyncExternalStore(e,t,n)},e.useTransition=function(){return T.H.useTransition()},e.version=`19.3.1`})),d=o(((e,t)=>{t.exports=u()})),f=o((e=>{var t=d().__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;e.c=function(e){return t.H.useMemoCache(e)}})),p=o(((e,t)=>{t.exports=f()}));async function m(e,t={}){let n;try{n=await fetch(`/api/invoke/${encodeURIComponent(e)}`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(t)})}catch(e){throw`cannot reach the DevBoard server (${String(e)})`}let r=null;try{r=await n.json()}catch{}if(!n.ok)throw r?.error??`${e} failed (HTTP ${n.status})`;return r?.result??null}var h=new Map,g=new Set,_=null,v=500,y=!1;function b(e){return`${location.protocol===`https:`?`wss:`:`ws:`}//${location.host}${e}`}function x(){if(_)return;let e=new WebSocket(b(`/ws`));_=e,e.onopen=()=>{v=500,y&&g.forEach(e=>e()),y=!0},e.onmessage=e=>{try{let{event:t,payload:n}=JSON.parse(e.data);h.get(t)?.forEach(e=>e({payload:n}))}catch{}},e.onclose=()=>{_=null,setTimeout(x,v),v=Math.min(v*2,1e4)},e.onerror=()=>e.close()}async function S(e,t){x();let n=h.get(e);return n||h.set(e,n=new Set),n.add(t),()=>{n.delete(t)}}function C(e){return x(),g.add(e),()=>{g.delete(e)}}function w(e,t,n){let r=new WebSocket(b(`/pty`)),i=[],a=[],o=[],s=!1,c=e=>{let t=JSON.stringify(e);r.readyState===WebSocket.OPEN?r.send(t):r.readyState===WebSocket.CONNECTING&&o.push(t)};r.onopen=()=>{r.send(JSON.stringify({type:`spawn`,file:e,args:t,cols:n.cols,rows:n.rows,cwd:n.cwd})),o.splice(0).forEach(e=>r.send(e))};let l=e=>{s||(s=!0,a.forEach(t=>t(e)))};return r.onmessage=e=>{let t;try{t=JSON.parse(e.data)}catch{return}t.type===`data`?i.forEach(e=>e(t.data)):t.type===`exit`?l({exitCode:t.exitCode,signal:t.signal}):t.type===`error`&&(i.forEach(e=>e(`\r\n\x1b[31m${t.message}\x1b[0m\r\n`)),l({exitCode:-1}))},r.onclose=()=>l({exitCode:-1}),{onData:e=>void i.push(e),onExit:e=>void a.push(e),write:e=>c({type:`data`,data:e}),resize:(e,t)=>c({type:`resize`,cols:e,rows:t}),kill:()=>{r.readyState===WebSocket.OPEN?(r.send(JSON.stringify({type:`kill`})),r.close()):r.readyState===WebSocket.CONNECTING&&(r.onopen=()=>r.close())}}}var T=e=>`/api/file?path=${encodeURIComponent(e)}`;async function E(e,t){let n=await fetch(e,{method:`POST`,body:t}),r=await n.json().catch(()=>null);if(!n.ok)throw r?.error??`upload failed (HTTP ${n.status})`;return r.path}var D=e=>E(`/api/upload/image`,e),O=e=>E(`/api/upload/file?name=${encodeURIComponent(e.name)}`,e);function k(){if(S(`notification`,({payload:e})=>{typeof Notification<`u`&&Notification.permission===`granted`&&new Notification(e.title,{body:e.body,tag:e.tag})}),typeof Notification<`u`&&Notification.permission==="default"){let e=()=>{Notification.requestPermission(),window.removeEventListener(`pointerdown`,e)};window.addEventListener(`pointerdown`,e,{once:!0})}}var A=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),j=o(((e,t)=>{t.exports=A()}));export{S as a,O as c,d,o as f,m as i,D as l,l as m,T as n,C as o,s as p,k as r,w as s,j as t,p as u};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Ct as e}from"./index-CQnL8JJ5.js";var t={name:`plus`,size:24,node:[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`M12 5v14`,key:`s699le`}]]};t.node;var n=e(t);export{n as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{d as e,f as t}from"./jsx-runtime-QmcfnBgR.js";var n=t((t=>{var n=e();function r(e){var t=`https://react.dev/errors/`+e;if(1<arguments.length){t+=`?args[]=`+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+=`&args[]=`+encodeURIComponent(arguments[n])}return`Minified React error #`+e+`; visit `+t+` for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`}function i(){}var a={d:{f:i,r:function(){throw Error(r(522))},D:i,C:i,L:i,m:i,X:i,S:i,M:i},p:0,findDOMNode:null},o=Symbol.for(`react.portal`),s=Symbol.for(`react.recoverable`),c=Symbol.for(`react.optimistic_key`);function l(e,t,n){var r=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:o,key:r==null?null:r===c?c:``+r,children:e,containerInfo:t,implementation:n}}var u=n.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function d(e,t){if(e===`font`)return``;if(typeof t==`string`)return t===`use-credentials`?t:``}t.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=a,t.browser=function(e){return{$$typeof:s,_reason:e}},t.createPortal=function(e,t){var n=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!t||t.nodeType!==1&&t.nodeType!==9&&t.nodeType!==11)throw Error(r(299));return l(e,t,null,n)},t.flushSync=function(e){var t=u.T,n=a.p;try{if(u.T=null,a.p=2,e)return e()}finally{u.T=t,a.p=n,a.d.f()}},t.preconnect=function(e,t){typeof e==`string`&&(t?(t=t.crossOrigin,t=typeof t==`string`?t===`use-credentials`?t:``:void 0):t=null,a.d.C(e,t))},t.prefetchDNS=function(e){typeof e==`string`&&a.d.D(e)},t.preinit=function(e,t){if(typeof e==`string`&&t&&typeof t.as==`string`){var n=t.as,r=d(n,t.crossOrigin),i=typeof t.integrity==`string`?t.integrity:void 0,o=typeof t.fetchPriority==`string`?t.fetchPriority:void 0;n===`style`?a.d.S(e,typeof t.precedence==`string`?t.precedence:void 0,{crossOrigin:r,integrity:i,fetchPriority:o}):n===`script`&&a.d.X(e,{crossOrigin:r,integrity:i,fetchPriority:o,nonce:typeof t.nonce==`string`?t.nonce:void 0})}},t.preinitModule=function(e,t){if(typeof e==`string`){if(typeof t==`object`&&t){if(t.as==null||t.as===`script`){var n=d(t.as,t.crossOrigin);a.d.M(e,{crossOrigin:n,integrity:typeof t.integrity==`string`?t.integrity:void 0,nonce:typeof t.nonce==`string`?t.nonce:void 0,fetchPriority:typeof t.fetchPriority==`string`?t.fetchPriority:void 0})}}else t??a.d.M(e)}},t.preload=function(e,t){if(typeof e==`string`&&typeof t==`object`&&t&&typeof t.as==`string`){var n=t.as,r=d(n,t.crossOrigin);a.d.L(e,n,{crossOrigin:r,integrity:typeof t.integrity==`string`?t.integrity:void 0,nonce:typeof t.nonce==`string`?t.nonce:void 0,type:typeof t.type==`string`?t.type:void 0,fetchPriority:typeof t.fetchPriority==`string`?t.fetchPriority:void 0,referrerPolicy:typeof t.referrerPolicy==`string`?t.referrerPolicy:void 0,imageSrcSet:typeof t.imageSrcSet==`string`?t.imageSrcSet:void 0,imageSizes:typeof t.imageSizes==`string`?t.imageSizes:void 0,media:typeof t.media==`string`?t.media:void 0})}},t.preloadModule=function(e,t){if(typeof e==`string`){if(t){var n=d(t.as,t.crossOrigin);a.d.m(e,{as:typeof t.as==`string`&&t.as!==`script`?t.as:void 0,crossOrigin:n,integrity:typeof t.integrity==`string`?t.integrity:void 0,nonce:typeof t.nonce==`string`?t.nonce:void 0,fetchPriority:typeof t.fetchPriority==`string`?t.fetchPriority:void 0})}else a.d.m(e)}},t.requestFormReset=function(e){a.d.r(e)},t.unstable_batchedUpdates=function(e,t){return e(t)},t.useFormState=function(e,t,n){return u.H.useFormState(e,t,n)},t.useFormStatus=function(){return u.H.useHostTransitionStatus()},t.version=`19.3.1`})),r=t(((e,t)=>{function r(){if(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<`u`&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE==`function`)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(r)}catch(e){console.error(e)}}r(),t.exports=n()}));export{r as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Ct as e}from"./index-CQnL8JJ5.js";var t={name:`x`,size:24,node:[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]};t.node;var n=e(t);export{n as t};
|
package/dist/index.html
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
7
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
8
|
+
<link
|
|
9
|
+
href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;500;600&display=swap"
|
|
10
|
+
rel="stylesheet"
|
|
11
|
+
/>
|
|
12
|
+
<title>DevBoard</title>
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-CQnL8JJ5.js"></script>
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-QmcfnBgR.js">
|
|
15
|
+
<link rel="modulepreload" crossorigin href="/assets/react-dom-DjtJUrx3.js">
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/assets/Surface-BQKeigoq.js">
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BtXUncAU.css">
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<div id="root"></div>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luuthuong/devboard",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "DevBoard — a local devboard board for orchestrating Claude Code sessions",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agents",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"devboard",
|
|
9
|
+
"kanban",
|
|
10
|
+
"tmux"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/luuthuong/devboard.git"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"devboard": "./bin/devboard.mjs"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"bin",
|
|
21
|
+
"dist",
|
|
22
|
+
"server-dist",
|
|
23
|
+
".claude"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"dev": "concurrently -k -n server,ui -c blue,green \"bun:dev:server\" \"bun:dev:ui\"",
|
|
31
|
+
"dev:server": "tsx watch server/index.ts",
|
|
32
|
+
"dev:ui": "vite",
|
|
33
|
+
"build": "tsc && vite build",
|
|
34
|
+
"start": "tsx server/index.ts",
|
|
35
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.server.json",
|
|
36
|
+
"test": "tsx --test --test-reporter=spec 'server/**/*.test.ts' 'src/**/*.test.ts'",
|
|
37
|
+
"preview": "vite preview",
|
|
38
|
+
"prepack": "node scripts/prepack.mjs",
|
|
39
|
+
"build:server": "node scripts/build-server.mjs"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@lydell/node-pty": "^1.2.0-beta.15",
|
|
43
|
+
"ws": "^8.22.0",
|
|
44
|
+
"yaml": "^2.9.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@devboard/ui": "0.1.0",
|
|
48
|
+
"@dnd-kit/core": "^6.3.1",
|
|
49
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
50
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
51
|
+
"@git-diff-view/react": "^0.1.8",
|
|
52
|
+
"@milkdown/kit": "^7.22.3",
|
|
53
|
+
"@milkdown/react": "^7.22.3",
|
|
54
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
55
|
+
"@tanstack/react-virtual": "^3.14.22",
|
|
56
|
+
"@types/node": "^26.6.5",
|
|
57
|
+
"@types/react": "^19.3.0",
|
|
58
|
+
"@types/react-dom": "^19.3.0",
|
|
59
|
+
"@types/ws": "^8.18.2",
|
|
60
|
+
"@vitejs/plugin-react": "^6.1.2",
|
|
61
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
62
|
+
"@xterm/addon-web-links": "^0.12.0",
|
|
63
|
+
"@xterm/addon-webgl": "^0.19.0",
|
|
64
|
+
"@xterm/xterm": "^6.0.0",
|
|
65
|
+
"concurrently": "^10.0.6",
|
|
66
|
+
"fuse.js": "^7.5.0",
|
|
67
|
+
"lowlight": "^3.3.0",
|
|
68
|
+
"lucide-react": "^1.61.0",
|
|
69
|
+
"oxc-transform-react": "^0.155.0",
|
|
70
|
+
"react": "^19.3.1",
|
|
71
|
+
"react-dom": "^19.3.1",
|
|
72
|
+
"react-markdown": "^10.1.0",
|
|
73
|
+
"remark-gfm": "^4.0.1",
|
|
74
|
+
"tailwindcss": "^4.3.3",
|
|
75
|
+
"tsx": "^4.23.15",
|
|
76
|
+
"typescript": "^7.0.2",
|
|
77
|
+
"vite": "^8.3.5",
|
|
78
|
+
"zustand": "^5.0.16"
|
|
79
|
+
},
|
|
80
|
+
"engines": {
|
|
81
|
+
"node": ">=24"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Remote shell wrapper for Claude Code
|
|
4
|
+
# Intercepts shell commands and executes them on the remote machine
|
|
5
|
+
# Falls back to local execution if remote is unavailable
|
|
6
|
+
#
|
|
7
|
+
# Windows port: requires Git for Windows (provides /bin/bash, /usr/bin/perl,
|
|
8
|
+
# /usr/bin/ssh, /usr/bin/sed). Configuration read from settings.json at the
|
|
9
|
+
# templated absolute POSIX path below. Online/offline notifications are
|
|
10
|
+
# delivered by writing JSON status files — the devboard server polls these and
|
|
11
|
+
# raises a notification.
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
# --- Recursion guard ---
|
|
15
|
+
if [[ -n "${__DEVBOARD_REMOTE_WRAPPER:-}" ]]; then
|
|
16
|
+
exec /bin/bash "$@"
|
|
17
|
+
fi
|
|
18
|
+
export __DEVBOARD_REMOTE_WRAPPER=1
|
|
19
|
+
|
|
20
|
+
# --- Hook/script fast-path ---
|
|
21
|
+
for __arg in "$@"; do
|
|
22
|
+
if [[ "$__arg" == "-c" ]] || [[ "$__arg" == "-l" ]] || [[ "$__arg" == "-i" ]]; then
|
|
23
|
+
continue
|
|
24
|
+
fi
|
|
25
|
+
__first_word="${__arg%% *}"
|
|
26
|
+
if [[ "$__first_word" == /* ]] && [[ -x "$__first_word" ]]; then
|
|
27
|
+
exec /bin/bash "$@"
|
|
28
|
+
fi
|
|
29
|
+
break
|
|
30
|
+
done
|
|
31
|
+
unset __arg __first_word
|
|
32
|
+
|
|
33
|
+
CONFIG_FILE="@@CONFIG_FILE@@"
|
|
34
|
+
STATUS_DIR="@@STATUS_DIR@@"
|
|
35
|
+
REMOTE_HOST=""
|
|
36
|
+
REMOTE_DIR=""
|
|
37
|
+
LOCAL_MOUNT=""
|
|
38
|
+
|
|
39
|
+
if [[ -f "$CONFIG_FILE" ]]; then
|
|
40
|
+
REMOTE_HOST=$(/usr/bin/perl -MJSON::PP -e 'open my $f,"<","'"$CONFIG_FILE"'" or exit;local $/;my $d=decode_json(<$f>);print $d->{remote}{host}//"" if $d->{remote}' 2>/dev/null || echo "")
|
|
41
|
+
REMOTE_DIR=$(/usr/bin/perl -MJSON::PP -e 'open my $f,"<","'"$CONFIG_FILE"'" or exit;local $/;my $d=decode_json(<$f>);print $d->{remote}{remotePath}//"" if $d->{remote}' 2>/dev/null || echo "")
|
|
42
|
+
LOCAL_MOUNT=$(/usr/bin/perl -MJSON::PP -e 'open my $f,"<","'"$CONFIG_FILE"'" or exit;local $/;my $d=decode_json(<$f>);print $d->{remote}{localPath}//"" if $d->{remote}' 2>/dev/null || echo "")
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# ControlMaster sockets in TMPDIR — Git-for-Windows bash maps that to a path
|
|
46
|
+
# native ssh.exe can also read. Falls back to /tmp on POSIX.
|
|
47
|
+
SSH_TMP="${TMPDIR:-/tmp}"
|
|
48
|
+
SSH_TMP="${SSH_TMP%/}"
|
|
49
|
+
SSH_OPTS="-o ControlMaster=auto -o ControlPath=${SSH_TMP}/ssh-devboard-%r@%h:%p -o ControlPersist=600 -o ConnectTimeout=5"
|
|
50
|
+
STATE_FILE="${SSH_TMP}/devboard-remote-state"
|
|
51
|
+
NOTIFY_COOLDOWN=300
|
|
52
|
+
MUTAGEN=$(command -v mutagen 2>/dev/null || echo "")
|
|
53
|
+
|
|
54
|
+
ensure_sync() {
|
|
55
|
+
[[ -n "$MUTAGEN" ]] || return 0
|
|
56
|
+
if ! "$MUTAGEN" sync list --label-selector devboard=true 2>/dev/null | grep -q "Name:"; then
|
|
57
|
+
"$MUTAGEN" sync create "$LOCAL_MOUNT" "${REMOTE_HOST}:${REMOTE_DIR}" \
|
|
58
|
+
--name devboard-sync \
|
|
59
|
+
--label devboard=true \
|
|
60
|
+
--sync-mode two-way-resolved \
|
|
61
|
+
--default-file-mode-beta 0644 \
|
|
62
|
+
--default-directory-mode-beta 0755 \
|
|
63
|
+
--ignore node_modules --ignore .venv --ignore .cache \
|
|
64
|
+
--ignore dist --ignore '.next*' --ignore __pycache__ \
|
|
65
|
+
--ignore .pytest_cache --ignore .mypy_cache --ignore .turbo \
|
|
66
|
+
--ignore '*.pyc' --ignore .DS_Store --ignore coverage \
|
|
67
|
+
--ignore .nyc_output --ignore target --ignore build \
|
|
68
|
+
--ignore .build --ignore .swiftpm \
|
|
69
|
+
>/dev/null 2>&1 || true
|
|
70
|
+
fi
|
|
71
|
+
"$MUTAGEN" sync flush --label-selector devboard=true >/dev/null 2>&1 || true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
read -r -d '' WORKTREE_FIX_FN << 'WFIX' || true
|
|
75
|
+
__relpath(){
|
|
76
|
+
local t="$1" b="$2"; t="${t%/}"; b="${b%/}"
|
|
77
|
+
local c="$b" r=""
|
|
78
|
+
while [ "${t#"$c"}" = "$t" ]; do c=$(dirname "$c"); r="../$r"; done
|
|
79
|
+
local f="${t#"$c"}"; f="${f#/}"; printf '%s\n' "${r}${f}"
|
|
80
|
+
}
|
|
81
|
+
__fix_gitlink(){
|
|
82
|
+
local f="$1" wp="$2" rp="$3"
|
|
83
|
+
[ -f "$f" ] || return 0
|
|
84
|
+
local c; c=$(cat "$f"); local ig=false p="$c"
|
|
85
|
+
case "$c" in gitdir:*) ig=true; p="${c#gitdir: }";; esac
|
|
86
|
+
p="${p//$wp/$rp}"
|
|
87
|
+
case "$p" in /*) ;; *) return 0;; esac
|
|
88
|
+
[ -e "$p" ] || return 0
|
|
89
|
+
local d; d=$(dirname "$f")
|
|
90
|
+
local rl; rl=$(__relpath "$p" "$d")
|
|
91
|
+
[ -n "$rl" ] || return 0
|
|
92
|
+
if $ig; then printf 'gitdir: %s\n' "$rl" > "$f"; else printf '%s\n' "$rl" > "$f"; fi
|
|
93
|
+
}
|
|
94
|
+
__fix_wt(){
|
|
95
|
+
local wp="$1" rp="$2" d; d=$(pwd); local gr=""
|
|
96
|
+
while [ "$d" != "/" ]; do
|
|
97
|
+
if [ -d "$d/.git" ]; then gr="$d"; break
|
|
98
|
+
elif [ -f "$d/.git" ]; then
|
|
99
|
+
__fix_gitlink "$d/.git" "$wp" "$rp"
|
|
100
|
+
local g; g=$(cat "$d/.git"); g="${g#gitdir: }"
|
|
101
|
+
case "$g" in /*) gr="${g%/.git/worktrees/*}";; *) gr=$(cd "$d/$g/../../.." 2>/dev/null && pwd);; esac
|
|
102
|
+
break
|
|
103
|
+
fi
|
|
104
|
+
d=$(dirname "$d")
|
|
105
|
+
done
|
|
106
|
+
[ -n "$gr" ] && [ -d "$gr/.git/worktrees" ] || return 0
|
|
107
|
+
for m in "$gr/.git/worktrees"/*/; do
|
|
108
|
+
[ -d "$m" ] || continue
|
|
109
|
+
__fix_gitlink "${m}gitdir" "$wp" "$rp"
|
|
110
|
+
local gc; [ -f "${m}gitdir" ] && gc=$(cat "${m}gitdir") || continue
|
|
111
|
+
[ -n "$gc" ] || continue
|
|
112
|
+
local wf
|
|
113
|
+
case "$gc" in /*) wf="$gc";;
|
|
114
|
+
*) wf=$(cd "$m" && cd "$(dirname "$gc")" 2>/dev/null && printf '%s/%s\n' "$(pwd)" "$(basename "$gc")") || continue;;
|
|
115
|
+
esac
|
|
116
|
+
[ -n "$wf" ] && __fix_gitlink "$wf" "$wp" "$rp"
|
|
117
|
+
done
|
|
118
|
+
}
|
|
119
|
+
WFIX
|
|
120
|
+
|
|
121
|
+
local_to_remote() { echo "${1/#$LOCAL_MOUNT/$REMOTE_DIR}"; }
|
|
122
|
+
remote_to_local() { echo "${1/#$REMOTE_DIR/$LOCAL_MOUNT}"; }
|
|
123
|
+
|
|
124
|
+
# Notification = write a status JSON file. The devboard server polls
|
|
125
|
+
# these and fires the notification. Rate-limited identically to the macOS path.
|
|
126
|
+
notify() {
|
|
127
|
+
local message="$1" state="$2"
|
|
128
|
+
local now=$(date +%s) last_state="" last_notify=0
|
|
129
|
+
if [[ -f "$STATE_FILE" ]]; then
|
|
130
|
+
last_state=$(head -1 "$STATE_FILE")
|
|
131
|
+
last_notify=$(tail -1 "$STATE_FILE")
|
|
132
|
+
fi
|
|
133
|
+
if [[ "$state" != "$last_state" ]] || { [[ "$state" == "offline" ]] && [[ $((now - last_notify)) -ge $NOTIFY_COOLDOWN ]]; }; then
|
|
134
|
+
mkdir -p "$STATUS_DIR" 2>/dev/null
|
|
135
|
+
local iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
136
|
+
local safe_host="${REMOTE_HOST//\//_}"
|
|
137
|
+
safe_host="${safe_host//:/_}"
|
|
138
|
+
printf '{"status":"%s","since":"%s","message":"%s"}\n' "$state" "$iso" "$message" \
|
|
139
|
+
> "$STATUS_DIR/status-${safe_host}.json"
|
|
140
|
+
echo -e "$state\n$now" > "$STATE_FILE"
|
|
141
|
+
fi
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
run_with_timeout() {
|
|
145
|
+
local secs="$1"; shift
|
|
146
|
+
/usr/bin/perl -e 'alarm shift @ARGV; exec @ARGV' "$secs" "$@"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
is_remote_available() {
|
|
150
|
+
local socket="${SSH_TMP}/ssh-devboard-${REMOTE_HOST}:22"
|
|
151
|
+
if [[ -S "$socket" ]]; then
|
|
152
|
+
if ! run_with_timeout 1 /usr/bin/ssh -o ControlPath="$socket" -O check "$REMOTE_HOST" 2>/dev/null; then
|
|
153
|
+
/bin/rm -f "$socket" 2>/dev/null
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
run_with_timeout 5 /usr/bin/ssh -o ConnectTimeout=5 -o BatchMode=yes "$REMOTE_HOST" "exit 0" 2>/dev/null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
while [[ $# -gt 0 ]]; do
|
|
160
|
+
case "$1" in
|
|
161
|
+
-c) shift ;;
|
|
162
|
+
-l|-i) shift ;;
|
|
163
|
+
*) cmd="$1"; break ;;
|
|
164
|
+
esac
|
|
165
|
+
done
|
|
166
|
+
|
|
167
|
+
if [[ -n "${cmd:-}" ]]; then
|
|
168
|
+
pwd_file=""
|
|
169
|
+
if [[ "$cmd" =~ (.*)(\&\&\ pwd\ -P\ \>\|\ ([^[:space:]]+))$ ]]; then
|
|
170
|
+
cmd="${BASH_REMATCH[1]}"
|
|
171
|
+
pwd_file="${BASH_REMATCH[3]}"
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
LOCAL_CWD="$(pwd -P)"
|
|
175
|
+
|
|
176
|
+
if [[ -z "$REMOTE_HOST" ]] || [[ -z "$REMOTE_DIR" ]] || [[ -z "$LOCAL_MOUNT" ]]; then
|
|
177
|
+
/bin/bash -c "$cmd"
|
|
178
|
+
exit_code=$?
|
|
179
|
+
[[ -n "$pwd_file" ]] && pwd -P > "$pwd_file"
|
|
180
|
+
exit $exit_code
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
if is_remote_available; then
|
|
184
|
+
notify "Remote instance available" "online"
|
|
185
|
+
REMOTE_CWD="$(local_to_remote "$LOCAL_CWD")"
|
|
186
|
+
cmd="${cmd//$LOCAL_MOUNT/$REMOTE_DIR}"
|
|
187
|
+
cmd=$(echo "$cmd" | /usr/bin/sed -E 's|/var/folders/[^[:space:];]+\.tmp|true|g')
|
|
188
|
+
ensure_sync
|
|
189
|
+
MARKER="__DEVBOARD_REMOTE_PWD__"
|
|
190
|
+
remote_cmd="${WORKTREE_FIX_FN}
|
|
191
|
+
source ~/.profile 2>/dev/null; source <(sed 's/return;;/;;/' ~/.bashrc) 2>/dev/null; cd '$REMOTE_CWD' 2>/dev/null || cd '$REMOTE_DIR'; __fix_wt '$LOCAL_MOUNT' '$REMOTE_DIR'; /bin/bash -c $(printf '%q' "$cmd"); __fix_wt '$LOCAL_MOUNT' '$REMOTE_DIR'; echo $MARKER; pwd -P"
|
|
192
|
+
remote_output=$(/usr/bin/ssh $SSH_OPTS "$REMOTE_HOST" "$remote_cmd")
|
|
193
|
+
exit_code=$?
|
|
194
|
+
"$MUTAGEN" sync flush --label-selector devboard=true >/dev/null 2>&1 || true
|
|
195
|
+
if [[ "$remote_output" == *"$MARKER"* ]]; then
|
|
196
|
+
cmd_output="${remote_output%$MARKER*}"
|
|
197
|
+
remote_pwd="${remote_output##*$MARKER}"
|
|
198
|
+
remote_pwd=$(echo "$remote_pwd" | tr -d '\n')
|
|
199
|
+
printf "%s" "$cmd_output"
|
|
200
|
+
if [[ -n "$pwd_file" ]]; then
|
|
201
|
+
echo "$(remote_to_local "$remote_pwd")" > "$pwd_file"
|
|
202
|
+
fi
|
|
203
|
+
else
|
|
204
|
+
echo "$remote_output"
|
|
205
|
+
[[ -n "$pwd_file" ]] && echo "$LOCAL_CWD" > "$pwd_file"
|
|
206
|
+
fi
|
|
207
|
+
else
|
|
208
|
+
notify "Remote unavailable - using local execution" "offline"
|
|
209
|
+
cmd="${cmd//$REMOTE_DIR/$LOCAL_MOUNT}"
|
|
210
|
+
MARKER="__DEVBOARD_LOCAL_PWD__"
|
|
211
|
+
local_output=$(/bin/bash -c "$cmd; echo $MARKER; pwd -P" 2>&1)
|
|
212
|
+
exit_code=$?
|
|
213
|
+
if [[ "$local_output" == *"$MARKER"* ]]; then
|
|
214
|
+
cmd_output="${local_output%$MARKER*}"
|
|
215
|
+
local_pwd="${local_output##*$MARKER}"
|
|
216
|
+
local_pwd=$(echo "$local_pwd" | tr -d '\n')
|
|
217
|
+
printf "%s" "$cmd_output"
|
|
218
|
+
[[ -n "$pwd_file" ]] && echo "$local_pwd" > "$pwd_file"
|
|
219
|
+
else
|
|
220
|
+
echo "$local_output"
|
|
221
|
+
[[ -n "$pwd_file" ]] && echo "$LOCAL_CWD" > "$pwd_file"
|
|
222
|
+
fi
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
exit $exit_code
|
|
226
|
+
else
|
|
227
|
+
if [[ -z "$REMOTE_HOST" ]] || [[ -z "$REMOTE_DIR" ]] || [[ -z "$LOCAL_MOUNT" ]]; then
|
|
228
|
+
exec /bin/bash -l
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
if is_remote_available; then
|
|
232
|
+
notify "Remote instance available" "online"
|
|
233
|
+
REMOTE_CWD="$(local_to_remote "$(pwd -P)")"
|
|
234
|
+
/usr/bin/ssh $SSH_OPTS -t "$REMOTE_HOST" "cd '$REMOTE_CWD' 2>/dev/null || cd '$REMOTE_DIR'; /bin/bash -l"
|
|
235
|
+
else
|
|
236
|
+
notify "Remote unavailable - using local shell" "offline"
|
|
237
|
+
/bin/bash -l
|
|
238
|
+
fi
|
|
239
|
+
fi
|