@crossdelta/platform-sdk 0.7.7 → 0.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -162,13 +162,17 @@ When you create a workspace with `pf`, you get a **Turborepo monorepo** with thi
162
162
  ```
163
163
  my-platform/
164
164
  ├── services/ # Microservices (Hono, NestJS)
165
- │ ├── orders/ # Example: Order processing service
166
- │ ├── notifications/# Example: Notification service
165
+ │ ├── orders/ # Example: Order processing service (Event Publisher)
166
+ │ ├── notifications/# Example: Notification service (Event Consumer)
167
167
  │ └── nats/ # NATS message broker (auto-scaffolded)
168
168
  ├── apps/ # Frontend apps (optional: Qwik, Next.js, etc.)
169
169
  ├── packages/ # Shared libraries
170
+ │ ├── contracts/ # Event contracts (Schema Registry - created when generating Event Consumers)
171
+ │ │ └── src/
172
+ │ │ └── index.ts # Export all contracts
170
173
  │ ├── cloudevents/ # Event publishing/consuming toolkit
171
- └── telemetry/ # OpenTelemetry setup
174
+ ├── telemetry/ # OpenTelemetry setup
175
+ │ └── infrastructure/ # Pulumi utilities and K8s builders
172
176
  ├── infra/ # Pulumi Infrastructure-as-Code
173
177
  │ ├── index.ts # Main Pulumi program
174
178
  │ └── services/ # Per-service K8s configs
@@ -180,28 +184,56 @@ my-platform/
180
184
  └── .env.local # Auto-generated from infra configs
181
185
  ```
182
186
 
187
+ > **Note:** The `packages/contracts` package starts with just an `index.ts`. Event contract files (e.g., `events/order-created.ts`) are automatically generated when you create **Event Consumer** services with `pf new hono-micro --ai`.
188
+
183
189
  ### Key Architectural Decisions
184
190
 
185
191
  1. **NATS + JetStream baseline** — Event-driven communication is built-in, not bolted on
186
- 2. **Infrastructure-as-Code by default** — Every service has a matching `infra/services/<name>.ts` config
187
- 3. **Auto-wiring everywhere** — Ports, env vars, and NATS subjects are derived automatically
188
- 4. **Opinionated conventions** — Event handlers live in `src/events/*.event.ts`, business logic in `src/use-cases/*.use-case.ts`
189
- 5. **Bun-first DX** — Ultra-fast installs, tests, and dev server with fallback to npm/yarn
192
+ 2. **Schema Registry Pattern** — All event schemas live in `packages/contracts` as single source of truth
193
+ 3. **Infrastructure-as-Code by default** — Every service has a matching `infra/services/<name>.ts` config
194
+ 4. **Auto-wiring everywhere** — Ports, env vars, NATS subjects, and event contracts are derived automatically
195
+ 5. **AI-assisted generation** — Services generate with correct event schemas from natural language descriptions
196
+ 6. **Opinionated conventions** — Event handlers in `src/events/*.event.ts`, business logic in `src/use-cases/*.use-case.ts`, schemas in `src/types/*.ts`
197
+ 7. **Bun-first DX** — Ultra-fast installs, tests, and dev server with fallback to npm/yarn
190
198
 
191
199
  ### Event-Driven Mental Model
192
200
 
193
- Services communicate via **CloudEvents** over **NATS JetStream**:
201
+ Services communicate via **CloudEvents** over **NATS JetStream** using the **Schema Registry** as single source of truth:
194
202
 
195
203
  ```typescript
196
- // Service A publishes an event
197
- await publish('orders.created', { orderId: '123', total: 99.99 })
204
+ // packages/contracts/src/events/order-created.ts (Schema Registry)
205
+ import { createContract } from '@crossdelta/cloudevents'
206
+ import { z } from 'zod'
207
+
208
+ export const OrderCreatedContract = createContract({
209
+ type: 'orders.created',
210
+ schema: z.object({
211
+ orderId: z.string(),
212
+ customerId: z.string(),
213
+ total: z.number(),
214
+ }),
215
+ })
216
+
217
+ export type OrderCreatedData = z.infer<typeof OrderCreatedContract.schema>
218
+
219
+ // Service A publishes an event (Event Publisher)
220
+ import { publish } from '@crossdelta/cloudevents'
221
+ import { OrderCreatedContract } from '@my-platform/contracts'
198
222
 
199
- // Service B auto-discovers and handles it
223
+ await publish(OrderCreatedContract, {
224
+ orderId: '123',
225
+ customerId: 'cust-456',
226
+ total: 99.99
227
+ })
228
+
229
+ // Service B auto-discovers and handles it (Event Consumer)
200
230
  // File: services/notifications/src/events/order-created.event.ts
201
- export default handleEvent(
202
- { schema: OrderCreatedSchema, type: 'orders.created' },
203
- async (data) => { await sendNotification(data) }
204
- )
231
+ import { handleEvent } from '@crossdelta/cloudevents'
232
+ import { OrderCreatedContract, type OrderCreatedData } from '@my-platform/contracts'
233
+
234
+ export default handleEvent(OrderCreatedContract, async (data: OrderCreatedData) => {
235
+ await sendNotification(data)
236
+ })
205
237
  ```
206
238
 
207
239
  No manual NATS subscriptions. No boilerplate. Just **convention over configuration**.
package/bin/cli.js CHANGED
@@ -93,7 +93,7 @@ Expecting one of '${s.join("', '")}'`);let i=`${e}Help`;return this.on(i,n=>{let
93
93
  `),m="";return c&&(m=u.style.error(c)),[`${h} ${d} ${o}`,[p,m].filter(Boolean).join(`
94
94
  `)]});var Wu=ne((r,e)=>{let{validate:t=()=>!0}=r,s=X(r.theme),[i,n]=P("idle"),[o,a]=P(),[c,l]=P(""),u=se({status:i,theme:s});ie(async(m,b)=>{if(i==="idle")if(re(m)){let x=c;n("loading");let g=await t(x);g===!0?(l(x),n("done"),e(x)):(b.write(c),a(g||"You must provide a valid value"),n("idle"))}else l(b.line),a(void 0)});let h=s.style.message(r.message,i),f="",d;r.mask?f=(typeof r.mask=="string"?r.mask:"*").repeat(c.length):i!=="done"&&(d=`${s.style.help("[input is masked]")}${gs}`),i==="done"&&(f=s.style.answer(f));let p="";return o&&(p=s.style.error(o)),[[u,h,r.mask?f:d].join(" "),p]});var Gr=E(cr(),1);var T8={icon:{cursor:Xe.pointer},style:{disabled:r=>Gr.default.dim(`- ${r}`),searchTerm:r=>Gr.default.cyan(r),description:r=>Gr.default.cyan(r),keysHelpTip:r=>r.map(([e,t])=>`${Gr.default.bold(e)} ${Gr.default.dim(t)}`).join(Gr.default.dim(" \u2022 "))},helpMode:"always"};function Uu(r){return!L.isSeparator(r)&&!r.disabled}function P8(r){return r.map(e=>{if(L.isSeparator(e))return e;if(typeof e=="string")return{value:e,name:e,short:e,disabled:!1};let t=e.name??String(e.value),s={value:e.value,name:t,short:e.short??t,disabled:e.disabled??!1};return e.description&&(s.description=e.description),s})}var O8=ne((r,e)=>{let{pageSize:t=7,validate:s=()=>!0}=r,i=X(T8,r.theme),[n,o]=P("loading"),[a,c]=P(""),[l,u]=P([]),[h,f]=P(),d=se({status:n,theme:i}),p=$e(()=>{let C=l.findIndex(Uu),T=l.findLastIndex(Uu);return{first:C,last:T}},[l]),[m=p.first,b]=P();Ue(()=>{let C=new AbortController;return o("loading"),f(void 0),(async()=>{try{let M=await r.source(a||void 0,{signal:C.signal});C.signal.aborted||(b(void 0),f(void 0),u(P8(M)),o("idle"))}catch(M){!C.signal.aborted&&M instanceof Error&&f(M.message)}})(),()=>{C.abort()}},[a]);let x=l[m];ie(async(C,T)=>{if(re(C))if(x){o("loading");let M=await s(x.value);o("idle"),M===!0?(o("done"),e(x.value)):x.name===a?f(M||"You must provide a valid value"):(T.write(x.name),c(x.name))}else T.write(a);else if(Ht(C)&&x)T.clearLine(0),T.write(x.name),c(x.name);else if(n!=="loading"&&(be(C)||Me(C))){if(T.clearLine(0),be(C)&&m!==p.first||Me(C)&&m!==p.last){let M=be(C)?-1:1,K=m;do K=(K+M+l.length)%l.length;while(!Uu(l[K]));b(K)}}else c(T.line)});let g=i.style.message(r.message,n),y;if(i.helpMode!=="never")if(r.instructions){let{pager:C,navigation:T}=r.instructions;y=i.style.help(l.length>t?C:T)}else y=i.style.keysHelpTip([["\u2191\u2193","navigate"],["\u23CE","select"]]);let w=Nr({items:l,active:m,renderItem({item:C,isActive:T}){if(L.isSeparator(C))return` ${C.separator}`;if(C.disabled){let Be=typeof C.disabled=="string"?C.disabled:"(disabled)";return i.style.disabled(`${C.name} ${Be}`)}let M=T?i.style.highlight:Be=>Be,K=T?i.icon.cursor:" ";return M(`${K} ${C.name}`)},pageSize:t,loop:!1}),v;h?v=i.style.error(h):l.length===0&&a!==""&&n==="idle"&&(v=i.style.error("No results found"));let k;if(n==="done"&&x)return[d,g,i.style.answer(x.short)].filter(Boolean).join(" ").trimEnd();k=i.style.searchTerm(a);let _=x?.description,O=[d,g,k].filter(Boolean).join(" ").trimEnd(),A=[v??w," ",_?i.style.description(_):"",y].filter(Boolean).join(`
95
95
  `).trimEnd();return[O,A]});var Cs=E(cr(),1);var I8={icon:{cursor:Xe.pointer},style:{disabled:r=>Cs.default.dim(`- ${r}`),description:r=>Cs.default.cyan(r),keysHelpTip:r=>r.map(([e,t])=>`${Cs.default.bold(e)} ${Cs.default.dim(t)}`).join(Cs.default.dim(" \u2022 "))},helpMode:"always",indexMode:"hidden",keybindings:[]};function vs(r){return!L.isSeparator(r)&&!r.disabled}function B8(r){return r.map(e=>{if(L.isSeparator(e))return e;if(typeof e=="string")return{value:e,name:e,short:e,disabled:!1};let t=e.name??String(e.value),s={value:e.value,name:t,short:e.short??t,disabled:e.disabled??!1};return e.description&&(s.description=e.description),s})}var xo=ne((r,e)=>{let{loop:t=!0,pageSize:s=7}=r,i=X(I8,r.theme),{keybindings:n}=i,[o,a]=P("idle"),c=se({status:o,theme:i}),l=$r(),u=!n.includes("vim"),h=$e(()=>B8(r.choices),[r.choices]),f=$e(()=>{let _=h.findIndex(vs),O=h.findLastIndex(vs);if(_===-1)throw new St("[select prompt] No selectable choices. All choices are disabled.");return{first:_,last:O}},[h]),d=$e(()=>"default"in r?h.findIndex(_=>vs(_)&&_.value===r.default):-1,[r.default,h]),[p,m]=P(d===-1?f.first:d),b=h[p];ie((_,O)=>{if(clearTimeout(l.current),re(_))a("done"),e(b.value);else if(be(_,n)||Me(_,n)){if(O.clearLine(0),t||be(_,n)&&p!==f.first||Me(_,n)&&p!==f.last){let A=be(_,n)?-1:1,C=p;do C=(C+A+h.length)%h.length;while(!vs(h[C]));m(C)}}else if(wi(_)&&!Number.isNaN(Number(O.line))){let A=Number(O.line)-1,C=-1,T=h.findIndex(K=>L.isSeparator(K)?!1:(C++,C===A)),M=h[T];M!=null&&vs(M)&&m(T),l.current=setTimeout(()=>{O.clearLine(0)},700)}else if(Br(_))O.clearLine(0);else if(u){let A=O.line.toLowerCase(),C=h.findIndex(T=>L.isSeparator(T)||!vs(T)?!1:T.name.toLowerCase().startsWith(A));C!==-1&&m(C),l.current=setTimeout(()=>{O.clearLine(0)},700)}}),Ue(()=>()=>{clearTimeout(l.current)},[]);let x=i.style.message(r.message,o),g;if(i.helpMode!=="never")if(r.instructions){let{pager:_,navigation:O}=r.instructions;g=i.style.help(h.length>s?_:O)}else g=i.style.keysHelpTip([["\u2191\u2193","navigate"],["\u23CE","select"]]);let y=0,w=Nr({items:h,active:p,renderItem({item:_,isActive:O,index:A}){if(L.isSeparator(_))return y++,` ${_.separator}`;let C=i.indexMode==="number"?`${A+1-y}. `:"";if(_.disabled){let K=typeof _.disabled=="string"?_.disabled:"(disabled)";return i.style.disabled(`${C}${_.name} ${K}`)}let T=O?i.style.highlight:K=>K,M=O?i.icon.cursor:" ";return T(`${M} ${C}${_.name}`)},pageSize:s,loop:t});if(o==="done")return[c,x,i.style.answer(b.short)].filter(Boolean).join(" ");let{description:v}=b;return`${[[c,x].filter(Boolean).join(" "),w," ",v?i.style.description(v):"",g].filter(Boolean).join(`
96
- `).trimEnd()}${gs}`});var H=E(require("chalk")),v2=E(require("ora"));var G=require("node:fs"),Se=require("node:path"),Lg=require("@anatine/zod-mock");var Bi=E(require("chalk"));function qu(r,e){if(r.length===0)return"No entries to display.";let t=e?.title||"Benchmark Results",s=e?.footer||"",i=e?.maxBarWidth||50,n=e?.unit||"",o=Math.max(...r.map(c=>c.value)),a=[];a.push(""),a.push(t),a.push("");for(let c of r){let l=R8(c.barColor??e?.barColor),u=M8(c,o,i,l,n,e?.labelColor);a.push(u)}return s&&(a.push(""),a.push(s)),a.join(`
96
+ `).trimEnd()}${gs}`});var G=E(require("chalk")),v2=E(require("ora"));var H=require("node:fs"),Se=require("node:path"),Lg=require("@anatine/zod-mock");var Bi=E(require("chalk"));function qu(r,e){if(r.length===0)return"No entries to display.";let t=e?.title||"Benchmark Results",s=e?.footer||"",i=e?.maxBarWidth||50,n=e?.unit||"",o=Math.max(...r.map(c=>c.value)),a=[];a.push(""),a.push(t),a.push("");for(let c of r){let l=R8(c.barColor??e?.barColor),u=M8(c,o,i,l,n,e?.labelColor);a.push(u)}return s&&(a.push(""),a.push(s)),a.join(`
97
97
  `)}function R8(r){return r?Bi.default[r]("\u2587"):"\u2587"}function M8(r,e,t,s,i,n){let o=Math.round(r.value/e*t),a=r.barColor?Bi.default[r.barColor](s.repeat(o)):s.repeat(o),c=r.color??n;return`${Bi.default.bold(c?Bi.default[c](r.label.padEnd(12)):r.label.padEnd(12))} ${a} ${r.value.toLocaleString()}${i?` ${i}`:""}`}var go=E(require("chalk"));var Hu=require("listr2");var Ri=E(require("chalk")),S={logs:[],breakLine:()=>(console.log(),S),success:(r,...e)=>(console.log(Ri.default.green(`\u2714 ${r}`),...e),S),info:(r,...e)=>(console.log(Ri.default.cyan(`\u{1F6C8} ${r}`),...e),S),warn:(r,...e)=>(console.warn(Ri.default.yellow(`\u26A0\uFE0E ${r}`),...e),S),error:(r,...e)=>(console.error(Ri.default.red(`\u2716 ${r}`),...e),S),log:(r,...e)=>(console.log(r,...e),S.logs.push({message:r,context:e.join()}),S),getStoredLogs:r=>r?S.logs.filter(e=>e.context?.includes(r)):S.logs,storeLog:(r,e)=>S.logs.push({message:r,context:e})};function hr(r){let e=new ce(r.name).description(go.default.bold(r.description)).showHelpAfterError();return r.arguments?.map(([t,s])=>e.argument(t,s)),r.options?.map(([t,s])=>e.option(t,s)),r.exampleUsage&&e.addHelpText("after",()=>`${go.default.cyan.bold(`
98
98
  Example:`)}
99
99
  ${go.default.bold(r.exampleUsage)}
@@ -110,7 +110,7 @@ To create a new workspace, run:
110
110
 
111
111
  \x1B[36mpf new workspace my-platform\x1B[0m
112
112
  `);r=e}return r}function E0(){let r=ae(),e=(0,Ge.join)(r,"package.json"),s=JSON.parse((0,It.readFileSync)(e,"utf-8")).name;return s.startsWith("@")?s.split("/")[0]:`@${s}`}function Jr(r,e,t=process.cwd()){let s=(0,Ge.join)(t,"package.json"),i=(0,Ls.readJsonSync)(s);z4(i,r,e),(0,Ls.writeJsonSync)(s,i,{spaces:2,EOL:`
113
- `,encoding:"utf-8"})}function z4(r,e,t){let s=e.split("."),i=r;for(let n=0;n<s.length-1;n++){let o=s[n];(!(o in i)||typeof i[o]!="object"||i[o]===null)&&(i[o]={}),i=i[o]}i[s[s.length-1]]=t}var Ws=r=>{let e=Rg();return(0,Ls.readJSONSync)((0,Ge.resolve)(e,r),{encoding:"utf-8"})},wr=Ws("package.json");var K4={firstname:"firstName",lastname:"lastName",fullname:"firstName",street:"streetAddress",address:"streetAddress",city:"city",state:"state",country:"country",phone:"phoneNumber",company:"companyName",organization:"companyName",productname:"productName",product:"productName",item:"productName",productdescription:"productDescription",description:"productDescription",password:"password",jobtitle:"string",job:"string",title:"string",sku:"string",code:"string",price:"number",amount:"number",total:"number",qty:"number",quantity:"number",count:"number",status:"string",date:"date",createdat:"date",updatedat:"date",birthday:"date",birthdate:"date"},Y4={email:"email",id:"uuid",createdat:"date",updatedat:"date",firstname:"firstName",lastname:"lastName"},J4=(r,e)=>{let t=r.toLowerCase();if(e.includes(".email()")||t.includes("email"))return"email";if(e.includes(".uuid()")||t.includes("id")&&!t.includes("email"))return"uuid";if(e.includes(".url()")||t.includes("url")||t.includes("website"))return"url";if(t==="name"&&!t.includes("username"))return"productName";if(t.includes("zipcode")||t.includes("zip")||t.includes("postal"))return"zipCode";for(let[s,i]of Object.entries(K4))if(t.includes(s))return i;return null},Z4=r=>{let e=r.match(/type:\s*['"]([^'"]+)['"]/s);if(!e)return null;let t=r.match(/const\s+(\w+Contract)\s*=\s*\{[\s\S]*?type:\s*['"]([^'"]+)['"][\s\S]*?schema:\s*z\.object\s*\(/s);if(t){let f=t[1],d=r.indexOf(`const ${f}`),p=r.indexOf("z.object(",d),m=r.indexOf("{",p),b=1,x=m+1;for(;x<r.length&&b>0;)r[x]==="{"&&b++,r[x]==="}"&&b--,x++;let g=r.indexOf(")",x),y=`const ${f}Schema = z.object(${r.substring(m,g+1)}`;return{eventType:e[1],schemaName:`${f}Schema`,schemaCode:y}}let s=r.match(/import\s+\{[^}]*?(\w+Schema)[^}]*?\}\s+from\s+['"]\.\.\/types\//);if(s)return{eventType:e[1],schemaName:s[1],schemaCode:""};let i=r.match(/const\s+(\w+Schema)\s*=\s*z\.object\(/s);if(!i)return null;let n=r.indexOf(`const ${i[1]}`),o=r.indexOf("z.object(",n),a=r.indexOf("{",o),c=1,l=a+1;for(;l<r.length&&c>0;)r[l]==="{"&&c++,r[l]==="}"&&c--,l++;let u=r.indexOf(")",l),h=r.substring(n,u+1);return{eventType:e[1],schemaName:i[1],schemaCode:h}},X4=(r,e,t)=>r==="email"?"user@example.com":r==="uuid"?"550e8400-e29b-41d4-a716-446655440000":r==="firstName"?"John":r==="lastName"?"Doe":e==="string"?`sample-${t}`:e==="number"?100:e==="boolean"?!0:e==="array"?[]:null,Q4=async(r,e,t)=>{let s={};try{let{createJiti:i}=await import("jiti"),o=i(process.cwd(),{interopDefault:!0})(t),a=Object.entries(o).find(([c])=>c.endsWith("Schema")||c==="schema");if(a?.[1]){let c=a[1],l=(0,Lg.generateMock)(c);if(l&&typeof l=="object"&&Object.keys(l).length>0)return S0(l,s,""),{data:l,faker:s}}}catch{}return Wg(r)},S0=(r,e,t)=>{!r||typeof r!="object"||Object.entries(r).forEach(([s,i])=>{let n=t?`${t}.${s}`:s,o=s.toLowerCase(),a=Object.entries(Y4).find(([c])=>o.includes(c));a&&(a[0]!=="id"||typeof i=="string")&&(e[n]=a[1]),typeof i=="object"&&!Array.isArray(i)?S0(i,e,n):Array.isArray(i)&&i.length>0&&S0(i[0],e,`${n}[0]`)})},Wg=r=>{let e={},t={},s=r.match(/z\.object\(\s*\{([\s\S]*)\}\s*\)/m);if(!s)return{data:e,faker:t};let i=s[1];return k0(i,e,t,""),{data:e,faker:t}},k0=(r,e,t,s)=>{let i=/^\s*(\w+):\s*z\.(\w+)\(/gm;Array.from(r.matchAll(i)).filter(a=>a.index!==void 0).map(a=>{let c=a[1],l=a[2],u=a.index,h=1,f=u+a[0].length;for(;f<r.length&&h>0;){let g=r[f];g==="("&&h++,g===")"&&h--,f++}let d=r.substring(u,f),p=r.substring(0,u),m=(p.match(/\{/g)||[]).length,b=(p.match(/\}/g)||[]).length,x=m-b;return{name:c,zodType:l,def:d,nestingLevel:x}}).filter(a=>a.nestingLevel===0).forEach(a=>{e5(a.name,a.zodType,a.def,e,t,s)})},e5=(r,e,t,s,i,n)=>{let o=n?`${n}.${r}`:r,a=t.includes(".optional()");e==="array"?t5(t,s,r,o,i):e==="object"?r5(t,s,r,o,i):s5(r,e,a,o,s,i)},t5=(r,e,t,s,i)=>{if(r.includes("z.object")){let n=r.match(/z\.object\(\s*\{([\s\S]*?)\}\s*\)/);if(n){let o={};k0(n[1],o,i,`${s}[0]`),e[t]=[o];return}}e[t]=[]},r5=(r,e,t,s,i)=>{let n=r.match(/z\.object\(\s*\{([\s\S]*?)\}\s*\)/);n&&(e[t]={},k0(n[1],e[t],i,s))},s5=(r,e,t,s,i,n)=>{let o=J4(r,`z.${e}()${t?".optional()":""}`),a=X4(o,e,r);o&&(n[s]=o),(!t||a!==null)&&(i[r]=a)},Ug=r=>r.replace(/\./g,"-"),i5=async(r,e,t)=>{let s=ae(),{eventsPath:i,indexPath:n,packagePath:o}=Bt(),a=t.outputDir??i;if(!(0,G.existsSync)(a))return S.error(`Contracts events directory not found: ${a}`),{mockPath:null};let c=e.match(/import\s+\{[^}]*?(\w+Contract)[^}]*?\}\s+from\s+['"]([^'"]+)\/contracts['"]/);if(!c)return{mockPath:null,skipped:!0,reason:"Advanced Mode handler (uses contracts)"};let l=c[1],u=c[2];if(!e.match(/handleEvent\s*\(\s*(\w+Contract)/))return{mockPath:null,skipped:!0,reason:"Could not parse handleEvent call"};let f=l.replace("Contract","").replace(/([A-Z])/g,(_,O,A)=>(A>0?"-":"")+_.toLowerCase()).toLowerCase(),d=`${f}.ts`,p=(0,Se.join)(a,d);if((0,G.existsSync)(p)){let _=(0,G.readFileSync)(p,"utf-8"),O=_.match(/export const (\w+)\s*=\s*z\.object\(\s*\{([\s\S]*?)\}\s*\)/m);if(O){let A=O[2],{data:C,faker:T}=Wg(`z.object({${A}})`),M=_.match(/type:\s*['"]([^'"]+)['"]/),K=M?M[1]:f.replace(/-/g,"."),Be=`${f}.mock.json`,Lt=(0,Se.join)(a,Be);if((0,G.existsSync)(Lt)&&!t.overwrite)return{mockPath:null,skipped:!0,reason:"Mock already exists"};let Sh={eventName:K,description:`Mock data for ${K} event`,data:C};return Object.keys(T).length>0&&(Sh.faker=T),(0,G.writeFileSync)(Lt,JSON.stringify(Sh,null,2),"utf-8"),{mockPath:Lt,skipped:!1,reason:void 0}}}let m=f.replace(/-/g,"."),b=l.replace("Contract","Data"),x=`import { createContract } from '@crossdelta/cloudevents'
113
+ `,encoding:"utf-8"})}function z4(r,e,t){let s=e.split("."),i=r;for(let n=0;n<s.length-1;n++){let o=s[n];(!(o in i)||typeof i[o]!="object"||i[o]===null)&&(i[o]={}),i=i[o]}i[s[s.length-1]]=t}var Ws=r=>{let e=Rg();return(0,Ls.readJSONSync)((0,Ge.resolve)(e,r),{encoding:"utf-8"})},wr=Ws("package.json");var K4={firstname:"firstName",lastname:"lastName",fullname:"firstName",street:"streetAddress",address:"streetAddress",city:"city",state:"state",country:"country",phone:"phoneNumber",company:"companyName",organization:"companyName",productname:"productName",product:"productName",item:"productName",productdescription:"productDescription",description:"productDescription",password:"password",jobtitle:"string",job:"string",title:"string",sku:"string",code:"string",price:"number",amount:"number",total:"number",qty:"number",quantity:"number",count:"number",status:"string",date:"date",createdat:"date",updatedat:"date",birthday:"date",birthdate:"date"},Y4={email:"email",id:"uuid",createdat:"date",updatedat:"date",firstname:"firstName",lastname:"lastName"},J4=(r,e)=>{let t=r.toLowerCase();if(e.includes(".email()")||t.includes("email"))return"email";if(e.includes(".uuid()")||t.includes("id")&&!t.includes("email"))return"uuid";if(e.includes(".url()")||t.includes("url")||t.includes("website"))return"url";if(t==="name"&&!t.includes("username"))return"productName";if(t.includes("zipcode")||t.includes("zip")||t.includes("postal"))return"zipCode";for(let[s,i]of Object.entries(K4))if(t.includes(s))return i;return null},Z4=r=>{let e=r.match(/type:\s*['"]([^'"]+)['"]/s);if(!e)return null;let t=r.match(/const\s+(\w+Contract)\s*=\s*\{[\s\S]*?type:\s*['"]([^'"]+)['"][\s\S]*?schema:\s*z\.object\s*\(/s);if(t){let f=t[1],d=r.indexOf(`const ${f}`),p=r.indexOf("z.object(",d),m=r.indexOf("{",p),b=1,x=m+1;for(;x<r.length&&b>0;)r[x]==="{"&&b++,r[x]==="}"&&b--,x++;let g=r.indexOf(")",x),y=`const ${f}Schema = z.object(${r.substring(m,g+1)}`;return{eventType:e[1],schemaName:`${f}Schema`,schemaCode:y}}let s=r.match(/import\s+\{[^}]*?(\w+Schema)[^}]*?\}\s+from\s+['"]\.\.\/types\//);if(s)return{eventType:e[1],schemaName:s[1],schemaCode:""};let i=r.match(/const\s+(\w+Schema)\s*=\s*z\.object\(/s);if(!i)return null;let n=r.indexOf(`const ${i[1]}`),o=r.indexOf("z.object(",n),a=r.indexOf("{",o),c=1,l=a+1;for(;l<r.length&&c>0;)r[l]==="{"&&c++,r[l]==="}"&&c--,l++;let u=r.indexOf(")",l),h=r.substring(n,u+1);return{eventType:e[1],schemaName:i[1],schemaCode:h}},X4=(r,e,t)=>r==="email"?"user@example.com":r==="uuid"?"550e8400-e29b-41d4-a716-446655440000":r==="firstName"?"John":r==="lastName"?"Doe":e==="string"?`sample-${t}`:e==="number"?100:e==="boolean"?!0:e==="array"?[]:null,Q4=async(r,e,t)=>{let s={};try{let{createJiti:i}=await import("jiti"),o=i(process.cwd(),{interopDefault:!0})(t),a=Object.entries(o).find(([c])=>c.endsWith("Schema")||c==="schema");if(a?.[1]){let c=a[1],l=(0,Lg.generateMock)(c);if(l&&typeof l=="object"&&Object.keys(l).length>0)return S0(l,s,""),{data:l,faker:s}}}catch{}return Wg(r)},S0=(r,e,t)=>{!r||typeof r!="object"||Object.entries(r).forEach(([s,i])=>{let n=t?`${t}.${s}`:s,o=s.toLowerCase(),a=Object.entries(Y4).find(([c])=>o.includes(c));a&&(a[0]!=="id"||typeof i=="string")&&(e[n]=a[1]),typeof i=="object"&&!Array.isArray(i)?S0(i,e,n):Array.isArray(i)&&i.length>0&&S0(i[0],e,`${n}[0]`)})},Wg=r=>{let e={},t={},s=r.match(/z\.object\(\s*\{([\s\S]*)\}\s*\)/m);if(!s)return{data:e,faker:t};let i=s[1];return k0(i,e,t,""),{data:e,faker:t}},k0=(r,e,t,s)=>{let i=/^\s*(\w+):\s*z\.(\w+)\(/gm;Array.from(r.matchAll(i)).filter(a=>a.index!==void 0).map(a=>{let c=a[1],l=a[2],u=a.index,h=1,f=u+a[0].length;for(;f<r.length&&h>0;){let g=r[f];g==="("&&h++,g===")"&&h--,f++}let d=r.substring(u,f),p=r.substring(0,u),m=(p.match(/\{/g)||[]).length,b=(p.match(/\}/g)||[]).length,x=m-b;return{name:c,zodType:l,def:d,nestingLevel:x}}).filter(a=>a.nestingLevel===0).forEach(a=>{e5(a.name,a.zodType,a.def,e,t,s)})},e5=(r,e,t,s,i,n)=>{let o=n?`${n}.${r}`:r,a=t.includes(".optional()");e==="array"?t5(t,s,r,o,i):e==="object"?r5(t,s,r,o,i):s5(r,e,a,o,s,i)},t5=(r,e,t,s,i)=>{if(r.includes("z.object")){let n=r.match(/z\.object\(\s*\{([\s\S]*?)\}\s*\)/);if(n){let o={};k0(n[1],o,i,`${s}[0]`),e[t]=[o];return}}e[t]=[]},r5=(r,e,t,s,i)=>{let n=r.match(/z\.object\(\s*\{([\s\S]*?)\}\s*\)/);n&&(e[t]={},k0(n[1],e[t],i,s))},s5=(r,e,t,s,i,n)=>{let o=J4(r,`z.${e}()${t?".optional()":""}`),a=X4(o,e,r);o&&(n[s]=o),(!t||a!==null)&&(i[r]=a)},Ug=r=>r.replace(/\./g,"-"),i5=async(r,e,t)=>{let s=ae(),{eventsPath:i,indexPath:n,packagePath:o}=Bt(),a=t.outputDir??i;if(!(0,H.existsSync)(a))return S.error(`Contracts events directory not found: ${a}`),{mockPath:null};let c=e.match(/import\s+\{[^}]*?(\w+Contract)[^}]*?\}\s+from\s+['"]([^'"]+)\/contracts['"]/);if(!c)return{mockPath:null,skipped:!0,reason:"Advanced Mode handler (uses contracts)"};let l=c[1],u=c[2];if(!e.match(/handleEvent\s*\(\s*(\w+Contract)/))return{mockPath:null,skipped:!0,reason:"Could not parse handleEvent call"};let f=l.replace("Contract","").replace(/([A-Z])/g,(_,O,A)=>(A>0?"-":"")+_.toLowerCase()).toLowerCase(),d=`${f}.ts`,p=(0,Se.join)(a,d);if((0,H.existsSync)(p)){let _=(0,H.readFileSync)(p,"utf-8"),O=_.match(/export const (\w+)\s*=\s*z\.object\(\s*\{([\s\S]*?)\}\s*\)/m);if(O){let A=O[2],{data:C,faker:T}=Wg(`z.object({${A}})`),M=_.match(/type:\s*['"]([^'"]+)['"]/),K=M?M[1]:f.replace(/-/g,"."),Be=`${f}.mock.json`,Lt=(0,Se.join)(a,Be);if((0,H.existsSync)(Lt)&&!t.overwrite)return{mockPath:null,skipped:!0,reason:"Mock already exists"};let Sh={eventName:K,description:`Mock data for ${K} event`,data:C};return Object.keys(T).length>0&&(Sh.faker=T),(0,H.writeFileSync)(Lt,JSON.stringify(Sh,null,2),"utf-8"),{mockPath:Lt,skipped:!1,reason:void 0}}}let m=f.replace(/-/g,"."),b=l.replace("Contract","Data"),x=`import { createContract } from '@crossdelta/cloudevents'
114
114
  import { z } from 'zod'
115
115
 
116
116
  // TODO: Define your event schema
@@ -126,9 +126,9 @@ export const ${l} = createContract({
126
126
  })
127
127
 
128
128
  export type ${b} = z.infer<typeof ${l}.schema>
129
- `;(0,G.writeFileSync)(p,x,"utf-8");let g=(0,G.readFileSync)(n,"utf-8"),y=`export * from './events/${f}'`;if(!g.includes(y)){let _=g.split(`
129
+ `;(0,H.existsSync)(p)||(0,H.writeFileSync)(p,x,"utf-8");let g=(0,H.readFileSync)(n,"utf-8"),y=`export * from './events/${f}'`;if(!g.includes(y)){let _=g.split(`
130
130
  `),O=_.findLastIndex(A=>A.startsWith("export"));O>=0?_.splice(O+1,0,y):_.push("",y),g=_.join(`
131
- `),(0,G.writeFileSync)(n,g,"utf-8")}let w=`${f}.mock.json`,v=(0,Se.join)(a,w),k={eventName:m,description:`Mock data for ${m} event`,data:{id:"550e8400-e29b-41d4-a716-446655440000",createdAt:new Date().toISOString()},faker:{id:"uuid",createdAt:"date"}};return(0,G.writeFileSync)(v,JSON.stringify(k,null,2),"utf-8"),{mockPath:v,skipped:!1,reason:void 0}},n5=async(r,e,t,s,i,n,o,a)=>{let{eventsPath:c,indexPath:l,packagePath:u}=Bt(),h=a??c;if(!(0,G.existsSync)(h)){S.error(`Contracts events directory not found: ${h}`);return}let f=(0,Se.join)(u,"package.json"),p=JSON.parse((0,G.readFileSync)(f,"utf-8")).name.split("/")[0],m=Ug(t),b=`${m}.ts`,x=(0,Se.join)(h,b),g=m.split("-").map(M=>M.charAt(0).toUpperCase()+M.slice(1)).join("")+"Contract",y=g.replace("Contract","Data"),w=`import { createContract } from '@crossdelta/cloudevents'
131
+ `),(0,H.writeFileSync)(n,g,"utf-8")}let w=`${f}.mock.json`,v=(0,Se.join)(a,w),k={eventName:m,description:`Mock data for ${m} event`,data:{id:"550e8400-e29b-41d4-a716-446655440000",createdAt:new Date().toISOString()},faker:{id:"uuid",createdAt:"date"}};return(0,H.writeFileSync)(v,JSON.stringify(k,null,2),"utf-8"),{mockPath:v,skipped:!1,reason:void 0}},n5=async(r,e,t,s,i,n,o,a)=>{let{eventsPath:c,indexPath:l,packagePath:u}=Bt(),h=a??c;if(!(0,H.existsSync)(h)){S.error(`Contracts events directory not found: ${h}`);return}let f=(0,Se.join)(u,"package.json"),p=JSON.parse((0,H.readFileSync)(f,"utf-8")).name.split("/")[0],m=Ug(t),b=`${m}.ts`,x=(0,Se.join)(h,b),g=m.split("-").map(M=>M.charAt(0).toUpperCase()+M.slice(1)).join("")+"Contract",y=g.replace("Contract","Data"),w=`import { createContract } from '@crossdelta/cloudevents'
132
132
  import { z } from 'zod'
133
133
 
134
134
  ${s}
@@ -139,9 +139,9 @@ export const ${g} = createContract({
139
139
  })
140
140
 
141
141
  export type ${y} = z.infer<typeof ${g}.schema>
142
- `;(0,G.writeFileSync)(x,w,"utf-8");let v=(0,G.readFileSync)(l,"utf-8"),k=`export * from './events/${m}'`;if(!v.includes(k)){let M=v.split(`
142
+ `;(0,H.writeFileSync)(x,w,"utf-8");let v=(0,H.readFileSync)(l,"utf-8"),k=`export * from './events/${m}'`;if(!v.includes(k)){let M=v.split(`
143
143
  `),K=M.findLastIndex(Be=>Be.startsWith("export"));K>=0?M.splice(K+1,0,k):M.push("",k),v=M.join(`
144
- `),(0,G.writeFileSync)(l,v,"utf-8")}let O=(0,G.readFileSync)(r,"utf-8").replace(/import\s+\{[^}]*\}\s+from\s+['"]\.\.\/types\/[^'"]+['"]/,`import { ${g}, type ${y} } from '${p}/contracts'`).replace(/handleEvent\s*\(\s*\{[\s\S]*?type:\s*['"]([^'"]+)['"][\s\S]*?schema:\s*\w+Schema[\s\S]*?\}\s*,/,`handleEvent(${g},`).replace(new RegExp(`\\b${i.replace("Schema","Event")}\\b`,"g"),y);(0,G.writeFileSync)(r,O,"utf-8");let A=`${m}.mock.json`,C=(0,Se.join)(c,A),T={eventName:t,description:`Mock data for ${t} event`,data:n};Object.keys(o).length>0&&(T.faker=o),(0,G.writeFileSync)(C,JSON.stringify(T,null,2),"utf-8")},o5=async(r,e={})=>{let{outputDir:t,overwrite:s=!1}=e;if(!(0,G.existsSync)(r))return S.error(`Event handler not found: ${r}`),{mockPath:null};let i=(0,G.readFileSync)(r,"utf-8");if(i.includes("Contract")&&(i.includes("contracts'")||i.includes('contracts"')))return await i5(r,i,e);let n=Z4(i);if(!n)return S.error(`Could not parse event handler: ${r}`),{mockPath:null};let{eventType:o,schemaCode:a,schemaName:c}=n,l=a;if(!a){let y=(0,Se.dirname)(r),w=(0,Se.dirname)(y),v=(0,Se.join)(w,"types","events.ts");if((0,G.existsSync)(v)){let k=(0,G.readFileSync)(v,"utf-8"),_=k.indexOf(`export const ${c}`);if(_>=0){let O=k.indexOf("z.object(",_),A=k.indexOf("{",O),C=1,T=A+1;for(;T<k.length&&C>0;)k[T]==="{"&&C++,k[T]==="}"&&C--,T++;let M=k.indexOf(")",T);M>=0&&(l=k.substring(_,M+1).replace(/^export\s+/,""))}}if(!l)return S.error(`Could not load schema ${c} from types file`),{mockPath:null}}let{data:u,faker:h}=await Q4(l,c,r),f=ae();if((i.includes("from '../types/")||i.includes('from "../types/'))&&!t)return await n5(r,f,o,l,c,u,h),{mockPath:null,skipped:!0,reason:"Migrated to Advanced Mode (contracts)"};let p=t??Bt().eventsPath;if(!(0,G.existsSync)(p))return S.error(`Contracts events directory not found: ${p}`),{mockPath:null};let b=`${Ug(o)}.mock.json`,x=(0,Se.join)(p,b);if((0,G.existsSync)(x)&&!s)return S.warn(`Mock file already exists: ${x}`),{mockPath:null};let g={eventName:o,description:`Mock data for ${o} event`,data:u};return Object.keys(h).length>0&&(g.faker=h),(0,G.writeFileSync)(x,JSON.stringify(g,null,2),"utf-8"),{mockPath:x}},Jo=async(r,e={})=>{let t=(0,Se.join)(r,"src","events");if(!(0,G.existsSync)(t))return{mockPaths:[],skippedHandlers:[]};let s=(0,G.readdirSync)(t).filter(a=>a.endsWith(".event.ts")).map(a=>(0,Se.join)(t,a)),i=await Promise.all(s.map(a=>o5(a,e))),n=i.filter(a=>a.mockPath!==null).map(a=>a.mockPath),o=i.filter(a=>a.skipped).map((a,c)=>`${(0,Se.basename)(s[c])} (${a.reason})`);return{mockPaths:n,skippedHandlers:o}};var Zr=require("node:fs"),i2=require("node:path"),O0=require("node:process"),n2=require("execa");var T0=require("node:child_process"),e2=require("node:path"),t2=require("execa");var Jg=require("node:fs"),Zg=require("node:path"),Xg=E(Yg()),F0=(r,e)=>{let t=(0,Zg.resolve)(e,r);return(0,Jg.existsSync)(t)?(0,Xg.config)({path:t,processEnv:{}}).parsed||{}:{}},Qg=(r,e)=>{let t=F0(r,e),s=[];for(let[i,n]of Object.entries(t))if(i.endsWith("_PORT")&&n){let o=Number.parseInt(n,10);!Number.isNaN(o)&&o>0&&s.push(o)}return s};var E5={...process.env,NODE_NO_WARNINGS:"1"};async function Dr(r,e,{cwd:t=process.cwd(),task:s,shell:i,context:n=r,quiet:o=!1,nonInteractive:a=!1,env:c}={}){try{e.length===0&&(e=r.split(" ").slice(1));let l=o?"pipe":s||a?["ignore","pipe","pipe"]:"inherit",u={...E5};c?u={...u,...c}:(s||a)&&(u={...u,CI:"true"});let h=(0,t2.execa)(r,e,{cwd:t,stdio:l,all:s||a?!0:void 0,shell:i??!0,env:u});h.all&&h.all.on("data",f=>{let d=f.toString().trim();s?(s.output=d,d.length&&S.storeLog(d,n)):a&&d.length&&console.log(d)}),await h}catch(l){let u=l instanceof Error?l.message:String(l),h=S5(u);throw new Error(h)}}var S5=r=>{let e=r.match(/error: (.+)/);return e?e[1].trim():r};function r2(r,e,t={}){let{cwd:s=process.cwd(),envFile:i,detached:n=!0,pipeStdout:o=!1,onStdout:a,onStderr:c,onExit:l}=t,u=i?F0(i,s):{},h=(0,e2.resolve)(s,"node_modules",".bin"),f=process.env.PATH||"",d=`${h}:${f}`,p={...process.env,...u,PATH:d,FORCE_COLOR:"1"},m=(0,T0.spawn)(r,e,{cwd:s,env:p,stdio:["inherit",o?"pipe":"inherit","pipe"],detached:n});return a&&o&&m.stdout?.on("data",a),c&&m.stderr?.on("data",c),l&&m.on("exit",l),m}function P0(r,e,t={}){let{cwd:s=process.cwd()}=t;(0,T0.spawn)(r,e,{cwd:s,stdio:"inherit",shell:!0}).on("exit",n=>process.exit(n||0))}async function s2(r){let{execSync:e}=await import("node:child_process");for(let t of r)try{let i=e(`lsof -ti:${t}`,{encoding:"utf8",stdio:"pipe"}).trim().split(`
144
+ `),(0,H.writeFileSync)(l,v,"utf-8")}let O=(0,H.readFileSync)(r,"utf-8").replace(/import\s+\{[^}]*\}\s+from\s+['"]\.\.\/types\/[^'"]+['"]/,`import { ${g}, type ${y} } from '${p}/contracts'`).replace(/handleEvent\s*\(\s*\{[\s\S]*?type:\s*['"]([^'"]+)['"][\s\S]*?schema:\s*\w+Schema[\s\S]*?\}\s*,/,`handleEvent(${g},`).replace(new RegExp(`\\b${i.replace("Schema","Event")}\\b`,"g"),y);(0,H.writeFileSync)(r,O,"utf-8");let A=`${m}.mock.json`,C=(0,Se.join)(c,A),T={eventName:t,description:`Mock data for ${t} event`,data:n};Object.keys(o).length>0&&(T.faker=o),(0,H.writeFileSync)(C,JSON.stringify(T,null,2),"utf-8")},o5=async(r,e={})=>{let{outputDir:t,overwrite:s=!1}=e;if(!(0,H.existsSync)(r))return S.error(`Event handler not found: ${r}`),{mockPath:null};let i=(0,H.readFileSync)(r,"utf-8");if(i.includes("Contract")&&(i.includes("contracts'")||i.includes('contracts"')))return await i5(r,i,e);let n=Z4(i);if(!n)return S.error(`Could not parse event handler: ${r}`),{mockPath:null};let{eventType:o,schemaCode:a,schemaName:c}=n,l=a;if(!a){let y=(0,Se.dirname)(r),w=(0,Se.dirname)(y),v=(0,Se.join)(w,"types","events.ts");if((0,H.existsSync)(v)){let k=(0,H.readFileSync)(v,"utf-8"),_=k.indexOf(`export const ${c}`);if(_>=0){let O=k.indexOf("z.object(",_),A=k.indexOf("{",O),C=1,T=A+1;for(;T<k.length&&C>0;)k[T]==="{"&&C++,k[T]==="}"&&C--,T++;let M=k.indexOf(")",T);M>=0&&(l=k.substring(_,M+1).replace(/^export\s+/,""))}}if(!l)return S.error(`Could not load schema ${c} from types file`),{mockPath:null}}let{data:u,faker:h}=await Q4(l,c,r),f=ae();if((i.includes("from '../types/")||i.includes('from "../types/'))&&!t)return await n5(r,f,o,l,c,u,h),{mockPath:null,skipped:!0,reason:"Migrated to Advanced Mode (contracts)"};let p=t??Bt().eventsPath;if(!(0,H.existsSync)(p))return S.error(`Contracts events directory not found: ${p}`),{mockPath:null};let b=`${Ug(o)}.mock.json`,x=(0,Se.join)(p,b);if((0,H.existsSync)(x)&&!s)return S.warn(`Mock file already exists: ${x}`),{mockPath:null};let g={eventName:o,description:`Mock data for ${o} event`,data:u};return Object.keys(h).length>0&&(g.faker=h),(0,H.writeFileSync)(x,JSON.stringify(g,null,2),"utf-8"),{mockPath:x}},Jo=async(r,e={})=>{let t=(0,Se.join)(r,"src","events");if(!(0,H.existsSync)(t))return{mockPaths:[],skippedHandlers:[]};let s=(0,H.readdirSync)(t).filter(a=>a.endsWith(".event.ts")).map(a=>(0,Se.join)(t,a)),i=await Promise.all(s.map(a=>o5(a,e))),n=i.filter(a=>a.mockPath!==null).map(a=>a.mockPath),o=i.filter(a=>a.skipped).map((a,c)=>`${(0,Se.basename)(s[c])} (${a.reason})`);return{mockPaths:n,skippedHandlers:o}};var Zr=require("node:fs"),i2=require("node:path"),O0=require("node:process"),n2=require("execa");var T0=require("node:child_process"),e2=require("node:path"),t2=require("execa");var Jg=require("node:fs"),Zg=require("node:path"),Xg=E(Yg()),F0=(r,e)=>{let t=(0,Zg.resolve)(e,r);return(0,Jg.existsSync)(t)?(0,Xg.config)({path:t,processEnv:{}}).parsed||{}:{}},Qg=(r,e)=>{let t=F0(r,e),s=[];for(let[i,n]of Object.entries(t))if(i.endsWith("_PORT")&&n){let o=Number.parseInt(n,10);!Number.isNaN(o)&&o>0&&s.push(o)}return s};var E5={...process.env,NODE_NO_WARNINGS:"1"};async function Dr(r,e,{cwd:t=process.cwd(),task:s,shell:i,context:n=r,quiet:o=!1,nonInteractive:a=!1,env:c}={}){try{e.length===0&&(e=r.split(" ").slice(1));let l=o?"pipe":s||a?["ignore","pipe","pipe"]:"inherit",u={...E5};c?u={...u,...c}:(s||a)&&(u={...u,CI:"true"});let h=(0,t2.execa)(r,e,{cwd:t,stdio:l,all:s||a?!0:void 0,shell:i??!0,env:u});h.all&&h.all.on("data",f=>{let d=f.toString().trim();s?(s.output=d,d.length&&S.storeLog(d,n)):a&&d.length&&console.log(d)}),await h}catch(l){let u=l instanceof Error?l.message:String(l),h=S5(u);throw new Error(h)}}var S5=r=>{let e=r.match(/error: (.+)/);return e?e[1].trim():r};function r2(r,e,t={}){let{cwd:s=process.cwd(),envFile:i,detached:n=!0,pipeStdout:o=!1,onStdout:a,onStderr:c,onExit:l}=t,u=i?F0(i,s):{},h=(0,e2.resolve)(s,"node_modules",".bin"),f=process.env.PATH||"",d=`${h}:${f}`,p={...process.env,...u,PATH:d,FORCE_COLOR:"1"},m=(0,T0.spawn)(r,e,{cwd:s,env:p,stdio:["inherit",o?"pipe":"inherit","pipe"],detached:n});return a&&o&&m.stdout?.on("data",a),c&&m.stderr?.on("data",c),l&&m.on("exit",l),m}function P0(r,e,t={}){let{cwd:s=process.cwd()}=t;(0,T0.spawn)(r,e,{cwd:s,stdio:"inherit",shell:!0}).on("exit",n=>process.exit(n||0))}async function s2(r){let{execSync:e}=await import("node:child_process");for(let t of r)try{let i=e(`lsof -ti:${t}`,{encoding:"utf8",stdio:"pipe"}).trim().split(`
145
145
  `).filter(Boolean);for(let n of i)try{process.kill(Number.parseInt(n,10),"SIGKILL")}catch{}}catch{}}var Xo=r=>Mi(r);function o2(r){let e=(0,i2.join)(nn(),"node_modules",r);return(0,Zr.existsSync)(e)}var a2=r=>Object.keys(wr.scripts??{}).includes(r);function I0(){if((0,Zr.existsSync)("bun.lock")||(0,Zr.existsSync)("bun.lockb"))return"bun";if((0,Zr.existsSync)("pnpm-lock.yaml"))return"pnpm";if((0,Zr.existsSync)("yarn.lock"))return"yarn";if((0,Zr.existsSync)("package-lock.json"))return"npm"}function Le(){return I0()??B0()}function B0(){return Xo("bun")?"bun":Xo("pnpm")?"pnpm":Xo("yarn")?"yarn":"npm"}function c2(){return["bun","pnpm","yarn","npm"].filter(Xo)}async function vr(r,e){let t=k5(r,e),{mergedOptions:s,packages:i}=t,{cwd:n=process.cwd(),flags:o=[],task:a}=s,c=s.packageManager??Le(),l=[],u=Array.isArray(i)?i:[i];c==="bun"&&(u=u.map(f=>f.replace("git+ssh://",""))),c==="pnpm"&&(l.push("--config.engine-strict=false"),l.push("--no-frozen-lockfile")),["yarn","npm"].includes(c)&&l.push("--ignore-engines");let h=u.length?c==="npm"?["install",...o,...l,...u]:["add",...o,...l,...u]:["install",...l];await Dr(c,h,{cwd:n,task:a})}async function l2(r){let e=["-g"],t=[...r.flags??[],...e];await vr({...r,flags:t,packageManager:r.packageManager??Le()})}function u2(r,e,t){let{args:s,mergedOptions:i}=f2(e??[],t),n=i.manager??Le(),{command:o,args:a}=h2(n);(0,n2.execaSync)(o,[...a,r,...s],{cwd:i.cwd??(0,O0.cwd)(),stdio:"inherit",preferLocal:!0})}async function Rt(r,e,t){let{args:s,mergedOptions:i}=f2(e??[],t),n=i.manager??Le(),{command:o,args:a}=h2(n);await Dr(o,[...a,r,...s],i)}async function Qo(r,e){let{script:t,mergedOptions:s}=_5(r,e),i=s.packageManager??Le();await Dr(i,["run",t,...s.args??[]],{cwd:s.cwd??(0,O0.cwd)(),task:s.task})}function h2(r){let t={bun:"bunx",pnpm:"npx",yarn:"npx",npm:"npx"}[r];if(!t)throw new Error(`No executor found for the detected package manager: ${r}`);let[s,...i]=t.split(" ");return{command:s,args:i}}function k5(r,e){return Array.isArray(r)?{packages:r,mergedOptions:{...e,packages:r}}:{packages:r.packages??[],mergedOptions:r}}function f2(r,e){return Array.isArray(r)?{args:r,mergedOptions:e??{}}:{args:[],mergedOptions:r}}function _5(r,e){return typeof r=="string"?{script:r,mergedOptions:e??{}}:{script:r.script,mergedOptions:r}}function A5(r){let e=r.split(/\s+/),t=e[0],s=e.slice(1);if(t==="pf"){t=process.argv[1];let i=s.filter(o=>o.startsWith("-"));s=[...s.filter(o=>!o.startsWith("-")),...i]}return{executable:t,args:s}}async function F5(r,e){try{let{executable:t,args:s}=A5(r.command);return await Dr(t,s,{cwd:e,shell:!1}),{command:r.command,success:!0}}catch(t){return{command:r.command,success:!1,error:t instanceof Error?t.message:String(t)}}}async function d2(r,e){let t=[];for(let s of r){let i=await F5(s,e);if(t.push(i),!i.success)break}return t}function p2(r){let e=[/^pf\s+new\s+/,/^pf\s+add\s+/,/^bun\s+pf\s+new\s+/,/^bun\s+pf\s+add\s+/];return r.filter(t=>e.some(s=>s.test(t.command)))}function m2(r){for(let e of r){let t=e.command.match(/(?:bun\s+)?pf\s+new\s+\S+\s+(\S+)/);if(t)return t[1]}return null}var qs=require("node:fs"),R0=require("node:path"),x2=r=>{let e=(0,R0.join)(r,"packages","contracts","src","schemas");if(!(0,qs.existsSync)(e))return[];let t=[];try{let s=(0,qs.readdirSync)(e).filter(i=>i.endsWith(".schema.ts"));for(let i of s){let n=(0,R0.join)(e,i),o=(0,qs.readFileSync)(n,"utf-8"),a=T5(o,n);a&&t.push(a)}}catch(s){return console.warn("Warning: Could not scan contracts package:",s),[]}return t},T5=(r,e)=>{try{let t=r.match(/export const (\w+Contract) = createContract/);if(!t)return null;let s=t[1],i=r.match(/export type (\w+(?:Data|Event)) = z\.infer/);if(!i)return null;let n=i[1],o=r.match(/type:\s*['"]([^'"]+)['"]/);if(!o)return null;let a=o[1],c=P5(r);return{name:s,typeName:n,eventType:a,fields:c,filePath:e}}catch{return null}},P5=r=>{let e=[],t=r.match(/z\.object\({([^}]+)\}/);if(!t)return e;let s=t[1],i=/(\w+):\s*z\.(\w+)\([^)]*\)/g,n;for(;(n=i.exec(s))!==null;){let[,o,a]=n,l={string:"string",number:"number",boolean:"boolean",array:"array",object:"object",date:"date"}[a]||a;e.push(`${o}: ${l}`)}if(r.includes("z.array(")){let o=/(\w+):\s*z\.array\(/g;for(;(n=o.exec(r))!==null;){let a=n[1];e.some(c=>c.startsWith(a))||e.push(`${a}: array`)}}return e},g2=(r,e)=>{if(r.length===0)return`
146
146
  **Available Contracts:**
147
147
 
@@ -163,7 +163,7 @@ The following event contracts are available in \`${e}/contracts\`:
163
163
  ${t}
164
164
 
165
165
  If the event type your service consumes matches one of these, **you MUST use Advanced Mode**.
166
- `.trim()};var Xr=require("node:fs"),ea=require("node:path");function b2(r,e){let t={written:[],skipped:[],failed:[]};for(let s of r){let i=(0,ea.join)(e.baseDir,s.path);try{if((0,Xr.existsSync)(i)&&!e.overwrite){t.skipped.push(s.path);continue}if(e.dryRun){t.written.push(s.path);continue}let n=(0,ea.dirname)(i);(0,Xr.existsSync)(n)||(0,Xr.mkdirSync)(n,{recursive:!0}),(0,Xr.writeFileSync)(i,s.content,"utf-8"),t.written.push(s.path)}catch(n){t.failed.push({path:s.path,error:n.message})}}return t}var ta=require("node:fs"),y2=require("node:path");var O5=[/####\s+`?([^\s`\n]+\.[a-z]+)`?\s*\n+```(\w+)?\n([\s\S]*?)```/gi,/\*\*(?:File|Datei):?\*\*\s*`?([^\s`\n]+\.[a-z]+)`?\s*\n+```(\w+)?\n([\s\S]*?)```/gi,/(?:File|Datei):?\s*`?([^\s`\n]+\.[a-z]+)`?\s*\n+```(\w+)?\n([\s\S]*?)```/gi,/`([^\s`]+\.[a-z]+)`[:\s]*\n+```(\w+)?\n([\s\S]*?)```/gi],I5=/```(\w+)?\n([\s\S]*?)```/g;function B5(r,e,t){let s=[[/new Hono\(\)/i,"src/index.ts"],[/@Module\(/i,"src/app.module.ts"],[/@Controller\(/i,"src/app.controller.ts"],[/@Injectable\(/i,"src/app.service.ts"],[/NestFactory\.create/i,"src/main.ts"],[/"name":\s*"[^"]+"/i,"package.json"],[/^FROM\s+/im,"Dockerfile"],[/"compilerOptions"/i,"tsconfig.json"],[/^[A-Z_]+=.+$/m,".env"]];for(let[o,a]of s)if(o.test(r))return a;let n={typescript:"ts",ts:"ts",javascript:"js",js:"js",json:"json",toml:"toml",yaml:"yaml",yml:"yml",plaintext:"txt",env:"env",dockerfile:""}[e?.toLowerCase()||"ts"]??"ts";return n?`src/file-${t+1}.${n}`:null}function R5(r){let e=[],t=/```commands?\n([\s\S]*?)```/gi,s=[...r.matchAll(t)];for(let i of s){let o=i[1].trim().split(`
166
+ `.trim()};var Xr=require("node:fs"),ea=require("node:path");function b2(r,e){let t={written:[],skipped:[],failed:[]};for(let s of r){let n=s.path.startsWith("packages/")&&e.workspaceRoot?e.workspaceRoot:e.baseDir,o=(0,ea.join)(n,s.path);try{if((0,Xr.existsSync)(o)&&!e.overwrite){t.skipped.push(s.path);continue}if(e.dryRun){t.written.push(s.path);continue}let a=(0,ea.dirname)(o);(0,Xr.existsSync)(a)||(0,Xr.mkdirSync)(a,{recursive:!0}),(0,Xr.writeFileSync)(o,s.content,"utf-8"),t.written.push(s.path)}catch(a){t.failed.push({path:s.path,error:a.message})}}return t}var ta=require("node:fs"),y2=require("node:path");var O5=[/####\s+`?([^\s`\n]+\.[a-z]+)`?\s*\n+```(\w+)?\n([\s\S]*?)```/gi,/\*\*(?:File|Datei):?\*\*\s*`?([^\s`\n]+\.[a-z]+)`?\s*\n+```(\w+)?\n([\s\S]*?)```/gi,/(?:File|Datei):?\s*`?([^\s`\n]+\.[a-z]+)`?\s*\n+```(\w+)?\n([\s\S]*?)```/gi,/`([^\s`]+\.[a-z]+)`[:\s]*\n+```(\w+)?\n([\s\S]*?)```/gi],I5=/```(\w+)?\n([\s\S]*?)```/g;function B5(r,e,t){let s=[[/new Hono\(\)/i,"src/index.ts"],[/@Module\(/i,"src/app.module.ts"],[/@Controller\(/i,"src/app.controller.ts"],[/@Injectable\(/i,"src/app.service.ts"],[/NestFactory\.create/i,"src/main.ts"],[/"name":\s*"[^"]+"/i,"package.json"],[/^FROM\s+/im,"Dockerfile"],[/"compilerOptions"/i,"tsconfig.json"],[/^[A-Z_]+=.+$/m,".env"]];for(let[o,a]of s)if(o.test(r))return a;let n={typescript:"ts",ts:"ts",javascript:"js",js:"js",json:"json",toml:"toml",yaml:"yaml",yml:"yml",plaintext:"txt",env:"env",dockerfile:""}[e?.toLowerCase()||"ts"]??"ts";return n?`src/file-${t+1}.${n}`:null}function R5(r){let e=[],t=/```commands?\n([\s\S]*?)```/gi,s=[...r.matchAll(t)];for(let i of s){let o=i[1].trim().split(`
167
167
  `).filter(a=>a.trim());for(let a of o){let c=a.trim();c&&!c.startsWith("#")&&e.push({command:c,isPfCommand:c.startsWith("pf ")||c.includes(" pf ")})}}return e}function M5(r){let e=[],t=/```(?:bash|shell|sh)\n([\s\S]*?)```/gi,s=[...r.matchAll(t)];for(let i of s){let o=i[1].trim().split(`
168
168
  `).filter(a=>a.trim());for(let a of o){let c=a.trim();c&&(c.startsWith("pf ")||c.startsWith("bun pf "))&&e.push({command:c.replace(/^bun\s+/,""),isPfCommand:!0})}}return e}function $5(r){let e=R5(r);return e.length>0?e:M5(r)}function N5(){try{let r=ae(),e=(0,y2.join)(r,"package.json");if(!(0,ta.existsSync)(e))return null;let s=JSON.parse((0,ta.readFileSync)(e,"utf-8")).name;return s?s.startsWith("@")?s.slice(1).split("/")[0]:s:null}catch{return null}}function j5(r){let t=/```dependencies?\n([\s\S]*?)```/gi.exec(r);if(!t)return[];let n=t[1].trim().split(`
169
169
  `).filter(a=>a.trim()).map(a=>a.trim()).filter(a=>a&&!a.startsWith("#")),o=N5();return n.map(a=>o&&a.match(new RegExp(`^@${o}/[\\w-]+$`))?`${a}@workspace:*`:a.match(/^@crossdelta\/[\w-]+$/)?`${a}@workspace:*`:a)}function L5(r,e){let t=[`${e}/`,`services/${e}/`,"./"];for(let s of t)if(r.startsWith(s))return r.slice(s.length);return r}function W5(r,e,t){let s=[];for(let i of O5){let n=[...r.matchAll(i)];for(let o of n){let[,a,c="typescript",l]=o,u=L5(a.trim(),e);t.has(u)||(t.add(u),s.push({path:u,content:l.trim(),language:c||"typescript"}))}}return s}function U5(r,e){let t=[],s=[...r.matchAll(I5)],i=0;for(let n of s){let[,o="typescript",a]=n,c=B5(a,o,i);c&&!e.has(c)&&(e.add(c),t.push({path:c,content:a.trim(),language:o||"typescript"})),i++}return t}function q5(r,e){let t=new Set,s=W5(r,e,t);return s.length>0?s:U5(r,t)}function w2(r,e){return{commands:$5(r),files:q5(r,e),dependencies:j5(r)}}function D2(r){let e=[];r.length===0&&e.push("No code files could be extracted from the AI response");let t=r.some(s=>s.path==="src/index.ts"||s.path==="src/main.ts"||s.path==="index.ts");r.length>0&&!t&&e.push("No entry point file (src/index.ts or src/main.ts) found");for(let s of r)(!s.content||s.content.trim().length===0)&&e.push(`File ${s.path} is empty`);return{valid:e.length===0,errors:e}}var H5=["docs/ai-guidelines.md"],G5=`You are an expert code generator. Generate clean, production-ready code.
@@ -190,8 +190,8 @@ Format your code blocks with the file path on the line before the code block:
190
190
 
191
191
  Follow these project-specific conventions and patterns when generating code:
192
192
 
193
- ${e}`},Z5=r=>`${H.default.dim(" \u2022 ")+H.default.cyan(r)}
194
- `,X5=r=>H.default.dim(` \u2728 Generated ${H.default.white.bold(r)} tokens
193
+ ${e}`},Z5=r=>`${G.default.dim(" \u2022 ")+G.default.cyan(r)}
194
+ `,X5=r=>G.default.dim(` \u2728 Generated ${G.default.white.bold(r)} tokens
195
195
  `),Q5=()=>{let r="",e=0,t=Date.now(),s=()=>{let n=r.match(/####\s+`([^`]+)`/);if(!n)return!1;process.stdout.write(Z5(n[1]));let o=r.indexOf(n[0])+n[0].length;return r=r.slice(o),!0},i=n=>{r+=n,e++,s(),r.length>500&&(r=r.slice(-200))};return i.getStats=()=>{let n=((Date.now()-t)/1e3).toFixed(2),o=(e/Number.parseFloat(n)).toFixed(0);return{tokenCount:e,duration:n,tokensPerSec:o}},i},eC=(r,e,t)=>`Generate source code for a ${t==="hono"?"Hono":"NestJS"} microservice at path "${r}".
196
196
 
197
197
  Use the patterns and conventions from the Project Guidelines above.
@@ -224,25 +224,25 @@ FORMATTING RULES:
224
224
  - Format each file with the path header (e.g., #### \`src/index.ts\`)
225
225
  - Do NOT include the service path in file headers (use \`src/index.ts\`, NOT \`${r}/src/index.ts\`)
226
226
  - Ensure code blocks are properly closed
227
- `,tC=(r,e)=>{let t=(0,Mt.join)(e.workspaceRoot,"packages/contracts");if(!(0,Xt.existsSync)(t))return[];let s=/import\s+(?:type\s+)?{([^}]+)}\s+from\s+['"]@\w+\/contracts['"]/g,i=new Set;for(let n of r){let o;for(;(o=s.exec(n.content))!==null;)o[1].split(",").map(c=>c.trim()).map(c=>c.replace(/^type\s+/,"").replace(/\s+as\s+.+$/,"").trim()).filter(c=>c.endsWith("Contract")).forEach(c=>i.add(c))}return Array.from(i).sort()},rC=(r,e)=>{r.length!==0&&(console.log(H.default.cyan(`\u{1F4E6} Using existing contracts:
228
- `)),r.forEach(t=>{console.log(H.default.dim(` ${e}/contracts \u2192 ${t}`))}),console.log())},sC=async r=>{console.log(H.default.cyan(`
227
+ `,tC=(r,e)=>{let t=(0,Mt.join)(e.workspaceRoot,"packages/contracts");if(!(0,Xt.existsSync)(t))return[];let s=/import\s+(?:type\s+)?{([^}]+)}\s+from\s+['"]@\w+\/contracts['"]/g,i=new Set;for(let n of r){let o;for(;(o=s.exec(n.content))!==null;)o[1].split(",").map(c=>c.trim()).map(c=>c.replace(/^type\s+/,"").replace(/\s+as\s+.+$/,"").trim()).filter(c=>c.endsWith("Contract")).forEach(c=>i.add(c))}return Array.from(i).sort()},rC=(r,e)=>{r.length!==0&&(console.log(G.default.cyan(`\u{1F4E6} Using existing contracts:
228
+ `)),r.forEach(t=>{console.log(G.default.dim(` ${e}/contracts \u2192 ${t}`))}),console.log())},sC=async r=>{console.log(G.default.cyan(`
229
229
  \u25B6 Scaffolding service structure...
230
- `));let t=(await d2(r,process.cwd())).find(s=>!s.success);if(t)throw console.log(H.default.red(`
231
- \u2717 Scaffolding failed: ${t.command}`)),t.error&&console.log(H.default.red(` Error: ${t.error}`)),new Error(`Command failed: ${t.command}${t.error?` - ${t.error}`:""}`);console.log(H.default.green(`\u2713 Service scaffolding complete
232
- `))},iC=async(r,e,t)=>{t.stop(),console.log(H.default.cyan(`
230
+ `));let t=(await d2(r,process.cwd())).find(s=>!s.success);if(t)throw console.log(G.default.red(`
231
+ \u2717 Scaffolding failed: ${t.command}`)),t.error&&console.log(G.default.red(` Error: ${t.error}`)),new Error(`Command failed: ${t.command}${t.error?` - ${t.error}`:""}`);console.log(G.default.green(`\u2713 Service scaffolding complete
232
+ `))},iC=async(r,e,t)=>{t.stop(),console.log(G.default.cyan(`
233
233
  \u25B6 Installing ${r.length} additional ${r.length===1?"package":"packages"}...
234
- `));try{await vr({packages:r,cwd:e,flags:["--silent"]}),console.log(H.default.green(`\u2713 Installed ${r.length} ${r.length===1?"package":"packages"}
235
- `))}catch(s){throw console.log(H.default.red(`\u2717 Package installation failed
236
- `)),s}},nC=async(r,e)=>{e.start("Applying code formatting...");try{await Rt("biome",["check","--fix","--unsafe",r],{cwd:process.cwd(),quiet:!0}),e.succeed("Code formatted!")}catch{e.warn("Code formatting completed with warnings")}},oC=async(r,e)=>{e.start("Scanning for event handlers...");let{mockPaths:t,skippedHandlers:s}=await Jo(r);t.length>0?(e.succeed(`Generated ${t.length} event ${t.length===1?"mock":"mocks"}`),console.log(),t.forEach(i=>{let n=i.replace(r+"/","");console.log(H.default.dim(` ${n}`))}),console.log()):e.info("No event handlers found"),s.length>0&&(s.forEach(i=>{console.log(H.default.dim(` \u2139 Skipped: ${i}`))}),console.log())},aC=async(r,e,t,s)=>{s.start("Writing generated files...");let i=b2(r,{baseDir:e,overwrite:!0});i.written.length>0?(s.succeed(`Created ${i.written.length} files`),console.log(),i.written.forEach(o=>console.log(H.default.dim(` ${o}`))),console.log()):s.succeed("No files to write");let n=tC(r,t);rC(n,t.scope),i.written.length>0&&(await nC(e,s),await oC(e,s))},Hs=()=>{if(!yi())return{valid:!1,error:H.default.red.bold(`\u274C AI configuration not found.
237
- `)+H.default.yellow("Please run ")+H.default.cyan.bold("pf setup --ai")+H.default.yellow(` first to configure your AI provider.
238
- `)};try{return{valid:!0,config:In()}}catch(r){return{valid:!1,error:H.default.red.bold(`\u274C ${r.message}
239
- `)}}},ra=async r=>r||Ii({message:"\u{1F4DD} Describe what this service should do:",validate:e=>e.trim().length>0||"Please provide a description for the AI to generate code"}),sa=async(r,e)=>{let{servicePath:t,description:s="",serviceType:i}=r,n=t.split("/").pop()||t,o=V5();console.log(H.default.cyan.bold(`
234
+ `));try{await vr({packages:r,cwd:e,flags:["--silent"]}),console.log(G.default.green(`\u2713 Installed ${r.length} ${r.length===1?"package":"packages"}
235
+ `))}catch(s){throw console.log(G.default.red(`\u2717 Package installation failed
236
+ `)),s}},nC=async(r,e)=>{e.start("Applying code formatting...");try{await Rt("biome",["check","--fix","--unsafe",r],{cwd:process.cwd(),quiet:!0}),e.succeed("Code formatted!")}catch{e.warn("Code formatting completed with warnings")}},oC=async(r,e)=>{e.start("Scanning for event handlers...");let{mockPaths:t,skippedHandlers:s}=await Jo(r);t.length>0?(e.succeed(`Generated ${t.length} event ${t.length===1?"mock":"mocks"}`),console.log(),t.forEach(i=>{let n=i.replace(r+"/","");console.log(G.default.dim(` ${n}`))}),console.log()):e.info("No event handlers found"),s.length>0&&(s.forEach(i=>{console.log(G.default.dim(` \u2139 Skipped: ${i}`))}),console.log())},aC=async(r,e,t,s)=>{s.start("Writing generated files...");let i=b2(r,{baseDir:e,workspaceRoot:t.workspaceRoot,overwrite:!0});i.written.length>0?(s.succeed(`Created ${i.written.length} files`),console.log(),i.written.forEach(o=>console.log(G.default.dim(` ${o}`))),console.log()):s.succeed("No files to write");let n=tC(r,t);rC(n,t.scope),i.written.length>0&&(await nC(e,s),await oC(e,s))},Hs=()=>{if(!yi())return{valid:!1,error:G.default.red.bold(`\u274C AI configuration not found.
237
+ `)+G.default.yellow("Please run ")+G.default.cyan.bold("pf setup --ai")+G.default.yellow(` first to configure your AI provider.
238
+ `)};try{return{valid:!0,config:In()}}catch(r){return{valid:!1,error:G.default.red.bold(`\u274C ${r.message}
239
+ `)}}},ra=async r=>r||Ii({message:"\u{1F4DD} Describe what this service should do:",validate:e=>e.trim().length>0||"Please provide a description for the AI to generate code"}),sa=async(r,e)=>{let{servicePath:t,description:s="",serviceType:i}=r,n=t.split("/").pop()||t,o=V5();console.log(G.default.cyan.bold(`
240
240
  \u{1F916} AI Generation
241
- `));let a=eC(t,s,i),c=J5(o),l=Q5(),u=await ep(e,a,{system:c,maxTokens:8192,temperature:.7,onToken:l}),h=l.getStats();console.log(X5(h.tokenCount)),console.log();let f=(0,v2.default)({text:"Processing AI response...",color:"cyan"}).start(),d=w2(u,n),{commands:p,files:m,dependencies:b}=d,x=p2(p);f.text="Validating extracted files...";let g=D2(m);if(!g.valid)return f.warn("Could not extract structured files from AI response"),console.log(H.default.yellow(`
242
- Warnings:`)),g.errors.forEach(k=>console.log(H.default.yellow(` \u2022 ${k}`))),!1;f.succeed(`Extracted ${m.length} files`);let y=m.filter(k=>!["package.json","tsconfig.json","Dockerfile"].includes(k.path)),w=m2(x);w&&!w.includes("/")&&!w.startsWith(".")&&(w=`${Jt().services}/${w}`);let v=w?(0,Mt.resolve)(o.cwd,w):(0,Mt.resolve)(o.cwd,t);return x.length>0?await sC(x):((0,Xt.mkdirSync)(v,{recursive:!0}),console.log(H.default.yellow(`\u26A0 No scaffolding command found - creating empty directory
243
- `))),await aC(y,v,o,f),b.length>0&&await iC(b,v,f),!0},ia=(r,e,t)=>{console.log(H.default.cyan.bold(`
241
+ `));let a=eC(t,s,i),c=J5(o),l=Q5(),u=await ep(e,a,{system:c,maxTokens:8192,temperature:.7,onToken:l}),h=l.getStats();console.log(X5(h.tokenCount)),console.log();let f=(0,v2.default)({text:"Processing AI response...",color:"cyan"}).start(),d=w2(u,n),{commands:p,files:m,dependencies:b}=d,x=p2(p);f.text="Validating extracted files...";let g=D2(m);if(!g.valid)return f.warn("Could not extract structured files from AI response"),console.log(G.default.yellow(`
242
+ Warnings:`)),g.errors.forEach(k=>console.log(G.default.yellow(` \u2022 ${k}`))),!1;f.succeed(`Extracted ${m.length} files`);let y=m.filter(k=>!["package.json","tsconfig.json","Dockerfile"].includes(k.path)),w=m2(x);w&&!w.includes("/")&&!w.startsWith(".")&&(w=`${Jt().services}/${w}`);let v=w?(0,Mt.resolve)(o.cwd,w):(0,Mt.resolve)(o.cwd,t);return x.length>0?await sC(x):((0,Xt.mkdirSync)(v,{recursive:!0}),console.log(G.default.yellow(`\u26A0 No scaffolding command found - creating empty directory
243
+ `))),await aC(y,v,o,f),b.length>0&&await iC(b,v,f),!0},ia=(r,e,t)=>{console.log(G.default.cyan.bold(`
244
244
  \u{1F916} AI-Powered Generation
245
- `)),console.log(H.default.dim(`Service: ${r.split("/").pop()}`)),console.log(H.default.dim(`Path: ${r}`)),console.log(H.default.dim(`Model: ${e.model}`)),console.log(H.default.dim(`Description: ${t}
245
+ `)),console.log(G.default.dim(`Service: ${r.split("/").pop()}`)),console.log(G.default.dim(`Path: ${r}`)),console.log(G.default.dim(`Model: ${e.model}`)),console.log(G.default.dim(`Description: ${t}
246
246
  `))};var na=require("node:fs"),an=E(require("node:path")),M0=E(require("chalk"));var E2=async r=>{await Dr("bash",["-c",`
247
247
  curl -fsSL https://bun.sh/install | bash && if [ -f "$HOME/.bun/bin/bun" ]; then export PATH="$HOME/.bun/bin:$PATH"; fi
248
248
  `],{task:r,shell:!0}),await cC()},$0=()=>Mi("bun"),S2=()=>qu([{label:"Bun",value:59026,color:"magenta",barColor:"magenta"},{label:"Deno",value:25335},{label:"Node.js",value:19039}],{title:'Express.js "hello world" HTTP requests per second (Linux x64)',barColor:"blackBright"}),k2=()=>qu([{label:"Bun",value:.36,color:"magenta",barColor:"magenta"},{label:"pnpm",value:6.44},{label:"npm",value:10.58},{label:"yarn",value:12.08}],{title:"Bun is an npm-compatible package manager.",unit:"s",footer:M0.default.blackBright("* Installing dependencies from cache for a Remix app."),barColor:"blackBright"}),cC=async()=>{let r='export PATH="$HOME/.bun/bin:$PATH"',e=process.env.HOME;if(!e){S.error("Could not detect $HOME environment variable. Cannot update shell config.");return}let t=[an.default.join(e,".zshrc"),an.default.join(e,".bashrc"),an.default.join(e,".bash_profile")];for(let s of t)try{if(await na.promises.stat(s).then(()=>!0).catch(()=>!1)){(await na.promises.readFile(s,"utf8")).includes(r)||(await na.promises.appendFile(s,`
@@ -2,6 +2,49 @@
2
2
 
3
3
  These rules define how AI-generated scaffolded services must be structured when produced by CLI tools.
4
4
 
5
+ ## 🚨 EMERGENCY RULES - READ FIRST 🚨
6
+
7
+ **FOR EVENT CONSUMER SERVICES (services that "listen to", "consume", "react to" events):**
8
+
9
+ **CRITICAL:** Contract schema fields MUST match event handler usage!
10
+
11
+ **MANDATORY 4-STEP WORKFLOW:**
12
+
13
+ 1. **Analyze service description** → Extract required fields
14
+ Example: "needs orderId, customerId, total" → Fields: `orderId`, `customerId`, `total`
15
+
16
+ 2. **CREATE contract FIRST** (`packages/contracts/src/events/<event-name>.ts`):
17
+ ```ts
18
+ export const OrderCreatedContract = createContract({
19
+ type: 'order.created',
20
+ schema: z.object({
21
+ orderId: z.string(), // ✅ From step 1
22
+ customerId: z.string(), // ✅ From step 1
23
+ total: z.number(), // ✅ From step 1
24
+ }),
25
+ })
26
+ ```
27
+
28
+ 3. **Export contract** (`packages/contracts/src/index.ts`):
29
+ ```ts
30
+ export * from './events/order-created'
31
+ ```
32
+
33
+ 4. **Create event handler** that uses SAME fields from contract:
34
+ ```ts
35
+ export default handleEvent(OrderCreatedContract, async (data) => {
36
+ await sendNotification({
37
+ orderId: data.orderId, // ✅ Exists in contract
38
+ customerId: data.customerId, // ✅ Exists in contract
39
+ total: data.total, // ✅ Exists in contract
40
+ })
41
+ })
42
+ ```
43
+
44
+ **DO NOT create contracts with generic fields like `id`, `createdAt` - use the actual fields from the service description!**
45
+
46
+ ---
47
+
5
48
  ## ⚠️ CRITICAL: Code Quality Guidelines
6
49
 
7
50
  **NEVER invent APIs, package names, or TypeScript types:**
@@ -100,15 +143,50 @@ Rules:
100
143
 
101
144
  Generated services must include:
102
145
 
103
- - `src/index.ts`
146
+ **For Event Consumers:**
147
+ - `src/index.ts` (with NATS setup)
104
148
  - `src/events/*.event.ts` (event handlers - NOT `src/handlers/`)
105
149
  - `src/use-cases/*.use-case.ts`
106
150
  - `src/use-cases/*.test.ts`
151
+ - `src/types/*.ts` (Zod schemas and types)
152
+ - `packages/contracts/src/events/*.ts` (event contracts)
153
+ - `packages/contracts/src/index.ts` (export contract)
154
+ - `README.md`
155
+
156
+ **For Event Publishers:**
157
+ - `src/index.ts` (REST API)
158
+ - `src/use-cases/*.use-case.ts`
159
+ - `src/use-cases/*.test.ts`
160
+ - `src/types/*.ts` (Zod schemas and types)
161
+ - `packages/contracts/src/events/*.ts` (RECOMMENDED: contracts for published events)
162
+ - `packages/contracts/src/index.ts` (export contract)
107
163
  - `README.md`
108
164
 
165
+ **Example for Event Consumer:**
166
+ ```
167
+ services/order-notifications/
168
+ ├── src/
169
+ │ ├── index.ts # NATS consumer setup
170
+ │ ├── events/
171
+ │ │ └── order-created.event.ts # Event handler
172
+ │ ├── use-cases/
173
+ │ │ ├── send-notification.use-case.ts
174
+ │ │ └── send-notification.test.ts
175
+ │ └── types/
176
+ │ └── notifications.ts # Zod schemas/types
177
+ └── README.md
178
+
179
+ packages/contracts/
180
+ ├── src/
181
+ │ ├── events/
182
+ │ │ └── order-created.ts # Contract definition
183
+ │ └── index.ts # Export contract
184
+ ```
185
+
109
186
  Rules:
110
187
  - Paths must be relative to the service root.
111
188
  - Event handlers MUST go in `src/events/`, NOT `src/handlers/`.
189
+ - Contracts MUST go in `packages/contracts/src/events/`, NOT in service directory.
112
190
  - Do NOT generate controller or module folders unless requested.
113
191
 
114
192
  ---
@@ -200,6 +278,55 @@ Bun.serve({ port, fetch: app.fetch })
200
278
 
201
279
  **Example:** "Notification service that sends emails when orders are created. Consumes order.created events."
202
280
 
281
+ **⚠️⚠️⚠️ MANDATORY WORKFLOW FOR EVENT CONSUMERS ⚠️⚠️⚠️**
282
+
283
+ **YOU MUST FOLLOW THIS EXACT ORDER:**
284
+
285
+ **STEP 1: Analyze Service Description**
286
+ Extract the required event data fields from the description.
287
+
288
+ Example: "sends push notifications when orders are created. Needs orderId, customerId, and total"
289
+ → Required fields: `orderId`, `customerId`, `total`
290
+
291
+ **STEP 2: CREATE Contract FIRST (packages/contracts/src/events/<event-name>.ts)**
292
+ ```ts
293
+ import { createContract } from '@crossdelta/cloudevents'
294
+ import { z } from 'zod'
295
+
296
+ export const OrderCreatedContract = createContract({
297
+ type: 'order.created',
298
+ schema: z.object({
299
+ orderId: z.string(), // ✅ From Step 1
300
+ customerId: z.string(), // ✅ From Step 1
301
+ total: z.number(), // ✅ From Step 1
302
+ }),
303
+ })
304
+
305
+ export type OrderCreatedData = z.infer<typeof OrderCreatedContract.schema>
306
+ ```
307
+
308
+ **STEP 3: Export Contract (packages/contracts/src/index.ts)**
309
+ ```ts
310
+ export * from './events/order-created'
311
+ ```
312
+
313
+ **STEP 4: Create Event Handler (src/events/order-created.event.ts)**
314
+ ```ts
315
+ import { handleEvent } from '@crossdelta/cloudevents'
316
+ import { OrderCreatedContract, type OrderCreatedData } from '@scope/contracts'
317
+ import { sendNotification } from '../use-cases/send-notification.use-case'
318
+
319
+ export default handleEvent(OrderCreatedContract, async (data: OrderCreatedData) => {
320
+ await sendNotification({
321
+ orderId: data.orderId, // ✅ Field exists in contract
322
+ customerId: data.customerId, // ✅ Field exists in contract
323
+ total: data.total, // ✅ Field exists in contract
324
+ })
325
+ })
326
+ ```
327
+
328
+ **CRITICAL:** The contract schema fields (Step 2) MUST match the event handler usage (Step 4)!
329
+
203
330
  **Structure:**
204
331
  - ✅ Event handlers in `src/events/*.event.ts`
205
332
  - ✅ Use `consumeJetStreamEvents()` in index.ts
@@ -536,140 +663,138 @@ The platform supports **Advanced Mode** (contracts) with automatic creation for
536
663
 
537
664
  ### 🎯 DECISION RULE (CRITICAL - READ FIRST!)
538
665
 
539
- **Always use Advanced Mode (contracts) - Missing contracts are auto-created!**
666
+ **Always use Advanced Mode (contracts) - You MUST create complete contracts!**
540
667
 
541
668
  When generating an Event Consumer service:
542
669
 
543
- 1. **Use contracts** from `{{scope}}/contracts` package
544
- 2. **Import and use** the contract in the event handler (`import { OrderCreatedContract } from '{{scope}}/contracts'`)
545
- 3. **If contract doesn't exist**, `pf event:generate` will create it automatically:
546
- - Extracts event name from handler
547
- - Creates contract in `packages/contracts/src/events/`
548
- - Updates `packages/contracts/src/index.ts`
549
- - Generates mock JSON file
670
+ 1. **CREATE the contract** in `packages/contracts/src/events/` with **complete schema** based on the service description
671
+ 2. **Use the contract** in the event handler (`import { OrderCreatedContract } from '{{scope}}/contracts'`)
672
+ 3. **Export the contract** in `packages/contracts/src/index.ts`
673
+
674
+ **⚠️ CRITICAL: Contract Schema Must Match Handler Usage!**
675
+
676
+ If the event handler uses fields like `orderId`, `customerId`, `total`, the contract schema MUST include those exact fields!
550
677
 
551
678
  **Example Flow:**
552
679
  ```
553
- User prompt: "Event Consumer that listens to order.created events"
554
- → AI creates: src/events/order-created.event.ts (imports OrderCreatedContract from @scope/contracts)
555
- pf event:generate runs:
556
- Detects missing contract
557
- Creates packages/contracts/src/events/order-created.ts (contract)
558
- Creates packages/contracts/src/events/order-created.mock.json
680
+ User prompt: "Notification service that sends push notifications when orders are created"
681
+
682
+ Step 1: Analyze what data the service needs:
683
+ - orderId (to identify the order)
684
+ - customerId (to send notification to customer)
685
+ - total (to show order amount)
686
+
687
+ Step 2: CREATE contract with those fields:
688
+ packages/contracts/src/events/order-created.ts:
689
+ schema: z.object({
690
+ orderId: z.string(),
691
+ customerId: z.string(),
692
+ total: z.number(),
693
+ createdAt: z.string().optional(), // Add optional context fields
694
+ })
695
+
696
+ Step 3: CREATE event handler that uses those fields:
697
+ services/order-notifications/src/events/order-created.event.ts:
698
+ import { OrderCreatedContract, type OrderCreatedData } from '@scope/contracts'
699
+ export default handleEvent(OrderCreatedContract, async (data) => {
700
+ await sendNotification({
701
+ orderId: data.orderId, // ✅ Field exists in contract
702
+ customerId: data.customerId, // ✅ Field exists in contract
703
+ total: data.total, // ✅ Field exists in contract
704
+ })
705
+ })
559
706
  ```
560
707
 
561
- **Why This Approach?**
562
- - ✅ AI generates contracts reference (type-safe from start)
563
- - ✅ Automatic contract creation when missing
564
- - ✅ End result: Strong type-safety via Schema Registry Pattern
565
- - ✅ No manual contract creation needed
708
+ **Why Create Contracts Immediately?**
709
+ - ✅ Schema matches handler requirements from the start
710
+ - ✅ No schema mismatch errors at runtime
711
+ - ✅ Type-safety between contract and handler
712
+ - ✅ Single source of truth for event structure
566
713
 
567
- **IMPORTANT:** Always include `pf event:generate` in post-commands block for Event Consumers!
714
+ **MANDATORY for Event Consumers:**
715
+ - Always create `packages/contracts/src/events/<event-name>.ts` file
716
+ - Schema must include ALL fields used by the event handler
717
+ - Export contract in `packages/contracts/src/index.ts`
718
+ - Include `pf event:generate` in post-commands to create mock JSON
568
719
 
569
720
  ### 🟢 ADVANCED MODE (Default - Recommended)
570
721
 
571
- **Use Advanced Mode for Event Consumers** - contracts will be auto-created if missing:
572
-
573
- 1. **Add dependency** `{{scope}}/contracts` in dependencies block
574
- 2. **Import contract** in event handler
575
- 3. **Use with `handleEvent`** for type-safe event handling
576
-
577
- **Example Event Handler:**
578
- ```ts
579
- import { handleEvent } from '@crossdelta/cloudevents'
580
- import { OrderCreatedContract, type OrderCreatedData } from '@scope/contracts'
581
- import { processOrder } from '../use-cases/process-order.use-case'
582
-
583
- export default handleEvent(OrderCreatedContract,
584
- async (data: OrderCreatedData) => {
585
- await processOrder(data)
586
- },
587
- )
588
- ```
589
-
590
- **Why Contracts as Default?**
591
- - ✅ Strong type-safety between Publisher and Consumer
592
- - ✅ Single source of truth for event schemas
593
- - ✅ Schema evolution and versioning
594
- - ✅ Automatic mock generation
595
- - ✅ Better documentation
596
-
597
- **IMPORTANT:** The `packages/contracts` package is your Schema Registry. Always use contracts from there for Event Consumers.
598
-
599
- ### � ADVANCED MODE (Shared Contracts - RECOMMENDED DEFAULT)
600
-
601
- Use **Advanced Mode** for all production services:
602
-
603
- **Steps for Advanced Mode:**
722
+ **Use Advanced Mode for Event Consumers** - you MUST create contracts with complete schemas:
604
723
 
605
- 1. **Check if contract exists** in `packages/contracts/src/events/`
606
- 2. **If exists:** Import and use the contract
607
- 3. **If NOT exists:** Create contract first in `packages/contracts`, then use it
724
+ **⚠️ CRITICAL CONTRACT CREATION RULES:**
608
725
 
609
- **Advanced Mode Example:**
726
+ 1. **Analyze service description** to determine required event fields
727
+ 2. **CREATE contract file** in `packages/contracts/src/events/<event-name>.ts`
728
+ 3. **Include ALL fields** that the event handler will use
729
+ 4. **Export in index** - add to `packages/contracts/src/index.ts`
730
+ 5. **Add dependency** `{{scope}}/contracts` to service's dependencies block
610
731
 
732
+ **Contract File Template:**
611
733
  ```ts
612
- // packages/contracts/src/events/order-created.ts (create if not exists)
734
+ // packages/contracts/src/events/<event-name>.ts
613
735
  import { createContract } from '@crossdelta/cloudevents'
614
736
  import { z } from 'zod'
615
737
 
616
738
  export const OrderCreatedContract = createContract({
617
- type: 'order.created',
739
+ type: 'order.created', // Exact event type string
618
740
  schema: z.object({
741
+ // REQUIRED: Fields used by the event handler
619
742
  orderId: z.string(),
620
743
  customerId: z.string(),
744
+ total: z.number(),
745
+
746
+ // OPTIONAL: Additional context fields
621
747
  items: z.array(z.object({
622
748
  productId: z.string(),
623
749
  quantity: z.number(),
624
750
  price: z.number(),
625
- })),
626
- total: z.number(),
627
- status: z.enum(['created']),
628
- createdAt: z.string(),
751
+ })).optional(),
752
+ status: z.enum(['created', 'processing', 'completed']).optional(),
753
+ createdAt: z.string().optional(),
629
754
  }),
630
755
  })
631
756
 
632
757
  export type OrderCreatedData = z.infer<typeof OrderCreatedContract.schema>
633
758
  ```
634
759
 
760
+ **Export in Index:**
635
761
  ```ts
636
- // services/my-service/src/events/order-created.event.ts
637
- import { handleEvent } from '@crossdelta/cloudevents'
638
- import { OrderCreatedContract, type OrderCreatedData } from '@scope/contracts'
639
- import { processOrder } from '../use-cases/process-order.use-case'
640
-
641
- export default handleEvent(
642
- OrderCreatedContract, // Type-safe contract
643
- async (data: OrderCreatedData) => {
644
- await processOrder(data)
645
- },
646
- )
762
+ // packages/contracts/src/index.ts
763
+ export * from './events/order-created'
764
+ // ... other exports
647
765
  ```
648
766
 
767
+ **Event Handler Using Contract:**
649
768
  ```ts
650
- // services/my-service/src/use-cases/process-order.use-case.ts
651
- import type { OrderCreatedData } from '@scope/contracts'
652
-
653
- export const processOrder = async (data: OrderCreatedData) => {
654
- // Business logic with fully typed data
655
- }
769
+ // services/order-notifications/src/events/order-created.event.ts
770
+ import { handleEvent } from '@crossdelta/cloudevents'
771
+ import { OrderCreatedContract, type OrderCreatedData } from '@scope/contracts'
772
+ import { sendNotification } from '../use-cases/send-notification.use-case'
773
+
774
+ export default handleEvent(OrderCreatedContract, async (data: OrderCreatedData) => {
775
+ // data has all fields from contract schema
776
+ await sendNotification({
777
+ orderId: data.orderId, // ✅ Exists in contract
778
+ customerId: data.customerId, // ✅ Exists in contract
779
+ total: data.total, // ✅ Exists in contract
780
+ })
781
+ })
656
782
  ```
657
783
 
658
- **Dependencies:**
659
- Add `{{scope}}/contracts` to your service's `package.json`:
660
- ```json
661
- {
662
- "dependencies": {
663
- "@scope/contracts": "workspace:*"
664
- }
665
- }
666
- ```
784
+ **Why Contracts as Default?**
785
+ - Strong type-safety between Publisher and Consumer
786
+ - ✅ Single source of truth for event schemas
787
+ - ✅ Schema evolution and versioning
788
+ - ✅ Automatic mock generation
789
+ - ✅ Better documentation
667
790
 
668
- ### DEPRECATED: Basic Mode
791
+ **IMPORTANT:** The `packages/contracts` package is your Schema Registry. Always create complete contracts for Event Consumers.
792
+
793
+ ### 📋 DEPRECATED: Basic Mode
669
794
 
670
795
  **Basic Mode is deprecated and no longer recommended.**
671
796
 
672
- The platform automatically creates missing contracts when you run `pf event:generate`.
797
+ Always use Advanced Mode (contracts) for Event Consumers.
673
798
 
674
799
  **Previous Basic Mode approach:**
675
800
  - ❌ Local schemas in `src/types/events.ts`
@@ -677,69 +802,12 @@ The platform automatically creates missing contracts when you run `pf event:gene
677
802
  - ❌ No type-safety between services
678
803
 
679
804
  **Current approach (Advanced Mode only):**
680
- - ✅ Always use contracts from `{{scope}}/contracts`
681
- - ✅ Contracts auto-created if missing
805
+ - ✅ Always create contracts in `packages/contracts/src/events/`
806
+ - ✅ Complete schemas with all required fields
682
807
  - ✅ Type-safety across all services
683
808
  - ✅ Single source of truth
684
809
 
685
- **Migration:** If you have Basic Mode handlers, run `pf event:generate` to automatically migrate them to Advanced Mode.
686
-
687
- export const processOrder = async (data: OrderCreatedData) => {
688
- // Use data.orderId, data.customerId, etc.
689
- }
690
- ```
691
-
692
- {{AVAILABLE_CONTRACTS}}
693
-
694
- **DO NOT:**
695
- - ❌ Redefine schemas that exist in contracts
696
- - ❌ Create inline schemas for existing event types
697
- - ❌ Duplicate event type definitions
698
-
699
- **Advanced Mode Example:**
700
- ```ts
701
- import { handleEvent } from '@crossdelta/cloudevents'
702
- import { OrderCreatedContract, type OrderCreatedData } from '{{scope}}/contracts'
703
- import { processOrder } from '../use-cases/process-order.use-case'
704
-
705
- export default handleEvent(
706
- OrderCreatedContract, // Import from contracts package
707
- async (data: OrderCreatedData) => {
708
- await processOrder(data)
709
- },
710
- )
711
- ```
712
-
713
- **If you need only a subset of fields**, extract them in the handler:
714
- ```ts
715
- export default handleEvent(
716
- OrderCreatedContract,
717
- async (data: OrderCreatedData) => {
718
- // Extract only what you need
719
- const { orderId, items } = data
720
- await updateInventory({ orderId, items })
721
- },
722
- )
723
- ```
724
-
725
- **Contract Definition** (in `packages/contracts/src/schemas/order-created.schema.ts`):
726
- ```ts
727
- import { createContract } from '@crossdelta/cloudevents'
728
- import { z } from 'zod'
729
-
730
- export const OrderCreatedSchema = z.object({
731
- orderId: z.string(),
732
- customerId: z.string(),
733
- total: z.number(),
734
- })
735
-
736
- export type OrderCreatedData = z.infer<typeof OrderCreatedSchema>
737
-
738
- export const OrderCreatedContract = createContract({
739
- type: 'orders.created',
740
- schema: OrderCreatedSchema,
741
- })
742
- ```
810
+ ---
743
811
 
744
812
  ## General Event Handler Requirements
745
813
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossdelta/platform-sdk",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
4
4
  "description": "Your AI-powered platform engineer. Scaffold complete Turborepo workspaces, generate microservice boilerplate with natural language, and deploy to the cloud — all from one CLI",
5
5
  "keywords": [
6
6
  "cli",