@turbobase/cli 1.1.16 → 1.1.17
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.
|
@@ -1785,7 +1785,7 @@ ${r}`}var f0=JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",comman
|
|
|
1785
1785
|
`))}async function Yg(t,e,r=no){let n=[...new Set(t.map(a=>a.trim()).filter(Boolean))];n.length===0&&S("name at least one skill slug. `turbobase skills list` shows them.");let o=await oo(e,r);if(!await Ni(`Publish ${n.length} skill(s)?`,"Yes \u2014 publish them","their text is injected into every matching turn from now on",e))return;let i;try{i=await fe("publishing",()=>o.skillsPublish(n))}catch(a){ke(a,"skills:publish","publish a skill"),S(`publish failed: ${a instanceof Error?a.message:String(a)}`)}T(x.green(`published ${i.published.length} skill(s) \u2014 live now.`)),i.unknown.length>0&&(le(x.yellow(`no such skill: ${i.unknown.join(", ")}`)),process.exitCode=1),le(x.dim(`release v${i.version.version_no} \xB7 undo with: turbobase skills unpublish ${i.published.join(" ")}`))}async function Xg(t,e,r=no){let n=[...new Set(t.map(a=>a.trim()).filter(Boolean))];n.length===0&&S("name at least one skill slug. `turbobase skills list` shows them.");let o=await oo(e,r);if(!await Ni(`Move ${n.length} skill(s) back to draft?`,"Yes \u2014 unpublish them","they stop being injected immediately; the text and its history are kept",e))return;let i;try{i=await fe("unpublishing",()=>o.skillsUnpublish(n))}catch(a){ke(a,"skills:publish","unpublish a skill"),S(`unpublish failed: ${a instanceof Error?a.message:String(a)}`)}T(x.green(`unpublished ${i.unpublished.length} skill(s) \u2014 retracted now.`)),le(x.dim(`release v${i.version.version_no} \xB7 re-publish with: turbobase skills publish ${i.unpublished.join(" ")}`)),i.unknown.length>0&&(le(x.yellow(`no such skill: ${i.unknown.join(", ")}`)),process.exitCode=1)}async function Qg(t,e,r=no){let n=Number(t);(!Number.isInteger(n)||n<1)&&S(`--version must be a release number (got '${t}'). \`turbobase skills releases\` lists them.`);let o=await oo(e,r);if(await Ni(`Roll the skills back to release v${n}?`,"Yes \u2014 restore that release","content only: a skill an operator has since disabled stays off",e))try{let i=await fe("rolling back",()=>o.skillsRollback(n));T(x.green(`rolled back to v${i.version.version_no}.`))}catch(i){ke(i,"skills:publish","roll back a skills release"),S(`rollback failed: ${i instanceof Error?i.message:String(i)}`)}}async function ey(t,e=no){let r=await oo(t,e),n;try{n=await fe("reading the releases",()=>r.skillsReleases())}catch(o){ke(o,"skills:publish","read the skills releases"),S(`could not read the releases: ${o instanceof Error?o.message:String(o)}`)}if(n.items.length===0){le("no releases yet.");return}for(let o of n.items){let s=o.is_current?x.green("*"):" ",i=o.release_note?` \u2014 ${o.release_note}`:"";T(`${s} v${o.version_no} ${o.skill_count} skill(s) ${x.dim(o.published_at)}${o.published_by?x.dim(` by ${o.published_by}`):""}${x.dim(i)}`)}}async function ty(t,e,r=no){let n=[...new Set((t??[]).map(l=>l.trim()).filter(Boolean))];n.length===0&&S("name at least one skill to delete. `turbobase skills list` shows the slugs.");let o=await oo(e,r);if(!await Ni(n.length===1?`Delete the skill '${n[0]}'?`:`Delete ${n.length} skills?
|
|
1786
1786
|
${n.join(`
|
|
1787
1787
|
`)}`,"Yes \u2014 delete them","permanent: the rows and their text are removed. Prior releases keep their own copy.",e))return;let i=[],a=[];for(let l of n)try{i.push(await fe(`deleting ${l}`,()=>o.skillsDelete(l)))}catch(u){if(u instanceof be&&u.status===404){a.push(l);continue}i.length>0&&le(x.dim(`deleted before the error: ${i.map(d=>d.slug).join(", ")}`)),ke(u,"skills:publish","delete a skill"),S(`delete failed on '${l}': ${u instanceof Error?u.message:String(u)}`)}i.length>0&&T(x.green(`deleted ${i.length} skill(s).`));let c=i.filter(l=>l.wasPublished).map(l=>l.slug);c.length>0&&le(x.yellow(`Published until now, so no longer injected: ${c.join(", ")}`)),a.length>0&&(le(x.yellow(`no such skill: ${a.join(", ")}`)),process.exitCode=1)}import{promises as ry}from"node:fs";import{join as j0,relative as L0,resolve as M0,sep as D0}from"node:path";var U0=new Set(["node_modules","dist","build","coverage",".git",".claude",".turbobase",".next",".svelte-kit",".vercel",".cache"]),Z0=new Set([".mcp.json",".env"]);function q0(t){return Z0.has(t)||t.startsWith(".env.")&&t!==".env.example"}function F0(t){return Buffer.compare(Buffer.from(t.toString("utf8"),"utf8"),t)===0}function B0(t){return t.includes(0)}async function ny(t){let e=M0(t),r={},n=[],o=[],s=[];async function i(a){let c=await ry.readdir(a,{withFileTypes:!0});for(let l of c){let u=j0(a,l.name);if(l.isDirectory()){if(U0.has(l.name))continue;await i(u);continue}if(!l.isFile())continue;let d=L0(e,u).split(D0).join("/");if(q0(l.name)){o.push(d);continue}let b=await ry.readFile(u);if(!F0(b)){n.push(d);continue}B0(b)&&s.push(d),r[d]=b.toString("utf8")}}return await i(e),{files:r,skipped:n,secrets:o,unstorable:s}}import{randomBytes as V0}from"node:crypto";var H0="/_tb/app-assets",K0=16,W0=/^[0-9a-f]{32}$/;function oy(){return V0(K0).toString("hex")}function G0(t){return W0.test(t)}function sy(t){if(!G0(t))throw new Error(`not a build id: ${t}`);return`${H0}/${t}/`}var iy=7;import ji from"node:fs/promises";import Qo from"node:path";var zl={esbuild:"0.28.1","isolated-vm":"6.1.2"},Nl="@turbobase/isolated-vm-prebuilds";function jl(t,e){return`${t}-${e}`}function Ll(t=process.versions){return`abi${t.modules}`}var ay=["darwin-x64","win32-arm64"];async function cy(t){let e=t.platform??process.platform,r=t.arch??process.arch,n=jl(e,r),o=Qo.join(t.home,"node_modules"),s=Qo.join(o,...Nl.split("/"),"prebuilds",n),i=Qo.join(o,"isolated-vm","prebuilds",n),a;try{a=await ji.readdir(s)}catch{return[]}let c=[];await ji.mkdir(i,{recursive:!0});for(let l of a){if(!l.endsWith(".node"))continue;let u=Qo.join(i,l);try{await ji.copyFile(Qo.join(s,l),u,ji.constants?.COPYFILE_EXCL??1),c.push(l)}catch{}}return c}import{homedir as J0}from"node:os";import{join as ly}from"node:path";import{promises as Ml}from"node:fs";function dy(){let t=process.env.TURBOBASE_HOME??ly(J0(),".turbobase");return{dir:t,file:ly(t,"sandboxes.json")}}var Y0={s:1e3,m:6e4,h:36e5,d:864e5};function X0(t){let e=/^\s*(\d+)\s*([smhd])\s*$/.exec(t);if(!e)throw new Error(`invalid --ttl '${t}': expected <number><unit> where unit is s|m|h|d (e.g. 72h, 30m, 7d).`);let r=Number(e[1]);if(!Number.isFinite(r)||r<=0)throw new Error(`invalid --ttl '${t}': duration must be a positive integer.`);return r*Y0[e[2]]}function Q0(t,e=Date.now()){return new Date(e+X0(t)).toISOString()}function eE(t,e=Date.now()){let r=Date.parse(t);return Number.isNaN(r)?!0:r<=e}function tE(t){let e=/^\s*(\d+(?:\.\d+)?)\s*%?\s*$/.exec(t);if(!e)throw new Error(`invalid --sample '${t}': expected a percent like 2% (or a bare number).`);let r=Number(e[1]);if(!Number.isFinite(r)||r<0||r>100)throw new Error(`invalid --sample '${t}': percent must be between 0 and 100.`);return r/100}function rE(t,e){if(e>=1)return[...t];if(e<=0||t.length===0)return[];let r=Math.max(1,Math.round(1/e)),n=[];for(let o=0;o<t.length;o+=r)n.push(t[o]);return n}var nE="[REDACTED]",es=class extends Error{constructor(e){super(e),this.name="SandboxDataCopyUnavailableError"}},Dl=class extends Error{table;column;constructor(e,r,n){super(`Refusing to copy ${e}.${r} into a sandbox: ${n}. A sandbox must never hold un-redacted PII \u2014 classify the column 'safe' (non-PII) or let redaction mask it, then retry.`),this.name="UnredactedPiiError",this.table=e,this.column=r}};function oE(t,e,r){let n={};for(let[o,s]of Object.entries(e)){let i=r[o];if(i===void 0)throw new Dl(t,o,"column has no PII classification (unknown = unsafe)");i==="pii"?n[o]=s==null?s:nE:n[o]=s}return n}function sE(t,e,r){return e.map(n=>oE(t,n,r))}var uy=new Set(["localhost","127.0.0.1","::1","0.0.0.0"]),iE=["oraclecloud.com","us-ashburn-1","amazonaws.com","rds.","supabase.co","turbobase.app","turbobase.io"];function aE(t){let e=t.trim();try{let o=new URL(e);if(o.hostname)return o.hostname.toLowerCase()}catch{}let r=e.replace(/^[a-z]+:\/\//i,""),n=(r.split("/")[0]??r).split("@").pop()??r;return(n.split(":")[0]??n).toLowerCase()}function Ul(t){if(!t||!t.trim())throw new Error("local-only guard: empty database target \u2014 refusing to proceed.");let e=t.toLowerCase();for(let n of iE)if(e.includes(n))throw new Error(`local-only guard: target '${t}' looks like a cloud/prod context (matched '${n}'). \`turbobase sandbox create\` is LOCAL-ONLY and will never touch a cloud database.`);let r=aE(t);if(!uy.has(r))throw new Error(`local-only guard: target host '${r}' is not a recognised local Postgres (${[...uy].join(", ")}). \`turbobase sandbox create\` is LOCAL-ONLY.`)}function cE(t,e=lE){return`sbx-${t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,24)||"tenant"}-${e()}`}function lE(){return Math.floor(Math.random()*16777215).toString(16).padStart(6,"0")}function uE(t){return`turbobase_${t.replace(/[^a-z0-9]+/gi,"_").toLowerCase()}`}function dE(t,e){return`${(e??process.env.TURBOBASE_SANDBOX_BASE_URL??"http://localhost:5173").replace(/\/+$/,"")}/?sandbox=${encodeURIComponent(t)}`}async function Zl(){let{file:t}=dy();try{let e=await Ml.readFile(t,"utf8"),r=JSON.parse(e);return Array.isArray(r)?r:[]}catch(e){if(e.code==="ENOENT")return[];throw e}}async function py(t){let{dir:e,file:r}=dy();await Ml.mkdir(e,{recursive:!0}),await Ml.writeFile(r,`${JSON.stringify(t,null,2)}
|
|
1788
|
-
`,{mode:384})}async function pE(t){let e=await Zl();e.push(t),await py(e)}async function fy(t){let e=await Zl(),r=e.findIndex(o=>o.name===t);if(r===-1)return null;let[n]=e.splice(r,1);return await py(e),n??null}async function Li(){return Zl()}function ql(t,e=Date.now()){let r=[],n=[];for(let o of t)eE(o.expiresAt,e)?n.push(o):r.push(o);return{active:r,expired:n}}async function hy(t,e){Ul(t.target());let r=t.dataCopyUnavailable?.();if(r)throw new es(r);let n=tE(e.sample),o=e.now??Date.now(),s=(e.nameGen??(l=>cE(l)))(e.fromTenant),i=uE(s);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let l of a){let u=rE(l.rows,n),d=sE(l.table,u,l.classification);d.length>0&&await t.copyRows(i,l.table,d)}let c={name:s,fromTenant:e.fromTenant,database:i,createdAt:new Date(o).toISOString(),expiresAt:Q0(e.ttl,o),sampleFraction:n,url:dE(s,e.baseUrl)};return await pE(c),c}async function my(t,e){Ul(t.target());let n=(await Li()).find(o=>o.name===e);return n?(await t.dropDatabase(n.database),await fy(e),n):null}async function Fl(t,e=Date.now()){Ul(t.target());let r=await Li(),{expired:n}=ql(r,e),o=[];for(let s of n){try{await t.dropDatabase(s.database)}catch{continue}await fy(s.name),o.push(s)}return o}var ts={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","skills:publish","data:read"],defaultTtlDays:90,mintable:!0,label:"Personal access token"},catalog:{prefix:"tb_cat_",defaultScopes:["catalog:read"],allowedScopes:["catalog:read"],defaultTtlDays:null,mintable:!0,label:"AI / catalog token"}},tU=Object.keys(ts).filter(t=>ts[t].mintable);var oU=ts.pat.prefix,fE=Object.entries(ts).map(([t,e])=>[e.prefix,t]),hE=/^[A-Za-z0-9_-]{20,}$/;function mE(t){if(typeof t!="string")return null;for(let[e,r]of fE)if(t.startsWith(e)&&hE.test(t.slice(e.length)))return r;return null}function gy(t){return mE(t)==="pat"}var rs="bundle.json",Mi="turbobase";function by(t){let e;try{e=JSON.parse(t)}catch(c){throw new Error(`bundle.json is not valid JSON: ${c instanceof Error?c.message:String(c)}`)}if(typeof e!="object"||e===null||Array.isArray(e))throw new Error("bundle.json must be a JSON object");let r=e,n=typeof r.version=="number"?r.version:0;if(n!==1)throw new Error(`bundle.json version ${n} is unsupported (expected 1)`);let o=c=>Array.isArray(c)?c:[],s=r.sync??{},i=r.guardrails??{},a={version:1,...typeof r.tenant=="string"?{tenant:r.tenant}:{},hooks:o(r.hooks),conduits:o(r.conduits),sync:{fields:o(s.fields),tables:o(s.tables)},guardrails:{validationHooks:o(i.validationHooks),fieldRules:o(i.fieldRules),conduits:o(i.conduits)},schedules:o(r.schedules)};return gE(a)}function gE(t){let e=r=>(n,o)=>r(n)<r(o)?-1:r(n)>r(o)?1:0;return{version:1,...t.tenant?{tenant:t.tenant}:{},hooks:[...t.hooks].sort(e(r=>r.id)),conduits:[...t.conduits].sort(e(r=>r.id)),sync:{fields:[...t.sync.fields].sort(e(yy)),tables:[...t.sync.tables].sort(e(r=>r.sourceRecordType))},guardrails:{validationHooks:[...t.guardrails.validationHooks].sort(),fieldRules:[...t.guardrails.fieldRules].sort(e(yy)),conduits:[...t.guardrails.conduits].sort(e(r=>r.conduitId))},schedules:[...t.schedules].sort(e(r=>r.name))}}function yy(t){return`${t.sourceSystem}::${t.recordType}::${t.fieldName}`}function wy(t,e){let r=(e??"").trim();if(!r)return{ok:!1,reason:"target instance slug is unknown \u2014 cannot confirm a promote"};let n=(t??"").trim();return n?n!==r?{ok:!1,reason:`--confirm "${n}" does not match the target instance "${r}" \u2014 refusing to apply`}:{ok:!0}:{ok:!1,reason:`--confirm <slug> is required for promote (expected "${r}")`}}async function Dj(t,e){try{let r=await fetch(`${t.replace(/\/+$/,"")}/auth/pat-exchange`,{method:"POST",headers:{Authorization:`Bearer ${e}`}});if(!r.ok)return null;let n=await r.json();return typeof n.token=="string"&&n.token.length>0?n.token:null}catch{return null}}async function Uj(t){let e=await Nr();e.length===0&&S("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),Ht()||S("this command needs an explicit <base-url> in a non-interactive shell.");try{return await Kt(t,e.map(r=>({value:r.baseUrl,label:r.baseUrl,description:`updated ${r.updatedAt}`})))}catch{S("cancelled.")}}async function Zj(t,e){let r;try{r=await fetch(`${t}/v1/catalog/records`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:AbortSignal.timeout(8e3)})}catch(o){return{ok:!1,kind:"unreachable",detail:o instanceof Error?o.message:String(o)}}let n=(r.headers.get("content-type")??"").includes("application/json");return r.status===200||r.status===403?{ok:!0}:r.status===401?{ok:!1,kind:"bad-token",detail:`HTTP ${r.status}`}:n?{ok:!0}:{ok:!1,kind:"wrong-host",detail:`HTTP ${r.status}`}}async function qj(t,e,r){let n=await ag({baseUrl:t,token:e}),o=(await Nr()).length-1;if(T(`signed in to ${n.baseUrl} \u2014 now the active instance`+(o>0?` (${o} other${o===1?"":"s"} stored; commands default here).`:".")),!r)return;let s=await Zj(n.baseUrl,e);s.ok||(s.kind==="wrong-host"?(le(`warning: ${n.baseUrl} did not respond to the Turbobase API (/v1) \u2014 got ${s.detail}.`),le(" In local dev the Turbobase API is on :4100, not the app dev-server port (e.g. :5173)."),le(" The token is stored, but commands will fail against this base-url until it points at the API.")):s.kind==="bad-token"?le(`warning: ${n.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):le(`warning: could not reach ${n.baseUrl} to verify the token (${s.detail}). Stored anyway.`))}async function Fj(t){let e=await Go(t),r=c=>{let l=new RegExp(`${c}\\s*:\\s*(['"\`])([^'"\`]+)\\1`).exec(e);return l?l[2]:void 0},n=/\bdefineConduit\s*[<(]/.test(e),o=r("id");if(n){let c=r("on"),l=r("recordType"),u=r("source");return(!o||!l||!c||!u)&&S(`could not parse id/on/from.source/from.recordType from ${t} \u2014 is it a defineConduit({...}) file?`),{id:o,recordType:l,event:c,source:u,sourceCode:e,extensionKind:"conduit"}}let s=r("recordType"),i=r("event"),a=r("source")??"standalone";return(!o||!s||!i)&&S(`could not parse id/recordType/event from ${t} \u2014 is it a defineHook({...}) file?`),{id:o,recordType:s,event:i,source:a,sourceCode:e,extensionKind:"hook"}}async function jo(t,e,r,n){try{return await ye.access(t),"skipped"}catch{}return await ye.mkdir(No(t),{recursive:!0}),await ye.writeFile(t,e,{mode:n??(r?493:420)}),n!==void 0&&await ye.chmod(t,n),"wrote"}async function d$(t,e){if(p$(t)!==".gitignore")return jo(t,e.content,e.executable,e.mode);let r=await ye.readFile(t,"utf8").catch(()=>null);if(r===null)return jo(t,e.content,e.executable,e.mode);let n=Ug(r);return n===null?"skipped":(await ye.writeFile(t,n,"utf8"),"merged")}function sm(t){if(t!==void 0){if(t==="junit"||t==="json")return t;S(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function Bj(t){return{name:"turbobase ext test",cases:t.map(r=>{if(r.pass)return{name:r.name,status:"passed"};let n=r.error?"error":r.rejection?.code??"mismatch",o=r.error?`error: ${r.error}`:r.observed!==r.expected?`expected ${r.expected}, observed ${r.observed}`+(r.rejection?` (rejected: ${r.rejection.code??"rejected"}${r.rejection.message?` \u2014 ${r.rejection.message}`:""})`:""):`rejected: ${r.rejection?.code??"rejected"}${r.rejection?.message?` \u2014 ${r.rejection.message}`:""}`;return{name:r.name,status:"failed",detail:o,type:n}})}}function Vj(t,e){let r=e.warnings.length===0?[{name:`${t} (${e.kind})`,status:"passed"}]:e.warnings.map(n=>({name:`${t}: ${n.rule}`,status:"failed",type:n.rule,detail:`${n.message} \u2192 fix: ${n.fix}`}));return{name:`turbobase ext lint ${t}`,cases:r}}function Hj(t){let e=[];if(t.error)return e.push({name:`replay ${t.fixtureName}`,status:"failed",type:"error",detail:t.error}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e};for(let r of t.guards)e.push(r.status==="failed"?{name:`guard: ${r.name}`,status:"failed",type:r.name,detail:r.detail}:{name:`guard: ${r.name}`,status:"passed",detail:r.status==="skipped-offline"?`skipped-offline: ${r.detail}`:void 0});return t.whenPassed&&t.applyBranch&&e.push({name:`apply-branch: ${t.applyBranch}`,status:"passed",detail:`${t.masterAction??""} (${t.wouldWriteBindings.length} binding(s) would be written)`.trim()}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e}}function dm({includeInternal:t=!0}={}){let e=new Pm;e.name("turbobase").description("Build apps, author extensions, and drive your Turbobase instance.").version("1.1.
|
|
1788
|
+
`,{mode:384})}async function pE(t){let e=await Zl();e.push(t),await py(e)}async function fy(t){let e=await Zl(),r=e.findIndex(o=>o.name===t);if(r===-1)return null;let[n]=e.splice(r,1);return await py(e),n??null}async function Li(){return Zl()}function ql(t,e=Date.now()){let r=[],n=[];for(let o of t)eE(o.expiresAt,e)?n.push(o):r.push(o);return{active:r,expired:n}}async function hy(t,e){Ul(t.target());let r=t.dataCopyUnavailable?.();if(r)throw new es(r);let n=tE(e.sample),o=e.now??Date.now(),s=(e.nameGen??(l=>cE(l)))(e.fromTenant),i=uE(s);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let l of a){let u=rE(l.rows,n),d=sE(l.table,u,l.classification);d.length>0&&await t.copyRows(i,l.table,d)}let c={name:s,fromTenant:e.fromTenant,database:i,createdAt:new Date(o).toISOString(),expiresAt:Q0(e.ttl,o),sampleFraction:n,url:dE(s,e.baseUrl)};return await pE(c),c}async function my(t,e){Ul(t.target());let n=(await Li()).find(o=>o.name===e);return n?(await t.dropDatabase(n.database),await fy(e),n):null}async function Fl(t,e=Date.now()){Ul(t.target());let r=await Li(),{expired:n}=ql(r,e),o=[];for(let s of n){try{await t.dropDatabase(s.database)}catch{continue}await fy(s.name),o.push(s)}return o}var ts={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","skills:publish","data:read"],defaultTtlDays:90,mintable:!0,label:"Personal access token"},catalog:{prefix:"tb_cat_",defaultScopes:["catalog:read"],allowedScopes:["catalog:read"],defaultTtlDays:null,mintable:!0,label:"AI / catalog token"}},tU=Object.keys(ts).filter(t=>ts[t].mintable);var oU=ts.pat.prefix,fE=Object.entries(ts).map(([t,e])=>[e.prefix,t]),hE=/^[A-Za-z0-9_-]{20,}$/;function mE(t){if(typeof t!="string")return null;for(let[e,r]of fE)if(t.startsWith(e)&&hE.test(t.slice(e.length)))return r;return null}function gy(t){return mE(t)==="pat"}var rs="bundle.json",Mi="turbobase";function by(t){let e;try{e=JSON.parse(t)}catch(c){throw new Error(`bundle.json is not valid JSON: ${c instanceof Error?c.message:String(c)}`)}if(typeof e!="object"||e===null||Array.isArray(e))throw new Error("bundle.json must be a JSON object");let r=e,n=typeof r.version=="number"?r.version:0;if(n!==1)throw new Error(`bundle.json version ${n} is unsupported (expected 1)`);let o=c=>Array.isArray(c)?c:[],s=r.sync??{},i=r.guardrails??{},a={version:1,...typeof r.tenant=="string"?{tenant:r.tenant}:{},hooks:o(r.hooks),conduits:o(r.conduits),sync:{fields:o(s.fields),tables:o(s.tables)},guardrails:{validationHooks:o(i.validationHooks),fieldRules:o(i.fieldRules),conduits:o(i.conduits)},schedules:o(r.schedules)};return gE(a)}function gE(t){let e=r=>(n,o)=>r(n)<r(o)?-1:r(n)>r(o)?1:0;return{version:1,...t.tenant?{tenant:t.tenant}:{},hooks:[...t.hooks].sort(e(r=>r.id)),conduits:[...t.conduits].sort(e(r=>r.id)),sync:{fields:[...t.sync.fields].sort(e(yy)),tables:[...t.sync.tables].sort(e(r=>r.sourceRecordType))},guardrails:{validationHooks:[...t.guardrails.validationHooks].sort(),fieldRules:[...t.guardrails.fieldRules].sort(e(yy)),conduits:[...t.guardrails.conduits].sort(e(r=>r.conduitId))},schedules:[...t.schedules].sort(e(r=>r.name))}}function yy(t){return`${t.sourceSystem}::${t.recordType}::${t.fieldName}`}function wy(t,e){let r=(e??"").trim();if(!r)return{ok:!1,reason:"target instance slug is unknown \u2014 cannot confirm a promote"};let n=(t??"").trim();return n?n!==r?{ok:!1,reason:`--confirm "${n}" does not match the target instance "${r}" \u2014 refusing to apply`}:{ok:!0}:{ok:!1,reason:`--confirm <slug> is required for promote (expected "${r}")`}}async function Dj(t,e){try{let r=await fetch(`${t.replace(/\/+$/,"")}/auth/pat-exchange`,{method:"POST",headers:{Authorization:`Bearer ${e}`}});if(!r.ok)return null;let n=await r.json();return typeof n.token=="string"&&n.token.length>0?n.token:null}catch{return null}}async function Uj(t){let e=await Nr();e.length===0&&S("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),Ht()||S("this command needs an explicit <base-url> in a non-interactive shell.");try{return await Kt(t,e.map(r=>({value:r.baseUrl,label:r.baseUrl,description:`updated ${r.updatedAt}`})))}catch{S("cancelled.")}}async function Zj(t,e){let r;try{r=await fetch(`${t}/v1/catalog/records`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:AbortSignal.timeout(8e3)})}catch(o){return{ok:!1,kind:"unreachable",detail:o instanceof Error?o.message:String(o)}}let n=(r.headers.get("content-type")??"").includes("application/json");return r.status===200||r.status===403?{ok:!0}:r.status===401?{ok:!1,kind:"bad-token",detail:`HTTP ${r.status}`}:n?{ok:!0}:{ok:!1,kind:"wrong-host",detail:`HTTP ${r.status}`}}async function qj(t,e,r){let n=await ag({baseUrl:t,token:e}),o=(await Nr()).length-1;if(T(`signed in to ${n.baseUrl} \u2014 now the active instance`+(o>0?` (${o} other${o===1?"":"s"} stored; commands default here).`:".")),!r)return;let s=await Zj(n.baseUrl,e);s.ok||(s.kind==="wrong-host"?(le(`warning: ${n.baseUrl} did not respond to the Turbobase API (/v1) \u2014 got ${s.detail}.`),le(" In local dev the Turbobase API is on :4100, not the app dev-server port (e.g. :5173)."),le(" The token is stored, but commands will fail against this base-url until it points at the API.")):s.kind==="bad-token"?le(`warning: ${n.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):le(`warning: could not reach ${n.baseUrl} to verify the token (${s.detail}). Stored anyway.`))}async function Fj(t){let e=await Go(t),r=c=>{let l=new RegExp(`${c}\\s*:\\s*(['"\`])([^'"\`]+)\\1`).exec(e);return l?l[2]:void 0},n=/\bdefineConduit\s*[<(]/.test(e),o=r("id");if(n){let c=r("on"),l=r("recordType"),u=r("source");return(!o||!l||!c||!u)&&S(`could not parse id/on/from.source/from.recordType from ${t} \u2014 is it a defineConduit({...}) file?`),{id:o,recordType:l,event:c,source:u,sourceCode:e,extensionKind:"conduit"}}let s=r("recordType"),i=r("event"),a=r("source")??"standalone";return(!o||!s||!i)&&S(`could not parse id/recordType/event from ${t} \u2014 is it a defineHook({...}) file?`),{id:o,recordType:s,event:i,source:a,sourceCode:e,extensionKind:"hook"}}async function jo(t,e,r,n){try{return await ye.access(t),"skipped"}catch{}return await ye.mkdir(No(t),{recursive:!0}),await ye.writeFile(t,e,{mode:n??(r?493:420)}),n!==void 0&&await ye.chmod(t,n),"wrote"}async function d$(t,e){if(p$(t)!==".gitignore")return jo(t,e.content,e.executable,e.mode);let r=await ye.readFile(t,"utf8").catch(()=>null);if(r===null)return jo(t,e.content,e.executable,e.mode);let n=Ug(r);return n===null?"skipped":(await ye.writeFile(t,n,"utf8"),"merged")}function sm(t){if(t!==void 0){if(t==="junit"||t==="json")return t;S(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function Bj(t){return{name:"turbobase ext test",cases:t.map(r=>{if(r.pass)return{name:r.name,status:"passed"};let n=r.error?"error":r.rejection?.code??"mismatch",o=r.error?`error: ${r.error}`:r.observed!==r.expected?`expected ${r.expected}, observed ${r.observed}`+(r.rejection?` (rejected: ${r.rejection.code??"rejected"}${r.rejection.message?` \u2014 ${r.rejection.message}`:""})`:""):`rejected: ${r.rejection?.code??"rejected"}${r.rejection?.message?` \u2014 ${r.rejection.message}`:""}`;return{name:r.name,status:"failed",detail:o,type:n}})}}function Vj(t,e){let r=e.warnings.length===0?[{name:`${t} (${e.kind})`,status:"passed"}]:e.warnings.map(n=>({name:`${t}: ${n.rule}`,status:"failed",type:n.rule,detail:`${n.message} \u2192 fix: ${n.fix}`}));return{name:`turbobase ext lint ${t}`,cases:r}}function Hj(t){let e=[];if(t.error)return e.push({name:`replay ${t.fixtureName}`,status:"failed",type:"error",detail:t.error}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e};for(let r of t.guards)e.push(r.status==="failed"?{name:`guard: ${r.name}`,status:"failed",type:r.name,detail:r.detail}:{name:`guard: ${r.name}`,status:"passed",detail:r.status==="skipped-offline"?`skipped-offline: ${r.detail}`:void 0});return t.whenPassed&&t.applyBranch&&e.push({name:`apply-branch: ${t.applyBranch}`,status:"passed",detail:`${t.masterAction??""} (${t.wouldWriteBindings.length} binding(s) would be written)`.trim()}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e}}function dm({includeInternal:t=!0}={}){let e=new Pm;e.name("turbobase").description("Build apps, author extensions, and drive your Turbobase instance.").version("1.1.17").option("--no-color","Disable ANSI colour + animations (also honoured via NO_COLOR / non-TTY)."),e.addHelpText("beforeAll",p=>p.command.parent?"":`${Tl()}
|
|
1789
1789
|
`),e.hook("preAction",(p,h)=>{qg(e.opts(),h.opts())});let r=e.command("auth").description("Sign in to an instance and manage stored credentials."),n=e.command("project").description("Set up the local project and the files an agent session reads."),o=e.command("ext").description("Author, test, ship, and observe extensions \u2014 hooks and conduits."),s=e.command("kb").description("Write into the instance's knowledge base."),i=e.command("skills").description("Write standing rules into the instance's agent skills.");r.command("login").description("Sign in to an instance by pasting a personal access token (PAT), stored in ~/.turbobase/credentials.").requiredOption("--base-url <url>","Instance base URL, e.g. https://acme.turbobase.app").option("--token <tb_pat_\u2026>","The PAT to store. Mint one in your app under Settings \u203A Access tokens.").option("--no-verify","Skip the post-login connectivity/token probe.").action(async p=>{let h=Ot(p.baseUrl);p.token||S(`auth login needs --token. Mint a PAT in your Turbobase app (Settings \u203A Access tokens), then:
|
|
1790
1790
|
${Yo} auth login --base-url ${h} --token tb_pat_\u2026`),gy(p.token)||S("--token does not look like a Turbobase PAT (tb_pat_\u2026)."),await qj(h,p.token,p.verify)}),r.command("status").description("Show the active instance (the no-`--base-url` default) and every stored sign-in.").action(async()=>{let p=await Nr();if(p.length===0){T("not signed in. Run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`.");return}let h=await ir(),w=_=>!!h&&_.baseUrl===h.baseUrl;if(wt())T(_n(p.map(_=>[w(_)?x.green("*"):" ",x.bold(_.baseUrl),x.dim(_.updatedAt)]),{head:["","instance","updated"]}));else for(let _ of p)T(`${w(_)?"* ":" "}${_.baseUrl} (updated ${_.updatedAt})`);h||le("no active default \u2014 run `turbobase auth use <base-url>` or pass --base-url.")}),r.command("use [base-url]").description("Make an already-stored instance the active default (omit the arg to pick interactively).").action(async p=>{let h=p??await Uj("Pick the active instance:"),w=await Ti(h);if(!w){let _=await Nr();S(`no stored sign-in for ${Ot(h)}.`+(_.length?` Stored: ${_.map($=>$.baseUrl).join(", ")}.`:"")+` Run \`turbobase auth login --base-url ${Ot(h)} --token tb_pat_\u2026\`.`)}T(`active instance \u2192 ${w.baseUrl}.`)}),r.command("logout").description("Remove a stored sign-in (defaults to the active instance).").option("--base-url <url>","Which instance to remove (defaults to the active/single one).").option("--all","Remove every stored sign-in.").action(async p=>{if(p.all){let _=await Nr();for(let $ of _)await ml($.baseUrl);T(`removed ${_.length} stored sign-in${_.length===1?"":"s"}.`);return}let h=await ml(p.baseUrl);h||S(p.baseUrl?`no stored sign-in for ${Ot(p.baseUrl)}.`:"nothing to sign out of (or ambiguous \u2014 pass --base-url / --all).");let w=await ir();T(`signed out of ${h}.`+(w?` Active instance \u2192 ${w.baseUrl}.`:""))}),n.command("types").description("Generate types.ts from the signed-in instance's catalog.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("-o, --out <file>","Output file","types.ts").option("--records-only","Emit only record-shape interfaces (skip the OpenAPI half).").action(async p=>{let{baseUrl:h,token:w}=await ne(p.baseUrl),_;try{_=await zr({baseUrl:h,token:w,recordsOnly:p.recordsOnly})}catch(g){let k=g instanceof Error?g.message:String(g);if(!p.recordsOnly&&/openapi\.json failed \(401/.test(k)){let A=await Dj(h,w);if(A)try{_=await zr({baseUrl:h,token:A,recordsOnly:!1});let C=Te(process.cwd(),p.out);await ye.writeFile(C,_,"utf8"),T(`wrote ${C} (${_.split(`
|
|
1791
1791
|
`).length} lines).`);return}catch{}le("types-gen: /v1/catalog/openapi.json needs a session \u2014 emitting record shapes only. To get the OpenAPI half headlessly, mint a PAT with the `data:read` scope (Settings \u203A Tokens); the CLI then exchanges it for a short-lived session automatically.");try{_=await zr({baseUrl:h,token:w,recordsOnly:!0})}catch(C){S(`types generation failed: ${C instanceof Error?C.message:String(C)}`)}}else S(`types generation failed: ${k}`)}let $=Te(process.cwd(),p.out);await ye.writeFile($,_,"utf8"),T(`wrote ${$} (${_.split(`
|
|
@@ -1816,7 +1816,7 @@ samples init: ${A} written, ${C} skipped.`)});let rr=e.command("guardrails").des
|
|
|
1816
1816
|
`+x.dim("create one with `turbobase guardrails create <name> --source <s> --when '\u2026'`"));T(_n($.map(tn),{head:["NAME","STATE","SOURCE","RECORD","WHEN","NOTE"]}))}),rr.command("create <name>").description("Create a DENY rule. Blocks PG\u2192source writes that match it.").option("--source <system>","Adapter id to scope to, or '*' for every source.","*").option("--record <type>","Record type to scope to, or '*' for every record.","*").option("--when <expr>","Condition, e.g. 'recordType not_in customer,invoice' or 'record.amount gt 10000'. Omit to deny unconditionally.").option("--message <text>","What a blocked writer is told.").option("--note <text>","Why this rule exists \u2014 for whoever reads `list` later.").option("--disabled","Create the rule parked, so it takes effect only when enabled.").option("--confirm-freeze-all","Required for an unconditional '*'/'*' rule.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(p,h)=>{let{baseUrl:w,token:_}=await ne(h.baseUrl),$=new ee({baseUrl:w,token:_}),g;try{g=await $.guardrailCreate({name:p,source:h.source,record:h.record,...h.when?{when:h.when}:{},...h.message?{message:h.message}:{},...h.note?{note:h.note}:{},...h.disabled?{enabled:!1}:{},...h.confirmFreezeAll?{confirmFreezeAll:!0}:{}})}catch(k){ke(k,"extensions:deploy","create a guardrail"),S(`create guardrail failed: ${k instanceof Error?k.message:String(k)}`)}T(`${x.green("\u2713")} guardrail ${x.bold(g.name)} created${g.enabled?"":x.dim(" (parked \u2014 enable it to take effect)")}`),T(x.dim(` denies ${g.source}/${g.record}${g.when?` where ${g.when}`:" (always)"}`)),T(x.dim(` verify with: turbobase guardrails test --source ${g.source==="*"?"<source>":g.source} --record <record>`))}),rr.command("edit <name>").description("Change a rule \u2014 most often widening its exception set as records are proven.").option("--when <expr>","New condition. Pass '' to make it unconditional.").option("--message <text>","What a blocked writer is told.").option("--note <text>","Why this rule exists.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(p,h)=>{h.when===void 0&&h.message===void 0&&h.note===void 0&&S("edit: nothing to change \u2014 pass --when, --message or --note.");let{baseUrl:w,token:_}=await ne(h.baseUrl),$=new ee({baseUrl:w,token:_}),g;try{g=await $.guardrailUpdate(p,{...h.when!==void 0?{when:h.when}:{},...h.message!==void 0?{message:h.message}:{},...h.note!==void 0?{note:h.note}:{}})}catch(k){ke(k,"extensions:deploy","edit a guardrail"),S(`edit guardrail failed: ${k instanceof Error?k.message:String(k)}`)}T(`${x.green("\u2713")} guardrail ${x.bold(g.name)} updated`),T(x.dim(` denies ${g.source}/${g.record}${g.when?` where ${g.when}`:" (always)"}`))});for(let[p,h,w]of[["enable",!0,"enabled"],["disable",!1,"disabled"]])rr.command(`${p} <name>`).description(h?"Arm a parked rule.":"Park a rule without deleting it \u2014 the reversible move mid-rollout.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(_,$)=>{let{baseUrl:g,token:k}=await ne($.baseUrl),A=new ee({baseUrl:g,token:k});try{await A.guardrailUpdate(_,{enabled:h})}catch(C){ke(C,"extensions:deploy",`${p} a guardrail`),S(`${p} guardrail failed: ${C instanceof Error?C.message:String(C)}`)}T(`${x.green("\u2713")} guardrail ${x.bold(_)} ${w}`),h||T(x.dim(" writes it was blocking are now permitted (subject to the other rules)."))});rr.command("delete <name>").description("Remove a rule. This OPENS a write path \u2014 prefer `disable` if unsure.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(p,h)=>{let{baseUrl:w,token:_}=await ne(h.baseUrl),$=new ee({baseUrl:w,token:_});try{await $.guardrailDelete(p)}catch(g){ke(g,"extensions:deploy","delete a guardrail"),S(`delete guardrail failed: ${g instanceof Error?g.message:String(g)}`)}T(`${x.green("\u2713")} guardrail ${x.bold(p)} deleted`)}),rr.command("test").description("Would a write be blocked? Runs the real evaluator; writes nothing.").requiredOption("--source <system>","Adapter id the write targets.").requiredOption("--record <type>","Record type the write targets.").option("--origin <origin>","Write origin to simulate ('user', 'agent').").option("--fields <json>","A candidate row as JSON, so `record.<column>` conditions can be decided.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--json","Emit raw JSON.").action(async p=>{let h;if(p.fields)try{h=JSON.parse(p.fields)}catch(k){S(`--fields must be valid JSON: ${k instanceof Error?k.message:String(k)}`)}let{baseUrl:w,token:_}=await ne(p.baseUrl),$=new ee({baseUrl:w,token:_}),g;try{g=await $.guardrailTest({source:p.source,record:p.record,...p.origin?{origin:p.origin}:{},...h?{fields:h}:{}})}catch(k){ke(k,"extensions:deploy","test guardrails"),S(`guardrails test failed: ${k instanceof Error?k.message:String(k)}`)}if(p.json)return T(JSON.stringify(g,null,2));g.allowed?T(`${x.green("\u2713")} ${p.source}/${p.record} \u2014 no guardrail blocks this write`):(T(`${x.red("\u2717")} ${p.source}/${p.record} \u2014 blocked by ${x.bold(g.blockedBy??"?")}`),g.message&&T(x.dim(` ${g.message}`))),g.undecided.length>0&&(T(x.yellow(` ${g.undecided.length} rule(s) need a row to decide: ${g.undecided.join(", ")}`)),T(x.dim(` re-run with --fields '{"\u2026":"\u2026"}' to evaluate them.`))),g.allowed||(process.exitCode=1)});let mr=e.command("tables").description("Author custom tables on your instance (create, evolve, inspect)."),Un=(p,h={})=>{let w=`${x.bold(p.table)} ${x.dim(p.displayName)}`;T(p.exists===!1?`${w} ${x.dim("(new)")}`:w);for(let _ of p.fields){let $=[_.required?"required":null].filter(Boolean).join(", "),g=h.showClaimants&&_.claimedBy?.length?` ${x.dim(`\u2190 ${_.claimedBy.join(", ")}`)}`:"";T(` ${_.column.padEnd(28)} ${_.type}${$?` (${$})`:""}${g}`)}h.showClaimants&&p.claimants.length>1&&T(x.dim(` claimed by ${p.claimants.join(", ")}`))};function Zn(p){if(p instanceof be&&p.status===409){let h=p.body;h?.error==="table_conflict"&&h.message&&(le(h.message),process.exit(1))}S(`tables: ${p instanceof Error?p.message:String(p)}`)}let ai=async p=>{let h=Rt(Te(process.cwd(),p),"public","turbobase.json"),w=await ye.readFile(h,"utf8").catch(()=>null);if(w===null)return null;let _;try{_=JSON.parse(w)}catch(g){S(`${h} is not valid JSON: ${g instanceof Error?g.message:String(g)}`)}let $=_.tables;return $===void 0?{tables:[],manifestPath:h}:(Array.isArray($)||S(`${h}: "tables" must be an array.`),{tables:$,manifestPath:h})};mr.command("list").description("List the custom tables on your instance and who claims each one.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--json","Emit raw JSON instead of a table.").action(async p=>{let{baseUrl:h,token:w}=await ne(p.baseUrl),_=new ee({baseUrl:h,token:w}),$;try{$=await _.tablesList()}catch(g){ke(g,"extensions:deploy","list tables"),S(`list tables failed: ${g instanceof Error?g.message:String(g)}`)}if(p.json)return T(JSON.stringify($,null,2));if($.length===0)return T("no custom tables yet \u2014 create one with `turbobase tables create <slug>`.");T(_n($.map(g=>[g.table,g.displayName,String(g.fields.length),g.status,g.claimedBy.join(", ")||"\u2014"]),{head:["TABLE","NAME","FIELDS","STATUS","CLAIMED BY"]}))}),mr.command("plan").description("Preview what this project's declared tables would do. Writes nothing; exits 1 on a conflict.").argument("[dir]","Project directory holding public/turbobase.json (default ./).").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--as <claimant>","Claim as `cli` or `app:<slug>` (default: the app slug in .turbobase, else cli).").action(async(p,h)=>{let w=await ai(p??".");if(w||S(`no public/turbobase.json under ${Te(process.cwd(),p??".")}.`),w.tables.length===0)return T(`${w.manifestPath} declares no tables \u2014 nothing to plan.`);let{baseUrl:_,token:$}=await ne(h.baseUrl),g=new ee({baseUrl:_,token:$}),k;try{k=await fe("planning tables",()=>g.tablesPlan({claimedBy:h.as,tables:w.tables}))}catch(A){ke(A,"extensions:deploy","plan tables"),Zn(A)}T(`claiming as ${x.bold(k.claimedBy)}`);for(let A of k.tables)Un(A,{showClaimants:!0});T(""),T(x.dim("nothing was applied \u2014 run `turbobase tables apply` (or deploy the app) to make it so."))}),mr.command("apply").description("Apply this project's declared tables to the instance.").argument("[dir]","Project directory holding public/turbobase.json (default ./).").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--as <claimant>","Claim as `cli` or `app:<slug>` (default: cli).").action(async(p,h)=>{let w=await ai(p??".");if(w||S(`no public/turbobase.json under ${Te(process.cwd(),p??".")}.`),w.tables.length===0)return T(`${w.manifestPath} declares no tables \u2014 nothing to apply.`);let{baseUrl:_,token:$}=await ne(h.baseUrl),g=new ee({baseUrl:_,token:$}),k;try{k=await fe("applying tables",()=>g.tablesApply({claimedBy:h.as,tables:w.tables}))}catch(A){ke(A,"extensions:deploy","apply tables"),Zn(A)}for(let A of k.tables)Un(A,{showClaimants:!0});T(`applied ${k.tables.length} table${k.tables.length===1?"":"s"} as ${k.claimedBy}.`)}),mr.command("create").description('Create a single table without a manifest (the CLI twin of "New custom table").').argument("<slug>","Table slug; the physical table is cr_<slug>.").requiredOption("-f, --field <name:type>","Field as name:type[:required] \u2014 text|number|date|bool|select. Repeatable.",(p,h)=>[...h??[],p],[]).option("--name <displayName>","Human label for the table (defaults to the slug).").option("--restrict-to-owner","Non-admins see only rows they created.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(p,h)=>{let w=h.field.map(A=>{let[C,L,D]=A.split(":");return(!C||!L)&&S(`--field "${A}" must be name:type[:required] (e.g. title:text:required).`),D!==void 0&&D!=="required"&&S(`--field "${A}": the third part may only be "required".`),{name:C,type:L,...D==="required"?{required:!0}:{}}}),{baseUrl:_,token:$}=await ne(h.baseUrl),g=new ee({baseUrl:_,token:$}),k;try{k=await fe(`creating cr_${p}`,()=>g.tablesApply({tables:[{slug:p,displayName:h.name??p,fields:w,...h.restrictToOwner?{rowLevelOwner:!0}:{}}]}))}catch(A){ke(A,"extensions:deploy","create a table"),Zn(A)}for(let A of k.tables)Un(A);T(""),T(x.dim("grant access under Settings \u203A Users, or in the Records page permissions matrix."))}),mr.command("drop").description("Drop a table and everything in it. Irreversible.").argument("<slug>","Table slug (without the cr_ prefix).").requiredOption("--confirm <slug>","Type the EXACT slug to confirm (fail-closed).").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(p,h)=>{h.confirm!==p&&S(`drop refused: --confirm "${h.confirm}" does not match "${p}".`);let{baseUrl:w,token:_}=await ne(h.baseUrl),$=new ee({baseUrl:w,token:_}),g;try{g=await fe(`dropping cr_${p}`,()=>$.tablesDrop(p,h.confirm))}catch(k){ke(k,"extensions:deploy","drop a table"),S(`drop failed: ${k instanceof Error?k.message:String(k)}`)}if(!g.removed)return T(`no table named "${p}" on this instance \u2014 nothing dropped.`);T(`dropped ${g.table} and every row in it.`)});let Oc=e.command("sandbox").description("Ephemeral local authoring sandboxes.");Oc.command("create").description("Stand up a fresh local sandbox seeded with a sampled+redacted slice of an instance.").requiredOption("--from <instance>","Source instance to clone the data slice from.").option("--ttl <duration>","Time-to-live before reaping (e.g. 72h, 30m, 7d).","72h").option("--sample <pct>","Percent of each table to copy (e.g. 2%).","2%").option("--base-url <url>","Local base URL the sandbox is reachable at.").action(async p=>{process.env.TURBOBASE_SANDBOX="1";let h=am(),w;try{w=await hy(h,{fromTenant:p.from,ttl:p.ttl,sample:p.sample,baseUrl:p.baseUrl})}catch(_){_ instanceof es&&S(_.message),S(`sandbox create failed: ${_ instanceof Error?_.message:String(_)}`)}T(`sandbox ${w.name} ready.`),T(` source: ${w.fromTenant} (sampled ${Math.round(w.sampleFraction*100)}%, redacted)`),T(` url: ${w.url}`),T(` expires: ${w.expiresAt}`)}),Oc.command("destroy").argument("<name>","The sandbox name to tear down.").description("Drop a sandbox database and remove it from the registry.").action(async p=>{let h=am();await Fl(h).catch(()=>[]);let w;try{w=await my(h,p)}catch(_){S(`sandbox destroy failed: ${_ instanceof Error?_.message:String(_)}`)}w||S(`no sandbox named '${p}'.`),T(`destroyed sandbox ${w.name} (dropped ${w.database}).`)}),Oc.command("list").description("List active sandboxes with TTL/expiry; reaps any past their TTL.").action(async()=>{let p=am(),h=await Fl(p).catch(()=>[]),w=await Li(),{active:_}=ql(w);if(_.length===0)T("no active sandboxes.");else{for(let $ of _)T(` ${$.name} from=${$.fromTenant} expires=${$.expiresAt} ${$.url}`);T(`
|
|
1817
1817
|
${_.length} active sandbox(es).`)}h.length>0&&T(`(reaped ${h.length} expired sandbox(es).)`)}),o.command("export").description("Snapshot the live instance into turbobase/bundle.json.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--out <dir>","Directory to write the bundle into.",Mi).action(async p=>{let{baseUrl:h,token:w}=await ne(p.baseUrl),_=new ee({baseUrl:h,token:w}),$;try{$=await fe("exporting instance config",()=>_.configExport())}catch(A){A instanceof be&&(A.status===401||A.status===403)&&S("ext export forbidden \u2014 the stored PAT lacks extensions:deploy. Re-run `turbobase auth login`."),S(`ext export failed: ${A instanceof Error?A.message:String(A)}`)}let g=typeof $.serialized=="string"?$.serialized:`${JSON.stringify($.manifest??$,null,2)}
|
|
1818
1818
|
`,k=Te(process.cwd(),p.out,rs);await ye.mkdir(No(k),{recursive:!0}),await ye.writeFile(k,g,"utf8"),T(`wrote ${p.out}/${rs} (${g.split(`
|
|
1819
|
-
`).length} lines).`)});let pm=o.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),f$=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],fm=()=>process.env.TURBOBASE_HOME?.trim()||Rt(Mj(),".turbobase"),h$=()=>"1.1.
|
|
1819
|
+
`).length} lines).`)});let pm=o.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),f$=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],fm=()=>process.env.TURBOBASE_HOME?.trim()||Rt(Mj(),".turbobase"),h$=()=>"1.1.17",ci=async()=>Promise.all(f$.map(async p=>{try{return await import(p.name),{...p,present:!0}}catch{return{...p,present:!1}}}));pm.command("status").description("Report which optional native packages are installed.").action(async()=>{let p=await ci();for(let h of p)T(`${h.present?lt.success("installed"):lt.warn("missing")} ${x.bold(h.name)} \u2014 ${h.needs}`);p.some(h=>!h.present)&&T(`
|
|
1820
1820
|
run \`${Yo} ext extras install\` to fetch them into ${fm()}.`)}),pm.command("install").description("Install the optional native packages into $TURBOBASE_HOME (needs npm).").action(async()=>{let p=await ci();if(p.every(C=>C.present)){T(lt.success("extras already installed \u2014 nothing to do."));return}let h=fm();await ye.mkdir(h,{recursive:!0});let w=p.filter(C=>!C.present).map(C=>C.name),_=jl(process.platform,process.arch),$=async(C,L)=>{let{code:D,stderr:oe}=await ii("npm",["install","--prefix",h,"--no-audit","--no-fund",...C]);D!==0&&S(`extras install failed while installing ${L} (npm exited ${D}).
|
|
1821
1821
|
${oe.trim()}
|
|
1822
1822
|
These are native packages \u2014 they need npm and a working toolchain. Everything except \`test\` and \`replay\` works without them.`)},g=w.filter(C=>C!=="isolated-vm");if(g.length>0){let C=g.map(L=>`${L}@${zl[L]??"latest"}`);T(`installing ${C.join(", ")} \u2192 ${h}/node_modules \u2026`),await $(C,g.join(", "))}if(w.includes("isolated-vm")){let C=`isolated-vm@${zl["isolated-vm"]}`;if(T(`installing ${C} \u2192 ${h}/node_modules \u2026`),await $(["--ignore-scripts",C],"isolated-vm"),ay.includes(_)){let D=`${Nl}@${h$()}`;T(`fetching ${D} (no upstream binary for ${_}) \u2026`);let{code:oe}=await ii("npm",["install","--prefix",h,"--no-audit","--no-fund","--ignore-scripts",D]);oe!==0&&T(lt.warn(`could not fetch ${D} \u2014 falling back to a source build.`))}let L=await cy({home:h});L.length>0&&T(` ${lt.success("prebuilt")} ${L.join(", ")} \u2192 isolated-vm/prebuilds/${_}`)}let k=await ci(),A=k.filter(C=>!C.present).map(C=>C.name);if(A.includes("isolated-vm")){T(lt.warn(`no prebuilt isolated-vm for ${_} / Node ${process.versions.node} (${Ll()}) \u2014 building from source. This needs Python and a C++ toolchain, and takes a few minutes.`));let{code:C}=await ii("npm",["rebuild","--prefix",h,"isolated-vm"]);C===0&&(k=await ci(),A=k.filter(L=>!L.present).map(L=>L.name))}A.length>0&&S(`npm succeeded but ${A.join(", ")} still will not load \u2014 no prebuilt binary for ${_} / Node ${process.versions.node} (${Ll()}), and the source build did not produce one either. Everything except \`test\` and \`replay\` works without them.`),T(lt.success("extras installed \u2014 `turbobase ext test` and `turbobase ext replay` are ready."))});let qn=e.command("release").description("Ship a release to your instance: deploy | status | rollback | list.");return qn.command("deploy").description("Enqueue a server-side release deploy. UPLOADS the SDF project src/ from the checkout (the producer swap) when present; else a solution-only live-fetch deploy. An approved target requires --approved-by.").requiredOption("--target <mode>","Deploy posture: auto (apply now) | approved (needs a recorded approval).").option("--solution <id>","The solution id the release delivers (omit for an upload-only deploy).").option("--project <dir>","SDF project dir whose src/ is uploaded (default: cwd).").option("--git-repo <repo>","Git provenance LINK: source repo (default $GITHUB_REPOSITORY or `git remote get-url origin`).").option("--git-ref <ref>","Git provenance LINK: ref/branch/tag (default $GITHUB_REF).").option("--ref <committish>","Git provenance: the ref/branch/tag/sha the upload was built from; resolved to a commit sha via `git rev-parse` (default $GITHUB_SHA or HEAD).").option("--pr-url <url>","Git provenance LINK: the PR this deploy came from (default $PR_URL).").option("--approved-by <email>","Record the approval (REQUIRED for --target approved).").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--follow","Poll the release deploy to completion (exit non-zero on failure).").option("--interval-ms <ms>","Poll interval for --follow (default 2000).").action(async p=>{let h=p.target==="approved"?"approved":p.target==="auto"?"auto":null;h||S(`--target must be 'auto' or 'approved' (got '${p.target}').`);let w=await Jj(Te(process.cwd(),p.project??".")),_=Object.keys(w).length;!p.solution&&_===0&&S("release deploy needs --solution <id> (live-fetch) or an SDF project (a src/ dir under --project, default cwd) to upload.");let g=(p.ref?await cm(["rev-parse",p.ref])??p.ref:null)??process.env.GITHUB_SHA??await cm(["rev-parse","HEAD"]),k=p.gitRepo??process.env.GITHUB_REPOSITORY??await cm(["remote","get-url","origin"]),A=p.gitRef??process.env.GITHUB_REF??null,C=p.prUrl??process.env.PR_URL??null,{baseUrl:L,token:D}=await ne(p.baseUrl),oe=new ee({baseUrl:L,token:D}),U;try{U=await oe.releaseDeploy({solutionId:p.solution??null,target:h,approvedBy:p.approvedBy??null,files:_>0?w:null,gitRepo:k,gitRef:A,gitSha:g,prUrl:C})}catch(me){ke(me,"releases:write","release deploy"),me instanceof be&&me.status===404&&S("solution_not_found \u2014 no solution with that id."),S(`release deploy failed: ${me instanceof Error?me.message:String(me)}`)}T(`release ${U.releaseId} enqueued \u2192 taskId=${U.taskId} (target=${h}`+(_>0?`, uploaded ${_} bundle file(s)`:", live-fetch")+")."),p.follow&&await Ol(()=>oe.releaseStatus(U.releaseId),`release ${U.releaseId}`,p.intervalMs)}),qn.command("pull").argument("<id>","The release id").description("Write the SDF project a FAILED release built into <dir>: owned/ (yours, editable) and context/ (the rest of the bundle, read-only). Edit owned/, then `release push`.").requiredOption("--dir <path>","Directory to write the project into (created if absent).").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(p,h)=>{let{baseUrl:w,token:_}=await ne(h.baseUrl),$=new ee({baseUrl:w,token:_}),g;try{g=await $.releaseFiles(p)}catch(C){C instanceof be&&C.status===404&&S(`release ${p} not found.`),C instanceof be&&C.status===409&&S(`no built project was retained for release ${p}. Files are kept for a FAILED attempt only \u2014 a shipped bundle is recoverable from the account.`),ke(C,"releases:write","pull a release"),S(`release pull failed: ${C instanceof Error?C.message:String(C)}`)}let k=Te(process.cwd(),h.dir),A=0;for(let[C,L]of[["owned",g.owned],["context",g.context]])for(let[D,oe]of Object.entries(L)){let U=Rt(k,C,D);await ye.mkdir(No(U),{recursive:!0}),await ye.writeFile(U,oe,"utf8"),A++}await ye.writeFile(Rt(k,".turbobase-release.json"),JSON.stringify({releaseId:g.releaseId,version:g.version,contentDigest:g.contentDigest},null,2),"utf8"),T(`pulled release ${g.releaseId} (${g.status}) \u2192 ${k}
|
package/dist/turbobase.js
CHANGED
|
@@ -1738,7 +1738,7 @@ ${n}`}var Vy=JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",comman
|
|
|
1738
1738
|
`))}async function ed(t,e,n=_n){let r=[...new Set(t.map(a=>a.trim()).filter(Boolean))];r.length===0&&$("name at least one skill slug. `turbobase skills list` shows them.");let s=await $n(e,n);if(!await ws(`Publish ${r.length} skill(s)?`,"Yes \u2014 publish them","their text is injected into every matching turn from now on",e))return;let i;try{i=await ee("publishing",()=>s.skillsPublish(r))}catch(a){ce(a,"skills:publish","publish a skill"),$(`publish failed: ${a instanceof Error?a.message:String(a)}`)}x(w.green(`published ${i.published.length} skill(s) \u2014 live now.`)),i.unknown.length>0&&(Z(w.yellow(`no such skill: ${i.unknown.join(", ")}`)),process.exitCode=1),Z(w.dim(`release v${i.version.version_no} \xB7 undo with: turbobase skills unpublish ${i.published.join(" ")}`))}async function td(t,e,n=_n){let r=[...new Set(t.map(a=>a.trim()).filter(Boolean))];r.length===0&&$("name at least one skill slug. `turbobase skills list` shows them.");let s=await $n(e,n);if(!await ws(`Move ${r.length} skill(s) back to draft?`,"Yes \u2014 unpublish them","they stop being injected immediately; the text and its history are kept",e))return;let i;try{i=await ee("unpublishing",()=>s.skillsUnpublish(r))}catch(a){ce(a,"skills:publish","unpublish a skill"),$(`unpublish failed: ${a instanceof Error?a.message:String(a)}`)}x(w.green(`unpublished ${i.unpublished.length} skill(s) \u2014 retracted now.`)),Z(w.dim(`release v${i.version.version_no} \xB7 re-publish with: turbobase skills publish ${i.unpublished.join(" ")}`)),i.unknown.length>0&&(Z(w.yellow(`no such skill: ${i.unknown.join(", ")}`)),process.exitCode=1)}async function nd(t,e,n=_n){let r=Number(t);(!Number.isInteger(r)||r<1)&&$(`--version must be a release number (got '${t}'). \`turbobase skills releases\` lists them.`);let s=await $n(e,n);if(await ws(`Roll the skills back to release v${r}?`,"Yes \u2014 restore that release","content only: a skill an operator has since disabled stays off",e))try{let i=await ee("rolling back",()=>s.skillsRollback(r));x(w.green(`rolled back to v${i.version.version_no}.`))}catch(i){ce(i,"skills:publish","roll back a skills release"),$(`rollback failed: ${i instanceof Error?i.message:String(i)}`)}}async function rd(t,e=_n){let n=await $n(t,e),r;try{r=await ee("reading the releases",()=>n.skillsReleases())}catch(s){ce(s,"skills:publish","read the skills releases"),$(`could not read the releases: ${s instanceof Error?s.message:String(s)}`)}if(r.items.length===0){Z("no releases yet.");return}for(let s of r.items){let o=s.is_current?w.green("*"):" ",i=s.release_note?` \u2014 ${s.release_note}`:"";x(`${o} v${s.version_no} ${s.skill_count} skill(s) ${w.dim(s.published_at)}${s.published_by?w.dim(` by ${s.published_by}`):""}${w.dim(i)}`)}}async function sd(t,e,n=_n){let r=[...new Set((t??[]).map(u=>u.trim()).filter(Boolean))];r.length===0&&$("name at least one skill to delete. `turbobase skills list` shows the slugs.");let s=await $n(e,n);if(!await ws(r.length===1?`Delete the skill '${r[0]}'?`:`Delete ${r.length} skills?
|
|
1739
1739
|
${r.join(`
|
|
1740
1740
|
`)}`,"Yes \u2014 delete them","permanent: the rows and their text are removed. Prior releases keep their own copy.",e))return;let i=[],a=[];for(let u of r)try{i.push(await ee(`deleting ${u}`,()=>s.skillsDelete(u)))}catch(p){if(p instanceof se&&p.status===404){a.push(u);continue}i.length>0&&Z(w.dim(`deleted before the error: ${i.map(h=>h.slug).join(", ")}`)),ce(p,"skills:publish","delete a skill"),$(`delete failed on '${u}': ${p instanceof Error?p.message:String(p)}`)}i.length>0&&x(w.green(`deleted ${i.length} skill(s).`));let c=i.filter(u=>u.wasPublished).map(u=>u.slug);c.length>0&&Z(w.yellow(`Published until now, so no longer injected: ${c.join(", ")}`)),a.length>0&&(Z(w.yellow(`no such skill: ${a.join(", ")}`)),process.exitCode=1)}import{promises as od}from"node:fs";import{join as mb,relative as gb,resolve as yb,sep as bb}from"node:path";var wb=new Set(["node_modules","dist","build","coverage",".git",".claude",".turbobase",".next",".svelte-kit",".vercel",".cache"]),vb=new Set([".mcp.json",".env"]);function Sb(t){return vb.has(t)||t.startsWith(".env.")&&t!==".env.example"}function kb(t){return Buffer.compare(Buffer.from(t.toString("utf8"),"utf8"),t)===0}function _b(t){return t.includes(0)}async function id(t){let e=yb(t),n={},r=[],s=[],o=[];async function i(a){let c=await od.readdir(a,{withFileTypes:!0});for(let u of c){let p=mb(a,u.name);if(u.isDirectory()){if(wb.has(u.name))continue;await i(p);continue}if(!u.isFile())continue;let h=gb(e,p).split(bb).join("/");if(Sb(u.name)){s.push(h);continue}let k=await od.readFile(p);if(!kb(k)){r.push(h);continue}_b(k)&&o.push(h),n[h]=k.toString("utf8")}}return await i(e),{files:n,skipped:r,secrets:s,unstorable:o}}import{randomBytes as $b}from"node:crypto";var xb="/_tb/app-assets",Tb=16,Rb=/^[0-9a-f]{32}$/;function ad(){return $b(Tb).toString("hex")}function Eb(t){return Rb.test(t)}function ld(t){if(!Eb(t))throw new Error(`not a build id: ${t}`);return`${xb}/${t}/`}var cd=7;import vs from"node:fs/promises";import lr from"node:path";var Ai={esbuild:"0.28.1","isolated-vm":"6.1.2"},Ii="@turbobase/isolated-vm-prebuilds";function Ci(t,e){return`${t}-${e}`}function Oi(t=process.versions){return`abi${t.modules}`}var ud=["darwin-x64","win32-arm64"];async function dd(t){let e=t.platform??process.platform,n=t.arch??process.arch,r=Ci(e,n),s=lr.join(t.home,"node_modules"),o=lr.join(s,...Ii.split("/"),"prebuilds",r),i=lr.join(s,"isolated-vm","prebuilds",r),a;try{a=await vs.readdir(o)}catch{return[]}let c=[];await vs.mkdir(i,{recursive:!0});for(let u of a){if(!u.endsWith(".node"))continue;let p=lr.join(i,u);try{await vs.copyFile(lr.join(o,u),p,vs.constants?.COPYFILE_EXCL??1),c.push(u)}catch{}}return c}import{homedir as Pb}from"node:os";import{join as pd}from"node:path";import{promises as Ni}from"node:fs";function hd(){let t=process.env.TURBOBASE_HOME??pd(Pb(),".turbobase");return{dir:t,file:pd(t,"sandboxes.json")}}var Ab={s:1e3,m:6e4,h:36e5,d:864e5};function Ib(t){let e=/^\s*(\d+)\s*([smhd])\s*$/.exec(t);if(!e)throw new Error(`invalid --ttl '${t}': expected <number><unit> where unit is s|m|h|d (e.g. 72h, 30m, 7d).`);let n=Number(e[1]);if(!Number.isFinite(n)||n<=0)throw new Error(`invalid --ttl '${t}': duration must be a positive integer.`);return n*Ab[e[2]]}function Cb(t,e=Date.now()){return new Date(e+Ib(t)).toISOString()}function Ob(t,e=Date.now()){let n=Date.parse(t);return Number.isNaN(n)?!0:n<=e}function Nb(t){let e=/^\s*(\d+(?:\.\d+)?)\s*%?\s*$/.exec(t);if(!e)throw new Error(`invalid --sample '${t}': expected a percent like 2% (or a bare number).`);let n=Number(e[1]);if(!Number.isFinite(n)||n<0||n>100)throw new Error(`invalid --sample '${t}': percent must be between 0 and 100.`);return n/100}function jb(t,e){if(e>=1)return[...t];if(e<=0||t.length===0)return[];let n=Math.max(1,Math.round(1/e)),r=[];for(let s=0;s<t.length;s+=n)r.push(t[s]);return r}var Lb="[REDACTED]",cr=class extends Error{constructor(e){super(e),this.name="SandboxDataCopyUnavailableError"}},ji=class extends Error{table;column;constructor(e,n,r){super(`Refusing to copy ${e}.${n} into a sandbox: ${r}. A sandbox must never hold un-redacted PII \u2014 classify the column 'safe' (non-PII) or let redaction mask it, then retry.`),this.name="UnredactedPiiError",this.table=e,this.column=n}};function zb(t,e,n){let r={};for(let[s,o]of Object.entries(e)){let i=n[s];if(i===void 0)throw new ji(t,s,"column has no PII classification (unknown = unsafe)");i==="pii"?r[s]=o==null?o:Lb:r[s]=o}return r}function Ub(t,e,n){return e.map(r=>zb(t,r,n))}var fd=new Set(["localhost","127.0.0.1","::1","0.0.0.0"]),Db=["oraclecloud.com","us-ashburn-1","amazonaws.com","rds.","supabase.co","turbobase.app","turbobase.io"];function qb(t){let e=t.trim();try{let s=new URL(e);if(s.hostname)return s.hostname.toLowerCase()}catch{}let n=e.replace(/^[a-z]+:\/\//i,""),r=(n.split("/")[0]??n).split("@").pop()??n;return(r.split(":")[0]??r).toLowerCase()}function Li(t){if(!t||!t.trim())throw new Error("local-only guard: empty database target \u2014 refusing to proceed.");let e=t.toLowerCase();for(let r of Db)if(e.includes(r))throw new Error(`local-only guard: target '${t}' looks like a cloud/prod context (matched '${r}'). \`turbobase sandbox create\` is LOCAL-ONLY and will never touch a cloud database.`);let n=qb(t);if(!fd.has(n))throw new Error(`local-only guard: target host '${n}' is not a recognised local Postgres (${[...fd].join(", ")}). \`turbobase sandbox create\` is LOCAL-ONLY.`)}function Mb(t,e=Bb){return`sbx-${t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,24)||"tenant"}-${e()}`}function Bb(){return Math.floor(Math.random()*16777215).toString(16).padStart(6,"0")}function Fb(t){return`turbobase_${t.replace(/[^a-z0-9]+/gi,"_").toLowerCase()}`}function Hb(t,e){return`${(e??process.env.TURBOBASE_SANDBOX_BASE_URL??"http://localhost:5173").replace(/\/+$/,"")}/?sandbox=${encodeURIComponent(t)}`}async function zi(){let{file:t}=hd();try{let e=await Ni.readFile(t,"utf8"),n=JSON.parse(e);return Array.isArray(n)?n:[]}catch(e){if(e.code==="ENOENT")return[];throw e}}async function md(t){let{dir:e,file:n}=hd();await Ni.mkdir(e,{recursive:!0}),await Ni.writeFile(n,`${JSON.stringify(t,null,2)}
|
|
1741
|
-
`,{mode:384})}async function Kb(t){let e=await zi();e.push(t),await md(e)}async function gd(t){let e=await zi(),n=e.findIndex(s=>s.name===t);if(n===-1)return null;let[r]=e.splice(n,1);return await md(e),r??null}async function Ss(){return zi()}function Ui(t,e=Date.now()){let n=[],r=[];for(let s of t)Ob(s.expiresAt,e)?r.push(s):n.push(s);return{active:n,expired:r}}async function yd(t,e){Li(t.target());let n=t.dataCopyUnavailable?.();if(n)throw new cr(n);let r=Nb(e.sample),s=e.now??Date.now(),o=(e.nameGen??(u=>Mb(u)))(e.fromTenant),i=Fb(o);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let u of a){let p=jb(u.rows,r),h=Ub(u.table,p,u.classification);h.length>0&&await t.copyRows(i,u.table,h)}let c={name:o,fromTenant:e.fromTenant,database:i,createdAt:new Date(s).toISOString(),expiresAt:Cb(e.ttl,s),sampleFraction:r,url:Hb(o,e.baseUrl)};return await Kb(c),c}async function bd(t,e){Li(t.target());let r=(await Ss()).find(s=>s.name===e);return r?(await t.dropDatabase(r.database),await gd(e),r):null}async function Di(t,e=Date.now()){Li(t.target());let n=await Ss(),{expired:r}=Ui(n,e),s=[];for(let o of r){try{await t.dropDatabase(o.database)}catch{continue}await gd(o.name),s.push(o)}return s}var ur={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","skills:publish","data:read"],defaultTtlDays:90,mintable:!0,label:"Personal access token"},catalog:{prefix:"tb_cat_",defaultScopes:["catalog:read"],allowedScopes:["catalog:read"],defaultTtlDays:null,mintable:!0,label:"AI / catalog token"}},DE=Object.keys(ur).filter(t=>ur[t].mintable);var BE=ur.pat.prefix,Vb=Object.entries(ur).map(([t,e])=>[e.prefix,t]),Gb=/^[A-Za-z0-9_-]{20,}$/;function Wb(t){if(typeof t!="string")return null;for(let[e,n]of Vb)if(t.startsWith(e)&&Gb.test(t.slice(e.length)))return n;return null}function wd(t){return Wb(t)==="pat"}var dr="bundle.json",ks="turbobase";function Sd(t){let e;try{e=JSON.parse(t)}catch(c){throw new Error(`bundle.json is not valid JSON: ${c instanceof Error?c.message:String(c)}`)}if(typeof e!="object"||e===null||Array.isArray(e))throw new Error("bundle.json must be a JSON object");let n=e,r=typeof n.version=="number"?n.version:0;if(r!==1)throw new Error(`bundle.json version ${r} is unsupported (expected 1)`);let s=c=>Array.isArray(c)?c:[],o=n.sync??{},i=n.guardrails??{},a={version:1,...typeof n.tenant=="string"?{tenant:n.tenant}:{},hooks:s(n.hooks),conduits:s(n.conduits),sync:{fields:s(o.fields),tables:s(o.tables)},guardrails:{validationHooks:s(i.validationHooks),fieldRules:s(i.fieldRules),conduits:s(i.conduits)},schedules:s(n.schedules)};return Jb(a)}function Jb(t){let e=n=>(r,s)=>n(r)<n(s)?-1:n(r)>n(s)?1:0;return{version:1,...t.tenant?{tenant:t.tenant}:{},hooks:[...t.hooks].sort(e(n=>n.id)),conduits:[...t.conduits].sort(e(n=>n.id)),sync:{fields:[...t.sync.fields].sort(e(vd)),tables:[...t.sync.tables].sort(e(n=>n.sourceRecordType))},guardrails:{validationHooks:[...t.guardrails.validationHooks].sort(),fieldRules:[...t.guardrails.fieldRules].sort(e(vd)),conduits:[...t.guardrails.conduits].sort(e(n=>n.conduitId))},schedules:[...t.schedules].sort(e(n=>n.name))}}function vd(t){return`${t.sourceSystem}::${t.recordType}::${t.fieldName}`}function kd(t,e){let n=(e??"").trim();if(!n)return{ok:!1,reason:"target instance slug is unknown \u2014 cannot confirm a promote"};let r=(t??"").trim();return r?r!==n?{ok:!1,reason:`--confirm "${r}" does not match the target instance "${n}" \u2014 refusing to apply`}:{ok:!0}:{ok:!1,reason:`--confirm <slug> is required for promote (expected "${n}")`}}async function pR(t,e){try{let n=await fetch(`${t.replace(/\/+$/,"")}/auth/pat-exchange`,{method:"POST",headers:{Authorization:`Bearer ${e}`}});if(!n.ok)return null;let r=await n.json();return typeof r.token=="string"&&r.token.length>0?r.token:null}catch{return null}}async function fR(t){let e=await Ct();e.length===0&&$("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),it()||$("this command needs an explicit <base-url> in a non-interactive shell.");try{return await at(t,e.map(n=>({value:n.baseUrl,label:n.baseUrl,description:`updated ${n.updatedAt}`})))}catch{$("cancelled.")}}async function hR(t,e){let n;try{n=await fetch(`${t}/v1/catalog/records`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:AbortSignal.timeout(8e3)})}catch(s){return{ok:!1,kind:"unreachable",detail:s instanceof Error?s.message:String(s)}}let r=(n.headers.get("content-type")??"").includes("application/json");return n.status===200||n.status===403?{ok:!0}:n.status===401?{ok:!1,kind:"bad-token",detail:`HTTP ${n.status}`}:r?{ok:!0}:{ok:!1,kind:"wrong-host",detail:`HTTP ${n.status}`}}async function mR(t,e,n){let r=await uu({baseUrl:t,token:e}),s=(await Ct()).length-1;if(x(`signed in to ${r.baseUrl} \u2014 now the active instance`+(s>0?` (${s} other${s===1?"":"s"} stored; commands default here).`:".")),!n)return;let o=await hR(r.baseUrl,e);o.ok||(o.kind==="wrong-host"?(Z(`warning: ${r.baseUrl} did not respond to the Turbobase API (/v1) \u2014 got ${o.detail}.`),Z(" In local dev the Turbobase API is on :4100, not the app dev-server port (e.g. :5173)."),Z(" The token is stored, but commands will fail against this base-url until it points at the API.")):o.kind==="bad-token"?Z(`warning: ${r.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):Z(`warning: could not reach ${r.baseUrl} to verify the token (${o.detail}). Stored anyway.`))}async function gR(t){let e=await sr(t),n=c=>{let u=new RegExp(`${c}\\s*:\\s*(['"\`])([^'"\`]+)\\1`).exec(e);return u?u[2]:void 0},r=/\bdefineConduit\s*[<(]/.test(e),s=n("id");if(r){let c=n("on"),u=n("recordType"),p=n("source");return(!s||!u||!c||!p)&&$(`could not parse id/on/from.source/from.recordType from ${t} \u2014 is it a defineConduit({...}) file?`),{id:s,recordType:u,event:c,source:p,sourceCode:e,extensionKind:"conduit"}}let o=n("recordType"),i=n("event"),a=n("source")??"standalone";return(!s||!o||!i)&&$(`could not parse id/recordType/event from ${t} \u2014 is it a defineHook({...}) file?`),{id:s,recordType:o,event:i,source:a,sourceCode:e,extensionKind:"hook"}}async function Yn(t,e,n,r){try{return await re.access(t),"skipped"}catch{}return await re.mkdir(Zn(t),{recursive:!0}),await re.writeFile(t,e,{mode:r??(n?493:420)}),r!==void 0&&await re.chmod(t,r),"wrote"}async function gg(t,e){if(yg(t)!==".gitignore")return Yn(t,e.content,e.executable,e.mode);let n=await re.readFile(t,"utf8").catch(()=>null);if(n===null)return Yn(t,e.content,e.executable,e.mode);let r=Fu(n);return r===null?"skipped":(await re.writeFile(t,r,"utf8"),"merged")}function Lc(t){if(t!==void 0){if(t==="junit"||t==="json")return t;$(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function yR(t){return{name:"turbobase ext test",cases:t.map(n=>{if(n.pass)return{name:n.name,status:"passed"};let r=n.error?"error":n.rejection?.code??"mismatch",s=n.error?`error: ${n.error}`:n.observed!==n.expected?`expected ${n.expected}, observed ${n.observed}`+(n.rejection?` (rejected: ${n.rejection.code??"rejected"}${n.rejection.message?` \u2014 ${n.rejection.message}`:""})`:""):`rejected: ${n.rejection?.code??"rejected"}${n.rejection?.message?` \u2014 ${n.rejection.message}`:""}`;return{name:n.name,status:"failed",detail:s,type:r}})}}function bR(t,e){let n=e.warnings.length===0?[{name:`${t} (${e.kind})`,status:"passed"}]:e.warnings.map(r=>({name:`${t}: ${r.rule}`,status:"failed",type:r.rule,detail:`${r.message} \u2192 fix: ${r.fix}`}));return{name:`turbobase ext lint ${t}`,cases:n}}function wR(t){let e=[];if(t.error)return e.push({name:`replay ${t.fixtureName}`,status:"failed",type:"error",detail:t.error}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e};for(let n of t.guards)e.push(n.status==="failed"?{name:`guard: ${n.name}`,status:"failed",type:n.name,detail:n.detail}:{name:`guard: ${n.name}`,status:"passed",detail:n.status==="skipped-offline"?`skipped-offline: ${n.detail}`:void 0});return t.whenPassed&&t.applyBranch&&e.push({name:`apply-branch: ${t.applyBranch}`,status:"passed",detail:`${t.masterAction??""} (${t.wouldWriteBindings.length} binding(s) would be written)`.trim()}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e}}function Bc({includeInternal:t=!0}={}){let e=new dR;e.name("turbobase").description("Build apps, author extensions, and drive your Turbobase instance.").version("1.1.
|
|
1741
|
+
`,{mode:384})}async function Kb(t){let e=await zi();e.push(t),await md(e)}async function gd(t){let e=await zi(),n=e.findIndex(s=>s.name===t);if(n===-1)return null;let[r]=e.splice(n,1);return await md(e),r??null}async function Ss(){return zi()}function Ui(t,e=Date.now()){let n=[],r=[];for(let s of t)Ob(s.expiresAt,e)?r.push(s):n.push(s);return{active:n,expired:r}}async function yd(t,e){Li(t.target());let n=t.dataCopyUnavailable?.();if(n)throw new cr(n);let r=Nb(e.sample),s=e.now??Date.now(),o=(e.nameGen??(u=>Mb(u)))(e.fromTenant),i=Fb(o);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let u of a){let p=jb(u.rows,r),h=Ub(u.table,p,u.classification);h.length>0&&await t.copyRows(i,u.table,h)}let c={name:o,fromTenant:e.fromTenant,database:i,createdAt:new Date(s).toISOString(),expiresAt:Cb(e.ttl,s),sampleFraction:r,url:Hb(o,e.baseUrl)};return await Kb(c),c}async function bd(t,e){Li(t.target());let r=(await Ss()).find(s=>s.name===e);return r?(await t.dropDatabase(r.database),await gd(e),r):null}async function Di(t,e=Date.now()){Li(t.target());let n=await Ss(),{expired:r}=Ui(n,e),s=[];for(let o of r){try{await t.dropDatabase(o.database)}catch{continue}await gd(o.name),s.push(o)}return s}var ur={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","skills:publish","data:read"],defaultTtlDays:90,mintable:!0,label:"Personal access token"},catalog:{prefix:"tb_cat_",defaultScopes:["catalog:read"],allowedScopes:["catalog:read"],defaultTtlDays:null,mintable:!0,label:"AI / catalog token"}},DE=Object.keys(ur).filter(t=>ur[t].mintable);var BE=ur.pat.prefix,Vb=Object.entries(ur).map(([t,e])=>[e.prefix,t]),Gb=/^[A-Za-z0-9_-]{20,}$/;function Wb(t){if(typeof t!="string")return null;for(let[e,n]of Vb)if(t.startsWith(e)&&Gb.test(t.slice(e.length)))return n;return null}function wd(t){return Wb(t)==="pat"}var dr="bundle.json",ks="turbobase";function Sd(t){let e;try{e=JSON.parse(t)}catch(c){throw new Error(`bundle.json is not valid JSON: ${c instanceof Error?c.message:String(c)}`)}if(typeof e!="object"||e===null||Array.isArray(e))throw new Error("bundle.json must be a JSON object");let n=e,r=typeof n.version=="number"?n.version:0;if(r!==1)throw new Error(`bundle.json version ${r} is unsupported (expected 1)`);let s=c=>Array.isArray(c)?c:[],o=n.sync??{},i=n.guardrails??{},a={version:1,...typeof n.tenant=="string"?{tenant:n.tenant}:{},hooks:s(n.hooks),conduits:s(n.conduits),sync:{fields:s(o.fields),tables:s(o.tables)},guardrails:{validationHooks:s(i.validationHooks),fieldRules:s(i.fieldRules),conduits:s(i.conduits)},schedules:s(n.schedules)};return Jb(a)}function Jb(t){let e=n=>(r,s)=>n(r)<n(s)?-1:n(r)>n(s)?1:0;return{version:1,...t.tenant?{tenant:t.tenant}:{},hooks:[...t.hooks].sort(e(n=>n.id)),conduits:[...t.conduits].sort(e(n=>n.id)),sync:{fields:[...t.sync.fields].sort(e(vd)),tables:[...t.sync.tables].sort(e(n=>n.sourceRecordType))},guardrails:{validationHooks:[...t.guardrails.validationHooks].sort(),fieldRules:[...t.guardrails.fieldRules].sort(e(vd)),conduits:[...t.guardrails.conduits].sort(e(n=>n.conduitId))},schedules:[...t.schedules].sort(e(n=>n.name))}}function vd(t){return`${t.sourceSystem}::${t.recordType}::${t.fieldName}`}function kd(t,e){let n=(e??"").trim();if(!n)return{ok:!1,reason:"target instance slug is unknown \u2014 cannot confirm a promote"};let r=(t??"").trim();return r?r!==n?{ok:!1,reason:`--confirm "${r}" does not match the target instance "${n}" \u2014 refusing to apply`}:{ok:!0}:{ok:!1,reason:`--confirm <slug> is required for promote (expected "${n}")`}}async function pR(t,e){try{let n=await fetch(`${t.replace(/\/+$/,"")}/auth/pat-exchange`,{method:"POST",headers:{Authorization:`Bearer ${e}`}});if(!n.ok)return null;let r=await n.json();return typeof r.token=="string"&&r.token.length>0?r.token:null}catch{return null}}async function fR(t){let e=await Ct();e.length===0&&$("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),it()||$("this command needs an explicit <base-url> in a non-interactive shell.");try{return await at(t,e.map(n=>({value:n.baseUrl,label:n.baseUrl,description:`updated ${n.updatedAt}`})))}catch{$("cancelled.")}}async function hR(t,e){let n;try{n=await fetch(`${t}/v1/catalog/records`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:AbortSignal.timeout(8e3)})}catch(s){return{ok:!1,kind:"unreachable",detail:s instanceof Error?s.message:String(s)}}let r=(n.headers.get("content-type")??"").includes("application/json");return n.status===200||n.status===403?{ok:!0}:n.status===401?{ok:!1,kind:"bad-token",detail:`HTTP ${n.status}`}:r?{ok:!0}:{ok:!1,kind:"wrong-host",detail:`HTTP ${n.status}`}}async function mR(t,e,n){let r=await uu({baseUrl:t,token:e}),s=(await Ct()).length-1;if(x(`signed in to ${r.baseUrl} \u2014 now the active instance`+(s>0?` (${s} other${s===1?"":"s"} stored; commands default here).`:".")),!n)return;let o=await hR(r.baseUrl,e);o.ok||(o.kind==="wrong-host"?(Z(`warning: ${r.baseUrl} did not respond to the Turbobase API (/v1) \u2014 got ${o.detail}.`),Z(" In local dev the Turbobase API is on :4100, not the app dev-server port (e.g. :5173)."),Z(" The token is stored, but commands will fail against this base-url until it points at the API.")):o.kind==="bad-token"?Z(`warning: ${r.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):Z(`warning: could not reach ${r.baseUrl} to verify the token (${o.detail}). Stored anyway.`))}async function gR(t){let e=await sr(t),n=c=>{let u=new RegExp(`${c}\\s*:\\s*(['"\`])([^'"\`]+)\\1`).exec(e);return u?u[2]:void 0},r=/\bdefineConduit\s*[<(]/.test(e),s=n("id");if(r){let c=n("on"),u=n("recordType"),p=n("source");return(!s||!u||!c||!p)&&$(`could not parse id/on/from.source/from.recordType from ${t} \u2014 is it a defineConduit({...}) file?`),{id:s,recordType:u,event:c,source:p,sourceCode:e,extensionKind:"conduit"}}let o=n("recordType"),i=n("event"),a=n("source")??"standalone";return(!s||!o||!i)&&$(`could not parse id/recordType/event from ${t} \u2014 is it a defineHook({...}) file?`),{id:s,recordType:o,event:i,source:a,sourceCode:e,extensionKind:"hook"}}async function Yn(t,e,n,r){try{return await re.access(t),"skipped"}catch{}return await re.mkdir(Zn(t),{recursive:!0}),await re.writeFile(t,e,{mode:r??(n?493:420)}),r!==void 0&&await re.chmod(t,r),"wrote"}async function gg(t,e){if(yg(t)!==".gitignore")return Yn(t,e.content,e.executable,e.mode);let n=await re.readFile(t,"utf8").catch(()=>null);if(n===null)return Yn(t,e.content,e.executable,e.mode);let r=Fu(n);return r===null?"skipped":(await re.writeFile(t,r,"utf8"),"merged")}function Lc(t){if(t!==void 0){if(t==="junit"||t==="json")return t;$(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function yR(t){return{name:"turbobase ext test",cases:t.map(n=>{if(n.pass)return{name:n.name,status:"passed"};let r=n.error?"error":n.rejection?.code??"mismatch",s=n.error?`error: ${n.error}`:n.observed!==n.expected?`expected ${n.expected}, observed ${n.observed}`+(n.rejection?` (rejected: ${n.rejection.code??"rejected"}${n.rejection.message?` \u2014 ${n.rejection.message}`:""})`:""):`rejected: ${n.rejection?.code??"rejected"}${n.rejection?.message?` \u2014 ${n.rejection.message}`:""}`;return{name:n.name,status:"failed",detail:s,type:r}})}}function bR(t,e){let n=e.warnings.length===0?[{name:`${t} (${e.kind})`,status:"passed"}]:e.warnings.map(r=>({name:`${t}: ${r.rule}`,status:"failed",type:r.rule,detail:`${r.message} \u2192 fix: ${r.fix}`}));return{name:`turbobase ext lint ${t}`,cases:n}}function wR(t){let e=[];if(t.error)return e.push({name:`replay ${t.fixtureName}`,status:"failed",type:"error",detail:t.error}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e};for(let n of t.guards)e.push(n.status==="failed"?{name:`guard: ${n.name}`,status:"failed",type:n.name,detail:n.detail}:{name:`guard: ${n.name}`,status:"passed",detail:n.status==="skipped-offline"?`skipped-offline: ${n.detail}`:void 0});return t.whenPassed&&t.applyBranch&&e.push({name:`apply-branch: ${t.applyBranch}`,status:"passed",detail:`${t.masterAction??""} (${t.wouldWriteBindings.length} binding(s) would be written)`.trim()}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e}}function Bc({includeInternal:t=!0}={}){let e=new dR;e.name("turbobase").description("Build apps, author extensions, and drive your Turbobase instance.").version("1.1.17").option("--no-color","Disable ANSI colour + animations (also honoured via NO_COLOR / non-TTY)."),e.addHelpText("beforeAll",d=>d.command.parent?"":`${ki()}
|
|
1742
1742
|
`),e.hook("preAction",(d,f)=>{Ku(e.opts(),f.opts())});let n=e.command("auth").description("Sign in to an instance and manage stored credentials."),r=e.command("project").description("Set up the local project and the files an agent session reads."),s=e.command("ext").description("Author, test, ship, and observe extensions \u2014 hooks and conduits."),o=e.command("kb").description("Write into the instance's knowledge base."),i=e.command("skills").description("Write standing rules into the instance's agent skills.");n.command("login").description("Sign in to an instance by pasting a personal access token (PAT), stored in ~/.turbobase/credentials.").requiredOption("--base-url <url>","Instance base URL, e.g. https://acme.turbobase.app").option("--token <tb_pat_\u2026>","The PAT to store. Mint one in your app under Settings \u203A Access tokens.").option("--no-verify","Skip the post-login connectivity/token probe.").action(async d=>{let f=We(d.baseUrl);d.token||$(`auth login needs --token. Mint a PAT in your Turbobase app (Settings \u203A Access tokens), then:
|
|
1743
1743
|
${ir} auth login --base-url ${f} --token tb_pat_\u2026`),wd(d.token)||$("--token does not look like a Turbobase PAT (tb_pat_\u2026)."),await mR(f,d.token,d.verify)}),n.command("status").description("Show the active instance (the no-`--base-url` default) and every stored sign-in.").action(async()=>{let d=await Ct();if(d.length===0){x("not signed in. Run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`.");return}let f=await gt(),g=y=>!!f&&y.baseUrl===f.baseUrl;if(qe())x(Xt(d.map(y=>[g(y)?w.green("*"):" ",w.bold(y.baseUrl),w.dim(y.updatedAt)]),{head:["","instance","updated"]}));else for(let y of d)x(`${g(y)?"* ":" "}${y.baseUrl} (updated ${y.updatedAt})`);f||Z("no active default \u2014 run `turbobase auth use <base-url>` or pass --base-url.")}),n.command("use [base-url]").description("Make an already-stored instance the active default (omit the arg to pick interactively).").action(async d=>{let f=d??await fR("Pick the active instance:"),g=await us(f);if(!g){let y=await Ct();$(`no stored sign-in for ${We(f)}.`+(y.length?` Stored: ${y.map(_=>_.baseUrl).join(", ")}.`:"")+` Run \`turbobase auth login --base-url ${We(f)} --token tb_pat_\u2026\`.`)}x(`active instance \u2192 ${g.baseUrl}.`)}),n.command("logout").description("Remove a stored sign-in (defaults to the active instance).").option("--base-url <url>","Which instance to remove (defaults to the active/single one).").option("--all","Remove every stored sign-in.").action(async d=>{if(d.all){let y=await Ct();for(let _ of y)await di(_.baseUrl);x(`removed ${y.length} stored sign-in${y.length===1?"":"s"}.`);return}let f=await di(d.baseUrl);f||$(d.baseUrl?`no stored sign-in for ${We(d.baseUrl)}.`:"nothing to sign out of (or ambiguous \u2014 pass --base-url / --all).");let g=await gt();x(`signed out of ${f}.`+(g?` Active instance \u2192 ${g.baseUrl}.`:""))}),r.command("types").description("Generate types.ts from the signed-in instance's catalog.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("-o, --out <file>","Output file","types.ts").option("--records-only","Emit only record-shape interfaces (skip the OpenAPI half).").action(async d=>{let{baseUrl:f,token:g}=await V(d.baseUrl),y;try{y=await It({baseUrl:f,token:g,recordsOnly:d.recordsOnly})}catch(m){let S=m instanceof Error?m.message:String(m);if(!d.recordsOnly&&/openapi\.json failed \(401/.test(S)){let P=await pR(f,g);if(P)try{y=await It({baseUrl:f,token:P,recordsOnly:!1});let A=pe(process.cwd(),d.out);await re.writeFile(A,y,"utf8"),x(`wrote ${A} (${y.split(`
|
|
1744
1744
|
`).length} lines).`);return}catch{}Z("types-gen: /v1/catalog/openapi.json needs a session \u2014 emitting record shapes only. To get the OpenAPI half headlessly, mint a PAT with the `data:read` scope (Settings \u203A Tokens); the CLI then exchanges it for a short-lived session automatically.");try{y=await It({baseUrl:f,token:g,recordsOnly:!0})}catch(A){$(`types generation failed: ${A instanceof Error?A.message:String(A)}`)}}else $(`types generation failed: ${S}`)}let _=pe(process.cwd(),d.out);await re.writeFile(_,y,"utf8"),x(`wrote ${_} (${y.split(`
|
|
@@ -1769,7 +1769,7 @@ samples init: ${P} written, ${A} skipped.`)});let mt=e.command("guardrails").des
|
|
|
1769
1769
|
`+w.dim("create one with `turbobase guardrails create <name> --source <s> --when '\u2026'`"));x(Xt(_.map(Zt),{head:["NAME","STATE","SOURCE","RECORD","WHEN","NOTE"]}))}),mt.command("create <name>").description("Create a DENY rule. Blocks PG\u2192source writes that match it.").option("--source <system>","Adapter id to scope to, or '*' for every source.","*").option("--record <type>","Record type to scope to, or '*' for every record.","*").option("--when <expr>","Condition, e.g. 'recordType not_in customer,invoice' or 'record.amount gt 10000'. Omit to deny unconditionally.").option("--message <text>","What a blocked writer is told.").option("--note <text>","Why this rule exists \u2014 for whoever reads `list` later.").option("--disabled","Create the rule parked, so it takes effect only when enabled.").option("--confirm-freeze-all","Required for an unconditional '*'/'*' rule.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,f)=>{let{baseUrl:g,token:y}=await V(f.baseUrl),_=new B({baseUrl:g,token:y}),m;try{m=await _.guardrailCreate({name:d,source:f.source,record:f.record,...f.when?{when:f.when}:{},...f.message?{message:f.message}:{},...f.note?{note:f.note}:{},...f.disabled?{enabled:!1}:{},...f.confirmFreezeAll?{confirmFreezeAll:!0}:{}})}catch(S){ce(S,"extensions:deploy","create a guardrail"),$(`create guardrail failed: ${S instanceof Error?S.message:String(S)}`)}x(`${w.green("\u2713")} guardrail ${w.bold(m.name)} created${m.enabled?"":w.dim(" (parked \u2014 enable it to take effect)")}`),x(w.dim(` denies ${m.source}/${m.record}${m.when?` where ${m.when}`:" (always)"}`)),x(w.dim(` verify with: turbobase guardrails test --source ${m.source==="*"?"<source>":m.source} --record <record>`))}),mt.command("edit <name>").description("Change a rule \u2014 most often widening its exception set as records are proven.").option("--when <expr>","New condition. Pass '' to make it unconditional.").option("--message <text>","What a blocked writer is told.").option("--note <text>","Why this rule exists.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,f)=>{f.when===void 0&&f.message===void 0&&f.note===void 0&&$("edit: nothing to change \u2014 pass --when, --message or --note.");let{baseUrl:g,token:y}=await V(f.baseUrl),_=new B({baseUrl:g,token:y}),m;try{m=await _.guardrailUpdate(d,{...f.when!==void 0?{when:f.when}:{},...f.message!==void 0?{message:f.message}:{},...f.note!==void 0?{note:f.note}:{}})}catch(S){ce(S,"extensions:deploy","edit a guardrail"),$(`edit guardrail failed: ${S instanceof Error?S.message:String(S)}`)}x(`${w.green("\u2713")} guardrail ${w.bold(m.name)} updated`),x(w.dim(` denies ${m.source}/${m.record}${m.when?` where ${m.when}`:" (always)"}`))});for(let[d,f,g]of[["enable",!0,"enabled"],["disable",!1,"disabled"]])mt.command(`${d} <name>`).description(f?"Arm a parked rule.":"Park a rule without deleting it \u2014 the reversible move mid-rollout.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(y,_)=>{let{baseUrl:m,token:S}=await V(_.baseUrl),P=new B({baseUrl:m,token:S});try{await P.guardrailUpdate(y,{enabled:f})}catch(A){ce(A,"extensions:deploy",`${d} a guardrail`),$(`${d} guardrail failed: ${A instanceof Error?A.message:String(A)}`)}x(`${w.green("\u2713")} guardrail ${w.bold(y)} ${g}`),f||x(w.dim(" writes it was blocking are now permitted (subject to the other rules)."))});mt.command("delete <name>").description("Remove a rule. This OPENS a write path \u2014 prefer `disable` if unsure.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,f)=>{let{baseUrl:g,token:y}=await V(f.baseUrl),_=new B({baseUrl:g,token:y});try{await _.guardrailDelete(d)}catch(m){ce(m,"extensions:deploy","delete a guardrail"),$(`delete guardrail failed: ${m instanceof Error?m.message:String(m)}`)}x(`${w.green("\u2713")} guardrail ${w.bold(d)} deleted`)}),mt.command("test").description("Would a write be blocked? Runs the real evaluator; writes nothing.").requiredOption("--source <system>","Adapter id the write targets.").requiredOption("--record <type>","Record type the write targets.").option("--origin <origin>","Write origin to simulate ('user', 'agent').").option("--fields <json>","A candidate row as JSON, so `record.<column>` conditions can be decided.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--json","Emit raw JSON.").action(async d=>{let f;if(d.fields)try{f=JSON.parse(d.fields)}catch(S){$(`--fields must be valid JSON: ${S instanceof Error?S.message:String(S)}`)}let{baseUrl:g,token:y}=await V(d.baseUrl),_=new B({baseUrl:g,token:y}),m;try{m=await _.guardrailTest({source:d.source,record:d.record,...d.origin?{origin:d.origin}:{},...f?{fields:f}:{}})}catch(S){ce(S,"extensions:deploy","test guardrails"),$(`guardrails test failed: ${S instanceof Error?S.message:String(S)}`)}if(d.json)return x(JSON.stringify(m,null,2));m.allowed?x(`${w.green("\u2713")} ${d.source}/${d.record} \u2014 no guardrail blocks this write`):(x(`${w.red("\u2717")} ${d.source}/${d.record} \u2014 blocked by ${w.bold(m.blockedBy??"?")}`),m.message&&x(w.dim(` ${m.message}`))),m.undecided.length>0&&(x(w.yellow(` ${m.undecided.length} rule(s) need a row to decide: ${m.undecided.join(", ")}`)),x(w.dim(` re-run with --fields '{"\u2026":"\u2026"}' to evaluate them.`))),m.allowed||(process.exitCode=1)});let _t=e.command("tables").description("Author custom tables on your instance (create, evolve, inspect)."),mn=(d,f={})=>{let g=`${w.bold(d.table)} ${w.dim(d.displayName)}`;x(d.exists===!1?`${g} ${w.dim("(new)")}`:g);for(let y of d.fields){let _=[y.required?"required":null].filter(Boolean).join(", "),m=f.showClaimants&&y.claimedBy?.length?` ${w.dim(`\u2190 ${y.claimedBy.join(", ")}`)}`:"";x(` ${y.column.padEnd(28)} ${y.type}${_?` (${_})`:""}${m}`)}f.showClaimants&&d.claimants.length>1&&x(w.dim(` claimed by ${d.claimants.join(", ")}`))};function gn(d){if(d instanceof se&&d.status===409){let f=d.body;f?.error==="table_conflict"&&f.message&&(Z(f.message),process.exit(1))}$(`tables: ${d instanceof Error?d.message:String(d)}`)}let ns=async d=>{let f=Ve(pe(process.cwd(),d),"public","turbobase.json"),g=await re.readFile(f,"utf8").catch(()=>null);if(g===null)return null;let y;try{y=JSON.parse(g)}catch(m){$(`${f} is not valid JSON: ${m instanceof Error?m.message:String(m)}`)}let _=y.tables;return _===void 0?{tables:[],manifestPath:f}:(Array.isArray(_)||$(`${f}: "tables" must be an array.`),{tables:_,manifestPath:f})};_t.command("list").description("List the custom tables on your instance and who claims each one.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--json","Emit raw JSON instead of a table.").action(async d=>{let{baseUrl:f,token:g}=await V(d.baseUrl),y=new B({baseUrl:f,token:g}),_;try{_=await y.tablesList()}catch(m){ce(m,"extensions:deploy","list tables"),$(`list tables failed: ${m instanceof Error?m.message:String(m)}`)}if(d.json)return x(JSON.stringify(_,null,2));if(_.length===0)return x("no custom tables yet \u2014 create one with `turbobase tables create <slug>`.");x(Xt(_.map(m=>[m.table,m.displayName,String(m.fields.length),m.status,m.claimedBy.join(", ")||"\u2014"]),{head:["TABLE","NAME","FIELDS","STATUS","CLAIMED BY"]}))}),_t.command("plan").description("Preview what this project's declared tables would do. Writes nothing; exits 1 on a conflict.").argument("[dir]","Project directory holding public/turbobase.json (default ./).").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--as <claimant>","Claim as `cli` or `app:<slug>` (default: the app slug in .turbobase, else cli).").action(async(d,f)=>{let g=await ns(d??".");if(g||$(`no public/turbobase.json under ${pe(process.cwd(),d??".")}.`),g.tables.length===0)return x(`${g.manifestPath} declares no tables \u2014 nothing to plan.`);let{baseUrl:y,token:_}=await V(f.baseUrl),m=new B({baseUrl:y,token:_}),S;try{S=await ee("planning tables",()=>m.tablesPlan({claimedBy:f.as,tables:g.tables}))}catch(P){ce(P,"extensions:deploy","plan tables"),gn(P)}x(`claiming as ${w.bold(S.claimedBy)}`);for(let P of S.tables)mn(P,{showClaimants:!0});x(""),x(w.dim("nothing was applied \u2014 run `turbobase tables apply` (or deploy the app) to make it so."))}),_t.command("apply").description("Apply this project's declared tables to the instance.").argument("[dir]","Project directory holding public/turbobase.json (default ./).").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--as <claimant>","Claim as `cli` or `app:<slug>` (default: cli).").action(async(d,f)=>{let g=await ns(d??".");if(g||$(`no public/turbobase.json under ${pe(process.cwd(),d??".")}.`),g.tables.length===0)return x(`${g.manifestPath} declares no tables \u2014 nothing to apply.`);let{baseUrl:y,token:_}=await V(f.baseUrl),m=new B({baseUrl:y,token:_}),S;try{S=await ee("applying tables",()=>m.tablesApply({claimedBy:f.as,tables:g.tables}))}catch(P){ce(P,"extensions:deploy","apply tables"),gn(P)}for(let P of S.tables)mn(P,{showClaimants:!0});x(`applied ${S.tables.length} table${S.tables.length===1?"":"s"} as ${S.claimedBy}.`)}),_t.command("create").description('Create a single table without a manifest (the CLI twin of "New custom table").').argument("<slug>","Table slug; the physical table is cr_<slug>.").requiredOption("-f, --field <name:type>","Field as name:type[:required] \u2014 text|number|date|bool|select. Repeatable.",(d,f)=>[...f??[],d],[]).option("--name <displayName>","Human label for the table (defaults to the slug).").option("--restrict-to-owner","Non-admins see only rows they created.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,f)=>{let g=f.field.map(P=>{let[A,C,O]=P.split(":");return(!A||!C)&&$(`--field "${P}" must be name:type[:required] (e.g. title:text:required).`),O!==void 0&&O!=="required"&&$(`--field "${P}": the third part may only be "required".`),{name:A,type:C,...O==="required"?{required:!0}:{}}}),{baseUrl:y,token:_}=await V(f.baseUrl),m=new B({baseUrl:y,token:_}),S;try{S=await ee(`creating cr_${d}`,()=>m.tablesApply({tables:[{slug:d,displayName:f.name??d,fields:g,...f.restrictToOwner?{rowLevelOwner:!0}:{}}]}))}catch(P){ce(P,"extensions:deploy","create a table"),gn(P)}for(let P of S.tables)mn(P);x(""),x(w.dim("grant access under Settings \u203A Users, or in the Records page permissions matrix."))}),_t.command("drop").description("Drop a table and everything in it. Irreversible.").argument("<slug>","Table slug (without the cr_ prefix).").requiredOption("--confirm <slug>","Type the EXACT slug to confirm (fail-closed).").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,f)=>{f.confirm!==d&&$(`drop refused: --confirm "${f.confirm}" does not match "${d}".`);let{baseUrl:g,token:y}=await V(f.baseUrl),_=new B({baseUrl:g,token:y}),m;try{m=await ee(`dropping cr_${d}`,()=>_.tablesDrop(d,f.confirm))}catch(S){ce(S,"extensions:deploy","drop a table"),$(`drop failed: ${S instanceof Error?S.message:String(S)}`)}if(!m.removed)return x(`no table named "${d}" on this instance \u2014 nothing dropped.`);x(`dropped ${m.table} and every row in it.`)});let Wo=e.command("sandbox").description("Ephemeral local authoring sandboxes.");Wo.command("create").description("Stand up a fresh local sandbox seeded with a sampled+redacted slice of an instance.").requiredOption("--from <instance>","Source instance to clone the data slice from.").option("--ttl <duration>","Time-to-live before reaping (e.g. 72h, 30m, 7d).","72h").option("--sample <pct>","Percent of each table to copy (e.g. 2%).","2%").option("--base-url <url>","Local base URL the sandbox is reachable at.").action(async d=>{process.env.TURBOBASE_SANDBOX="1";let f=Uc(),g;try{g=await yd(f,{fromTenant:d.from,ttl:d.ttl,sample:d.sample,baseUrl:d.baseUrl})}catch(y){y instanceof cr&&$(y.message),$(`sandbox create failed: ${y instanceof Error?y.message:String(y)}`)}x(`sandbox ${g.name} ready.`),x(` source: ${g.fromTenant} (sampled ${Math.round(g.sampleFraction*100)}%, redacted)`),x(` url: ${g.url}`),x(` expires: ${g.expiresAt}`)}),Wo.command("destroy").argument("<name>","The sandbox name to tear down.").description("Drop a sandbox database and remove it from the registry.").action(async d=>{let f=Uc();await Di(f).catch(()=>[]);let g;try{g=await bd(f,d)}catch(y){$(`sandbox destroy failed: ${y instanceof Error?y.message:String(y)}`)}g||$(`no sandbox named '${d}'.`),x(`destroyed sandbox ${g.name} (dropped ${g.database}).`)}),Wo.command("list").description("List active sandboxes with TTL/expiry; reaps any past their TTL.").action(async()=>{let d=Uc(),f=await Di(d).catch(()=>[]),g=await Ss(),{active:y}=Ui(g);if(y.length===0)x("no active sandboxes.");else{for(let _ of y)x(` ${_.name} from=${_.fromTenant} expires=${_.expiresAt} ${_.url}`);x(`
|
|
1770
1770
|
${y.length} active sandbox(es).`)}f.length>0&&x(`(reaped ${f.length} expired sandbox(es).)`)}),s.command("export").description("Snapshot the live instance into turbobase/bundle.json.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--out <dir>","Directory to write the bundle into.",ks).action(async d=>{let{baseUrl:f,token:g}=await V(d.baseUrl),y=new B({baseUrl:f,token:g}),_;try{_=await ee("exporting instance config",()=>y.configExport())}catch(P){P instanceof se&&(P.status===401||P.status===403)&&$("ext export forbidden \u2014 the stored PAT lacks extensions:deploy. Re-run `turbobase auth login`."),$(`ext export failed: ${P instanceof Error?P.message:String(P)}`)}let m=typeof _.serialized=="string"?_.serialized:`${JSON.stringify(_.manifest??_,null,2)}
|
|
1771
1771
|
`,S=pe(process.cwd(),d.out,dr);await re.mkdir(Zn(S),{recursive:!0}),await re.writeFile(S,m,"utf8"),x(`wrote ${d.out}/${dr} (${m.split(`
|
|
1772
|
-
`).length} lines).`)});let Fc=s.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),bg=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],Hc=()=>process.env.TURBOBASE_HOME?.trim()||Ve(uR(),".turbobase"),wg=()=>"1.1.
|
|
1772
|
+
`).length} lines).`)});let Fc=s.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),bg=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],Hc=()=>process.env.TURBOBASE_HOME?.trim()||Ve(uR(),".turbobase"),wg=()=>"1.1.17",rs=async()=>Promise.all(bg.map(async d=>{try{return await import(d.name),{...d,present:!0}}catch{return{...d,present:!1}}}));Fc.command("status").description("Report which optional native packages are installed.").action(async()=>{let d=await rs();for(let f of d)x(`${f.present?Ne.success("installed"):Ne.warn("missing")} ${w.bold(f.name)} \u2014 ${f.needs}`);d.some(f=>!f.present)&&x(`
|
|
1773
1773
|
run \`${ir} ext extras install\` to fetch them into ${Hc()}.`)}),Fc.command("install").description("Install the optional native packages into $TURBOBASE_HOME (needs npm).").action(async()=>{let d=await rs();if(d.every(A=>A.present)){x(Ne.success("extras already installed \u2014 nothing to do."));return}let f=Hc();await re.mkdir(f,{recursive:!0});let g=d.filter(A=>!A.present).map(A=>A.name),y=Ci(process.platform,process.arch),_=async(A,C)=>{let{code:O,stderr:G}=await ts("npm",["install","--prefix",f,"--no-audit","--no-fund",...A]);O!==0&&$(`extras install failed while installing ${C} (npm exited ${O}).
|
|
1774
1774
|
${G.trim()}
|
|
1775
1775
|
These are native packages \u2014 they need npm and a working toolchain. Everything except \`test\` and \`replay\` works without them.`)},m=g.filter(A=>A!=="isolated-vm");if(m.length>0){let A=m.map(C=>`${C}@${Ai[C]??"latest"}`);x(`installing ${A.join(", ")} \u2192 ${f}/node_modules \u2026`),await _(A,m.join(", "))}if(g.includes("isolated-vm")){let A=`isolated-vm@${Ai["isolated-vm"]}`;if(x(`installing ${A} \u2192 ${f}/node_modules \u2026`),await _(["--ignore-scripts",A],"isolated-vm"),ud.includes(y)){let O=`${Ii}@${wg()}`;x(`fetching ${O} (no upstream binary for ${y}) \u2026`);let{code:G}=await ts("npm",["install","--prefix",f,"--no-audit","--no-fund","--ignore-scripts",O]);G!==0&&x(Ne.warn(`could not fetch ${O} \u2014 falling back to a source build.`))}let C=await dd({home:f});C.length>0&&x(` ${Ne.success("prebuilt")} ${C.join(", ")} \u2192 isolated-vm/prebuilds/${y}`)}let S=await rs(),P=S.filter(A=>!A.present).map(A=>A.name);if(P.includes("isolated-vm")){x(Ne.warn(`no prebuilt isolated-vm for ${y} / Node ${process.versions.node} (${Oi()}) \u2014 building from source. This needs Python and a C++ toolchain, and takes a few minutes.`));let{code:A}=await ts("npm",["rebuild","--prefix",f,"isolated-vm"]);A===0&&(S=await rs(),P=S.filter(C=>!C.present).map(C=>C.name))}P.length>0&&$(`npm succeeded but ${P.join(", ")} still will not load \u2014 no prebuilt binary for ${y} / Node ${process.versions.node} (${Oi()}), and the source build did not produce one either. Everything except \`test\` and \`replay\` works without them.`),x(Ne.success("extras installed \u2014 `turbobase ext test` and `turbobase ext replay` are ready."))});let yn=e.command("release").description("Ship a release to your instance: deploy | status | rollback | list.");return yn.command("deploy").description("Enqueue a server-side release deploy. UPLOADS the SDF project src/ from the checkout (the producer swap) when present; else a solution-only live-fetch deploy. An approved target requires --approved-by.").requiredOption("--target <mode>","Deploy posture: auto (apply now) | approved (needs a recorded approval).").option("--solution <id>","The solution id the release delivers (omit for an upload-only deploy).").option("--project <dir>","SDF project dir whose src/ is uploaded (default: cwd).").option("--git-repo <repo>","Git provenance LINK: source repo (default $GITHUB_REPOSITORY or `git remote get-url origin`).").option("--git-ref <ref>","Git provenance LINK: ref/branch/tag (default $GITHUB_REF).").option("--ref <committish>","Git provenance: the ref/branch/tag/sha the upload was built from; resolved to a commit sha via `git rev-parse` (default $GITHUB_SHA or HEAD).").option("--pr-url <url>","Git provenance LINK: the PR this deploy came from (default $PR_URL).").option("--approved-by <email>","Record the approval (REQUIRED for --target approved).").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--follow","Poll the release deploy to completion (exit non-zero on failure).").option("--interval-ms <ms>","Poll interval for --follow (default 2000).").action(async d=>{let f=d.target==="approved"?"approved":d.target==="auto"?"auto":null;f||$(`--target must be 'auto' or 'approved' (got '${d.target}').`);let g=await _R(pe(process.cwd(),d.project??".")),y=Object.keys(g).length;!d.solution&&y===0&&$("release deploy needs --solution <id> (live-fetch) or an SDF project (a src/ dir under --project, default cwd) to upload.");let m=(d.ref?await Dc(["rev-parse",d.ref])??d.ref:null)??process.env.GITHUB_SHA??await Dc(["rev-parse","HEAD"]),S=d.gitRepo??process.env.GITHUB_REPOSITORY??await Dc(["remote","get-url","origin"]),P=d.gitRef??process.env.GITHUB_REF??null,A=d.prUrl??process.env.PR_URL??null,{baseUrl:C,token:O}=await V(d.baseUrl),G=new B({baseUrl:C,token:O}),N;try{N=await G.releaseDeploy({solutionId:d.solution??null,target:f,approvedBy:d.approvedBy??null,files:y>0?g:null,gitRepo:S,gitRef:P,gitSha:m,prUrl:A})}catch(ne){ce(ne,"releases:write","release deploy"),ne instanceof se&&ne.status===404&&$("solution_not_found \u2014 no solution with that id."),$(`release deploy failed: ${ne instanceof Error?ne.message:String(ne)}`)}x(`release ${N.releaseId} enqueued \u2192 taskId=${N.taskId} (target=${f}`+(y>0?`, uploaded ${y} bundle file(s)`:", live-fetch")+")."),d.follow&&await Ei(()=>G.releaseStatus(N.releaseId),`release ${N.releaseId}`,d.intervalMs)}),yn.command("pull").argument("<id>","The release id").description("Write the SDF project a FAILED release built into <dir>: owned/ (yours, editable) and context/ (the rest of the bundle, read-only). Edit owned/, then `release push`.").requiredOption("--dir <path>","Directory to write the project into (created if absent).").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,f)=>{let{baseUrl:g,token:y}=await V(f.baseUrl),_=new B({baseUrl:g,token:y}),m;try{m=await _.releaseFiles(d)}catch(A){A instanceof se&&A.status===404&&$(`release ${d} not found.`),A instanceof se&&A.status===409&&$(`no built project was retained for release ${d}. Files are kept for a FAILED attempt only \u2014 a shipped bundle is recoverable from the account.`),ce(A,"releases:write","pull a release"),$(`release pull failed: ${A instanceof Error?A.message:String(A)}`)}let S=pe(process.cwd(),f.dir),P=0;for(let[A,C]of[["owned",m.owned],["context",m.context]])for(let[O,G]of Object.entries(C)){let N=Ve(S,A,O);await re.mkdir(Zn(N),{recursive:!0}),await re.writeFile(N,G,"utf8"),P++}await re.writeFile(Ve(S,".turbobase-release.json"),JSON.stringify({releaseId:m.releaseId,version:m.version,contentDigest:m.contentDigest},null,2),"utf8"),x(`pulled release ${m.releaseId} (${m.status}) \u2192 ${S}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@turbobase/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.17",
|
|
4
4
|
"description": "Author, test, and deploy Turbobase extensions (hooks + conduits) against a tenant over HTTP + a personal access token.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|