@zibby/cli 0.4.16 → 0.4.18

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.
Files changed (40) hide show
  1. package/dist/bin/zibby.js +2 -2
  2. package/dist/commands/init.js +64 -64
  3. package/dist/commands/workflows/generate.js +108 -108
  4. package/dist/commands/workflows/schedule.js +10 -0
  5. package/dist/commands/workflows/validate-helpers.js +1 -0
  6. package/dist/commands/workflows/validate.js +4 -0
  7. package/dist/package.json +5 -3
  8. package/dist/templates/.claude/CLAUDE.md +425 -0
  9. package/dist/templates/.claude/commands/add-node.md +63 -0
  10. package/dist/templates/.claude/commands/add-skill.md +83 -0
  11. package/dist/templates/.claude/commands/new-workflow.md +61 -0
  12. package/dist/templates/.claude/commands/validate-workflow.md +67 -0
  13. package/package.json +5 -3
  14. package/templates/.claude/CLAUDE.md +425 -0
  15. package/templates/.claude/commands/add-node.md +63 -0
  16. package/templates/.claude/commands/add-skill.md +83 -0
  17. package/templates/.claude/commands/new-workflow.md +61 -0
  18. package/templates/.claude/commands/validate-workflow.md +67 -0
  19. package/templates/zibby-workflow-claude/agents-md-block.md +173 -0
  20. package/templates/zibby-workflow-claude/claude/agents/zibby-test-author.md +87 -0
  21. package/templates/zibby-workflow-claude/claude/agents/zibby-workflow-builder.md +101 -0
  22. package/templates/zibby-workflow-claude/claude/commands/zibby-add-node.md +75 -0
  23. package/templates/zibby-workflow-claude/claude/commands/zibby-debug.md +67 -0
  24. package/templates/zibby-workflow-claude/claude/commands/zibby-delete.md +37 -0
  25. package/templates/zibby-workflow-claude/claude/commands/zibby-deploy.md +87 -0
  26. package/templates/zibby-workflow-claude/claude/commands/zibby-list.md +30 -0
  27. package/templates/zibby-workflow-claude/claude/commands/zibby-memory-cost.md +39 -0
  28. package/templates/zibby-workflow-claude/claude/commands/zibby-memory-pull.md +47 -0
  29. package/templates/zibby-workflow-claude/claude/commands/zibby-memory-remote-use-hosted.md +61 -0
  30. package/templates/zibby-workflow-claude/claude/commands/zibby-memory-stats.md +38 -0
  31. package/templates/zibby-workflow-claude/claude/commands/zibby-static-ip.md +70 -0
  32. package/templates/zibby-workflow-claude/claude/commands/zibby-tail.md +53 -0
  33. package/templates/zibby-workflow-claude/claude/commands/zibby-test-debug.md +59 -0
  34. package/templates/zibby-workflow-claude/claude/commands/zibby-test-generate.md +39 -0
  35. package/templates/zibby-workflow-claude/claude/commands/zibby-test-run.md +49 -0
  36. package/templates/zibby-workflow-claude/claude/commands/zibby-test-write.md +46 -0
  37. package/templates/zibby-workflow-claude/claude/commands/zibby-trigger.md +56 -0
  38. package/templates/zibby-workflow-claude/claude/settings.json +10 -0
  39. package/templates/zibby-workflow-claude/cursor/rules/zibby-workflows.mdc +119 -0
  40. package/templates/zibby-workflow-claude/manifest.json +47 -0
@@ -0,0 +1,10 @@
1
+ var $=Object.create;var R=Object.defineProperty;var Z=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var W=Object.getPrototypeOf,z=Object.prototype.hasOwnProperty;var F=(u,f)=>()=>(f||u((f={exports:{}}).exports,f),f.exports);var j=(u,f,y,w)=>{if(f&&typeof f=="object"||typeof f=="function")for(let g of P(f))!z.call(u,g)&&g!==y&&R(u,g,{get:()=>f[g],enumerable:!(w=Z(f,g))||w.enumerable});return u};var V=(u,f,y)=>(y=u!=null?$(W(u)):{},j(f||!u||!u.__esModule?R(y,"default",{value:u,enumerable:!0}):y,u));var B=F((M,D)=>{(function(f,y){typeof M=="object"&&typeof D=="object"?D.exports=y():typeof define=="function"&&define.amd?define("cronstrue",[],y):typeof M=="object"?M.cronstrue=y():f.cronstrue=y()})(globalThis,()=>(()=>{"use strict";var u={949(g,d,m){Object.defineProperty(d,"__esModule",{value:!0}),d.CronParser=void 0;var r=m(515),b=(function(){function v(t,e,o){e===void 0&&(e=!0),o===void 0&&(o=!1),this.expression=t,this.dayOfWeekStartIndexZero=e,this.monthStartIndexZero=o}return v.prototype.parse=function(){var t,e,o=(t=this.expression)!==null&&t!==void 0?t:"";if(o==="@reboot")return e=["@reboot","","","","","",""],e;if(o.startsWith("@")){var n=this.parseSpecial(this.expression);e=this.extractParts(n)}else e=this.extractParts(this.expression);return this.normalize(e),this.validate(e),e},v.prototype.parseSpecial=function(t){var e={"@yearly":"0 0 1 1 *","@annually":"0 0 1 1 *","@monthly":"0 0 1 * *","@weekly":"0 0 * * 0","@daily":"0 0 * * *","@midnight":"0 0 * * *","@hourly":"0 * * * *","@reboot":"@reboot"},o=e[t];if(!o)throw new Error("Unknown special expression.");return o},v.prototype.extractParts=function(t){if(!this.expression)throw new Error("cron expression is empty");for(var e=t.trim().split(/[ ]+/),o=0;o<e.length;o++)if(e[o].includes(",")){var n=e[o].split(",").map(function(a){return a.trim()}).filter(function(a){return a!==""}).map(function(a){return isNaN(Number(a))?a:Number(a)}).filter(function(a){return a!==null&&a!==""});n.length===0&&n.push("*"),n.sort(function(a,s){return a!==null&&s!==null?a-s:0}),e[o]=n.map(function(a){return a!==null?a.toString():""}).join(",")}if(e.length<5)throw new Error("Expression has only ".concat(e.length," part").concat(e.length==1?"":"s",". At least 5 parts are required."));if(e.length==5)e.unshift(""),e.push("");else if(e.length==6){var i=/\d{4}$/.test(e[5])||e[4]=="?"||e[2]=="?";i?e.unshift(""):e.push("")}else if(e.length>7)throw new Error("Expression has ".concat(e.length," parts; too many!"));return e},v.prototype.normalize=function(t){var e=this;if(t[3]=t[3].replace("?","*"),t[5]=t[5].replace("?","*"),t[2]=t[2].replace("?","*"),t[0].indexOf("0/")==0&&(t[0]=t[0].replace("0/","*/")),t[1].indexOf("0/")==0&&(t[1]=t[1].replace("0/","*/")),t[2].indexOf("0/")==0&&(t[2]=t[2].replace("0/","*/")),t[3].indexOf("1/")==0&&(t[3]=t[3].replace("1/","*/")),t[4].indexOf("1/")==0&&(t[4]=t[4].replace("1/","*/")),t[6].indexOf("1/")==0&&(t[6]=t[6].replace("1/","*/")),t[5]=t[5].replace(/(^\d)|([^#/\s]\d)/g,function(l){var O=l.replace(/\D/,""),h=O;return e.dayOfWeekStartIndexZero?O=="7"&&(h="0"):h=(parseInt(O)-1).toString(),l.replace(O,h)}),t[5]=="L"&&(t[5]="6"),t[3]=="?"&&(t[3]="*"),t[3].indexOf("W")>-1&&(t[3].indexOf(",")>-1||t[3].indexOf("-")>-1))throw new Error("The 'W' character can be specified only when the day-of-month is a single day, not a range or list of days.");var o={SUN:0,MON:1,TUE:2,WED:3,THU:4,FRI:5,SAT:6};for(var n in o)t[5]=t[5].replace(new RegExp(n,"gi"),o[n].toString());t[4]=t[4].replace(/(^\d{1,2})|([^#/\s]\d{1,2})/g,function(l){var O=l.replace(/\D/,""),h=O;return e.monthStartIndexZero&&(h=(parseInt(O)+1).toString()),l.replace(O,h)});var i={JAN:1,FEB:2,MAR:3,APR:4,MAY:5,JUN:6,JUL:7,AUG:8,SEP:9,OCT:10,NOV:11,DEC:12};for(var a in i)t[4]=t[4].replace(new RegExp(a,"gi"),i[a].toString());t[0]=="0"&&(t[0]=""),!/\*|\-|\,|\//.test(t[2])&&(/\*|\//.test(t[1])||/\*|\//.test(t[0]))&&(t[2]+="-".concat(t[2]));for(var s=0;s<t.length;s++)if(t[s].indexOf(",")!=-1&&(t[s]=t[s].split(",").filter(function(l){return l!==""}).join(",")||"*"),t[s]=="*/1"&&(t[s]="*"),t[s].indexOf("/")>-1&&!/^\*|\-|\,/.test(t[s])){var c=null;switch(s){case 4:c="12";break;case 5:c="6";break;case 6:c="9999";break;default:c=null;break}if(c!==null){var p=t[s].split("/");t[s]="".concat(p[0],"-").concat(c,"/").concat(p[1])}}},v.prototype.validate=function(t){var e="0-9,\\-*/";this.validateOnlyExpectedCharactersFound(t[0],e),this.validateOnlyExpectedCharactersFound(t[1],e),this.validateOnlyExpectedCharactersFound(t[2],e),this.validateOnlyExpectedCharactersFound(t[3],"0-9,\\-*/LW"),this.validateOnlyExpectedCharactersFound(t[4],e),this.validateOnlyExpectedCharactersFound(t[5],"0-9,\\-*/L#"),this.validateOnlyExpectedCharactersFound(t[6],e),this.validateAnyRanges(t)},v.prototype.validateAnyRanges=function(t){r.default.secondRange(t[0]),r.default.minuteRange(t[1]),r.default.hourRange(t[2]),r.default.dayOfMonthRange(t[3]),r.default.monthRange(t[4],this.monthStartIndexZero),r.default.dayOfWeekRange(t[5],this.dayOfWeekStartIndexZero)},v.prototype.validateOnlyExpectedCharactersFound=function(t,e){var o=t.match(new RegExp("[^".concat(e,"]+"),"gi"));if(o&&o.length)throw new Error("Expression contains invalid values: '".concat(o.toString(),"'"))},v})();d.CronParser=b},333(g,d,m){Object.defineProperty(d,"__esModule",{value:!0}),d.ExpressionDescriptor=void 0;var r=m(823),b=m(949),v=(function(){function t(e,o){if(this.expression=e,this.options=o,this.expressionParts=new Array(5),!this.options.locale&&t.defaultLocale&&(this.options.locale=t.defaultLocale),!t.locales[this.options.locale]){var n=Object.keys(t.locales)[0];console.warn("Locale '".concat(this.options.locale,"' could not be found; falling back to '").concat(n,"'.")),this.options.locale=n}this.i18n=t.locales[this.options.locale],o.use24HourTimeFormat===void 0&&(o.use24HourTimeFormat=this.i18n.use24HourTimeFormatByDefault())}return t.toString=function(e,o){var n=o===void 0?{}:o,i=n.throwExceptionOnParseError,a=i===void 0?!0:i,s=n.verbose,c=s===void 0?!1:s,p=n.dayOfWeekStartIndexZero,l=p===void 0?!0:p,O=n.monthStartIndexZero,h=O===void 0?!1:O,S=n.use24HourTimeFormat,_=n.trimHoursLeadingZero,U=_===void 0?!1:_,X=n.locale,E=X===void 0?null:X,x=n.logicalAndDayFields,A=x===void 0?!1:x,k={throwExceptionOnParseError:a,verbose:c,dayOfWeekStartIndexZero:l,monthStartIndexZero:h,use24HourTimeFormat:S,trimHoursLeadingZero:U,locale:E,logicalAndDayFields:A};k.tzOffset&&console.warn("'tzOffset' option has been deprecated and is no longer supported.");var Y=new t(e,k);return Y.getFullDescription()},t.initialize=function(e,o){o===void 0&&(o="en"),t.specialCharacters=["/","-",",","*"],t.defaultLocale=o,e.load(t.locales)},t.prototype.getFullDescription=function(){var e,o,n="";try{var i=new b.CronParser(this.expression,this.options.dayOfWeekStartIndexZero,this.options.monthStartIndexZero);if(this.expressionParts=i.parse(),this.expressionParts[0]==="@reboot")return((o=(e=this.i18n).atReboot)===null||o===void 0?void 0:o.call(e))||"Run once, at startup";var a=this.getTimeOfDayDescription(),s=this.getDayOfMonthDescription(),c=this.getMonthDescription(),p=this.getDayOfWeekDescription(),l=this.getYearDescription();n+=a+s+p+c+l,n=this.transformVerbosity(n,!!this.options.verbose),n=n.charAt(0).toLocaleUpperCase()+n.substr(1)}catch(O){if(!this.options.throwExceptionOnParseError)n=this.i18n.anErrorOccuredWhenGeneratingTheExpressionD();else throw"".concat(O)}return n},t.prototype.getTimeOfDayDescription=function(){var e=this.expressionParts[0],o=this.expressionParts[1],n=this.expressionParts[2],i="";if(!r.StringUtilities.containsAny(o,t.specialCharacters)&&!r.StringUtilities.containsAny(n,t.specialCharacters)&&!r.StringUtilities.containsAny(e,t.specialCharacters))i+=this.i18n.atSpace()+this.formatTime(n,o,e);else if(!e&&o.indexOf("-")>-1&&!(o.indexOf(",")>-1)&&!(o.indexOf("/")>-1)&&!r.StringUtilities.containsAny(n,t.specialCharacters)){var a=o.split("-");i+=r.StringUtilities.format(this.i18n.everyMinuteBetweenX0AndX1(),this.formatTime(n,a[0],""),this.formatTime(n,a[1],""))}else if(!e&&n.indexOf(",")>-1&&n.indexOf("-")==-1&&n.indexOf("/")==-1&&!r.StringUtilities.containsAny(o,t.specialCharacters)){var s=n.split(",");i+=this.i18n.at();for(var c=0;c<s.length;c++)i+=" ",i+=this.formatTime(s[c],o,""),c<s.length-2&&(i+=","),c==s.length-2&&(i+=this.i18n.spaceAnd())}else{var p=this.getSecondsDescription(),l=this.getMinutesDescription(),O=this.getHoursDescription();if(i+=p,i&&l&&(i+=", "),i+=l,l===O)return i;i&&O&&(i+=", "),i+=O}return i},t.prototype.getSecondsDescription=function(){var e=this,o=this.getSegmentDescription(this.expressionParts[0],this.i18n.everySecond(),function(n){return n},function(n){return r.StringUtilities.format(e.i18n.everyX0Seconds(n),n)},function(n){return e.i18n.secondsX0ThroughX1PastTheMinute()},function(n){return n=="0"?"":parseInt(n)<20?e.i18n.atX0SecondsPastTheMinute(n):e.i18n.atX0SecondsPastTheMinuteGt20()||e.i18n.atX0SecondsPastTheMinute(n)});return o},t.prototype.getMinutesDescription=function(){var e=this,o=this.expressionParts[0],n=this.expressionParts[2],i=this.getSegmentDescription(this.expressionParts[1],this.i18n.everyMinute(),function(a){return a},function(a){return r.StringUtilities.format(e.i18n.everyX0Minutes(a),a)},function(a){return e.i18n.minutesX0ThroughX1PastTheHour()},function(a){var s,c;try{return a=="0"&&n.indexOf("/")==-1&&o==""?e.i18n.everyHour():a=="0"?((c=(s=e.i18n).onTheHour)===null||c===void 0?void 0:c.call(s))||e.i18n.atX0MinutesPastTheHour(a):parseInt(a)<20?e.i18n.atX0MinutesPastTheHour(a):e.i18n.atX0MinutesPastTheHourGt20()||e.i18n.atX0MinutesPastTheHour(a)}catch{return e.i18n.atX0MinutesPastTheHour(a)}});return i},t.prototype.getHoursDescription=function(){var e=this,o=this.expressionParts[2],n=0,i=[];o.split("/")[0].split(",").forEach(function(c){var p=c.split("-");p.length===2&&i.push({value:p[1],index:n+1}),n+=p.length});var a=0,s=this.getSegmentDescription(o,this.i18n.everyHour(),function(c){var p=i.find(function(O){return O.value===c&&O.index===a}),l=p&&e.expressionParts[1]!=="0";return a++,l?e.formatTime(c,"59",""):e.formatTime(c,"0","")},function(c){return r.StringUtilities.format(e.i18n.everyX0Hours(c),c)},function(c){return e.i18n.betweenX0AndX1()},function(c){return e.i18n.atX0()});return s},t.prototype.getDayOfWeekDescription=function(){var e=this,o=this.i18n.daysOfTheWeek(),n=null;return this.expressionParts[5]=="*"?n="":n=this.getSegmentDescription(this.expressionParts[5],this.i18n.commaEveryDay(),function(i,a){var s=i;i.indexOf("#")>-1?s=i.substring(0,i.indexOf("#")):i.indexOf("L")>-1&&(s=s.replace("L",""));var c=parseInt(s),p=e.i18n.daysOfTheWeekInCase?e.i18n.daysOfTheWeekInCase(a)[c]:o[c];if(i.indexOf("#")>-1){var l=null,O=i.substring(i.indexOf("#")+1),h=i.substring(0,i.indexOf("#"));switch(O){case"1":l=e.i18n.first(h);break;case"2":l=e.i18n.second(h);break;case"3":l=e.i18n.third(h);break;case"4":l=e.i18n.fourth(h);break;case"5":l=e.i18n.fifth(h);break}p=l+" "+p}return p},function(i){return parseInt(i)==1?"":r.StringUtilities.format(e.i18n.commaEveryX0DaysOfTheWeek(i),i)},function(i){var a=i.substring(0,i.indexOf("-")),s=e.expressionParts[3]!="*";return s?e.i18n.commaAndX0ThroughX1(a):e.i18n.commaX0ThroughX1(a)},function(i){var a=null;if(i.indexOf("#")>-1){var s=i.substring(i.indexOf("#")+1),c=i.substring(0,i.indexOf("#"));a=e.i18n.commaOnThe(s,c).trim()+e.i18n.spaceX0OfTheMonth()}else if(i.indexOf("L")>-1)a=e.i18n.commaOnTheLastX0OfTheMonth(i.replace("L",""));else{var p=e.expressionParts[3]!="*";p?e.options.logicalAndDayFields?a=e.i18n.commaOnlyOnX0(i):a=e.i18n.commaAndOnX0():a=e.i18n.commaOnlyOnX0(i)}return a}),n},t.prototype.getMonthDescription=function(){var e=this,o=this.i18n.monthsOfTheYear(),n=this.getSegmentDescription(this.expressionParts[4],"",function(i,a){return a&&e.i18n.monthsOfTheYearInCase?e.i18n.monthsOfTheYearInCase(a)[parseInt(i)-1]:o[parseInt(i)-1]},function(i){return parseInt(i)==1?"":r.StringUtilities.format(e.i18n.commaEveryX0Months(i),i)},function(i){return e.i18n.commaMonthX0ThroughMonthX1()||e.i18n.commaX0ThroughX1()},function(i){return e.i18n.commaOnlyInMonthX0?e.i18n.commaOnlyInMonthX0():e.i18n.commaOnlyInX0()});return n},t.prototype.getDayOfMonthDescription=function(){var e=this,o=null,n=this.expressionParts[3];switch(n){case"L":o=this.i18n.commaOnTheLastDayOfTheMonth();break;case"WL":case"LW":o=this.i18n.commaOnTheLastWeekdayOfTheMonth();break;default:var i=n.match(/(\d{1,2}W)|(W\d{1,2})/);if(i){var a=parseInt(i[0].replace("W","")),s=a==1?this.i18n.firstWeekday():r.StringUtilities.format(this.i18n.weekdayNearestDayX0(),a.toString());o=r.StringUtilities.format(this.i18n.commaOnTheX0OfTheMonth(),s);break}else{var c=n.match(/L-(\d{1,2})/);if(c){var p=c[1];o=r.StringUtilities.format(this.i18n.commaDaysBeforeTheLastDayOfTheMonth(p),p);break}else{if(n=="*"&&this.expressionParts[5]!="*")return"";o=this.getSegmentDescription(n,this.i18n.commaEveryDay(),function(l){return l=="L"?e.i18n.lastDay():e.i18n.dayX0?r.StringUtilities.format(e.i18n.dayX0(),l):l},function(l){return l=="1"?e.i18n.commaEveryDay():e.i18n.commaEveryX0Days(l)},function(l){return e.i18n.commaBetweenDayX0AndX1OfTheMonth(l)},function(l){return e.i18n.commaOnDayX0OfTheMonth(l)})}break}}return o},t.prototype.getYearDescription=function(){var e=this,o=this.getSegmentDescription(this.expressionParts[6],"",function(n){return/^\d+$/.test(n)?new Date(parseInt(n),1).getFullYear().toString():n},function(n){return r.StringUtilities.format(e.i18n.commaEveryX0Years(n),n)},function(n){return e.i18n.commaYearX0ThroughYearX1()||e.i18n.commaX0ThroughX1()},function(n){return e.i18n.commaOnlyInYearX0?e.i18n.commaOnlyInYearX0():e.i18n.commaOnlyInX0()});return o},t.prototype.getSegmentDescription=function(e,o,n,i,a,s){var c=null,p=e.indexOf("/")>-1,l=e.indexOf("-")>-1,O=e.indexOf(",")>-1;if(!e)c="";else if(e==="*")c=o;else if(!p&&!l&&!O)c=r.StringUtilities.format(s(e),n(e));else if(O){for(var h=e.split(","),S="",_=0;_<h.length;_++)if(_>0&&h.length>2&&(S+=",",_<h.length-1&&(S+=" ")),_>0&&h.length>1&&(_==h.length-1||h.length==2)&&(S+="".concat(this.i18n.spaceAnd()," ")),h[_].indexOf("/")>-1||h[_].indexOf("-")>-1){var U=h[_].indexOf("-")>-1&&h[_].indexOf("/")==-1,X=this.getSegmentDescription(h[_],o,n,i,U?this.i18n.commaX0ThroughX1:a,s);U&&(X=X.replace(", ","")),S+=X}else if(!p)S+=n(h[_]);else{var E=this.getSegmentDescription(h[_],o,n,i,a,s);E&&E.startsWith(", ")&&(E=E.substring(2)),S+=E}p?c=S:c=r.StringUtilities.format(s(e),S)}else if(p){var h=e.split("/");if(c=r.StringUtilities.format(i(h[1]),h[1]),h[0].indexOf("-")>-1){var x=this.generateRangeSegmentDescription(h[0],a,n);x.indexOf(", ")!=0&&(c+=", "),c+=x}else if(h[0].indexOf("*")==-1){var A=r.StringUtilities.format(s(h[0]),n(h[0]));A=A.replace(", ",""),c+=r.StringUtilities.format(this.i18n.commaStartingX0(),A)}}else l&&(c=this.generateRangeSegmentDescription(e,a,n));return c},t.prototype.generateRangeSegmentDescription=function(e,o,n){var i="",a=e.split("-"),s=n(a[0],1),c=n(a[1],2),p=o(e);return i+=r.StringUtilities.format(p,s,c),i},t.prototype.formatTime=function(e,o,n){var i=0,a=0,s=parseInt(e)+i,c=parseInt(o)+a;c>=60?(c-=60,s+=1):c<0&&(c+=60,s-=1),s>=24?s=s-24:s<0&&(s=24+s);var p="",l=!1;this.options.use24HourTimeFormat||(l=!!(this.i18n.setPeriodBeforeTime&&this.i18n.setPeriodBeforeTime()),p=l?"".concat(this.getPeriod(s)," "):" ".concat(this.getPeriod(s)),s>12&&(s-=12),s===0&&(s=12));var O="";n&&(O=":".concat(("00"+n).substring(n.length)));var h=s.toString(),S=("00"+h).substring(h.length),_=c.toString(),U=("00"+_).substring(_.length),X=this.options.trimHoursLeadingZero?h:S;return"".concat(l?p:"").concat(X,":").concat(U).concat(O).concat(l?"":p)},t.prototype.transformVerbosity=function(e,o){if(!o&&(e=e.replace(new RegExp(", ".concat(this.i18n.everyMinute()),"g"),""),e=e.replace(new RegExp(", ".concat(this.i18n.everyHour()),"g"),""),e=e.replace(new RegExp(this.i18n.commaEveryDay(),"g"),""),e=e.replace(/\, ?$/,""),this.i18n.conciseVerbosityReplacements))for(var n=0,i=Object.entries(this.i18n.conciseVerbosityReplacements());n<i.length;n++){var a=i[n],s=a[0],c=a[1];e=e.replace(new RegExp(s,"g"),c)}return e},t.prototype.getPeriod=function(e){return e>=12?this.i18n.pm&&this.i18n.pm()||"PM":this.i18n.am&&this.i18n.am()||"AM"},t.locales={},t})();d.ExpressionDescriptor=v},747(g,d,m){Object.defineProperty(d,"__esModule",{value:!0}),d.enLocaleLoader=void 0;var r=m(486),b=(function(){function v(){}return v.prototype.load=function(t){t.en=new r.en},v})();d.enLocaleLoader=b},486(g,d){Object.defineProperty(d,"__esModule",{value:!0}),d.en=void 0;var m=(function(){function r(){}return r.prototype.atX0SecondsPastTheMinuteGt20=function(){return null},r.prototype.atX0MinutesPastTheHourGt20=function(){return null},r.prototype.commaMonthX0ThroughMonthX1=function(){return null},r.prototype.commaYearX0ThroughYearX1=function(){return null},r.prototype.use24HourTimeFormatByDefault=function(){return!1},r.prototype.anErrorOccuredWhenGeneratingTheExpressionD=function(){return"An error occurred when generating the expression description. Check the cron expression syntax."},r.prototype.everyMinute=function(){return"every minute"},r.prototype.everyHour=function(){return"every hour"},r.prototype.atSpace=function(){return"At "},r.prototype.everyMinuteBetweenX0AndX1=function(){return"Every minute between %s and %s"},r.prototype.at=function(){return"At"},r.prototype.spaceAnd=function(){return" and"},r.prototype.everySecond=function(){return"every second"},r.prototype.everyX0Seconds=function(){return"every %s seconds"},r.prototype.secondsX0ThroughX1PastTheMinute=function(){return"seconds %s through %s past the minute"},r.prototype.atX0SecondsPastTheMinute=function(){return"at %s seconds past the minute"},r.prototype.everyX0Minutes=function(){return"every %s minutes"},r.prototype.minutesX0ThroughX1PastTheHour=function(){return"minutes %s through %s past the hour"},r.prototype.atX0MinutesPastTheHour=function(){return"at %s minutes past the hour"},r.prototype.everyX0Hours=function(){return"every %s hours"},r.prototype.betweenX0AndX1=function(){return"between %s and %s"},r.prototype.atX0=function(){return"at %s"},r.prototype.commaEveryDay=function(){return", every day"},r.prototype.commaEveryX0DaysOfTheWeek=function(){return", every %s days of the week"},r.prototype.commaX0ThroughX1=function(){return", %s through %s"},r.prototype.commaAndX0ThroughX1=function(){return", %s through %s"},r.prototype.first=function(){return"first"},r.prototype.second=function(){return"second"},r.prototype.third=function(){return"third"},r.prototype.fourth=function(){return"fourth"},r.prototype.fifth=function(){return"fifth"},r.prototype.commaOnThe=function(){return", on the "},r.prototype.spaceX0OfTheMonth=function(){return" %s of the month"},r.prototype.lastDay=function(){return"the last day"},r.prototype.commaOnTheLastX0OfTheMonth=function(){return", on the last %s of the month"},r.prototype.commaOnlyOnX0=function(){return", only on %s"},r.prototype.commaAndOnX0=function(){return", and on %s"},r.prototype.commaEveryX0Months=function(){return", every %s months"},r.prototype.commaOnlyInX0=function(){return", only in %s"},r.prototype.commaOnTheLastDayOfTheMonth=function(){return", on the last day of the month"},r.prototype.commaOnTheLastWeekdayOfTheMonth=function(){return", on the last weekday of the month"},r.prototype.commaDaysBeforeTheLastDayOfTheMonth=function(){return", %s days before the last day of the month"},r.prototype.firstWeekday=function(){return"first weekday"},r.prototype.weekdayNearestDayX0=function(){return"weekday nearest day %s"},r.prototype.commaOnTheX0OfTheMonth=function(){return", on the %s of the month"},r.prototype.commaEveryX0Days=function(){return", every %s days in a month"},r.prototype.commaBetweenDayX0AndX1OfTheMonth=function(){return", between day %s and %s of the month"},r.prototype.commaOnDayX0OfTheMonth=function(){return", on day %s of the month"},r.prototype.commaEveryHour=function(){return", every hour"},r.prototype.commaEveryX0Years=function(){return", every %s years"},r.prototype.commaStartingX0=function(){return", starting %s"},r.prototype.daysOfTheWeek=function(){return["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]},r.prototype.monthsOfTheYear=function(){return["January","February","March","April","May","June","July","August","September","October","November","December"]},r.prototype.atReboot=function(){return"Run once, at startup"},r.prototype.onTheHour=function(){return"on the hour"},r})();d.en=m},515(g,d){Object.defineProperty(d,"__esModule",{value:!0});function m(b,v){if(!b)throw new Error(v)}var r=(function(){function b(){}return b.secondRange=function(v){for(var t=v.split(","),e=0;e<t.length;e++)if(!isNaN(parseInt(t[e],10))){var o=parseInt(t[e],10);m(o>=0&&o<=59,"seconds part must be >= 0 and <= 59")}},b.minuteRange=function(v){for(var t=v.split(","),e=0;e<t.length;e++)if(!isNaN(parseInt(t[e],10))){var o=parseInt(t[e],10);m(o>=0&&o<=59,"minutes part must be >= 0 and <= 59")}},b.hourRange=function(v){for(var t=v.split(","),e=0;e<t.length;e++)if(!isNaN(parseInt(t[e],10))){var o=parseInt(t[e],10);m(o>=0&&o<=23,"hours part must be >= 0 and <= 23")}},b.dayOfMonthRange=function(v){for(var t=v.split(","),e=0;e<t.length;e++)if(!isNaN(parseInt(t[e],10))){var o=parseInt(t[e],10);m(o>=1&&o<=31,"DOM part must be >= 1 and <= 31")}},b.monthRange=function(v,t){for(var e=v.split(","),o=0;o<e.length;o++)if(!isNaN(parseInt(e[o],10))){var n=parseInt(e[o],10);m(n>=1&&n<=12,t?"month part must be >= 0 and <= 11":"month part must be >= 1 and <= 12")}},b.dayOfWeekRange=function(v,t){for(var e=v.split(","),o=0;o<e.length;o++)if(!isNaN(parseInt(e[o],10))){var n=parseInt(e[o],10);m(n>=0&&n<=6,t?"DOW part must be >= 0 and <= 6":"DOW part must be >= 1 and <= 7")}},b})();d.default=r},823(g,d){Object.defineProperty(d,"__esModule",{value:!0}),d.StringUtilities=void 0;var m=(function(){function r(){}return r.format=function(b){for(var v=[],t=1;t<arguments.length;t++)v[t-1]=arguments[t];return b.replace(/%s/g,function(e){for(var o=[],n=1;n<arguments.length;n++)o[n-1]=arguments[n];return v.shift()})},r.containsAny=function(b,v){return v.some(function(t){return b.indexOf(t)>-1})},r})();d.StringUtilities=m}},f={};function y(g){var d=f[g];if(d!==void 0)return d.exports;var m=f[g]={exports:{}};return u[g](m,m.exports,y),m.exports}var w={};return(()=>{var g=w;Object.defineProperty(g,"__esModule",{value:!0}),g.toString=void 0;var d=y(333),m=y(747);d.ExpressionDescriptor.initialize(new m.enLocaleLoader),g.default=d.ExpressionDescriptor;var r=d.ExpressionDescriptor.toString;g.toString=r})(),w})())});var H=V(B(),1);import T from"chalk";import{readFileSync as J,existsSync as G}from"fs";import{homedir as K}from"os";import{join as q}from"path";var I={local:{name:"Local Development",apiUrl:"http://localhost:3001",accountApiUrl:"http://localhost:3001",frontendUrl:"http://localhost:3000",description:"Local backend running on port 3001"},prod:{name:"Production",apiUrl:process.env.ZIBBY_PROD_API_URL||"https://api-prod.zibby.app",accountApiUrl:process.env.ZIBBY_PROD_ACCOUNT_API_URL||"https://account-api-prod.zibby.app",frontendUrl:process.env.ZIBBY_PROD_FRONTEND_URL||"https://studio.zibby.dev",description:"Production environment"}};function N(){let u;if(process.env.ZIBBY_API_URL)u=process.env.ZIBBY_API_URL;else{let f=process.env.ZIBBY_ENV||"prod";I[f]?u=I[f].apiUrl:u=I.prod.apiUrl}try{let f=new URL(u);return f.protocol!=="http:"&&f.protocol!=="https:"?(console.error(`\u26A0\uFE0F Invalid API URL protocol: ${f.protocol} (only http/https allowed)`),I.prod.apiUrl):u}catch{return console.error(`\u26A0\uFE0F Invalid API URL: ${u}`),I.prod.apiUrl}}function Q(u){if(!u)return null;try{return H.default.toString(u,{use24HourTimeFormat:!1,verbose:!1})}catch{return null}}function ee(u){if(u)return u;let f=q(K(),".zibby","config.json");if(G(f))try{let y=JSON.parse(J(f,"utf-8"));if(y.sessionToken)return y.sessionToken}catch{}if(process.env.ZIBBY_API_KEY)return process.env.ZIBBY_API_KEY;console.error(T.red(`
2
+ Not authenticated`)),console.error(T.gray(" Run: zibby login")),console.error(T.gray(` OR set ZIBBY_API_KEY env var
3
+ `)),process.exit(1)}function te(u=[]){let f={};for(let y of u){let w=y.indexOf("=");if(w<=0)throw new Error(`-p expects key=value, got: ${y}`);f[y.slice(0,w)]=y.slice(w+1)}return f}async function L(u,f,y,w){let g=await fetch(f,{method:u,headers:{"Content-Type":"application/json",Authorization:`Bearer ${y}`},...w!==void 0?{body:JSON.stringify(w)}:{}}),d=await g.text(),m=null;try{m=d?JSON.parse(d):null}catch{}if(!g.ok){let r=m?.error||d||`HTTP ${g.status}`;throw new Error(`API ${u} ${g.status}: ${r}`)}return m}function C(u){if(!u)return T.gray(" (no schedule)");let f=Q(u.cron),y=[` ${T.bold("Cron:")} ${T.cyan(u.cron)}${f?T.gray(` (${f})`):""}`,` ${T.bold("Timezone:")} ${u.timezone||"UTC"}`];return u.input&&Object.keys(u.input).length&&y.push(` ${T.bold("Input:")} ${JSON.stringify(u.input)}`),u.createdAt&&y.push(` ${T.bold("Created:")} ${new Date(u.createdAt).toLocaleString()}`),u.updatedAt&&u.updatedAt!==u.createdAt&&y.push(` ${T.bold("Updated:")} ${new Date(u.updatedAt).toLocaleString()}`),y.join(`
4
+ `)}async function ue(u,f,y,w={}){u||(console.error(T.red("Workflow UUID required.")),console.error(T.gray("Usage: zibby workflow schedule <uuid> [get|set <cron>|clear]")),process.exit(1));let g=ee(w.apiKey),d=N(),m=`${String(d).replace(/\/+$/,"")}/workflows/${encodeURIComponent(u)}/schedule`,r=(f||"get").toLowerCase();try{if(r==="get"){let b=await L("GET",m,g);console.log(T.bold(`
5
+ Schedule for ${u}:`)),console.log(C(b?.schedule)),console.log("");return}if(r==="set"){y||(console.error(T.red("Missing cron expression.")),console.error(T.gray('Usage: zibby workflow schedule <uuid> set "0 9 * * 1-5"')),process.exit(1));let b={cron:y};w.tz&&(b.timezone=w.tz);let v=te(w.param||[]);Object.keys(v).length&&(b.input=v);let t=await L("POST",m,g,b);console.log(T.green(`
6
+ \u2714 Schedule set for ${u}`)),console.log(C(t?.schedule)),console.log(T.gray("\n EventBridge will fire on this cron. Watch with `zibby workflow logs -t`.")),console.log("");return}if(r==="clear"||r==="delete"||r==="remove"){await L("DELETE",m,g),console.log(T.green(`
7
+ \u2714 Schedule cleared for ${u}
8
+ `));return}console.error(T.red(`Unknown action: "${f}"`)),console.error(T.gray("Valid actions: get | set <cron> | clear")),process.exit(1)}catch(b){console.error(T.red(`
9
+ \u2717 ${b.message}
10
+ `)),process.exit(1)}}export{ue as scheduleCommand};
@@ -0,0 +1 @@
1
+ var c=new Set(["claude","cursor","codex","gemini","assistant"]);function u(e,t={}){let r=[],o=t.location||"workflow.json";return!e||typeof e!="object"?(r.push({severity:"error",code:"workflow-json-missing-or-invalid",message:"workflow.json is missing or not a JSON object",location:o}),r):(!e.name||typeof e.name!="string"?r.push({severity:"error",code:"workflow-json-missing-name",message:"workflow.json must have a string `name`",location:o}):/^[a-z][a-z0-9_-]{0,62}[a-z0-9]$/.test(e.name)||r.push({severity:"error",code:"workflow-json-bad-name",message:`workflow.json \`name\` must match [a-z][a-z0-9_-]*[a-z0-9] (got "${e.name}")`,location:o}),(!e.description||typeof e.description!="string")&&r.push({severity:"warning",code:"workflow-json-missing-description",message:"workflow.json should have a `description` (one-line, what the workflow does)",location:o}),e.defaultAgent!=null&&!c.has(e.defaultAgent)&&r.push({severity:"error",code:"workflow-json-bad-agent",message:`workflow.json \`defaultAgent\` must be one of ${[...c].join(", ")} (got "${e.defaultAgent}")`,location:o}),r)}function h(e){return!!e&&typeof e=="object"&&typeof e._def<"u"&&typeof e.parse=="function"}function p(e,t,r={}){let o=[],s=r.location||`node "${e}"`;if(!t||typeof t!="object")return o.push({severity:"error",code:"node-missing",message:`Node "${e}" is missing or not an object`,location:s}),o;t.outputSchema?h(t.outputSchema)||o.push({severity:"warning",code:"node-non-zod-output-schema",message:`Node "${e}" \`outputSchema\` is not a recognizable Zod schema (no _def + parse). Plain-object schemas will be coerced via OutputParser, which is more permissive than the Zod path.`,location:s}):o.push({severity:"error",code:"node-missing-output-schema",message:`Node "${e}" has no \`outputSchema\` \u2014 every node needs a Zod schema`,location:s});let n=t.prompt!=null||t.config&&t.config.prompt!=null,i=typeof t.customExecute=="function";return!n&&!i&&o.push({severity:"error",code:"node-no-prompt-or-execute",message:`Node "${e}" has neither \`prompt\` nor \`execute\` \u2014 needs one of them (prompt for LLM nodes, execute for custom-code nodes)`,location:s}),n&&i&&o.push({severity:"warning",code:"node-both-prompt-and-execute",message:`Node "${e}" defines both \`prompt\` and \`execute\` \u2014 \`execute\` always wins (LLM is skipped). Probably want to drop one.`,location:s}),o}var d=new Set(["END","__end__","end"]);function g(e){let t=[];if(!e||!(e.nodes instanceof Map)||!(e.edges instanceof Map))return t.push({severity:"error",code:"graph-not-a-workflow-graph",message:"buildGraph() did not return a WorkflowGraph instance (or returned an unrecognizable shape)"}),t;e.entryPoint?e.nodes.has(e.entryPoint)||t.push({severity:"error",code:"graph-entry-point-unknown",message:`Graph entry point "${e.entryPoint}" is not a registered node`}):t.push({severity:"error",code:"graph-no-entry-point",message:'Graph has no entry point \u2014 call `graph.setEntryPoint("nodeName")` before returning'});let r=[];for(let[s,n]of e.edges)if(e.nodes.has(s)||t.push({severity:"error",code:"graph-edge-from-unknown",message:`Edge starts from "${s}" but no such node is registered`,location:`addEdge("${s}", \u2026)`}),typeof n=="string")r.push({from:s,to:n});else if(n&&n.conditional&&typeof n.routes=="function"){let a=n.routes.toString().match(/['"`]([A-Za-z_][\w-]*)['"`]/g)||[];for(let f of a){let l=f.slice(1,-1);r.push({from:s,to:l})}}else t.push({severity:"error",code:"graph-bad-edge-target",message:`Edge from "${s}" has an unrecognizable target shape`,location:`addEdge / addConditionalEdges from "${s}"`});for(let{from:s,to:n}of r)d.has(n)||e.nodes.has(n)||t.push({severity:"error",code:"graph-edge-to-unknown",message:`Edge from "${s}" goes to "${n}" but no such node is registered (and it isn't the END sentinel)`,location:`addEdge("${s}", "${n}")`});if(e.entryPoint){let s=new Set([e.entryPoint]),n=!0;for(;n;){n=!1;for(let{from:i,to:a}of r)s.has(i)&&!d.has(a)&&!s.has(a)&&e.nodes.has(a)&&(s.add(a),n=!0)}for(let i of e.nodes.keys())s.has(i)||t.push({severity:"warning",code:"graph-orphan-node",message:`Node "${i}" is registered but unreachable from the entry point "${e.entryPoint}". Add an edge or remove the node.`})}let o=new Set(d);for(let s=0;s<e.nodes.size+1;s++){let n=!1;for(let{from:i,to:a}of r)o.has(a)&&!o.has(i)&&(o.add(i),n=!0);if(!n)break}for(let s of e.nodes.keys())o.has(s)||t.push({severity:"warning",code:"graph-dead-end",message:`Node "${s}" has no path that reaches END \u2014 the workflow will run forever or crash. Add an edge to END (or another terminal-reaching node).`});return t}function m(e,t){let r=[];if(!(e instanceof Map))return r;for(let[o,s]of e){let n=s.config?.skills||[];if(!Array.isArray(n)){r.push({severity:"error",code:"node-skills-not-array",message:`Node "${o}".skills must be an array of strings (got ${typeof n})`,location:`node "${o}"`});continue}for(let i of n){if(typeof i!="string"){r.push({severity:"error",code:"node-skill-not-string",message:`Node "${o}".skills contains a non-string entry: ${JSON.stringify(i)}`,location:`node "${o}"`});continue}t.has(i)||r.push({severity:"error",code:"node-skill-not-registered",message:`Node "${o}" references skill "${i}" which is not registered. Import the skill file in graph.mjs (e.g. import "./skills/${i}.mjs") BEFORE creating the WorkflowGraph.`,location:`node "${o}"`})}}return r}function w({workflowJson:e,graph:t,registeredSkills:r}){let o=[];if(o.push(...u(e)),o.push(...g(t)),t&&t.nodes instanceof Map){for(let[s,n]of t.nodes)o.push(...p(s,n));o.push(...m(t.nodes,r||new Set))}return o}export{w as runAllValidators,g as validateGraphTopology,p as validateNode,m as validateSkillReferences,u as validateWorkflowJson};
@@ -0,0 +1,4 @@
1
+ import i from"chalk";import{readFileSync as E,existsSync as g}from"fs";import{join as f}from"path";import{pathToFileURL as N}from"url";var w=new Set(["claude","cursor","codex","gemini","assistant"]);function y(e,r={}){let s=[],o=r.location||"workflow.json";return!e||typeof e!="object"?(s.push({severity:"error",code:"workflow-json-missing-or-invalid",message:"workflow.json is missing or not a JSON object",location:o}),s):(!e.name||typeof e.name!="string"?s.push({severity:"error",code:"workflow-json-missing-name",message:"workflow.json must have a string `name`",location:o}):/^[a-z][a-z0-9_-]{0,62}[a-z0-9]$/.test(e.name)||s.push({severity:"error",code:"workflow-json-bad-name",message:`workflow.json \`name\` must match [a-z][a-z0-9_-]*[a-z0-9] (got "${e.name}")`,location:o}),(!e.description||typeof e.description!="string")&&s.push({severity:"warning",code:"workflow-json-missing-description",message:"workflow.json should have a `description` (one-line, what the workflow does)",location:o}),e.defaultAgent!=null&&!w.has(e.defaultAgent)&&s.push({severity:"error",code:"workflow-json-bad-agent",message:`workflow.json \`defaultAgent\` must be one of ${[...w].join(", ")} (got "${e.defaultAgent}")`,location:o}),s)}function k(e){return!!e&&typeof e=="object"&&typeof e._def<"u"&&typeof e.parse=="function"}function $(e,r,s={}){let o=[],t=s.location||`node "${e}"`;if(!r||typeof r!="object")return o.push({severity:"error",code:"node-missing",message:`Node "${e}" is missing or not an object`,location:t}),o;r.outputSchema?k(r.outputSchema)||o.push({severity:"warning",code:"node-non-zod-output-schema",message:`Node "${e}" \`outputSchema\` is not a recognizable Zod schema (no _def + parse). Plain-object schemas will be coerced via OutputParser, which is more permissive than the Zod path.`,location:t}):o.push({severity:"error",code:"node-missing-output-schema",message:`Node "${e}" has no \`outputSchema\` \u2014 every node needs a Zod schema`,location:t});let n=r.prompt!=null||r.config&&r.config.prompt!=null,a=typeof r.customExecute=="function";return!n&&!a&&o.push({severity:"error",code:"node-no-prompt-or-execute",message:`Node "${e}" has neither \`prompt\` nor \`execute\` \u2014 needs one of them (prompt for LLM nodes, execute for custom-code nodes)`,location:t}),n&&a&&o.push({severity:"warning",code:"node-both-prompt-and-execute",message:`Node "${e}" defines both \`prompt\` and \`execute\` \u2014 \`execute\` always wins (LLM is skipped). Probably want to drop one.`,location:t}),o}var p=new Set(["END","__end__","end"]);function b(e){let r=[];if(!e||!(e.nodes instanceof Map)||!(e.edges instanceof Map))return r.push({severity:"error",code:"graph-not-a-workflow-graph",message:"buildGraph() did not return a WorkflowGraph instance (or returned an unrecognizable shape)"}),r;e.entryPoint?e.nodes.has(e.entryPoint)||r.push({severity:"error",code:"graph-entry-point-unknown",message:`Graph entry point "${e.entryPoint}" is not a registered node`}):r.push({severity:"error",code:"graph-no-entry-point",message:'Graph has no entry point \u2014 call `graph.setEntryPoint("nodeName")` before returning'});let s=[];for(let[t,n]of e.edges)if(e.nodes.has(t)||r.push({severity:"error",code:"graph-edge-from-unknown",message:`Edge starts from "${t}" but no such node is registered`,location:`addEdge("${t}", \u2026)`}),typeof n=="string")s.push({from:t,to:n});else if(n&&n.conditional&&typeof n.routes=="function"){let l=n.routes.toString().match(/['"`]([A-Za-z_][\w-]*)['"`]/g)||[];for(let u of l){let d=u.slice(1,-1);s.push({from:t,to:d})}}else r.push({severity:"error",code:"graph-bad-edge-target",message:`Edge from "${t}" has an unrecognizable target shape`,location:`addEdge / addConditionalEdges from "${t}"`});for(let{from:t,to:n}of s)p.has(n)||e.nodes.has(n)||r.push({severity:"error",code:"graph-edge-to-unknown",message:`Edge from "${t}" goes to "${n}" but no such node is registered (and it isn't the END sentinel)`,location:`addEdge("${t}", "${n}")`});if(e.entryPoint){let t=new Set([e.entryPoint]),n=!0;for(;n;){n=!1;for(let{from:a,to:l}of s)t.has(a)&&!p.has(l)&&!t.has(l)&&e.nodes.has(l)&&(t.add(l),n=!0)}for(let a of e.nodes.keys())t.has(a)||r.push({severity:"warning",code:"graph-orphan-node",message:`Node "${a}" is registered but unreachable from the entry point "${e.entryPoint}". Add an edge or remove the node.`})}let o=new Set(p);for(let t=0;t<e.nodes.size+1;t++){let n=!1;for(let{from:a,to:l}of s)o.has(l)&&!o.has(a)&&(o.add(a),n=!0);if(!n)break}for(let t of e.nodes.keys())o.has(t)||r.push({severity:"warning",code:"graph-dead-end",message:`Node "${t}" has no path that reaches END \u2014 the workflow will run forever or crash. Add an edge to END (or another terminal-reaching node).`});return r}function v(e,r){let s=[];if(!(e instanceof Map))return s;for(let[o,t]of e){let n=t.config?.skills||[];if(!Array.isArray(n)){s.push({severity:"error",code:"node-skills-not-array",message:`Node "${o}".skills must be an array of strings (got ${typeof n})`,location:`node "${o}"`});continue}for(let a of n){if(typeof a!="string"){s.push({severity:"error",code:"node-skill-not-string",message:`Node "${o}".skills contains a non-string entry: ${JSON.stringify(a)}`,location:`node "${o}"`});continue}r.has(a)||s.push({severity:"error",code:"node-skill-not-registered",message:`Node "${o}" references skill "${a}" which is not registered. Import the skill file in graph.mjs (e.g. import "./skills/${a}.mjs") BEFORE creating the WorkflowGraph.`,location:`node "${o}"`})}}return s}function m({workflowJson:e,graph:r,registeredSkills:s}){let o=[];if(o.push(...y(e)),o.push(...b(r)),r&&r.nodes instanceof Map){for(let[t,n]of r.nodes)o.push(...$(t,n));o.push(...v(r.nodes,s||new Set))}return o}function x(e,r){let s=[f(e,".zibby","workflows",r),f(e,r)];for(let o of s)if(g(f(o,"workflow.json"))&&g(f(o,"graph.mjs")))return o;return null}function S(e){let r=f(e,"workflow.json");if(!g(r))return null;try{return JSON.parse(E(r,"utf-8"))}catch(s){return{__parseError:s.message}}}async function j(e){let r=f(e,"graph.mjs");if(!g(r))throw new Error(`graph.mjs not found at ${r}`);let o=await import(`${N(r).href}?ts=${Date.now()}`),t=o.default||o.buildGraph;if(typeof t!="function")throw new Error("graph.mjs must export a default function (or named `buildGraph`) that returns a WorkflowGraph");return t()}async function z(){try{let{listSkillIds:e}=await import("@zibby/agent-workflow");return new Set(e())}catch{return null}}function _(e){let r=e.severity==="error"?i.red("\u2717 ERROR "):i.yellow("\u26A0 WARN "),s=i.gray(`[${e.code}]`),o=e.location?i.gray(` (${e.location})`):"";return` ${r}${s}${o}
2
+ ${e.message}`}async function M(e,r={}){let s=r.cwd||process.cwd();e||(console.error(i.red("Workflow name required.")),console.error(i.gray("Usage: zibby workflow validate <name>")),console.error(i.gray(" e.g. zibby workflow validate code-review")),process.exit(1));let o=x(s,e);o||(console.error(i.red(`Workflow "${e}" not found.`)),console.error(i.gray(` Looked for .zibby/workflows/${e}/workflow.json + graph.mjs under ${s}`)),console.error(i.gray(" Run `zibby workflow new <name>` to scaffold one, or `zibby workflow list` to see what exists.")),process.exit(1)),console.log(i.bold(`
3
+ Validating workflow: ${i.cyan(e)}`)),console.log(i.gray(` at ${o}
4
+ `));let t=S(o);t?.__parseError&&(console.error(i.red(`\u2717 workflow.json is not valid JSON: ${t.__parseError}`)),process.exit(1));let n;try{n=await j(o)}catch(c){console.error(i.red(`\u2717 Failed to load graph.mjs: ${c.message}`)),c.stack&&r.verbose&&console.error(i.gray(c.stack)),process.exit(1)}let a=await z(),l=m({workflowJson:t,graph:n,registeredSkills:a||new Set}),u=a===null?l.filter(c=>c.code!=="node-skill-not-registered"):l,d=u.filter(c=>c.severity==="error"),h=u.filter(c=>c.severity==="warning");for(let c of u)console.log(_(c));console.log(""),d.length===0&&h.length===0?console.log(i.green(`\u2714 ${e} is valid. Ready for \`zibby workflow run\`.`)):console.log(`${d.length>0?i.red(`${d.length} error(s)`):i.gray("0 errors")}, ${h.length>0?i.yellow(`${h.length} warning(s)`):i.gray("0 warnings")}`),a===null&&console.log(i.gray(" (skill-registration checks skipped \u2014 @zibby/agent-workflow not resolvable; run `npm install` in the workflow folder first)")),console.log(""),d.length>0&&process.exit(1)}export{M as validateCommand};
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/cli",
3
- "version": "0.4.16",
3
+ "version": "0.4.18",
4
4
  "description": "Zibby CLI - Test automation generator and runner",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,13 +34,14 @@
34
34
  "dependencies": {
35
35
  "@aws-sdk/client-sqs": "^3.1038.0",
36
36
  "@zibby/agent-workflow": "^0.3.2",
37
- "@zibby/core": "^0.3.6",
38
- "@zibby/ui-memory": "^1.0.0",
37
+ "@zibby/core": "^0.4.0",
39
38
  "@zibby/skills": "^0.1.11",
39
+ "@zibby/ui-memory": "^1.0.0",
40
40
  "adm-zip": "^0.5.17",
41
41
  "chalk": "^5.3.0",
42
42
  "cli-highlight": "^2.1.11",
43
43
  "commander": "^12.0.0",
44
+ "cronstrue": "^3.14.0",
44
45
  "dotenv": "^17.4.1",
45
46
  "express": "^4.18.2",
46
47
  "glob": "^13.0.0",
@@ -54,6 +55,7 @@
54
55
  },
55
56
  "files": [
56
57
  "dist/",
58
+ "templates/",
57
59
  "README.md",
58
60
  "LICENSE"
59
61
  ],
@@ -0,0 +1,425 @@
1
+ # Zibby project — how to write and ship workflows
2
+
3
+ This file is auto-loaded by Claude Code / Cursor / Codex when working in
4
+ this repo. It's the canonical reference for building Zibby workflows.
5
+
6
+ You are an AI agent. The user describes what they want; you write the
7
+ workflow code (graph + nodes + skills), test it locally, and deploy. The
8
+ user shouldn't need to read the code you produce — they just describe
9
+ the intent.
10
+
11
+ ---
12
+
13
+ ## 0. The 30-second tour
14
+
15
+ ```
16
+ .zibby/workflows/<name>/
17
+ ├── workflow.json # name, description, version, default agent
18
+ ├── graph.mjs # graph topology (nodes + edges)
19
+ ├── nodes/ # one file per node — prompt + outputSchema + skills
20
+ │ ├── plan.mjs
21
+ │ ├── implement.mjs
22
+ │ └── verify.mjs
23
+ └── package.json # @zibby/agent-workflow + zod, nothing else needed
24
+ ```
25
+
26
+ Lifecycle:
27
+
28
+ ```bash
29
+ zibby workflow new <name> # scaffold (creates .zibby/workflows/<name>/)
30
+ zibby workflow run <name> -p key=val # run locally — one-shot, no server
31
+ zibby workflow start <name> # run locally with hot-reload (server)
32
+ zibby workflow validate <name> # static check (graph topology, schemas, skills)
33
+ zibby workflow deploy <name> # push to Zibby Cloud, returns UUID
34
+ zibby workflow trigger <uuid> -p ... # remote run
35
+ zibby workflow schedule <uuid> set … # recurring cron run (Unix 5-field)
36
+ zibby workflow logs -t # tail cloud logs
37
+ ```
38
+
39
+ **Always `zibby workflow run` before `deploy`.** Local run is ~5s cold
40
+ start; cloud is ~60s. Iterating in cloud is 12× slower.
41
+
42
+ ---
43
+
44
+ ## 1. Anatomy of a workflow
45
+
46
+ ### `workflow.json`
47
+
48
+ ```json
49
+ {
50
+ "name": "code-review",
51
+ "description": "Review a git diff and return structured findings",
52
+ "version": "0.1.0",
53
+ "defaultAgent": "claude",
54
+ "stateSchema": {
55
+ "diff": "string",
56
+ "findings": "array"
57
+ }
58
+ }
59
+ ```
60
+
61
+ `defaultAgent` is one of: `claude`, `cursor`, `codex`, `gemini`. Any node
62
+ can override with `agent: 'cursor'` in its config.
63
+
64
+ ### `graph.mjs`
65
+
66
+ ```js
67
+ import { WorkflowGraph } from '@zibby/agent-workflow';
68
+ import { z } from 'zod';
69
+ import { planNode } from './nodes/plan.mjs';
70
+ import { implementNode } from './nodes/implement.mjs';
71
+ import { verifyNode } from './nodes/verify.mjs';
72
+
73
+ export default function buildGraph() {
74
+ const graph = new WorkflowGraph();
75
+
76
+ graph.addNode('plan', planNode);
77
+ graph.addNode('implement', implementNode);
78
+ graph.addNode('verify', verifyNode);
79
+
80
+ graph.addEdge('plan', 'implement');
81
+ graph.addEdge('implement', 'verify');
82
+ graph.addEdge('verify', 'END'); // 'END' is the terminal sentinel
83
+
84
+ graph.setEntryPoint('plan');
85
+
86
+ return graph;
87
+ }
88
+ ```
89
+
90
+ ### A node — `nodes/plan.mjs`
91
+
92
+ ```js
93
+ import { z } from 'zod';
94
+
95
+ export const planNode = {
96
+ name: 'plan',
97
+ agent: 'claude', // optional — falls back to defaultAgent
98
+ outputSchema: z.object({
99
+ steps: z.array(z.string()),
100
+ risks: z.array(z.string()),
101
+ }),
102
+ prompt: (state) => `
103
+ You are planning a code change. The user wants:
104
+ ${state.userRequest}
105
+
106
+ Return:
107
+ - steps: ordered list of actions to take
108
+ - risks: anything that might go wrong
109
+ `,
110
+ };
111
+ ```
112
+
113
+ Three required fields on every LLM node: `name`, `outputSchema` (a Zod
114
+ schema), `prompt` (string or function of state).
115
+
116
+ ### A custom-code node (no LLM)
117
+
118
+ ```js
119
+ export const fetchDiffNode = {
120
+ name: 'fetch_diff',
121
+ outputSchema: z.object({ diff: z.string(), filesChanged: z.array(z.string()) }),
122
+ execute: async (context) => {
123
+ const { execSync } = await import('node:child_process');
124
+ const diff = execSync('git diff --staged', { encoding: 'utf-8' });
125
+ const filesChanged = execSync('git diff --staged --name-only', { encoding: 'utf-8' })
126
+ .trim().split('\n').filter(Boolean);
127
+ return { diff, filesChanged };
128
+ },
129
+ };
130
+ ```
131
+
132
+ Custom-code nodes use `execute(context)` instead of `prompt`. They skip
133
+ the LLM entirely. Use them for deterministic work: git ops, file IO,
134
+ HTTP calls, parsing.
135
+
136
+ ---
137
+
138
+ ## 2. State — read and write
139
+
140
+ ```js
141
+ // READ — state is passed to prompt fns + execute fns as the first arg
142
+ prompt: (state) => `Plan a change for: ${state.userRequest}`
143
+
144
+ execute: async (state) => {
145
+ const diff = state.diff; // read prior node output here
146
+ // ...
147
+ }
148
+ ```
149
+
150
+ **Each node's output is stored at `state[nodeName]`.** If `plan` returns
151
+ `{ steps: [...], risks: [...] }`, the next node reads them as
152
+ `state.plan.steps` and `state.plan.risks`.
153
+
154
+ Initial input goes at the TOP of state (not nested under `input`). When
155
+ the user runs `zibby workflow run code-review -p userRequest="fix login"`,
156
+ the first node sees `state.userRequest = "fix login"`.
157
+
158
+ **Don't use `state.set()` / `state.get()` inside `execute()`** — those
159
+ are internal to the graph runtime. Just `return` a plain object that
160
+ matches your `outputSchema`. The runtime puts it under `state[nodeName]`.
161
+
162
+ ---
163
+
164
+ ## 3. Conditional routing
165
+
166
+ ```js
167
+ graph.addConditionalEdges('verify', (state) => {
168
+ if (state.verify.passed) return 'END';
169
+ if (state.verify.retries < 3) return 'implement';
170
+ return 'fail';
171
+ });
172
+ ```
173
+
174
+ The router function receives the full state, returns the name of the
175
+ next node (or `'END'`). All possible target nodes must also be declared
176
+ elsewhere via `addNode`.
177
+
178
+ ---
179
+
180
+ ## 4. Skills — pluggable MCP tool bundles
181
+
182
+ A **skill** is a named bundle of MCP tools a node can opt into. Built-in
183
+ skills: `browser`, `memory`. Custom skills: register them yourself.
184
+
185
+ ### Using a skill in a node
186
+
187
+ ```js
188
+ export const navigateNode = {
189
+ name: 'navigate',
190
+ skills: ['browser'], // ← opt in
191
+ outputSchema: z.object({ url: z.string(), title: z.string() }),
192
+ prompt: (state) => `Navigate to ${state.target} and report the title.`,
193
+ };
194
+ ```
195
+
196
+ The agent (claude/cursor/codex/gemini) auto-discovers the skill's tools.
197
+ `browser` exposes `mcp__playwright__browser_navigate`, `_snapshot`,
198
+ `_click`, etc. The agent's `allowedTools` is set automatically — you
199
+ don't list each tool.
200
+
201
+ ### Writing a custom skill
202
+
203
+ ```js
204
+ // .zibby/workflows/<name>/skills/slack.mjs
205
+ import { registerSkill } from '@zibby/agent-workflow';
206
+
207
+ registerSkill({
208
+ id: 'slack', // referenced by `skills: ['slack']`
209
+ serverName: 'slack-mcp',
210
+ command: 'npx',
211
+ args: ['-y', '@modelcontextprotocol/server-slack'],
212
+ allowedTools: ['mcp__slack__*'],
213
+ envKeys: ['SLACK_BOT_TOKEN'], // required env to run
214
+ description: 'Read channels, post messages, search history',
215
+ });
216
+ ```
217
+
218
+ Then import it from your `graph.mjs` BEFORE building the graph:
219
+
220
+ ```js
221
+ import './skills/slack.mjs'; // side-effect: registers the skill
222
+ import { WorkflowGraph } from '@zibby/agent-workflow';
223
+ // ...
224
+ ```
225
+
226
+ Skills register on `globalThis` so any node that runs in this process
227
+ can use them. In cloud, set required `envKeys` via
228
+ `zibby workflow env set slack ZIBBY_SECRET SLACK_BOT_TOKEN=xoxb-...`.
229
+
230
+ ### Custom skill via a non-MCP function
231
+
232
+ If you don't have an MCP server, expose a Node function directly:
233
+
234
+ ```js
235
+ registerSkill({
236
+ id: 'jira-fetch',
237
+ resolve: () => null, // no MCP — use middleware instead
238
+ middleware: async () => async (nodeName, next, state) => {
239
+ // attach a JS-only helper. Less common — prefer MCP when possible.
240
+ state.jiraFetch = async (issueId) => { /* ... */ };
241
+ return next();
242
+ },
243
+ });
244
+ ```
245
+
246
+ ---
247
+
248
+ ## 5. Agent strategies — claude / cursor / codex / gemini
249
+
250
+ The same node can run under any agent — they all read `prompt`, return
251
+ the same shaped `outputSchema`. Differences:
252
+
253
+ | Agent | Best for | Auth env |
254
+ |--------|-----------------------------------|-----------------------------------|
255
+ | claude | Reasoning, planning, structured output | `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN` |
256
+ | cursor | Code editing in a repo | `CURSOR_API_KEY` |
257
+ | codex | Direct shell + code generation | `OPENAI_API_KEY` |
258
+ | gemini | Cheap volume tasks | `GEMINI_API_KEY` |
259
+
260
+ Pick per-node (override `defaultAgent`):
261
+
262
+ ```js
263
+ graph.addNode('plan', { ...planNode, agent: 'claude' });
264
+ graph.addNode('implement', { ...implementNode, agent: 'cursor' });
265
+ graph.addNode('verify', { ...verifyNode, agent: 'codex' });
266
+ ```
267
+
268
+ Model overrides go in `.zibby.config.mjs`:
269
+
270
+ ```js
271
+ export default {
272
+ models: {
273
+ plan: 'claude-opus-4-7',
274
+ implement: 'cursor-fast',
275
+ verify: 'gpt-5-codex',
276
+ },
277
+ };
278
+ ```
279
+
280
+ ---
281
+
282
+ ## 6. Running and shipping
283
+
284
+ ### Local dry-run (FAST — do this every iteration)
285
+
286
+ ```bash
287
+ zibby workflow run code-review -p userRequest="add rate limiting to /api"
288
+ ```
289
+
290
+ One-shot run. ~5s cold, then ~2s per warm iteration when the agent has
291
+ cached prompts. Reads + writes to your local filesystem (no cloud
292
+ upload).
293
+
294
+ ### Static validate (FASTER — does NOT run any agent)
295
+
296
+ ```bash
297
+ zibby workflow validate code-review
298
+ ```
299
+
300
+ Checks: graph topology (no orphan nodes, entry point exists, edges
301
+ reach END), node shapes (every node has `outputSchema`), skill
302
+ references (every `skills: ['x']` is registered), schema validity. If
303
+ this fails, `run` will definitely fail too — fix it first.
304
+
305
+ ### Deploy
306
+
307
+ ```bash
308
+ zibby workflow deploy code-review
309
+ # → returns UUID, caches in .zibby-deploy.json
310
+ ```
311
+
312
+ Bundles the workflow folder, uploads to Zibby Cloud. Cloud runs on
313
+ Fargate, picks up per-node API keys you set via `zibby workflow env`.
314
+
315
+ ### Trigger remote run
316
+
317
+ ```bash
318
+ zibby workflow trigger <uuid> -p userRequest="fix login bug" -t
319
+ ```
320
+
321
+ `-t` tails logs in Heroku style. Cmd-C stops the tail (workflow keeps
322
+ running in cloud).
323
+
324
+ ### Schedule recurring runs (cron)
325
+
326
+ ```bash
327
+ # Set / update a schedule (standard Unix 5-field cron)
328
+ zibby workflow schedule <uuid> set "0 9 * * 1-5" # 9am weekdays UTC
329
+ zibby workflow schedule <uuid> set "*/15 * * * *" # every 15 min
330
+ zibby workflow schedule <uuid> set "0 9 * * *" --tz America/Los_Angeles # 9am LA time
331
+ zibby workflow schedule <uuid> set "0 0 * * *" -p kind=nightly # fixed input params
332
+
333
+ # Inspect / clear
334
+ zibby workflow schedule <uuid> # show current
335
+ zibby workflow schedule <uuid> clear # remove
336
+ ```
337
+
338
+ Backed by EventBridge Scheduler. Each scheduled fire goes through the
339
+ same `runWorkflow` path as manual + webhook triggers — same Fargate
340
+ container, same memory sync, same logs.
341
+
342
+ Cron format is **standard Unix 5-field** (`minute hour day-of-month
343
+ month day-of-week`). Use `crontab.guru` to debug expressions. The
344
+ backend converts to EventBridge's 6-field form internally; you never
345
+ see AWS's flavor.
346
+
347
+ **Minimum interval is 5 minutes.** `* * * * *`, `*/2 * * * *`, etc.
348
+ are rejected with 400. Use `*/5 * * * *` (every 5 min) or slower —
349
+ this matches GitHub Actions cron's rule. Each fire = a full Fargate
350
+ run = compute + LLM tokens + workflow-execution quota burn, so
351
+ sub-5-min runs aren't economical at our pricing. If you genuinely
352
+ need sub-minute reactivity, use the **webhook trigger** instead.
353
+
354
+ A workflow has at most ONE schedule. `set` is upsert (create if absent,
355
+ update if present). When the workflow is deleted, the schedule is
356
+ cleaned up too.
357
+
358
+ ---
359
+
360
+ ## 7. Common pitfalls — read before debugging
361
+
362
+ | Symptom | Fix |
363
+ |--------------------------------------------------|------------------------------------------------|
364
+ | `Node 'X' must define outputSchema` | Add `outputSchema: z.object({...})` to the node config |
365
+ | `Skill 'foo' not registered` | Import the skill file in graph.mjs BEFORE `new WorkflowGraph()` |
366
+ | `state.previousNode is undefined` in a prompt | Wrong order — add `graph.addEdge('previousNode', 'thisNode')` |
367
+ | Custom-code node returns nothing | `return` an object matching `outputSchema`. `undefined` = failure |
368
+ | Agent ignores your skill's tools | Add `skills: ['name']` to the node config, not just register |
369
+ | Zod error: "Expected string, received undefined" | The previous node's outputSchema doesn't match its return — fix the producer, not the consumer |
370
+ | `workflow trigger` works but `run` doesn't | Local run reads env from `.env` / shell; cloud reads from `zibby workflow env`. Set both. |
371
+ | Hangs forever on a node | Add `retries: 0` to fail fast while debugging; check the prompt isn't asking the agent to wait |
372
+
373
+ ---
374
+
375
+ ## 8. The agent's job (this is YOU)
376
+
377
+ When the user says **"write me a workflow that does X"**:
378
+
379
+ 1. **Sketch the graph first.** What are the 2-5 nodes? Which can be
380
+ custom-code (deterministic) and which need an LLM (judgement)?
381
+ 2. **Run `zibby workflow new <name>`** to scaffold.
382
+ 3. **Edit the files** — `graph.mjs`, `nodes/*.mjs`. Use Zod for every
383
+ schema. Default to `claude` unless the user has a preference.
384
+ 4. **Run `zibby workflow validate <name>`.** Fix any reported issues.
385
+ 5. **Run `zibby workflow run <name> -p ...`** with a realistic input.
386
+ Watch the timeline output. If a node fails, read its `raw` output
387
+ to understand what the LLM returned vs what the schema expected.
388
+ 6. **Iterate on prompts** — the user shouldn't need to. If a node's
389
+ output doesn't match the schema, tighten the prompt or relax the
390
+ schema (in that order).
391
+ 7. **Once it works locally**, ask the user if they want to deploy.
392
+ Don't deploy without asking — cloud has cost.
393
+
394
+ Read `.claude/commands/` for slash commands the user can invoke:
395
+ `/new-workflow`, `/add-node`, `/add-skill`, `/validate-workflow`.
396
+
397
+ ---
398
+
399
+ ## 9. Quick reference
400
+
401
+ ```js
402
+ import { WorkflowGraph, registerSkill, registerStrategy, AgentStrategy } from '@zibby/agent-workflow';
403
+ import { z } from 'zod';
404
+
405
+ // Graph
406
+ const graph = new WorkflowGraph();
407
+ graph.addNode(name, { prompt, outputSchema, execute, skills, agent, retries });
408
+ graph.addEdge(from, to);
409
+ graph.addConditionalEdges(from, (state) => 'nextNodeName');
410
+ graph.setEntryPoint(name);
411
+
412
+ // Skill
413
+ registerSkill({ id, serverName, command, args, allowedTools, envKeys });
414
+
415
+ // Strategy (rare — only when wrapping a non-built-in LLM)
416
+ class MyAgent extends AgentStrategy { /* implement invoke() */ }
417
+ registerStrategy(new MyAgent());
418
+
419
+ // State (inside execute / prompt fns)
420
+ // READ: state.someKey or state.previousNode.field
421
+ // WRITE: return { ... } from execute() — runtime puts it at state[nodeName]
422
+ ```
423
+
424
+ That's the whole API. Anything else is either a convenience or a
425
+ footgun — when in doubt, prefer the above.
@@ -0,0 +1,63 @@
1
+ ---
2
+ description: Add a node to an existing Zibby workflow graph
3
+ argument-hint: <workflow-name> <node-purpose>
4
+ ---
5
+
6
+ # /add-node
7
+
8
+ The user wants to extend an existing workflow with a new node.
9
+
10
+ **Arguments:** $ARGUMENTS
11
+
12
+ ## Steps
13
+
14
+ 1. **Find the workflow** — should be at `.zibby/workflows/<name>/`. If
15
+ the user didn't specify a name and there's only one workflow, use
16
+ that. If there are multiple, ask which.
17
+
18
+ 2. **Read the existing `graph.mjs`** to understand:
19
+ - Current node sequence
20
+ - State shape (what each prior node returns)
21
+ - Where the new node should slot in (before / after / parallel)
22
+
23
+ 3. **Decide LLM vs custom-code:**
24
+ - Custom-code (`execute`): the work is deterministic — git ops,
25
+ file IO, HTTP, parsing structured data, math
26
+ - LLM (`prompt`): the work needs judgement — summarization,
27
+ classification, generation, planning
28
+
29
+ 4. **Create the node file** at `.zibby/workflows/<name>/nodes/<node>.mjs`:
30
+
31
+ ```js
32
+ import { z } from 'zod';
33
+
34
+ export const myNode = {
35
+ name: 'my_node',
36
+ outputSchema: z.object({ /* what this returns to state */ }),
37
+ prompt: (state) => `…use state.previousNodeName.field…`,
38
+ // OR for custom-code:
39
+ // execute: async (state) => ({ /* match outputSchema */ }),
40
+ };
41
+ ```
42
+
43
+ 5. **Wire it into `graph.mjs`:**
44
+ - `import { myNode } from './nodes/my-node.mjs';`
45
+ - `graph.addNode('my_node', myNode);`
46
+ - `graph.addEdge('prev_node', 'my_node');`
47
+ - `graph.addEdge('my_node', 'next_node');` (or 'END')
48
+
49
+ 6. **Validate + run** to confirm it integrates:
50
+ ```bash
51
+ zibby workflow validate <name>
52
+ zibby workflow run <name> -p ...
53
+ ```
54
+
55
+ 7. **Report**: what node you added, how state changed, the test result.
56
+
57
+ ## Watch for
58
+
59
+ - The new node's `prompt` references `state.X.field` — make sure that
60
+ field exists in the previous node's `outputSchema`. If it doesn't,
61
+ fix the producer schema, not the consumer prompt.
62
+ - Don't change other nodes' schemas without telling the user — that's
63
+ a breaking change to downstream consumers.