@lahat-group/cli 2.1.2 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ Expecting one of '${n.join("', '")}'`);return this._lifeCycleHooks[e]?this._life
22
22
  `),this._exit(0,"commander.version",e)}),this}description(e,t){return e===void 0&&t===void 0?this._description:(this._description=e,t&&(this._argsDescription=t),this)}summary(e){return e===void 0?this._summary:(this._summary=e,this)}alias(e){if(e===void 0)return this._aliases[0];let t=this;if(this.commands.length!==0&&this.commands[this.commands.length-1]._executableHandler&&(t=this.commands[this.commands.length-1]),e===t._name)throw new Error("Command alias can't be the same as its name");let n=this.parent?._findCommand(e);if(n){let i=[n.name()].concat(n.aliases()).join("|");throw new Error(`cannot add alias '${e}' to command '${this.name()}' as already have command '${i}'`)}return t._aliases.push(e),this}aliases(e){return e===void 0?this._aliases:(e.forEach(t=>this.alias(t)),this)}usage(e){if(e===void 0){if(this._usage)return this._usage;let t=this.registeredArguments.map(n=>xn(n));return[].concat(this.options.length||this._helpOption!==null?"[options]":[],this.commands.length?"[command]":[],this.registeredArguments.length?t:[]).join(" ")}return this._usage=e,this}name(e){return e===void 0?this._name:(this._name=e,this)}nameFromFilename(e){return this._name=$.basename(e,$.extname(e)),this}executableDir(e){return e===void 0?this._executableDir:(this._executableDir=e,this)}helpInformation(e){let t=this.createHelp();return t.helpWidth===void 0&&(t.helpWidth=e&&e.error?this._outputConfiguration.getErrHelpWidth():this._outputConfiguration.getOutHelpWidth()),t.formatHelp(this,t)}_getHelpContext(e){e=e||{};let t={error:!!e.error},n;return t.error?n=i=>this._outputConfiguration.writeErr(i):n=i=>this._outputConfiguration.writeOut(i),t.write=e.write||n,t.command=this,t}outputHelp(e){let t;typeof e=="function"&&(t=e,e=void 0);let n=this._getHelpContext(e);this._getCommandAndAncestors().reverse().forEach(o=>o.emit("beforeAllHelp",n)),this.emit("beforeHelp",n);let i=this.helpInformation(n);if(t&&(i=t(i),typeof i!="string"&&!Buffer.isBuffer(i)))throw new Error("outputHelp callback must return a string or a Buffer");n.write(i),this._getHelpOption()?.long&&this.emit(this._getHelpOption().long),this.emit("afterHelp",n),this._getCommandAndAncestors().forEach(o=>o.emit("afterAllHelp",n))}helpOption(e,t){return typeof e=="boolean"?(e?this._helpOption=this._helpOption??void 0:this._helpOption=null,this):(e=e??"-h, --help",t=t??"display help for command",this._helpOption=this.createOption(e,t),this)}_getHelpOption(){return this._helpOption===void 0&&this.helpOption(void 0,void 0),this._helpOption}addHelpOption(e){return this._helpOption=e,this}help(e){this.outputHelp(e);let t=g.exitCode||0;t===0&&e&&typeof e!="function"&&e.error&&(t=1),this._exit(t,"commander.help","(outputHelp)")}addHelpText(e,t){let n=["beforeAll","before","after","afterAll"];if(!n.includes(e))throw new Error(`Unexpected value for position to addHelpText.
23
23
  Expecting one of '${n.join("', '")}'`);let i=`${e}Help`;return this.on(i,o=>{let s;typeof t=="function"?s=t({error:o.error,command:o.command}):s=t,s&&o.write(`${s}
24
24
  `)}),this}_outputHelpIfRequested(e){let t=this._getHelpOption();t&&e.find(i=>t.is(i))&&(this.outputHelp(),this._exit(0,"commander.helpDisplayed","(outputHelp)"))}};function Le(r){return r.map(e=>{if(!e.startsWith("--inspect"))return e;let t,n="127.0.0.1",i="9229",o;return(o=e.match(/^(--inspect(-brk)?)$/))!==null?t=o[1]:(o=e.match(/^(--inspect(-brk|-port)?)=([^:]+)$/))!==null?(t=o[1],/^\d+$/.test(o[3])?i=o[3]:n=o[3]):(o=e.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/))!==null&&(t=o[1],n=o[3],i=o[4]),t&&i!=="0"?`${t}=${n}:${parseInt(i)+1}`:e})}Me.Command=ge});var ze=k(x=>{"use strict";u();var{Argument:Ue}=z(),{Command:be}=Be(),{CommanderError:An,InvalidArgumentError:We}=F(),{Help:Sn}=ce(),{Option:Je}=he();x.program=new be;x.createCommand=r=>new be(r);x.createOption=(r,e)=>new Je(r,e);x.createArgument=(r,e)=>new Ue(r,e);x.Command=be;x.Option=Je;x.Argument=Ue;x.Help=Sn;x.CommanderError=An;x.InvalidArgumentError=We;x.InvalidOptionArgumentError=We});u();u();var Ge=mn(ze(),1),{program:b,createCommand:Er,createArgument:Pr,createOption:kr,CommanderError:Rr,InvalidArgumentError:Tr,InvalidOptionArgumentError:qr,Command:Nr,Argument:Ir,Option:Dr,Help:Hr}=Ge.default;u();import{existsSync as Wn}from"fs";u();u();u();u();var O=class extends Error{response;request;options;constructor(e,t,n){let i=e.status||e.status===0?e.status:"",o=e.statusText??"",s=`${i} ${o}`.trim(),c=s?`status code ${s}`:"an unknown error";super(`Request failed with ${c}: ${t.method} ${t.url}`),this.name="HTTPError",this.response=e,this.request=t,this.options=n}};u();var D=class extends Error{name="NonError";value;constructor(e){let t="Non-error value was thrown";try{typeof e=="string"?t=e:e&&typeof e=="object"&&"message"in e&&typeof e.message=="string"&&(t=e.message)}catch{}super(t),this.value=e}};u();var H=class extends Error{name="ForceRetryError";customDelay;code;customRequest;constructor(e){let t=e?.cause?e.cause instanceof Error?e.cause:new D(e.cause):void 0;super(e?.code?`Forced retry: ${e.code}`:"Forced retry",t?{cause:t}:void 0),this.customDelay=e?.delay,this.code=e?.code,this.customRequest=e?.request}};u();u();var ye=(()=>{let r=!1,e=!1,t=typeof globalThis.ReadableStream=="function",n=typeof globalThis.Request=="function";if(t&&n)try{e=new globalThis.Request("https://empty.invalid",{body:new globalThis.ReadableStream,method:"POST",get duplex(){return r=!0,"half"}}).headers.has("Content-Type")}catch(i){if(i instanceof Error&&i.message==="unsupported BodyInit type")return!1;throw i}return r&&!e})(),Ke=typeof globalThis.AbortController=="function",G=typeof globalThis.AbortSignal=="function"&&typeof globalThis.AbortSignal.any=="function",Ye=typeof globalThis.ReadableStream=="function",Qe=typeof globalThis.FormData=="function",K=["get","post","put","patch","head","delete"],En=()=>{};En();var Xe={json:"application/json",text:"text/*",formData:"multipart/form-data",arrayBuffer:"*/*",blob:"*/*",bytes:"*/*"},Y=2147483647,Ze=new TextEncoder().encode("------WebKitFormBoundaryaxpyiPgbbPti10Rw").length,Q=Symbol("stop"),L=class{options;constructor(e){this.options=e}},et=r=>new L(r),tt={json:!0,parseJson:!0,stringifyJson:!0,searchParams:!0,prefixUrl:!0,retry:!0,timeout:!0,hooks:!0,throwHttpErrors:!0,onDownloadProgress:!0,onUploadProgress:!0,fetch:!0,context:!0},nt={next:!0},rt={method:!0,headers:!0,body:!0,mode:!0,credentials:!0,cache:!0,redirect:!0,referrer:!0,referrerPolicy:!0,integrity:!0,keepalive:!0,signal:!0,window:!0,duplex:!0};var Pn=r=>{if(!r)return 0;if(r instanceof FormData){let e=0;for(let[t,n]of r)e+=Ze,e+=new TextEncoder().encode(`Content-Disposition: form-data; name="${t}"`).length,e+=typeof n=="string"?new TextEncoder().encode(n).length:n.size;return e}if(r instanceof Blob)return r.size;if(r instanceof ArrayBuffer)return r.byteLength;if(typeof r=="string")return new TextEncoder().encode(r).length;if(r instanceof URLSearchParams)return new TextEncoder().encode(r.toString()).length;if("byteLength"in r)return r.byteLength;if(typeof r=="object"&&r!==null)try{let e=JSON.stringify(r);return new TextEncoder().encode(e).length}catch{return 0}return 0},it=(r,e,t)=>{let n,i=0;return r.pipeThrough(new TransformStream({transform(o,s){if(s.enqueue(o),n){i+=n.byteLength;let c=e===0?0:i/e;c>=1&&(c=1-Number.EPSILON),t?.({percent:c,totalBytes:Math.max(e,i),transferredBytes:i},n)}n=o},flush(){n&&(i+=n.byteLength,t?.({percent:1,totalBytes:Math.max(e,i),transferredBytes:i},n))}}))},ot=(r,e)=>{if(!r.body)return r;if(r.status===204)return new Response(null,{status:r.status,statusText:r.statusText,headers:r.headers});let t=Math.max(0,Number(r.headers.get("content-length"))||0);return new Response(it(r.body,t,e),{status:r.status,statusText:r.statusText,headers:r.headers})},st=(r,e,t)=>{if(!r.body)return r;let n=Pn(t??r.body);return new Request(r,{duplex:"half",body:it(r.body,n,e)})};u();u();var S=r=>r!==null&&typeof r=="object";var M=(...r)=>{for(let e of r)if((!S(e)||Array.isArray(e))&&e!==void 0)throw new TypeError("The `options` argument must be an object");return je({},...r)},we=(r={},e={})=>{let t=new globalThis.Headers(r),n=e instanceof globalThis.Headers,i=new globalThis.Headers(e);for(let[o,s]of i.entries())n&&s==="undefined"||s===void 0?t.delete(o):t.set(o,s);return t};function X(r,e,t){return Object.hasOwn(e,t)&&e[t]===void 0?[]:je(r[t]??[],e[t]??[])}var _e=(r={},e={})=>({beforeRequest:X(r,e,"beforeRequest"),beforeRetry:X(r,e,"beforeRetry"),afterResponse:X(r,e,"afterResponse"),beforeError:X(r,e,"beforeError")}),kn=(r,e)=>{let t=new URLSearchParams;for(let n of[r,e])if(n!==void 0)if(n instanceof URLSearchParams)for(let[i,o]of n.entries())t.append(i,o);else if(Array.isArray(n))for(let i of n){if(!Array.isArray(i)||i.length!==2)throw new TypeError("Array search parameters must be provided in [[key, value], ...] format");t.append(String(i[0]),String(i[1]))}else if(S(n))for(let[i,o]of Object.entries(n))o!==void 0&&t.append(i,String(o));else{let i=new URLSearchParams(n);for(let[o,s]of i.entries())t.append(o,s)}return t},je=(...r)=>{let e={},t={},n={},i,o=[];for(let s of r)if(Array.isArray(s))Array.isArray(e)||(e=[]),e=[...e,...s];else if(S(s)){for(let[c,a]of Object.entries(s)){if(c==="signal"&&a instanceof globalThis.AbortSignal){o.push(a);continue}if(c==="context"){if(a!=null&&(!S(a)||Array.isArray(a)))throw new TypeError("The `context` option must be an object");e={...e,context:a==null?{}:{...e.context,...a}};continue}if(c==="searchParams"){a==null?i=void 0:i=i===void 0?a:kn(i,a);continue}S(a)&&c in e&&(a=je(e[c],a)),e={...e,[c]:a}}S(s.hooks)&&(n=_e(n,s.hooks),e.hooks=n),S(s.headers)&&(t=we(t,s.headers),e.headers=t)}return i!==void 0&&(e.searchParams=i),o.length>0&&(o.length===1?e.signal=o[0]:G?e.signal=AbortSignal.any(o):e.signal=o.at(-1)),e};u();var ct=r=>K.includes(r)?r.toUpperCase():r,Rn=["get","put","head","delete","options","trace"],Tn=[408,413,429,500,502,503,504],qn=[413,429,503],at={limit:2,methods:Rn,statusCodes:Tn,afterStatusCodes:qn,maxRetryAfter:Number.POSITIVE_INFINITY,backoffLimit:Number.POSITIVE_INFINITY,delay:r=>.3*2**(r-1)*1e3,jitter:void 0,retryOnTimeout:!1},ut=(r={})=>{if(typeof r=="number")return{...at,limit:r};if(r.methods&&!Array.isArray(r.methods))throw new Error("retry.methods must be an array");if(r.methods&&=r.methods.map(t=>t.toLowerCase()),r.statusCodes&&!Array.isArray(r.statusCodes))throw new Error("retry.statusCodes must be an array");let e=Object.fromEntries(Object.entries(r).filter(([,t])=>t!==void 0));return{...at,...e}};u();u();var R=class extends Error{request;constructor(e){super(`Request timed out: ${e.method} ${e.url}`),this.name="TimeoutError",this.request=e}};async function ve(r,e,t,n){return new Promise((i,o)=>{let s=setTimeout(()=>{t&&t.abort(),o(new R(r))},n.timeout);n.fetch(r,e).then(i).catch(o).then(()=>{clearTimeout(s)})})}u();async function Oe(r,{signal:e}){return new Promise((t,n)=>{e&&(e.throwIfAborted(),e.addEventListener("abort",i,{once:!0}));function i(){clearTimeout(o),n(e.reason)}let o=setTimeout(()=>{e?.removeEventListener("abort",i),t()},r)})}u();var lt=(r,e)=>{let t={};for(let n in e)Object.hasOwn(e,n)&&!(n in rt)&&!(n in tt)&&(!(n in r)||n in nt)&&(t[n]=e[n]);return t},dt=r=>r===void 0?!1:Array.isArray(r)?r.length>0:r instanceof URLSearchParams?r.size>0:typeof r=="object"?Object.keys(r).length>0:typeof r=="string"?r.trim().length>0:!!r;u();function ht(r){return r instanceof O||r?.name===O.name}function mt(r){return r instanceof R||r?.name===R.name}var B=class r{static create(e,t){let n=new r(e,t),i=async()=>{if(typeof n.#e.timeout=="number"&&n.#e.timeout>Y)throw new RangeError(`The \`timeout\` option cannot be greater than ${Y}`);await Promise.resolve();let s=await n.#b();for(let c of n.#e.hooks.afterResponse){let a=n.#h(s.clone()),l;try{l=await c(n.request,n.#c(),a,{retryCount:n.#n})}catch(f){throw n.#o(a),n.#o(s),f}if(l instanceof L)throw n.#o(a),n.#o(s),new H(l.options);let d=l instanceof globalThis.Response?l:s;a!==d&&n.#o(a),s!==d&&n.#o(s),s=d}if(n.#h(s),!s.ok&&(typeof n.#e.throwHttpErrors=="function"?n.#e.throwHttpErrors(s.status):n.#e.throwHttpErrors)){let c=new O(s,n.request,n.#c());for(let a of n.#e.hooks.beforeError)c=await a(c,{retryCount:n.#n});throw c}if(n.#e.onDownloadProgress){if(typeof n.#e.onDownloadProgress!="function")throw new TypeError("The `onDownloadProgress` option must be a function");if(!Ye)throw new Error("Streams are not supported in your environment. `ReadableStream` is missing.");let c=s.clone();return n.#o(s),ot(c,n.#e.onDownloadProgress)}return s},o=n.#m(i).finally(()=>{let s=n.#s;n.#l(s?.body??void 0),n.#l(n.request.body??void 0)});for(let[s,c]of Object.entries(Xe))s==="bytes"&&typeof globalThis.Response?.prototype?.bytes!="function"||(o[s]=async()=>{n.request.headers.set("accept",n.request.headers.get("accept")||c);let a=await o;if(s==="json"){if(a.status===204)return"";let l=await a.text();return l===""?"":t.parseJson?t.parseJson(l):JSON.parse(l)}return a[s]()});return o}static#f(e){return e&&typeof e=="object"&&!Array.isArray(e)&&!(e instanceof URLSearchParams)?Object.fromEntries(Object.entries(e).filter(([,t])=>t!==void 0)):e}request;#r;#n=0;#t;#e;#s;#i;#a;constructor(e,t={}){if(this.#t=e,this.#e={...t,headers:we(this.#t.headers,t.headers),hooks:_e({beforeRequest:[],beforeRetry:[],beforeError:[],afterResponse:[]},t.hooks),method:ct(t.method??this.#t.method??"GET"),prefixUrl:String(t.prefixUrl||""),retry:ut(t.retry),throwHttpErrors:t.throwHttpErrors??!0,timeout:t.timeout??1e4,fetch:t.fetch??globalThis.fetch.bind(globalThis),context:t.context??{}},typeof this.#t!="string"&&!(this.#t instanceof URL||this.#t instanceof globalThis.Request))throw new TypeError("`input` must be a string, URL, or Request");if(this.#e.prefixUrl&&typeof this.#t=="string"){if(this.#t.startsWith("/"))throw new Error("`input` must not begin with a slash when using `prefixUrl`");this.#e.prefixUrl.endsWith("/")||(this.#e.prefixUrl+="/"),this.#t=this.#e.prefixUrl+this.#t}Ke&&G&&(this.#i=this.#e.signal??this.#t.signal,this.#r=new globalThis.AbortController,this.#e.signal=this.#i?AbortSignal.any([this.#i,this.#r.signal]):this.#r.signal),ye&&(this.#e.duplex="half"),this.#e.json!==void 0&&(this.#e.body=this.#e.stringifyJson?.(this.#e.json)??JSON.stringify(this.#e.json),this.#e.headers.set("content-type",this.#e.headers.get("content-type")??"application/json"));let n=t.headers&&new globalThis.Headers(t.headers).has("content-type");if(this.#t instanceof globalThis.Request&&(Qe&&this.#e.body instanceof globalThis.FormData||this.#e.body instanceof URLSearchParams)&&!n&&this.#e.headers.delete("content-type"),this.request=new globalThis.Request(this.#t,this.#e),dt(this.#e.searchParams)){let o="?"+(typeof this.#e.searchParams=="string"?this.#e.searchParams.replace(/^\?/,""):new URLSearchParams(r.#f(this.#e.searchParams)).toString()),s=this.request.url.replace(/(?:\?.*?)?(?=#|$)/,o);this.request=new globalThis.Request(s,this.#e)}if(this.#e.onUploadProgress){if(typeof this.#e.onUploadProgress!="function")throw new TypeError("The `onUploadProgress` option must be a function");if(!ye)throw new Error("Request streams are not supported in your environment. The `duplex` option for `Request` is not available.");this.request=this.#p(this.request,this.#e.body??void 0)}}#u(){let e=this.#e.retry.delay(this.#n),t=e;this.#e.retry.jitter===!0?t=Math.random()*e:typeof this.#e.retry.jitter=="function"&&(t=this.#e.retry.jitter(e),(!Number.isFinite(t)||t<0)&&(t=e));let n=this.#e.retry.backoffLimit??Number.POSITIVE_INFINITY;return Math.min(n,t)}async#g(e){if(this.#n++,this.#n>this.#e.retry.limit)throw e;let t=e instanceof Error?e:new D(e);if(t instanceof H)return t.customDelay??this.#u();if(!this.#e.retry.methods.includes(this.request.method.toLowerCase()))throw e;if(this.#e.retry.shouldRetry!==void 0){let n=await this.#e.retry.shouldRetry({error:t,retryCount:this.#n});if(n===!1)throw e;if(n===!0)return this.#u()}if(mt(e)&&!this.#e.retry.retryOnTimeout)throw e;if(ht(e)){if(!this.#e.retry.statusCodes.includes(e.response.status))throw e;let n=e.response.headers.get("Retry-After")??e.response.headers.get("RateLimit-Reset")??e.response.headers.get("X-RateLimit-Retry-After")??e.response.headers.get("X-RateLimit-Reset")??e.response.headers.get("X-Rate-Limit-Reset");if(n&&this.#e.retry.afterStatusCodes.includes(e.response.status)){let i=Number(n)*1e3;Number.isNaN(i)?i=Date.parse(n)-Date.now():i>=Date.parse("2024-01-01")&&(i-=Date.now());let o=this.#e.retry.maxRetryAfter??i;return i<o?i:o}if(e.response.status===413)throw e}return this.#u()}#h(e){return this.#e.parseJson&&(e.json=async()=>this.#e.parseJson(await e.text())),e}#l(e){e&&e.cancel().catch(()=>{})}#o(e){this.#l(e.body??void 0)}async#m(e){try{return await e()}catch(t){let n=Math.min(await this.#g(t),Y);if(this.#n<1)throw t;if(await Oe(n,this.#i?{signal:this.#i}:{}),t instanceof H&&t.customRequest){let i=this.#e.signal?new globalThis.Request(t.customRequest,{signal:this.#e.signal}):new globalThis.Request(t.customRequest);this.#d(i)}for(let i of this.#e.hooks.beforeRetry){let o=await i({request:this.request,options:this.#c(),error:t,retryCount:this.#n});if(o instanceof globalThis.Request){this.#d(o);break}if(o instanceof globalThis.Response)return o;if(o===Q)return}return this.#m(e)}}async#b(){this.#r?.signal.aborted&&(this.#r=new globalThis.AbortController,this.#e.signal=this.#i?AbortSignal.any([this.#i,this.#r.signal]):this.#r.signal,this.request=new globalThis.Request(this.request,{signal:this.#e.signal}));for(let t of this.#e.hooks.beforeRequest){let n=await t(this.request,this.#c(),{retryCount:this.#n});if(n instanceof Response)return n;if(n instanceof globalThis.Request){this.#d(n);break}}let e=lt(this.request,this.#e);return this.#s=this.request,this.request=this.#s.clone(),this.#e.timeout===!1?this.#e.fetch(this.#s,e):ve(this.#s,e,this.#r,this.#e)}#c(){if(!this.#a){let{hooks:e,...t}=this.#e;this.#a=Object.freeze(t)}return this.#a}#d(e){this.#a=void 0,this.request=this.#p(e)}#p(e,t){return!this.#e.onUploadProgress||!e.body?e:st(e,this.#e.onUploadProgress,t??this.#e.body??void 0)}};var xe=r=>{let e=(t,n)=>B.create(t,M(r,n));for(let t of K)e[t]=(n,i)=>B.create(n,M(r,i,{method:t}));return e.create=t=>xe(M(t)),e.extend=t=>(typeof t=="function"&&(t=t(r??{})),xe(M(r,t))),e.stop=Q,e.retry=et,e},Nn=xe(),U=Nn;var In="https://pro.lahat.group/api";function $e(){return process.env.WARDROBE_API_URL??In}function j(r){if(!r)throw new Error("Not authenticated. Run `wardrobe login` first.");return U.create({prefixUrl:$e(),headers:{Authorization:`Bearer ${r}`},retry:0})}u();import{readFileSync as Dn,writeFileSync as Hn,mkdirSync as Vn,rmSync as Fn,existsSync as Ln}from"fs";import{homedir as Mn}from"os";import{join as pt}from"path";function ft(){return process.env.WARDROBE_CONFIG_DIR??pt(Mn(),".config","wardrobe")}function Ce(){return pt(ft(),"config.json")}function _(){let r=Ce();if(!Ln(r))return null;try{return JSON.parse(Dn(r,"utf8"))}catch{return null}}function gt(r){Vn(ft(),{recursive:!0,mode:448}),Hn(Ce(),JSON.stringify(r,null,2),{mode:384})}function bt(){Fn(Ce(),{force:!0})}u();import{readFileSync as Bn}from"fs";function Un(){return new Promise((r,e)=>{let t="";process.stdin.setEncoding("utf8"),process.stdin.on("data",n=>t+=n),process.stdin.on("end",()=>r(t)),process.stdin.on("error",e)})}async function C(r){let e=r==="-"?await Un():Bn(r,"utf8");try{return JSON.parse(e)}catch(t){throw new Error(`Failed to parse JSON input: ${t.message}`)}}function h(r,e,t){e?process.stdout.write(JSON.stringify(r,null,2)+`
25
- `):t()}function Ae(){return j(_()?.accessToken??null)}async function yt(r={}){let e={};for(let n of["width","height","depth"])r[n]!==void 0&&(e[n]=r[n]);let t=await Ae().get("configs/init",{searchParams:e}).json();process.stdout.write(JSON.stringify(t.config,null,2)+`
25
+ `):t()}function Ae(){return j(_()?.accessToken??null)}async function yt(r={}){let e={};for(let n of["width","height","ceiling","depth","position"])r[n]!==void 0&&(e[n]=r[n]);let t=await Ae().get("configs/init",{searchParams:e}).json();process.stdout.write(JSON.stringify(t.config,null,2)+`
26
26
  `)}async function wt(r,e={}){let t=await C(r),n=await Ae().post("configs/validate",{json:{config:t}}).json();h(n,!!e.json,()=>{if(n.valid)process.stdout.write(`valid
27
27
  `);else for(let i of n.errors)process.stdout.write(`${i.severity}: ${i.path} \u2014 ${i.message}
28
28
  `)}),n.valid||(process.exitCode=1)}async function _t(r,e={}){let t=Ae(),n=r==="-"||Wn(r)?await C(r):(await t.get(`designs/${r}`).json()).config,i=await t.post("configs/describe",{json:{config:n,locale:e.locale??"en"}}).json();h(i.lines,!!e.json,()=>process.stdout.write(i.lines.join(`
@@ -86,5 +86,5 @@ To authorize this CLI, visit:
86
86
  `);for(let s of i.warnings)process.stdout.write(`warning: ${s}
87
87
  `)})}u();import{fileURLToPath as Qn}from"url";import{dirname as en,join as N}from"path";import{homedir as Xn}from"os";import{existsSync as Zn,mkdirSync as er,readdirSync as tr,copyFileSync as nr,statSync as rr}from"fs";function ir(){let r=en(Qn(import.meta.url));for(let e=0;e<6;e++){let t=N(r,"skill");if(Zn(N(t,"SKILL.md")))return t;r=en(r)}throw new Error("Bundled SKILL.md not found next to the CLI package")}function or(r){let e=r?N(process.cwd(),".claude"):N(Xn(),".claude");return N(e,"skills","wardrobe-configurator")}function sr(r={}){let e=r.sourceDir??ir(),t=r.targetDir??or(!!r.project);er(t,{recursive:!0});let n=[];for(let i of tr(e)){let o=N(e,i);rr(o).isFile()&&(nr(o,N(t,i)),n.push(i))}return{target:t,files:n}}function tn(r){let e=sr({project:r.project});h(e,!!r.json,()=>{process.stdout.write(`Installed ${e.files.length} file(s) to ${e.target}
88
88
  `),process.stdout.write(`Restart Claude Code, then invoke /wardrobe-configurator
89
- `)})}u();async function ar(r){if(!(r instanceof O))return null;try{return await r.response.json()}catch{return null}}async function nn(r,e){let t=await ar(r);if(e)return JSON.stringify(t?{error:t.error??"request_failed",...t}:{error:r.message},null,2);if(!t)return`Error: ${r.message}`;let n=[t.error??"Request failed"];return t.detail&&n.push(t.detail),t.fix_hint&&n.push(`fix: ${t.fix_hint}`),t.issues&&n.push(JSON.stringify(t.issues)),`Error: ${n.join(" \u2014 ")}`}u();var rn="2.1.2";function ee(r){return r.option("--assembly","buy assembly for this design").option("--no-assembly","do not buy assembly").option("--transport","buy transport for this design").option("--no-transport","do not buy transport").option("--measurement","buy a measurement visit for this design").option("--no-measurement","do not buy a measurement visit")}b.name("wardrobe").description("Wardrobe configurator CLI").version(rn);b.command("login").description("Authenticate via the OAuth device flow").option("--json","machine-readable output").action(r=>jt(r));b.command("whoami").description("Show the authenticated user").option("--json","machine-readable output").action(r=>vt(r));b.command("logout").description("Remove the stored agent key").option("--json","machine-readable output").action(r=>Ot(r));b.command("init").description("Emit a fresh, valid WardrobeConfig JSON (via the API)").option("--width <cm>","total width in cm",r=>Number(r)).option("--height <cm>","height in cm",r=>Number(r)).option("--depth <cm>","depth in cm",r=>Number(r)).action(r=>yt(r));b.command("validate <file>").description("Validate a config against the live rules (file or - for stdin)").option("--json","machine-readable output").action((r,e)=>wt(r,e));b.command("describe <idOrFile>").description("Describe a config (local file/-) or saved design (id), via the API").option("--locale <locale>","en or he","en").option("--json","machine-readable output").action((r,e)=>_t(r,e));b.command("catalog <kind>").description(`List what a config may name, from the live catalogs: ${ke.join(" | ")}. Read this before choosing a colour, handle or aluminium front \u2014 never invent ids`).option("--json","machine-readable output").action((r,e)=>Yt(r,e));b.command("prompt <projectId> <text>").description("Describe a wardrobe in words (he/en) and get a draft config file + the assumptions made. Saves nothing; counts against a daily per-key limit; takes 10-30 s").option("--locale <locale>","he or en","he").option("--out <file>","where to write the draft config (default ./prompt-<timestamp>.json)").option("--json","print the whole response (config, assumptions, adjustments, warnings) instead of writing a file").action((r,e,t)=>Zt(r,e,t));b.command("price <file>").description("Price a config via the API (file or - for stdin)").option("--json","machine-readable output").action((r,e)=>Vt(r,e));b.command("plan <file>").description("Dry-run: price a config + BOM preview (mutates nothing)").option("--json","machine-readable output").action((r,e)=>Bt(r,e));ee(b.command("apply <file>").description("Save a config as a design (file or - for stdin)").requiredOption("--project <id>","project id").option("--name <name>","design name").option("--json","machine-readable output")).action((r,e)=>Pe(r,e));var I=b.command("designs").description("Manage saved designs");I.command("list").description("List designs in a project, or the newest across every project with --project omitted").option("--project <id>","project id").option("--limit <n>","cross-project mode only: max rows (default 20, max 100)",r=>Number(r)).option("--since <iso>","cross-project mode only: only designs updated at/after this ISO 8601 timestamp").option("--json","machine-readable output").action(r=>Ft(r));ee(I.command("create <file>").description("Save a config as a new design in a project (same as `apply`)").requiredOption("--project <id>","project id").option("--name <name>","design name").option("--json","machine-readable output")).action((r,e)=>Pe(r,e));I.command("get <id>").description("Show one saved design with its stored config \u2014 read this before `designs update`, which replaces the whole config").option("--config","print only the bare config: exactly what `designs update --file` accepts").option("--json","machine-readable output: id, projectId, name, version, nisTotal, serviceTotal, config").action((r,e)=>Tt(r,e));ee(I.command("update <id>").description("Revise a saved design (config, name and/or services). Services you do not mention keep their current value").option("--file <file>","new config JSON (file or - for stdin)").option("--name <name>","new design name").option("--json","machine-readable output")).action((r,e)=>Ut(r,e));ee(I.command("duplicate <id>").description("Copy a design (into the same project unless --project is given). Services are not copied \u2014 pass the flags to set them").option("--project <id>","target project (default: the source design's project)").option("--name <name>",'name of the copy (default: "<name> \u05E2\u05D5\u05EA\u05E7")').option("--json","machine-readable output")).action((r,e)=>zt(r,e));I.command("delete <id>").description("Archive a design (soft delete; there is no restore in the app). Requires --yes").option("--yes","confirm the archive").option("--json","machine-readable output").action((r,e)=>Jt(r,e));I.command("thumbnail <id>").description("Download a design's thumbnail image; renders it on the server first if none exists yet").option("--out <file>","destination path").option("--render","re-render on the server even if a thumbnail exists").option("--json","machine-readable output").action((r,e)=>Nt(r,e));var Re=b.command("projects").description("Manage projects");Re.command("list").description("List accessible projects").option("--phone <number>","filter by the customer's phone number").option("--designer <idOrName>","filter by the designer who owns the project").option("--json","machine-readable output").action(r=>Mt(r));Re.command("get <id>").description("Show one project").option("--json","machine-readable output").action((r,e)=>$t(r,e));Re.command("members <id>").description("List a project's owner and members").option("--json","machine-readable output").action((r,e)=>Ct(r,e));var Te=b.command("customers").description("Read customers");Te.command("list").description("List your customers").option("--json","machine-readable output").action(r=>At(r));Te.command("get <id>").description("Show one customer").option("--json","machine-readable output").action((r,e)=>St(r,e));Te.command("projects <id>").description("List a customer's projects").option("--json","machine-readable output").action((r,e)=>Et(r,e));var te=b.command("orders").description("Read orders, or move one along the workflow (orders:transition, office accounts only)");te.command("list").description("List a project's orders, or the newest-updated across every project with --project omitted").option("--project <id>","project id").option("--open","only the project's active (non-cancelled) order (requires --project)").option("--limit <n>","cross-project mode only: max rows (default 20, max 100)",r=>Number(r)).option("--since <iso>","cross-project mode only: only orders updated at/after this ISO 8601 timestamp").option("--json","machine-readable output").action(r=>Pt(r));te.command("get <id>").description("Show one order, with its designs embedded").option("--json","machine-readable output").action((r,e)=>kt(r,e));te.command("pdf <id>").description("Download an order PDF").option("--out <file>","destination path").option("--json","machine-readable output").action((r,e)=>Rt(r,e));te.command("set-status <id> <status>").description("Move an order to its next status (e.g. paid, pending_assembly); only legal steps, never cancelled").option("--dry-run","check the step is allowed without changing anything").option("--note <text>","kept on the order's status history (e.g. a receipt number)").option("--json","machine-readable output").action((r,e,t)=>Gt(r,e,t));var qe=b.command("quotations").description("Read quotations (quotations:read), or mint a revision (quotations:write)");qe.command("list").description("Show a project's quote history, or the newest-generated across every project with --project omitted").option("--project <id>","project id").option("--limit <n>","cross-project mode only: max rows (default 20, max 100)",r=>Number(r)).option("--since <iso>","cross-project mode only: only quotations generated at/after this ISO 8601 timestamp").option("--json","machine-readable output").action(r=>Lt(r));qe.command("pdf <projectId>").description("Download a quotation PDF (default: the project's current revision) \u2014 never mints a new one").option("--revision <n>","a specific historical revision instead of the current one",r=>Number(r)).option("--out <file>","destination path").option("--json","machine-readable output").action((r,e)=>qt(r,e));qe.command("mint <projectId>").description("File the project's quotation at its current price and download the PDF \u2014 unlike `pdf`, this can mint a new revision (requires quotations:write)").option("--out <file>","destination path").option("--json","machine-readable output: path, bytes, quoteNumber, revision, minted").action((r,e)=>Wt(r,e));var on=b.command("users").description("Read registered accounts (requires staff:read)");on.command("list").description("List registered users, newest-registered first").option("--limit <n>","max rows (default 20, max 100)",r=>Number(r)).option("--since <iso>","only users registered at/after this ISO 8601 timestamp").option("--before <iso>","only users registered before this ISO 8601 timestamp (page back with the last row's createdAt)").option("--role <role>","only users with this role: admin, support, designer, client").option("--json","machine-readable output").action(r=>It(r));on.command("by-phone <phone>").description("Find the account(s) whose own phone is this number (+972\u2026 and 05\u2026 forms match)").option("--json","machine-readable output").action((r,e)=>Dt(r,e));b.command("help-texts").description("Read the help-text table").option("--json","machine-readable output").action(r=>Ht(r));var cr=b.command("skill").description("Manage the bundled Claude Skill");cr.command("install").description("Install the wardrobe-configurator Skill for Claude Code").option("--project","install into ./.claude/skills instead of ~/.claude/skills").option("--json","machine-readable output").action(r=>tn(r));b.parseAsync().catch(async r=>{let e=await nn(r,process.argv.includes("--json"));process.stderr.write(`${e}
89
+ `)})}u();async function ar(r){if(!(r instanceof O))return null;try{return await r.response.json()}catch{return null}}async function nn(r,e){let t=await ar(r);if(e)return JSON.stringify(t?{error:t.error??"request_failed",...t}:{error:r.message},null,2);if(!t)return`Error: ${r.message}`;let n=[t.error??"Request failed"];return t.detail&&n.push(t.detail),t.fix_hint&&n.push(`fix: ${t.fix_hint}`),t.issues&&n.push(JSON.stringify(t.issues)),`Error: ${n.join(" \u2014 ")}`}u();var rn="2.1.3";function ee(r){return r.option("--assembly","buy assembly for this design").option("--no-assembly","do not buy assembly").option("--transport","buy transport for this design").option("--no-transport","do not buy transport").option("--measurement","buy a measurement visit for this design").option("--no-measurement","do not buy a measurement visit")}b.name("wardrobe").description("Wardrobe configurator CLI").version(rn);b.command("login").description("Authenticate via the OAuth device flow").option("--json","machine-readable output").action(r=>jt(r));b.command("whoami").description("Show the authenticated user").option("--json","machine-readable output").action(r=>vt(r));b.command("logout").description("Remove the stored agent key").option("--json","machine-readable output").action(r=>Ot(r));b.command("init").description("Emit a fresh, valid WardrobeConfig JSON (via the API)").option("--width <cm>","total width in cm, as measured on the wall (gross; a niche: its opening)",r=>Number(r)).option("--height <cm>","wardrobe height in cm (the ceiling follows, 3 cm above)",r=>Number(r)).option("--ceiling <cm>","room ceiling height in cm (the wardrobe stands 3 cm under it)",r=>Number(r)).option("--depth <cm>","depth in cm, doors included (default 60)",r=>Number(r)).option("--position <pos>","right-corner (default) | left-corner | niche | freestanding").action(r=>yt(r));b.command("validate <file>").description("Validate a config against the live rules (file or - for stdin)").option("--json","machine-readable output").action((r,e)=>wt(r,e));b.command("describe <idOrFile>").description("Describe a config (local file/-) or saved design (id), via the API").option("--locale <locale>","en or he","en").option("--json","machine-readable output").action((r,e)=>_t(r,e));b.command("catalog <kind>").description(`List what a config may name, from the live catalogs: ${ke.join(" | ")}. Read this before choosing a colour, handle or aluminium front \u2014 never invent ids`).option("--json","machine-readable output").action((r,e)=>Yt(r,e));b.command("prompt <projectId> <text>").description("Describe a wardrobe in words (he/en) and get a draft config file + the assumptions made. Saves nothing; counts against a daily per-key limit; takes 10-30 s").option("--locale <locale>","he or en","he").option("--out <file>","where to write the draft config (default ./prompt-<timestamp>.json)").option("--json","print the whole response (config, assumptions, adjustments, warnings) instead of writing a file").action((r,e,t)=>Zt(r,e,t));b.command("price <file>").description("Price a config via the API (file or - for stdin)").option("--json","machine-readable output").action((r,e)=>Vt(r,e));b.command("plan <file>").description("Dry-run: price a config + BOM preview (mutates nothing)").option("--json","machine-readable output").action((r,e)=>Bt(r,e));ee(b.command("apply <file>").description("Save a config as a design (file or - for stdin)").requiredOption("--project <id>","project id").option("--name <name>","design name").option("--json","machine-readable output")).action((r,e)=>Pe(r,e));var I=b.command("designs").description("Manage saved designs");I.command("list").description("List designs in a project, or the newest across every project with --project omitted").option("--project <id>","project id").option("--limit <n>","cross-project mode only: max rows (default 20, max 100)",r=>Number(r)).option("--since <iso>","cross-project mode only: only designs updated at/after this ISO 8601 timestamp").option("--json","machine-readable output").action(r=>Ft(r));ee(I.command("create <file>").description("Save a config as a new design in a project (same as `apply`)").requiredOption("--project <id>","project id").option("--name <name>","design name").option("--json","machine-readable output")).action((r,e)=>Pe(r,e));I.command("get <id>").description("Show one saved design with its stored config \u2014 read this before `designs update`, which replaces the whole config").option("--config","print only the bare config: exactly what `designs update --file` accepts").option("--json","machine-readable output: id, projectId, name, version, nisTotal, serviceTotal, config").action((r,e)=>Tt(r,e));ee(I.command("update <id>").description("Revise a saved design (config, name and/or services). Services you do not mention keep their current value").option("--file <file>","new config JSON (file or - for stdin)").option("--name <name>","new design name").option("--json","machine-readable output")).action((r,e)=>Ut(r,e));ee(I.command("duplicate <id>").description("Copy a design (into the same project unless --project is given). Services are not copied \u2014 pass the flags to set them").option("--project <id>","target project (default: the source design's project)").option("--name <name>",'name of the copy (default: "<name> \u05E2\u05D5\u05EA\u05E7")').option("--json","machine-readable output")).action((r,e)=>zt(r,e));I.command("delete <id>").description("Archive a design (soft delete; there is no restore in the app). Requires --yes").option("--yes","confirm the archive").option("--json","machine-readable output").action((r,e)=>Jt(r,e));I.command("thumbnail <id>").description("Download a design's thumbnail image; renders it on the server first if none exists yet").option("--out <file>","destination path").option("--render","re-render on the server even if a thumbnail exists").option("--json","machine-readable output").action((r,e)=>Nt(r,e));var Re=b.command("projects").description("Manage projects");Re.command("list").description("List accessible projects").option("--phone <number>","filter by the customer's phone number").option("--designer <idOrName>","filter by the designer who owns the project").option("--json","machine-readable output").action(r=>Mt(r));Re.command("get <id>").description("Show one project").option("--json","machine-readable output").action((r,e)=>$t(r,e));Re.command("members <id>").description("List a project's owner and members").option("--json","machine-readable output").action((r,e)=>Ct(r,e));var Te=b.command("customers").description("Read customers");Te.command("list").description("List your customers").option("--json","machine-readable output").action(r=>At(r));Te.command("get <id>").description("Show one customer").option("--json","machine-readable output").action((r,e)=>St(r,e));Te.command("projects <id>").description("List a customer's projects").option("--json","machine-readable output").action((r,e)=>Et(r,e));var te=b.command("orders").description("Read orders, or move one along the workflow (orders:transition, office accounts only)");te.command("list").description("List a project's orders, or the newest-updated across every project with --project omitted").option("--project <id>","project id").option("--open","only the project's active (non-cancelled) order (requires --project)").option("--limit <n>","cross-project mode only: max rows (default 20, max 100)",r=>Number(r)).option("--since <iso>","cross-project mode only: only orders updated at/after this ISO 8601 timestamp").option("--json","machine-readable output").action(r=>Pt(r));te.command("get <id>").description("Show one order, with its designs embedded").option("--json","machine-readable output").action((r,e)=>kt(r,e));te.command("pdf <id>").description("Download an order PDF").option("--out <file>","destination path").option("--json","machine-readable output").action((r,e)=>Rt(r,e));te.command("set-status <id> <status>").description("Move an order to its next status (e.g. paid, pending_assembly); only legal steps, never cancelled").option("--dry-run","check the step is allowed without changing anything").option("--note <text>","kept on the order's status history (e.g. a receipt number)").option("--json","machine-readable output").action((r,e,t)=>Gt(r,e,t));var qe=b.command("quotations").description("Read quotations (quotations:read), or mint a revision (quotations:write)");qe.command("list").description("Show a project's quote history, or the newest-generated across every project with --project omitted").option("--project <id>","project id").option("--limit <n>","cross-project mode only: max rows (default 20, max 100)",r=>Number(r)).option("--since <iso>","cross-project mode only: only quotations generated at/after this ISO 8601 timestamp").option("--json","machine-readable output").action(r=>Lt(r));qe.command("pdf <projectId>").description("Download a quotation PDF (default: the project's current revision) \u2014 never mints a new one").option("--revision <n>","a specific historical revision instead of the current one",r=>Number(r)).option("--out <file>","destination path").option("--json","machine-readable output").action((r,e)=>qt(r,e));qe.command("mint <projectId>").description("File the project's quotation at its current price and download the PDF \u2014 unlike `pdf`, this can mint a new revision (requires quotations:write)").option("--out <file>","destination path").option("--json","machine-readable output: path, bytes, quoteNumber, revision, minted").action((r,e)=>Wt(r,e));var on=b.command("users").description("Read registered accounts (requires staff:read)");on.command("list").description("List registered users, newest-registered first").option("--limit <n>","max rows (default 20, max 100)",r=>Number(r)).option("--since <iso>","only users registered at/after this ISO 8601 timestamp").option("--before <iso>","only users registered before this ISO 8601 timestamp (page back with the last row's createdAt)").option("--role <role>","only users with this role: admin, support, designer, client").option("--json","machine-readable output").action(r=>It(r));on.command("by-phone <phone>").description("Find the account(s) whose own phone is this number (+972\u2026 and 05\u2026 forms match)").option("--json","machine-readable output").action((r,e)=>Dt(r,e));b.command("help-texts").description("Read the help-text table").option("--json","machine-readable output").action(r=>Ht(r));var cr=b.command("skill").description("Manage the bundled Claude Skill");cr.command("install").description("Install the wardrobe-configurator Skill for Claude Code").option("--project","install into ./.claude/skills instead of ~/.claude/skills").option("--json","machine-readable output").action(r=>tn(r));b.parseAsync().catch(async r=>{let e=await nn(r,process.argv.includes("--json"));process.stderr.write(`${e}
90
90
  `),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lahat-group/cli",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "CLI + Claude Skill to build, validate, price, and save wardrobe designs.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
package/skill/SKILL.md CHANGED
@@ -44,8 +44,10 @@ retired — they are not in `catalog materials`, so don't use them.
44
44
 
45
45
  ## The loop
46
46
 
47
- 1. **init** — `wardrobe init [--width <cm>] [--height <cm>] [--depth <cm>] > wardrobe.json`
48
- emits a fresh, valid `WardrobeConfig` (full-width, split into optimal columns).
47
+ 1. **init** — `wardrobe init [--width <cm>] [--position <pos>] [--height <cm> | --ceiling <cm>] [--depth <cm>] > wardrobe.json`
48
+ emits a fresh, valid `WardrobeConfig` on the Lahat Pro defaults below (full-width,
49
+ split into columns). `--width` is the **gross** wall measurement; the server turns it
50
+ into the net width for `--position`, and sets the height/ceiling pair.
49
51
  2. **edit** — modify `wardrobe.json` directly (see shape below).
50
52
  3. **validate** — `wardrobe validate wardrobe.json` (or `-` for stdin). Runs the
51
53
  **live** constraint pipeline server-side — the same rules `wardrobe price` enforces,
@@ -60,6 +62,30 @@ retired — they are not in `catalog materials`, so don't use them.
60
62
  required.** `wardrobe designs create <file> --project <id>` is the same verb under
61
63
  the `designs` group, if that reads more clearly in context.
62
64
 
65
+ ### Lahat Pro defaults — apply them unless the user says otherwise
66
+
67
+ `init`, `wardrobe prompt` and the web wizard all start from these; keep to them when you
68
+ edit a config by hand.
69
+
70
+ - **Width is gross.** Every width the user gives is what they measure on the wall (a niche:
71
+ its opening). Pass it to `init --width` with the right `--position`. The config's own
72
+ `width` is **net** — if you change it by hand, net = gross − 5 (corner), − 4
73
+ (freestanding), − 6 (niche).
74
+ - **Position.** Not in a niche → against a side wall: **`right-corner`** unless the user
75
+ says left. `freestanding` only when they say it stands free of side walls.
76
+ - **Height and ceiling stand 3 cm apart.** A ceiling → `--ceiling` (height = ceiling − 3,
77
+ at most 278). A wardrobe height → `--height` (ceiling = height + 3). Neither → 277 under
78
+ a 280 ceiling. Editing JSON, keep `ceilingHeight` = `height` + 3.
79
+ - **Depth 60 cm** (doors included) unless stated. **On the 8 cm sockel** (`"sockel": true`).
80
+ - **Interior colour:** warm oak mix (אלון חם מיקס, `interior-kh62-dt`) unless asked.
81
+ - **Drawers:** "one drawer" → a pair of small (15 cm) drawers; "two drawers" → two large
82
+ (33.5 cm) drawers; both on the **second level from the bottom**. A size the user names
83
+ wins. `wardrobe prompt` and the wizard place them; editing modules, leave one level free
84
+ under the stack.
85
+ - **Hanging rail:** one level below the highest position (one shelf above it), back at the
86
+ top only when it would not clear the drawers. A standard rail — a pull-down rail
87
+ (סרווטו, `railKind: "pull-down"`) only when the user asks for one.
88
+
63
89
  ### Describing a wardrobe in words
64
90
 
65
91
  If the user describes what they want instead of giving dimensions —
@@ -237,13 +263,14 @@ Don't reach for these; they are not CLI verbs and not agent-reachable:
237
263
 
238
264
  ```jsonc
239
265
  {
240
- "roomPosition": "freestanding", // placement
241
- "width": 100, "height": 240, "depth": 60, // cm
266
+ "roomPosition": "right-corner", // placement
267
+ "width": 115, "height": 277, "ceilingHeight": 280, "depth": 60, // cm; width is NET
268
+ "sockel": true,
242
269
  "material": "<externalMaterialId>",
243
270
  "materialInternal": "<internalMaterialId>",
244
271
  "materialExternal": "<externalMaterialId>",
245
272
  "columns": [ // one or more columns, widths sum to `width`
246
- { "id": "<nanoid>", "width": 100, "modules": [ /* rails, shelves, drawers */ ] }
273
+ { "id": "<nanoid>", "width": 115, "modules": [ /* rails, shelves, drawers */ ] }
247
274
  ],
248
275
  "ruleVersion": "<current rule version>"
249
276
  }