@turbobase/cli 1.12.0 → 1.14.0

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.
@@ -3611,7 +3611,7 @@ ${o.map(S0).join(`
3611
3611
  `))}async function _b(t,e,r=mo){let n=[...new Set(t.map(a=>a.trim()).filter(Boolean))];n.length===0&&k("name at least one skill slug. `turbobase skills list` shows them.");let o=await go(e,r);if(!await ua(`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 ae("publishing",()=>o.skillsPublish(n))}catch(a){se(a,"skills:publish","publish a skill"),k(`publish failed: ${a instanceof Error?a.message:String(a)}`)}v(g.green(`published ${i.published.length} skill(s) \u2014 live now.`)),i.unknown.length>0&&(q(g.yellow(`no such skill: ${i.unknown.join(", ")}`)),process.exitCode=1),q(g.dim(`release v${i.version.version_no} \xB7 undo with: turbobase skills unpublish ${i.published.join(" ")}`))}async function xb(t,e,r=mo){let n=[...new Set(t.map(a=>a.trim()).filter(Boolean))];n.length===0&&k("name at least one skill slug. `turbobase skills list` shows them.");let o=await go(e,r);if(!await ua(`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 ae("unpublishing",()=>o.skillsUnpublish(n))}catch(a){se(a,"skills:publish","unpublish a skill"),k(`unpublish failed: ${a instanceof Error?a.message:String(a)}`)}v(g.green(`unpublished ${i.unpublished.length} skill(s) \u2014 retracted now.`)),q(g.dim(`release v${i.version.version_no} \xB7 re-publish with: turbobase skills publish ${i.unpublished.join(" ")}`)),i.unknown.length>0&&(q(g.yellow(`no such skill: ${i.unknown.join(", ")}`)),process.exitCode=1)}async function kb(t,e,r=mo){let n=Number(t);(!Number.isInteger(n)||n<1)&&k(`--version must be a release number (got '${t}'). \`turbobase skills releases\` lists them.`);let o=await go(e,r);if(await ua(`Roll the skills back to release v${n}?`,"Yes \u2014 restore that release","content only: a skill an operator has since unpublished stays off",e))try{let i=await ae("rolling back",()=>o.skillsRollback(n));v(g.green(`rolled back to v${i.version.version_no}.`))}catch(i){se(i,"skills:publish","roll back a skills release"),k(`rollback failed: ${i instanceof Error?i.message:String(i)}`)}}async function $b(t,e=mo){let r=await go(t,e),n;try{n=await ae("reading the releases",()=>r.skillsReleases())}catch(o){se(o,"skills:publish","read the skills releases"),k(`could not read the releases: ${o instanceof Error?o.message:String(o)}`)}if(n.items.length===0){q("no releases yet.");return}for(let o of n.items){let s=o.is_current?g.green("*"):" ",i=o.release_note?` \u2014 ${o.release_note}`:"";v(`${s} v${o.version_no} ${o.skill_count} skill(s) ${g.dim(o.published_at)}${o.published_by?g.dim(` by ${o.published_by}`):""}${g.dim(i)}`)}}async function Sb(t,e,r=mo){let n=[...new Set((t??[]).map(l=>l.trim()).filter(Boolean))];n.length===0&&k("name at least one skill to delete. `turbobase skills list` shows the slugs.");let o=await go(e,r);if(!await ua(n.length===1?`Delete the skill '${n[0]}'?`:`Delete ${n.length} skills?
3612
3612
  ${n.join(`
3613
3613
  `)}`,"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 ae(`deleting ${l}`,()=>o.skillsDelete(l)))}catch(u){if(u instanceof le&&u.status===404){a.push(l);continue}i.length>0&&q(g.dim(`deleted before the error: ${i.map(p=>p.slug).join(", ")}`)),se(u,"skills:publish","delete a skill"),k(`delete failed on '${l}': ${u instanceof Error?u.message:String(u)}`)}i.length>0&&v(g.green(`deleted ${i.length} skill(s).`));let c=i.filter(l=>l.wasPublished).map(l=>l.slug);c.length>0&&q(g.yellow(`Published until now, so no longer injected: ${c.join(", ")}`)),a.length>0&&(q(g.yellow(`no such skill: ${a.join(", ")}`)),process.exitCode=1)}import{promises as Tb}from"node:fs";import{join as Y0,relative as X0,resolve as Q0,sep as eP}from"node:path";var tP=new Set(["node_modules","dist","build","coverage",".git",".claude",".turbobase",".next",".svelte-kit",".vercel",".cache"]),rP=new Set([".mcp.json",".env"]);function nP(t){return rP.has(t)||t.startsWith(".env.")&&t!==".env.example"}function oP(t){return Buffer.compare(Buffer.from(t.toString("utf8"),"utf8"),t)===0}function sP(t){return t.includes(0)}async function Eb(t){let e=Q0(t),r={},n=[],o=[],s=[];async function i(a){let c=await Tb.readdir(a,{withFileTypes:!0});for(let l of c){let u=Y0(a,l.name);if(tP.has(l.name))continue;if(l.isDirectory()){await i(u);continue}if(!l.isFile())continue;let p=X0(e,u).split(eP).join("/");if(nP(l.name)){o.push(p);continue}let b=await Tb.readFile(u);if(!oP(b)){n.push(p);continue}sP(b)&&s.push(p),r[p]=b.toString("utf8")}}return await i(e),{files:r,skipped:n,secrets:o,unstorable:s}}import{randomBytes as iP}from"node:crypto";var aP="/_tb/app-assets",cP=16,lP=/^[0-9a-f]{32}$/;function Rb(){return iP(cP).toString("hex")}function uP(t){return lP.test(t)}function Pb(t){if(!uP(t))throw new Error(`not a build id: ${t}`);return`${aP}/${t}/`}var Ab=7;_u();var xu=50*1024*1024;import da from"node:fs/promises";import fs from"node:path";var ku={esbuild:"0.28.1","isolated-vm":"6.1.2"},$u="@turbobase/isolated-vm-prebuilds";function Su(t,e){return`${t}-${e}`}function Tu(t=process.versions){return`abi${t.modules}`}var Ib=["darwin-x64","win32-arm64"];async function Ob(t){let e=t.platform??process.platform,r=t.arch??process.arch,n=Su(e,r),o=fs.join(t.home,"node_modules"),s=fs.join(o,...$u.split("/"),"prebuilds",n),i=fs.join(o,"isolated-vm","prebuilds",n),a;try{a=await da.readdir(s)}catch{return[]}let c=[];await da.mkdir(i,{recursive:!0});for(let l of a){if(!l.endsWith(".node"))continue;let u=fs.join(i,l);try{await da.copyFile(fs.join(s,l),u,da.constants?.COPYFILE_EXCL??1),c.push(l)}catch{}}return c}import{homedir as dP}from"node:os";import{join as Cb}from"node:path";import{promises as Eu}from"node:fs";function zb(){let t=process.env.TURBOBASE_HOME??Cb(dP(),".turbobase");return{dir:t,file:Cb(t,"sandboxes.json")}}var pP={s:1e3,m:6e4,h:36e5,d:864e5};function hP(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*pP[e[2]]}function fP(t,e=Date.now()){return new Date(e+hP(t)).toISOString()}function mP(t,e=Date.now()){let r=Date.parse(t);return Number.isNaN(r)?!0:r<=e}function gP(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 yP(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 bP="[REDACTED]",ms=class extends Error{constructor(e){super(e),this.name="SandboxDataCopyUnavailableError"}},Ru=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 wP(t,e,r){let n={};for(let[o,s]of Object.entries(e)){let i=r[o];if(i===void 0)throw new Ru(t,o,"column has no PII classification (unknown = unsafe)");i==="pii"?n[o]=s==null?s:bP:n[o]=s}return n}function vP(t,e,r){return e.map(n=>wP(t,n,r))}var Nb=new Set(["localhost","127.0.0.1","::1","0.0.0.0"]),_P=["oraclecloud.com","us-ashburn-1","amazonaws.com","rds.","supabase.co","turbobase.app","turbobase.io"];function xP(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 Pu(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 _P)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=xP(t);if(!Nb.has(r))throw new Error(`local-only guard: target host '${r}' is not a recognised local Postgres (${[...Nb].join(", ")}). \`turbobase sandbox create\` is LOCAL-ONLY.`)}function kP(t,e=$P){return`sbx-${t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,24)||"tenant"}-${e()}`}function $P(){return Math.floor(Math.random()*16777215).toString(16).padStart(6,"0")}function SP(t){return`turbobase_${t.replace(/[^a-z0-9]+/gi,"_").toLowerCase()}`}function TP(t,e){return`${(e??process.env.TURBOBASE_SANDBOX_BASE_URL??"http://localhost:5173").replace(/\/+$/,"")}/?sandbox=${encodeURIComponent(t)}`}async function Au(){let{file:t}=zb();try{let e=await Eu.readFile(t,"utf8"),r=JSON.parse(e);return Array.isArray(r)?r:[]}catch(e){if(e.code==="ENOENT")return[];throw e}}async function Lb(t){let{dir:e,file:r}=zb();await Eu.mkdir(e,{recursive:!0}),await Eu.writeFile(r,`${JSON.stringify(t,null,2)}
3614
- `,{mode:384})}async function EP(t){let e=await Au();e.push(t),await Lb(e)}async function jb(t){let e=await Au(),r=e.findIndex(o=>o.name===t);if(r===-1)return null;let[n]=e.splice(r,1);return await Lb(e),n??null}async function pa(){return Au()}function Iu(t,e=Date.now()){let r=[],n=[];for(let o of t)mP(o.expiresAt,e)?n.push(o):r.push(o);return{active:r,expired:n}}async function Db(t,e){Pu(t.target());let r=t.dataCopyUnavailable?.();if(r)throw new ms(r);let n=gP(e.sample),o=e.now??Date.now(),s=(e.nameGen??(l=>kP(l)))(e.fromTenant),i=SP(s);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let l of a){let u=yP(l.rows,n),p=vP(l.table,u,l.classification);p.length>0&&await t.copyRows(i,l.table,p)}let c={name:s,fromTenant:e.fromTenant,database:i,createdAt:new Date(o).toISOString(),expiresAt:fP(e.ttl,o),sampleFraction:n,url:TP(s,e.baseUrl)};return await EP(c),c}async function Mb(t,e){Pu(t.target());let n=(await pa()).find(o=>o.name===e);return n?(await t.dropDatabase(n.database),await jb(e),n):null}async function Ou(t,e=Date.now()){Pu(t.target());let r=await pa(),{expired:n}=Iu(r,e),o=[];for(let s of n){try{await t.dropDatabase(s.database)}catch{continue}await jb(s.name),o.push(s)}return o}var gs={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","skills:publish","docs:publish"],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"}},oB=Object.keys(gs).filter(t=>gs[t].mintable);var aB=gs.pat.prefix,RP=Object.entries(gs).map(([t,e])=>[e.prefix,t]),PP=/^[A-Za-z0-9_-]{20,}$/;function AP(t){if(typeof t!="string")return null;for(let[e,r]of RP)if(t.startsWith(e)&&PP.test(t.slice(e.length)))return r;return null}function Ub(t){return AP(t)==="pat"}os();var IP=8e3,OP={revoked:"the instance REVOKED this token \u2014 mint a new PAT (Settings \u203A Access tokens) and run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`",expired:"this token has EXPIRED \u2014 mint a new PAT (Settings \u203A Access tokens) and run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`",unknown:"this instance has never seen this token \u2014 it was deleted, or it belongs to a DIFFERENT instance; check the base-url before minting a new PAT",suspended:"the user this token belongs to is SUSPENDED \u2014 an instance administrator has to restore the account; a new token will not help",malformed:"the stored credential is not shaped like a Turbobase PAT (tb_pat_\u2026) \u2014 sign in again",missing:"no token reached the instance \u2014 sign in again",transient:"the instance could not verify the token right now (a storage error, not a decision about the token) \u2014 retry"};function CP(t){return OP[t]??`the instance rejected this token (${t}) \u2014 run \`turbobase auth login\` to store a fresh PAT`}async function NP(t,e,r=fetch){let n;try{n=await r(`${t}/v1/catalog/records`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:AbortSignal.timeout(IP)})}catch(s){return{state:"unreachable",detail:s instanceof Error?s.message:String(s)}}if(n.status===200||n.status===403)return{state:"accepted"};if(n.status===401){let s=await n.json().catch(()=>null);return{state:"rejected",reason:typeof s?.reason=="string"&&s.reason?s.reason:"unknown"}}return(n.headers.get("content-type")??"").includes("application/json")?{state:"accepted"}:{state:"unreachable",detail:`HTTP ${n.status} \u2014 this host does not serve the /v1 API`}}function qb(t){return t.state==="accepted"?et()?g.green("accepted"):"accepted":t.state==="rejected"?et()?g.red(`rejected \xB7 ${t.reason}`):`rejected \xB7 ${t.reason}`:et()?g.yellow("unreachable"):"unreachable"}async function Zb(t={},e=fetch){let r=await Sr(),{file:n}=Bi();if(r.length===0){v("not signed in. Run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),q(g.dim(`no sign-ins stored in ${n}.`));return}let o=await Jt(),s=await Promise.all(r.map(async i=>({baseUrl:i.baseUrl,active:!!o&&i.baseUrl===o.baseUrl,updatedAt:i.updatedAt,check:t.probe===!1?{state:"accepted"}:await NP(i.baseUrl,i.token,e)})));if(t.probe===!1)for(let i of s)v(`${i.active?"* ":" "}${i.baseUrl} (updated ${i.updatedAt})`);else if(et())v(Yt(s.map(i=>[i.active?g.green("*"):" ",g.bold(i.baseUrl),qb(i.check),g.dim(i.updatedAt)]),{head:["","instance","token","updated"]}));else for(let i of s)v(`${i.active?"* ":" "}${i.baseUrl} ${qb(i.check)} (updated ${i.updatedAt})`);q(g.dim(`tokens read from ${n}`));for(let i of s)i.check.state==="rejected"?(q(g.red(`${i.baseUrl}: ${CP(i.check.reason)}.`)),q(g.dim(" then re-run `turbobase project init` \u2014 .mcp.json holds its own copy of the token, and until it is rewritten the authoring MCP tools stay absent from your session."))):i.check.state==="unreachable"&&q(g.yellow(`${i.baseUrl}: could not reach the instance (${i.check.detail}).`));o||q("no active default \u2014 run `turbobase auth use <base-url>` or pass --base-url.")}async function lM(t){let e=await Sr();e.length===0&&k("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),ft()||k("this command needs an explicit <base-url> in a non-interactive shell.");try{return await mt(t,e.map(r=>({value:r.baseUrl,label:r.baseUrl,description:`updated ${r.updatedAt}`})))}catch{k("cancelled.")}}async function uM(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}`}}function dM(t){q(""),q("Sharing is ON. Two things reach the Turbobase product team from this instance:"),q(" \xB7 anonymized pattern cards from your proposals, solutions and INSTANCE-LEARNINGS.md \u2014"),q(" a pattern label, a record class, a fix shape, an outcome. No scripts, values or ids."),q(" \xB7 redacted prose from your projects\u2019 TURBOBASE-FEEDBACK.md \u2014 the findings you write"),q(" about OUR platform, with instance name, user names and ids masked before they leave."),q(`Turn it off: ${t}/admin/settings \u2192 Telemetry \u2192 "Share learnings and platform feedback".`),q("")}async function pM(t,e,r){let n=await Jg({baseUrl:t,token:e}),o=(await Sr()).length-1;if(v(`signed in to ${n.baseUrl} \u2014 now the active instance`+(o>0?` (${o} other${o===1?"":"s"} stored; commands default here).`:".")),dM(n.baseUrl),!r)return;let s=await uM(n.baseUrl,e);s.ok||(s.kind==="wrong-host"?(q(`warning: ${n.baseUrl} did not respond to the Turbobase API (/v1) \u2014 got ${s.detail}.`),q(" In local dev the Turbobase API is on :4100, not the app dev-server port (e.g. :5173)."),q(" The token is stored, but commands will fail against this base-url until it points at the API.")):s.kind==="bad-token"?q(`warning: ${n.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):q(`warning: could not reach ${n.baseUrl} to verify the token (${s.detail}). Stored anyway.`))}async function hM(t){let e=await cs(t),r=hu(e);if(!r){let n=/\bdefineConduit\s*[<(]/.test(e);k(n?`could not parse id/on/from.source/from.recordType from ${t} \u2014 is it a defineConduit({...}) file?`:`could not parse id/recordType/event from ${t} \u2014 is it a defineHook({...}) file?`)}return{...r,sourceCode:e}}async function Ko(t,e,r,n){try{return await ye.access(t),"skipped"}catch{}return await ye.mkdir(Vo(t),{recursive:!0}),await ye.writeFile(t,e,{mode:n??(r?493:420)}),n!==void 0&&await ye.chmod(t,n),"wrote"}async function US(t,e){if(ZS(t)!==".gitignore")return Ko(t,e.content,e.executable,e.mode);let r=await ye.readFile(t,"utf8").catch(()=>null);if(r===null)return Ko(t,e.content,e.executable,e.mode);let n=zy(r);return n===null?"skipped":(await ye.writeFile(t,n,"utf8"),"merged")}function Wm(t){if(t!==void 0){if(t==="junit"||t==="json")return t;k(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function il(t){process.exit(t)}function fM(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 mM(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 gM(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 qS(t){return`${(Math.ceil(t/1048576*10)/10).toFixed(1).replace(/\.0$/,"")} MiB`}function eg({includeInternal:t=!0}={}){let e=new wg;e.name("turbobase").description("Build apps, author extensions, and drive your Turbobase instance.").version("1.12.0").option("--no-color","Disable ANSI colour + animations (also honoured via NO_COLOR / non-TTY)."),e.addHelpText("beforeAll",d=>d.command.parent?"":`${au()}
3614
+ `,{mode:384})}async function EP(t){let e=await Au();e.push(t),await Lb(e)}async function jb(t){let e=await Au(),r=e.findIndex(o=>o.name===t);if(r===-1)return null;let[n]=e.splice(r,1);return await Lb(e),n??null}async function pa(){return Au()}function Iu(t,e=Date.now()){let r=[],n=[];for(let o of t)mP(o.expiresAt,e)?n.push(o):r.push(o);return{active:r,expired:n}}async function Db(t,e){Pu(t.target());let r=t.dataCopyUnavailable?.();if(r)throw new ms(r);let n=gP(e.sample),o=e.now??Date.now(),s=(e.nameGen??(l=>kP(l)))(e.fromTenant),i=SP(s);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let l of a){let u=yP(l.rows,n),p=vP(l.table,u,l.classification);p.length>0&&await t.copyRows(i,l.table,p)}let c={name:s,fromTenant:e.fromTenant,database:i,createdAt:new Date(o).toISOString(),expiresAt:fP(e.ttl,o),sampleFraction:n,url:TP(s,e.baseUrl)};return await EP(c),c}async function Mb(t,e){Pu(t.target());let n=(await pa()).find(o=>o.name===e);return n?(await t.dropDatabase(n.database),await jb(e),n):null}async function Ou(t,e=Date.now()){Pu(t.target());let r=await pa(),{expired:n}=Iu(r,e),o=[];for(let s of n){try{await t.dropDatabase(s.database)}catch{continue}await jb(s.name),o.push(s)}return o}var gs={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","skills:publish","docs:publish"],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"}},oB=Object.keys(gs).filter(t=>gs[t].mintable);var aB=gs.pat.prefix,RP=Object.entries(gs).map(([t,e])=>[e.prefix,t]),PP=/^[A-Za-z0-9_-]{20,}$/;function AP(t){if(typeof t!="string")return null;for(let[e,r]of RP)if(t.startsWith(e)&&PP.test(t.slice(e.length)))return r;return null}function Ub(t){return AP(t)==="pat"}os();var IP=8e3,OP={revoked:"the instance REVOKED this token \u2014 mint a new PAT (Settings \u203A Access tokens) and run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`",expired:"this token has EXPIRED \u2014 mint a new PAT (Settings \u203A Access tokens) and run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`",unknown:"this instance has never seen this token \u2014 it was deleted, or it belongs to a DIFFERENT instance; check the base-url before minting a new PAT",suspended:"the user this token belongs to is SUSPENDED \u2014 an instance administrator has to restore the account; a new token will not help",malformed:"the stored credential is not shaped like a Turbobase PAT (tb_pat_\u2026) \u2014 sign in again",missing:"no token reached the instance \u2014 sign in again",transient:"the instance could not verify the token right now (a storage error, not a decision about the token) \u2014 retry"};function CP(t){return OP[t]??`the instance rejected this token (${t}) \u2014 run \`turbobase auth login\` to store a fresh PAT`}async function NP(t,e,r=fetch){let n;try{n=await r(`${t}/v1/catalog/records`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:AbortSignal.timeout(IP)})}catch(s){return{state:"unreachable",detail:s instanceof Error?s.message:String(s)}}if(n.status===200||n.status===403)return{state:"accepted"};if(n.status===401){let s=await n.json().catch(()=>null);return{state:"rejected",reason:typeof s?.reason=="string"&&s.reason?s.reason:"unknown"}}return(n.headers.get("content-type")??"").includes("application/json")?{state:"accepted"}:{state:"unreachable",detail:`HTTP ${n.status} \u2014 this host does not serve the /v1 API`}}function qb(t){return t.state==="accepted"?et()?g.green("accepted"):"accepted":t.state==="rejected"?et()?g.red(`rejected \xB7 ${t.reason}`):`rejected \xB7 ${t.reason}`:et()?g.yellow("unreachable"):"unreachable"}async function Zb(t={},e=fetch){let r=await Sr(),{file:n}=Bi();if(r.length===0){v("not signed in. Run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),q(g.dim(`no sign-ins stored in ${n}.`));return}let o=await Jt(),s=await Promise.all(r.map(async i=>({baseUrl:i.baseUrl,active:!!o&&i.baseUrl===o.baseUrl,updatedAt:i.updatedAt,check:t.probe===!1?{state:"accepted"}:await NP(i.baseUrl,i.token,e)})));if(t.probe===!1)for(let i of s)v(`${i.active?"* ":" "}${i.baseUrl} (updated ${i.updatedAt})`);else if(et())v(Yt(s.map(i=>[i.active?g.green("*"):" ",g.bold(i.baseUrl),qb(i.check),g.dim(i.updatedAt)]),{head:["","instance","token","updated"]}));else for(let i of s)v(`${i.active?"* ":" "}${i.baseUrl} ${qb(i.check)} (updated ${i.updatedAt})`);q(g.dim(`tokens read from ${n}`));for(let i of s)i.check.state==="rejected"?(q(g.red(`${i.baseUrl}: ${CP(i.check.reason)}.`)),q(g.dim(" then re-run `turbobase project init` \u2014 .mcp.json holds its own copy of the token, and until it is rewritten the authoring MCP tools stay absent from your session."))):i.check.state==="unreachable"&&q(g.yellow(`${i.baseUrl}: could not reach the instance (${i.check.detail}).`));o||q("no active default \u2014 run `turbobase auth use <base-url>` or pass --base-url.")}async function lM(t){let e=await Sr();e.length===0&&k("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),ft()||k("this command needs an explicit <base-url> in a non-interactive shell.");try{return await mt(t,e.map(r=>({value:r.baseUrl,label:r.baseUrl,description:`updated ${r.updatedAt}`})))}catch{k("cancelled.")}}async function uM(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}`}}function dM(t){q(""),q("Sharing is ON. Two things reach the Turbobase product team from this instance:"),q(" \xB7 anonymized pattern cards from your proposals, solutions and INSTANCE-LEARNINGS.md \u2014"),q(" a pattern label, a record class, a fix shape, an outcome. No scripts, values or ids."),q(" \xB7 redacted prose from your projects\u2019 TURBOBASE-FEEDBACK.md \u2014 the findings you write"),q(" about OUR platform, with instance name, user names and ids masked before they leave."),q(`Turn it off: ${t}/admin/settings \u2192 Telemetry \u2192 "Share learnings and platform feedback".`),q("")}async function pM(t,e,r){let n=await Jg({baseUrl:t,token:e}),o=(await Sr()).length-1;if(v(`signed in to ${n.baseUrl} \u2014 now the active instance`+(o>0?` (${o} other${o===1?"":"s"} stored; commands default here).`:".")),dM(n.baseUrl),!r)return;let s=await uM(n.baseUrl,e);s.ok||(s.kind==="wrong-host"?(q(`warning: ${n.baseUrl} did not respond to the Turbobase API (/v1) \u2014 got ${s.detail}.`),q(" In local dev the Turbobase API is on :4100, not the app dev-server port (e.g. :5173)."),q(" The token is stored, but commands will fail against this base-url until it points at the API.")):s.kind==="bad-token"?q(`warning: ${n.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):q(`warning: could not reach ${n.baseUrl} to verify the token (${s.detail}). Stored anyway.`))}async function hM(t){let e=await cs(t),r=hu(e);if(!r){let n=/\bdefineConduit\s*[<(]/.test(e);k(n?`could not parse id/on/from.source/from.recordType from ${t} \u2014 is it a defineConduit({...}) file?`:`could not parse id/recordType/event from ${t} \u2014 is it a defineHook({...}) file?`)}return{...r,sourceCode:e}}async function Ko(t,e,r,n){try{return await ye.access(t),"skipped"}catch{}return await ye.mkdir(Vo(t),{recursive:!0}),await ye.writeFile(t,e,{mode:n??(r?493:420)}),n!==void 0&&await ye.chmod(t,n),"wrote"}async function US(t,e){if(ZS(t)!==".gitignore")return Ko(t,e.content,e.executable,e.mode);let r=await ye.readFile(t,"utf8").catch(()=>null);if(r===null)return Ko(t,e.content,e.executable,e.mode);let n=zy(r);return n===null?"skipped":(await ye.writeFile(t,n,"utf8"),"merged")}function Wm(t){if(t!==void 0){if(t==="junit"||t==="json")return t;k(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function il(t){process.exit(t)}function fM(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 mM(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 gM(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 qS(t){return`${(Math.ceil(t/1048576*10)/10).toFixed(1).replace(/\.0$/,"")} MiB`}function eg({includeInternal:t=!0}={}){let e=new wg;e.name("turbobase").description("Build apps, author extensions, and drive your Turbobase instance.").version("1.14.0").option("--no-color","Disable ANSI colour + animations (also honoured via NO_COLOR / non-TTY)."),e.addHelpText("beforeAll",d=>d.command.parent?"":`${au()}
3615
3615
  `),e.hook("preAction",(d,f)=>{Ky(e.opts(),f.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 d=>{let f=Lt(d.baseUrl);d.token||k(`auth login needs --token. Mint a PAT in your Turbobase app (Settings \u203A Access tokens), then:
3616
3616
  ${ds} auth login --base-url ${f} --token tb_pat_\u2026`),Ub(d.token)||k("--token does not look like a Turbobase PAT (tb_pat_\u2026)."),await pM(f,d.token,d.verify)}),r.command("status").description("Show every stored sign-in, where its token was read from, and whether the instance still accepts it.").option("--no-probe","List the stored sign-ins without asking each instance about its token.").action(async d=>Zb({probe:d.probe})),r.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 lM("Pick the active instance:"),_=await Fi(f);if(!_){let T=await Sr();k(`no stored sign-in for ${Lt(f)}.`+(T.length?` Stored: ${T.map(x=>x.baseUrl).join(", ")}.`:"")+` Run \`turbobase auth login --base-url ${Lt(f)} --token tb_pat_\u2026\`.`)}v(`active instance \u2192 ${_.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 d=>{if(d.all){let T=await Sr();for(let x of T)await Kl(x.baseUrl);v(`removed ${T.length} stored sign-in${T.length===1?"":"s"}.`);return}let f=await Kl(d.baseUrl);f||k(d.baseUrl?`no stored sign-in for ${Lt(d.baseUrl)}.`:"nothing to sign out of (or ambiguous \u2014 pass --base-url / --all).");let _=await Jt();v(`signed out of ${f}.`+(_?` Active instance \u2192 ${_.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 d=>{let{baseUrl:f,token:_}=await ne(d.baseUrl),T;try{T=await En({baseUrl:f,token:_,recordsOnly:d.recordsOnly})}catch(y){let S=y instanceof Error?y.message:String(y);if(!d.recordsOnly&&/openapi\.json failed \(401/.test(S)){q("types-gen: /v1/catalog/openapi.json needs a browser session \u2014 emitting record shapes only. The record shapes are the half apps generate against; for the OpenAPI half, run it from a signed-in browser session.");try{T=await En({baseUrl:f,token:_,recordsOnly:!0})}catch(R){k(`types generation failed: ${R instanceof Error?R.message:String(R)}`)}}else k(`types generation failed: ${S}`)}let x=ke(process.cwd(),d.out);await ye.writeFile(x,T,"utf8"),v(`wrote ${x} (${T.split(`
3617
3617
  `).length} lines).`)}),Jy(n),n.command("grounding").description("Write the instance's grounding brief (source primer + record anatomy + KB retrieval) to a file for agent sessions.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--records <list>","Comma-separated record types to focus, e.g. purchaseOrder,itemReceipt.").option("--query <text>","Retrieval question for the knowledge base (e.g. what the app is about).").option("-o, --out <file>","Output file","INSTANCE.md").action(async d=>{let{baseUrl:f,token:_}=await ne(d.baseUrl),T=new URLSearchParams;d.records&&T.set("records",d.records),d.query&&T.set("query",d.query);let x=T.toString(),y=await fetch(`${f}/v1/catalog/grounding${x?`?${x}`:""}`,{headers:{Authorization:`Bearer ${_}`,Accept:"application/json"}});y.ok||k(`grounding fetch failed: GET /v1/catalog/grounding \u2192 ${y.status}`);let R=((await y.json()).markdown??"").trim(),C=ke(process.cwd(),d.out),L=d.records?` records=${d.records}`:"",j=`<!-- GENERATED by \`turbobase project grounding\` against ${f}${L} \u2014 do not edit; re-run to refresh. -->
@@ -3641,7 +3641,7 @@ samples init: ${R} written, ${C} skipped.`)});let ir=e.command("guardrails").des
3641
3641
  `+g.dim("create one with `turbobase guardrails create <name> --source <s> --record <r>`"));v(Yt(x.map(xi),{head:cl})),v(g.dim(" first match wins; lower PRIO is evaluated first."))}),ir.command("create <name>").description("Create a rule gating 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("--op <glob>","Raw op glob, when gating something other than a record write.").option("--target <glob>","Row address glob, e.g. 'salesOrder.12345'.").option("--actor <glob>","Only match this caller, e.g. 'agent:*' to gate AI writes but not human ones.").option("--decision <d>","allow | allow_with_audit | require_confirm | deny.","deny").option("--priority <n>","Lower is evaluated first (default 100).",d=>Number(d)).option("--when <expr>","Row predicate, e.g. 'total gt 10000'. Omit to match every row.").option("--reason <text>","Why this rule exists \u2014 shown to a blocked writer and in `list`.").option("--disabled","Create the rule parked, so it takes effect only when activated.").option("--confirm-freeze-all","Required for an unconditional deny across '*'/'*'.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,f)=>{let{baseUrl:_,token:T}=await ne(f.baseUrl),x=new ee({baseUrl:_,token:T}),y;try{y=await x.guardrailCreate({name:d,source:f.source,record:f.record,decision:f.decision,...f.op?{op:f.op}:{},...f.target?{target:f.target}:{},...f.actor?{actor:f.actor}:{},...Number.isFinite(f.priority)?{priority:Number(f.priority)}:{},...f.when?{when:f.when}:{},...f.reason?{reason:f.reason}:{},...f.disabled?{enabled:!1}:{},...f.confirmFreezeAll?{confirmFreezeAll:!0}:{}})}catch(S){se(S,"extensions:deploy","create a guardrail"),k(`create guardrail failed: ${S instanceof Error?S.message:String(S)}`)}v(`${g.green("\u2713")} guardrail ${g.bold(y.name??y.id)} created${y.status==="active"?"":g.dim(` (${y.status} \u2014 activate it to take effect)`)}`),v(g.dim(` ${y.decision} ${y.source}/${y.record??y.op}${y.actor?` actor=${y.actor}`:""}${y.when?` where ${y.when}`:""} @prio ${y.priority}`)),v(g.dim(` verify with: turbobase guardrails test --source ${y.source==="*"?"<source>":y.source} --record <record>`))}),ir.command("edit <name>").description("Change a rule \u2014 its decision, priority, predicate or reason.").option("--decision <d>","allow | allow_with_audit | require_confirm | deny.").option("--priority <n>","Lower is evaluated first.",d=>Number(d)).option("--when <expr>","New row predicate. Pass '' to match every row.").option("--reason <text>","Why this rule exists.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,f)=>{f.decision===void 0&&f.priority===void 0&&f.when===void 0&&f.reason===void 0&&k("edit: nothing to change \u2014 pass --decision, --priority, --when or --reason.");let{baseUrl:_,token:T}=await ne(f.baseUrl),x=new ee({baseUrl:_,token:T}),y;try{y=await x.guardrailUpdate(d,{...f.decision!==void 0?{decision:f.decision}:{},...Number.isFinite(f.priority)?{priority:Number(f.priority)}:{},...f.when!==void 0?{when:f.when}:{},...f.reason!==void 0?{reason:f.reason}:{}})}catch(S){se(S,"extensions:deploy","edit a guardrail"),k(`edit guardrail failed: ${S instanceof Error?S.message:String(S)}`)}v(`${g.green("\u2713")} guardrail ${g.bold(y.name??y.id)} updated`),v(g.dim(` ${y.decision} ${y.source}/${y.record??y.op}${y.when?` where ${y.when}`:""} @prio ${y.priority}`))});for(let[d,f,_]of[["activate","active","activated"],["deactivate","inactive","deactivated"]])ir.command(`${d} <name>`).description(f==="active"?"Arm a parked or proposed 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(T,x)=>{let{baseUrl:y,token:S}=await ne(x.baseUrl),R=new ee({baseUrl:y,token:S});try{await R.guardrailUpdate(T,{status:f})}catch(C){se(C,"extensions:deploy",`${d} a guardrail`),k(`${d} guardrail failed: ${C instanceof Error?C.message:String(C)}`)}v(`${g.green("\u2713")} guardrail ${g.bold(T)} ${_}`),f==="inactive"&&v(g.dim(" writes it was gating now fall through to the other rules."))});ir.command("delete <name>").description("Remove a rule. Prefer `deactivate` if you may want it back.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,f)=>{let{baseUrl:_,token:T}=await ne(f.baseUrl),x=new ee({baseUrl:_,token:T});try{await x.guardrailDelete(d)}catch(y){se(y,"extensions:deploy","delete a guardrail"),k(`delete guardrail failed: ${y instanceof Error?y.message:String(y)}`)}v(`${g.green("\u2713")} guardrail ${g.bold(d)} deleted`)}),ir.command("posture [mode]").description("Show or set the GLOBAL default. 'allow' = deny-list (unmatched writes allowed, carve with deny rules). 'deny' = allow-list (unmatched writes denied, open with allow rules).").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,f)=>{let{baseUrl:_,token:T}=await ne(f.baseUrl),x=new ee({baseUrl:_,token:T}),y=C=>C==="deny"?"deny-by-default (allow-list)":"allow-by-default (deny-list)";if(d===void 0){let C;try{C=await x.guardrailPostureGet()}catch(L){se(L,"extensions:deploy","read the guardrail posture"),k(`read posture failed: ${L instanceof Error?L.message:String(L)}`)}v(`guardrail posture: ${g.bold(y(C.mode))}`),v(g.dim(C.mode==="deny"?" Unmatched writes are DENIED. Open records with `guardrails create <name> --decision allow`.":" Unmatched writes are allowed-with-audit. Freeze records with `guardrails create <name> --decision deny`."));return}let S=d.trim().toLowerCase();S!=="allow"&&S!=="deny"&&k("posture must be 'allow' or 'deny'.");let R;try{R=await x.guardrailPostureSet(S)}catch(C){se(C,"extensions:deploy","set the guardrail posture"),k(`set posture failed: ${C instanceof Error?C.message:String(C)}`)}v(`${g.green("\u2713")} guardrail posture set to ${g.bold(y(R.mode))}`),R.mode==="deny"&&v(g.dim(" Every record with no active ALLOW rule is now frozen for write-back. Verify with `guardrails test --source <s> --record <r>`."))}),ir.command("test").description("What happens to this write? Runs the real evaluator; writes nothing.").requiredOption("--source <system>","Adapter id the write targets.").requiredOption("--record <type>","Record type the write targets.").option("--actor <actor>","Caller to simulate, e.g. 'agent:ai' or 'user:<id>'.").option("--external-id <id>","Source-side row id, for target-scoped rules.").option("--fields <json>","A candidate row as JSON, so `when` predicates can be decided.").option("--op <op>","Write op: 'delete' tests <record>.delete; omitted tests <record>.write.").option("--phase <phase>","boundary | cdc | worker | adapter. Defaults to worker (the last gate).").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(R){k(`--fields must be valid JSON: ${R instanceof Error?R.message:String(R)}`)}let{baseUrl:_,token:T}=await ne(d.baseUrl),x=new ee({baseUrl:_,token:T}),y;try{y=await x.guardrailTest({source:d.source,record:d.record,...d.actor?{actor:d.actor}:{},...d.externalId?{externalId:d.externalId}:{},...f?{fields:f}:{},...d.op?{op:d.op}:{},...d.phase?{phase:d.phase}:{}})}catch(R){se(R,"extensions:deploy","test guardrails"),k(`guardrails test failed: ${R instanceof Error?R.message:String(R)}`)}if(d.json)return v(JSON.stringify(y,null,2));let S=`${d.source}/${y.op??d.record}`;y.degraded?(v(`${g.red("\u2717")} ${S} \u2014 guardrail policy is UNREADABLE; writes fail closed`),v(g.dim(` ${y.reason}`))):y.allowed?v(`${g.green("\u2713")} ${S} \u2014 ${y.decision}`+(y.blockedBy?g.dim(` (rule ${y.blockedBy})`):g.dim(" (no rule matched)"))):(v(`${g.red("\u2717")} ${S} \u2014 ${y.decision} by ${g.bold(y.blockedBy??"?")} at the ${y.phase} gate`),y.message&&v(g.dim(` ${y.message}`))),y.decision==="require_confirm"&&v(g.yellow(" this write is HELD for operator approval, not refused.")),y.allowed||(process.exitCode=1)});let Wo=e.command("tables").description("Author custom tables on your instance (create, evolve, inspect)."),ki=(d,f={})=>{let _=`${g.bold(d.table)} ${g.dim(d.displayName)}`;v(d.exists===!1?`${_} ${g.dim("(new)")}`:_);for(let T of d.fields){let x=[T.required?"required":null].filter(Boolean).join(", "),y=f.showClaimants&&T.claimedBy?.length?` ${g.dim(`\u2190 ${T.claimedBy.join(", ")}`)}`:"";v(` ${T.column.padEnd(28)} ${T.type}${x?` (${x})`:""}${y}`)}f.showClaimants&&d.claimants.length>1&&v(g.dim(` claimed by ${d.claimants.join(", ")}`))};function $i(d){if(d instanceof le&&d.status===409){let f=d.body;f?.error==="table_conflict"&&f.message&&(q(f.message),process.exit(1))}k(`tables: ${d instanceof Error?d.message:String(d)}`)}let tg=async d=>{let f=At(ke(process.cwd(),d),"public","turbobase.json"),_=await ye.readFile(f,"utf8").catch(()=>null);if(_===null)return null;let T;try{T=JSON.parse(_)}catch(y){k(`${f} is not valid JSON: ${y instanceof Error?y.message:String(y)}`)}let x=T.tables;return x===void 0?{tables:[],manifestPath:f}:(Array.isArray(x)||k(`${f}: "tables" must be an array.`),{tables:x,manifestPath:f})};Wo.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:_}=await ne(d.baseUrl),T=new ee({baseUrl:f,token:_}),x;try{x=await T.tablesList()}catch(y){se(y,"extensions:deploy","list tables"),k(`list tables failed: ${y instanceof Error?y.message:String(y)}`)}if(d.json)return v(JSON.stringify(x,null,2));if(x.length===0)return v("no custom tables yet \u2014 create one with `turbobase tables create <slug>`.");v(Yt(x.map(y=>[y.table,y.displayName,String(y.fields.length),y.status,y.claimedBy.join(", ")||"\u2014"]),{head:["TABLE","NAME","FIELDS","STATUS","CLAIMED BY"]}))}),Wo.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 _=await tg(d??".");if(_||k(`no public/turbobase.json under ${ke(process.cwd(),d??".")}.`),_.tables.length===0)return v(`${_.manifestPath} declares no tables \u2014 nothing to plan.`);let{baseUrl:T,token:x}=await ne(f.baseUrl),y=new ee({baseUrl:T,token:x}),S;try{S=await ae("planning tables",()=>y.tablesPlan({claimedBy:f.as,tables:_.tables}))}catch(R){se(R,"extensions:deploy","plan tables"),$i(R)}v(`claiming as ${g.bold(S.claimedBy)}`);for(let R of S.tables)ki(R,{showClaimants:!0});v(""),v(g.dim("nothing was applied \u2014 run `turbobase tables apply` (or deploy the app) to make it so."))}),Wo.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 _=await tg(d??".");if(_||k(`no public/turbobase.json under ${ke(process.cwd(),d??".")}.`),_.tables.length===0)return v(`${_.manifestPath} declares no tables \u2014 nothing to apply.`);let{baseUrl:T,token:x}=await ne(f.baseUrl),y=new ee({baseUrl:T,token:x}),S;try{S=await ae("applying tables",()=>y.tablesApply({claimedBy:f.as,tables:_.tables}))}catch(R){se(R,"extensions:deploy","apply tables"),$i(R)}for(let R of S.tables)ki(R,{showClaimants:!0});v(`applied ${S.tables.length} table${S.tables.length===1?"":"s"} as ${S.claimedBy}.`)}),Wo.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 _=f.field.map(R=>{let[C,L,j]=R.split(":");return(!C||!L)&&k(`--field "${R}" must be name:type[:required] (e.g. title:text:required).`),j!==void 0&&j!=="required"&&k(`--field "${R}": the third part may only be "required".`),{name:C,type:L,...j==="required"?{required:!0}:{}}}),{baseUrl:T,token:x}=await ne(f.baseUrl),y=new ee({baseUrl:T,token:x}),S;try{S=await ae(`creating cr_${d}`,()=>y.tablesApply({tables:[{slug:d,displayName:f.name??d,fields:_,...f.restrictToOwner?{rowLevelOwner:!0}:{}}]}))}catch(R){se(R,"extensions:deploy","create a table"),$i(R)}for(let R of S.tables)ki(R);v(""),v(g.dim("grant access under Settings \u203A Users, or in the Records page permissions matrix."))}),Wo.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&&k(`drop refused: --confirm "${f.confirm}" does not match "${d}".`);let{baseUrl:_,token:T}=await ne(f.baseUrl),x=new ee({baseUrl:_,token:T}),y;try{y=await ae(`dropping cr_${d}`,()=>x.tablesDrop(d,f.confirm))}catch(S){se(S,"extensions:deploy","drop a table"),k(`drop failed: ${S instanceof Error?S.message:String(S)}`)}if(!y.removed)return v(`no table named "${d}" on this instance \u2014 nothing dropped.`);v(`dropped ${y.table} and every row in it.`)});let ll=e.command("sandbox").description("Ephemeral local authoring sandboxes.");ll.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=Jm(),_;try{_=await Db(f,{fromTenant:d.from,ttl:d.ttl,sample:d.sample,baseUrl:d.baseUrl})}catch(T){T instanceof ms&&k(T.message),k(`sandbox create failed: ${T instanceof Error?T.message:String(T)}`)}v(`sandbox ${_.name} ready.`),v(` source: ${_.fromTenant} (sampled ${Math.round(_.sampleFraction*100)}%, redacted)`),v(` url: ${_.url}`),v(` expires: ${_.expiresAt}`)}),ll.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=Jm();await Ou(f).catch(()=>[]);let _;try{_=await Mb(f,d)}catch(T){k(`sandbox destroy failed: ${T instanceof Error?T.message:String(T)}`)}_||k(`no sandbox named '${d}'.`),v(`destroyed sandbox ${_.name} (dropped ${_.database}).`)}),ll.command("list").description("List active sandboxes with TTL/expiry; reaps any past their TTL.").action(async()=>{let d=Jm(),f=await Ou(d).catch(()=>[]),_=await pa(),{active:T}=Iu(_);if(T.length===0)v("no active sandboxes.");else{for(let x of T)v(` ${x.name} from=${x.fromTenant} expires=${x.expiresAt} ${x.url}`);v(`
3642
3642
  ${T.length} active sandbox(es).`)}f.length>0&&v(`(reaped ${f.length} expired sandbox(es).)`)}),o.command("export").description("Pull the live instance into turbobase/bundle.json (adopt an instance into a repo).").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--out <dir>","Directory to write the bundle into.",ta).action(async d=>{let{baseUrl:f,token:_}=await ne(d.baseUrl),T=new ee({baseUrl:f,token:_}),x;try{x=await ae("exporting instance config",()=>T.configExport())}catch(R){R instanceof le&&(R.status===401||R.status===403)&&k("ext export forbidden \u2014 the stored PAT lacks extensions:deploy. Re-run `turbobase auth login`."),k(`ext export failed: ${R instanceof Error?R.message:String(R)}`)}let y=typeof x.serialized=="string"?x.serialized:`${JSON.stringify(x.manifest??x,null,2)}
3643
3643
  `,S=ke(process.cwd(),d.out,us);await ye.mkdir(Vo(S),{recursive:!0}),await ye.writeFile(S,y,"utf8"),v(`wrote ${d.out}/${us} (${y.split(`
3644
- `).length} lines).`)});let rg=o.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),BS=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],ng=()=>process.env.TURBOBASE_HOME?.trim()||At(cM(),".turbobase"),FS=()=>"1.12.0",Si=async()=>Promise.all(BS.map(async d=>{try{return await import(d.name),{...d,present:!0}}catch{return{...d,present:!1}}}));rg.command("status").description("Report which optional native packages are installed.").action(async()=>{let d=await Si();for(let f of d)v(`${f.present?He.success("installed"):He.warn("missing")} ${g.bold(f.name)} \u2014 ${f.needs}`);d.some(f=>!f.present)&&v(`
3644
+ `).length} lines).`)});let rg=o.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),BS=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],ng=()=>process.env.TURBOBASE_HOME?.trim()||At(cM(),".turbobase"),FS=()=>"1.14.0",Si=async()=>Promise.all(BS.map(async d=>{try{return await import(d.name),{...d,present:!0}}catch{return{...d,present:!1}}}));rg.command("status").description("Report which optional native packages are installed.").action(async()=>{let d=await Si();for(let f of d)v(`${f.present?He.success("installed"):He.warn("missing")} ${g.bold(f.name)} \u2014 ${f.needs}`);d.some(f=>!f.present)&&v(`
3645
3645
  run \`${ds} ext extras install\` to fetch them into ${ng()}.`)}),rg.command("install").description("Install the optional native packages into $TURBOBASE_HOME (needs npm).").action(async()=>{let d=await Si();if(d.every(C=>C.present)){v(He.success("extras already installed \u2014 nothing to do."));return}let f=ng();await ye.mkdir(f,{recursive:!0});let _=d.filter(C=>!C.present).map(C=>C.name),T=Su(process.platform,process.arch),x=async(C,L)=>{let{code:j,stderr:F}=await _i("npm",["install","--prefix",f,"--no-audit","--no-fund",...C]);j!==0&&k(`extras install failed while installing ${L} (npm exited ${j}).
3646
3646
  ${F.trim()}
3647
3647
  These are native packages \u2014 they need npm and a working toolchain. Everything except \`test\` and \`replay\` works without them.`)},y=_.filter(C=>C!=="isolated-vm");if(y.length>0){let C=y.map(L=>`${L}@${ku[L]??"latest"}`);v(`installing ${C.join(", ")} \u2192 ${f}/node_modules \u2026`),await x(C,y.join(", "))}if(_.includes("isolated-vm")){let C=`isolated-vm@${ku["isolated-vm"]}`;if(v(`installing ${C} \u2192 ${f}/node_modules \u2026`),await x(["--ignore-scripts",C],"isolated-vm"),Ib.includes(T)){let j=`${$u}@${FS()}`;v(`fetching ${j} (no upstream binary for ${T}) \u2026`);let{code:F}=await _i("npm",["install","--prefix",f,"--no-audit","--no-fund","--ignore-scripts",j]);F!==0&&v(He.warn(`could not fetch ${j} \u2014 falling back to a source build.`))}let L=await Ob({home:f});L.length>0&&v(` ${He.success("prebuilt")} ${L.join(", ")} \u2192 isolated-vm/prebuilds/${T}`)}let S=await Si(),R=S.filter(C=>!C.present).map(C=>C.name);if(R.includes("isolated-vm")){v(He.warn(`no prebuilt isolated-vm for ${T} / Node ${process.versions.node} (${Tu()}) \u2014 building from source. This needs Python and a C++ toolchain, and takes a few minutes.`));let{code:C}=await _i("npm",["rebuild","--prefix",f,"isolated-vm"]);C===0&&(S=await Si(),R=S.filter(L=>!L.present).map(L=>L.name))}R.length>0&&k(`npm succeeded but ${R.join(", ")} still will not load \u2014 no prebuilt binary for ${T} / Node ${process.versions.node} (${Tu()}), and the source build did not produce one either. Everything except \`test\` and \`replay\` works without them.`),v(He.success("extras installed \u2014 `turbobase ext test` and `turbobase ext replay` are ready."))});let Gn=e.command("release").description("Ship a release to your instance: deploy | status | rollback | list.");return Gn.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||k(`--target must be 'auto' or 'approved' (got '${d.target}').`);let _=await xM(ke(process.cwd(),d.project??".")),T=Object.keys(_).length;!d.solution&&T===0&&k("release deploy needs --solution <id> (live-fetch) or an SDF project (a src/ dir under --project, default cwd) to upload.");let y=(d.ref?await Ym(["rev-parse",d.ref])??d.ref:null)??process.env.GITHUB_SHA??await Ym(["rev-parse","HEAD"]),S=d.gitRepo??process.env.GITHUB_REPOSITORY??await Ym(["remote","get-url","origin"]),R=d.gitRef??process.env.GITHUB_REF??null,C=d.prUrl??process.env.PR_URL??null,{baseUrl:L,token:j}=await ne(d.baseUrl),F=new ee({baseUrl:L,token:j}),z;try{z=await F.releaseDeploy({solutionId:d.solution??null,target:f,approvedBy:d.approvedBy??null,files:T>0?_:null,gitRepo:S,gitRef:R,gitSha:y,prUrl:C})}catch(Q){se(Q,"releases:write","release deploy"),Q instanceof le&&Q.status===404&&k("solution_not_found \u2014 no solution with that id."),k(`release deploy failed: ${Q instanceof Error?Q.message:String(Q)}`)}v(`release ${z.releaseId} enqueued \u2192 taskId=${z.taskId} (target=${f}`+(T>0?`, uploaded ${T} bundle file(s)`:", live-fetch")+")."),d.follow&&await mu(()=>F.releaseStatus(z.releaseId),`release ${z.releaseId}`,d.intervalMs)}),Gn.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:_,token:T}=await ne(f.baseUrl),x=new ee({baseUrl:_,token:T}),y;try{y=await x.releaseFiles(d)}catch(C){C instanceof le&&C.status===404&&k(`release ${d} not found.`),C instanceof le&&C.status===409&&k(`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.`),se(C,"releases:write","pull a release"),k(`release pull failed: ${C instanceof Error?C.message:String(C)}`)}let S=ke(process.cwd(),f.dir),R=0;for(let[C,L]of[["owned",y.owned],["context",y.context]])for(let[j,F]of Object.entries(L)){let z=At(S,C,j);await ye.mkdir(Vo(z),{recursive:!0}),await ye.writeFile(z,F,"utf8"),R++}await ye.writeFile(At(S,".turbobase-release.json"),JSON.stringify({releaseId:y.releaseId,version:y.version,contentDigest:y.contentDigest},null,2),"utf8"),v(`pulled release ${y.releaseId} (${y.status}) \u2192 ${S}
package/dist/turbobase.js CHANGED
@@ -3564,7 +3564,7 @@ ${s.map(sv).join(`
3564
3564
  `))}async function $p(t,e,n=Ln){let r=[...new Set(t.map(a=>a.trim()).filter(Boolean))];r.length===0&&k("name at least one skill slug. `turbobase skills list` shows them.");let s=await jn(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 Y("publishing",()=>s.skillsPublish(r))}catch(a){W(a,"skills:publish","publish a skill"),k(`publish failed: ${a instanceof Error?a.message:String(a)}`)}b(f.green(`published ${i.published.length} skill(s) \u2014 live now.`)),i.unknown.length>0&&(L(f.yellow(`no such skill: ${i.unknown.join(", ")}`)),process.exitCode=1),L(f.dim(`release v${i.version.version_no} \xB7 undo with: turbobase skills unpublish ${i.published.join(" ")}`))}async function xp(t,e,n=Ln){let r=[...new Set(t.map(a=>a.trim()).filter(Boolean))];r.length===0&&k("name at least one skill slug. `turbobase skills list` shows them.");let s=await jn(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 Y("unpublishing",()=>s.skillsUnpublish(r))}catch(a){W(a,"skills:publish","unpublish a skill"),k(`unpublish failed: ${a instanceof Error?a.message:String(a)}`)}b(f.green(`unpublished ${i.unpublished.length} skill(s) \u2014 retracted now.`)),L(f.dim(`release v${i.version.version_no} \xB7 re-publish with: turbobase skills publish ${i.unpublished.join(" ")}`)),i.unknown.length>0&&(L(f.yellow(`no such skill: ${i.unknown.join(", ")}`)),process.exitCode=1)}async function Tp(t,e,n=Ln){let r=Number(t);(!Number.isInteger(r)||r<1)&&k(`--version must be a release number (got '${t}'). \`turbobase skills releases\` lists them.`);let s=await jn(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 unpublished stays off",e))try{let i=await Y("rolling back",()=>s.skillsRollback(r));b(f.green(`rolled back to v${i.version.version_no}.`))}catch(i){W(i,"skills:publish","roll back a skills release"),k(`rollback failed: ${i instanceof Error?i.message:String(i)}`)}}async function Rp(t,e=Ln){let n=await jn(t,e),r;try{r=await Y("reading the releases",()=>n.skillsReleases())}catch(s){W(s,"skills:publish","read the skills releases"),k(`could not read the releases: ${s instanceof Error?s.message:String(s)}`)}if(r.items.length===0){L("no releases yet.");return}for(let s of r.items){let o=s.is_current?f.green("*"):" ",i=s.release_note?` \u2014 ${s.release_note}`:"";b(`${o} v${s.version_no} ${s.skill_count} skill(s) ${f.dim(s.published_at)}${s.published_by?f.dim(` by ${s.published_by}`):""}${f.dim(i)}`)}}async function Ep(t,e,n=Ln){let r=[...new Set((t??[]).map(u=>u.trim()).filter(Boolean))];r.length===0&&k("name at least one skill to delete. `turbobase skills list` shows the slugs.");let s=await jn(e,n);if(!await Ws(r.length===1?`Delete the skill '${r[0]}'?`:`Delete ${r.length} skills?
3565
3565
  ${r.join(`
3566
3566
  `)}`,"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 Y(`deleting ${u}`,()=>s.skillsDelete(u)))}catch(p){if(p instanceof X&&p.status===404){a.push(u);continue}i.length>0&&L(f.dim(`deleted before the error: ${i.map(g=>g.slug).join(", ")}`)),W(p,"skills:publish","delete a skill"),k(`delete failed on '${u}': ${p instanceof Error?p.message:String(p)}`)}i.length>0&&b(f.green(`deleted ${i.length} skill(s).`));let l=i.filter(u=>u.wasPublished).map(u=>u.slug);l.length>0&&L(f.yellow(`Published until now, so no longer injected: ${l.join(", ")}`)),a.length>0&&(L(f.yellow(`no such skill: ${a.join(", ")}`)),process.exitCode=1)}import{promises as Pp}from"node:fs";import{join as Av,relative as Iv,resolve as Cv,sep as Ov}from"node:path";var Nv=new Set(["node_modules","dist","build","coverage",".git",".claude",".turbobase",".next",".svelte-kit",".vercel",".cache"]),Lv=new Set([".mcp.json",".env"]);function jv(t){return Lv.has(t)||t.startsWith(".env.")&&t!==".env.example"}function Dv(t){return Buffer.compare(Buffer.from(t.toString("utf8"),"utf8"),t)===0}function Uv(t){return t.includes(0)}async function Ap(t){let e=Cv(t),n={},r=[],s=[],o=[];async function i(a){let l=await Pp.readdir(a,{withFileTypes:!0});for(let u of l){let p=Av(a,u.name);if(Nv.has(u.name))continue;if(u.isDirectory()){await i(p);continue}if(!u.isFile())continue;let g=Iv(e,p).split(Ov).join("/");if(jv(u.name)){s.push(g);continue}let $=await Pp.readFile(p);if(!Dv($)){r.push(g);continue}Uv($)&&o.push(g),n[g]=$.toString("utf8")}}return await i(e),{files:n,skipped:r,secrets:s,unstorable:o}}import{randomBytes as Mv}from"node:crypto";var zv="/_tb/app-assets",qv=16,Bv=/^[0-9a-f]{32}$/;function Ip(){return Mv(qv).toString("hex")}function Fv(t){return Bv.test(t)}function Cp(t){if(!Fv(t))throw new Error(`not a build id: ${t}`);return`${zv}/${t}/`}var Op=7;ba();var ya=50*1024*1024;import Js from"node:fs/promises";import _r from"node:path";var wa={esbuild:"0.28.1","isolated-vm":"6.1.2"},va="@turbobase/isolated-vm-prebuilds";function ka(t,e){return`${t}-${e}`}function Sa(t=process.versions){return`abi${t.modules}`}var Np=["darwin-x64","win32-arm64"];async function Lp(t){let e=t.platform??process.platform,n=t.arch??process.arch,r=ka(e,n),s=_r.join(t.home,"node_modules"),o=_r.join(s,...va.split("/"),"prebuilds",r),i=_r.join(s,"isolated-vm","prebuilds",r),a;try{a=await Js.readdir(o)}catch{return[]}let l=[];await Js.mkdir(i,{recursive:!0});for(let u of a){if(!u.endsWith(".node"))continue;let p=_r.join(i,u);try{await Js.copyFile(_r.join(o,u),p,Js.constants?.COPYFILE_EXCL??1),l.push(u)}catch{}}return l}import{homedir as Hv}from"node:os";import{join as jp}from"node:path";import{promises as _a}from"node:fs";function Up(){let t=process.env.TURBOBASE_HOME??jp(Hv(),".turbobase");return{dir:t,file:jp(t,"sandboxes.json")}}var Kv={s:1e3,m:6e4,h:36e5,d:864e5};function Vv(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*Kv[e[2]]}function Gv(t,e=Date.now()){return new Date(e+Vv(t)).toISOString()}function Wv(t,e=Date.now()){let n=Date.parse(t);return Number.isNaN(n)?!0:n<=e}function Jv(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 Yv(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 Zv="[REDACTED]",$r=class extends Error{constructor(e){super(e),this.name="SandboxDataCopyUnavailableError"}},$a=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 Xv(t,e,n){let r={};for(let[s,o]of Object.entries(e)){let i=n[s];if(i===void 0)throw new $a(t,s,"column has no PII classification (unknown = unsafe)");i==="pii"?r[s]=o==null?o:Zv:r[s]=o}return r}function Qv(t,e,n){return e.map(r=>Xv(t,r,n))}var Dp=new Set(["localhost","127.0.0.1","::1","0.0.0.0"]),ek=["oraclecloud.com","us-ashburn-1","amazonaws.com","rds.","supabase.co","turbobase.app","turbobase.io"];function tk(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 xa(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 ek)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=tk(t);if(!Dp.has(n))throw new Error(`local-only guard: target host '${n}' is not a recognised local Postgres (${[...Dp].join(", ")}). \`turbobase sandbox create\` is LOCAL-ONLY.`)}function nk(t,e=rk){return`sbx-${t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,24)||"tenant"}-${e()}`}function rk(){return Math.floor(Math.random()*16777215).toString(16).padStart(6,"0")}function sk(t){return`turbobase_${t.replace(/[^a-z0-9]+/gi,"_").toLowerCase()}`}function ok(t,e){return`${(e??process.env.TURBOBASE_SANDBOX_BASE_URL??"http://localhost:5173").replace(/\/+$/,"")}/?sandbox=${encodeURIComponent(t)}`}async function Ta(){let{file:t}=Up();try{let e=await _a.readFile(t,"utf8"),n=JSON.parse(e);return Array.isArray(n)?n:[]}catch(e){if(e.code==="ENOENT")return[];throw e}}async function Mp(t){let{dir:e,file:n}=Up();await _a.mkdir(e,{recursive:!0}),await _a.writeFile(n,`${JSON.stringify(t,null,2)}
3567
- `,{mode:384})}async function ik(t){let e=await Ta();e.push(t),await Mp(e)}async function zp(t){let e=await Ta(),n=e.findIndex(s=>s.name===t);if(n===-1)return null;let[r]=e.splice(n,1);return await Mp(e),r??null}async function Ys(){return Ta()}function Ra(t,e=Date.now()){let n=[],r=[];for(let s of t)Wv(s.expiresAt,e)?r.push(s):n.push(s);return{active:n,expired:r}}async function qp(t,e){xa(t.target());let n=t.dataCopyUnavailable?.();if(n)throw new $r(n);let r=Jv(e.sample),s=e.now??Date.now(),o=(e.nameGen??(u=>nk(u)))(e.fromTenant),i=sk(o);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let u of a){let p=Yv(u.rows,r),g=Qv(u.table,p,u.classification);g.length>0&&await t.copyRows(i,u.table,g)}let l={name:o,fromTenant:e.fromTenant,database:i,createdAt:new Date(s).toISOString(),expiresAt:Gv(e.ttl,s),sampleFraction:r,url:ok(o,e.baseUrl)};return await ik(l),l}async function Bp(t,e){xa(t.target());let r=(await Ys()).find(s=>s.name===e);return r?(await t.dropDatabase(r.database),await zp(e),r):null}async function Ea(t,e=Date.now()){xa(t.target());let n=await Ys(),{expired:r}=Ra(n,e),s=[];for(let o of r){try{await t.dropDatabase(o.database)}catch{continue}await zp(o.name),s.push(o)}return s}var xr={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","skills:publish","docs:publish"],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"}},BI=Object.keys(xr).filter(t=>xr[t].mintable);var KI=xr.pat.prefix,ak=Object.entries(xr).map(([t,e])=>[e.prefix,t]),lk=/^[A-Za-z0-9_-]{20,}$/;function ck(t){if(typeof t!="string")return null;for(let[e,n]of ak)if(t.startsWith(e)&&lk.test(t.slice(e.length)))return n;return null}function Fp(t){return ck(t)==="pat"}hr();var dk=8e3,uk={revoked:"the instance REVOKED this token \u2014 mint a new PAT (Settings \u203A Access tokens) and run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`",expired:"this token has EXPIRED \u2014 mint a new PAT (Settings \u203A Access tokens) and run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`",unknown:"this instance has never seen this token \u2014 it was deleted, or it belongs to a DIFFERENT instance; check the base-url before minting a new PAT",suspended:"the user this token belongs to is SUSPENDED \u2014 an instance administrator has to restore the account; a new token will not help",malformed:"the stored credential is not shaped like a Turbobase PAT (tb_pat_\u2026) \u2014 sign in again",missing:"no token reached the instance \u2014 sign in again",transient:"the instance could not verify the token right now (a storage error, not a decision about the token) \u2014 retry"};function pk(t){return uk[t]??`the instance rejected this token (${t}) \u2014 run \`turbobase auth login\` to store a fresh PAT`}async function hk(t,e,n=fetch){let r;try{r=await n(`${t}/v1/catalog/records`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:AbortSignal.timeout(dk)})}catch(o){return{state:"unreachable",detail:o instanceof Error?o.message:String(o)}}if(r.status===200||r.status===403)return{state:"accepted"};if(r.status===401){let o=await r.json().catch(()=>null);return{state:"rejected",reason:typeof o?.reason=="string"&&o.reason?o.reason:"unknown"}}return(r.headers.get("content-type")??"").includes("application/json")?{state:"accepted"}:{state:"unreachable",detail:`HTTP ${r.status} \u2014 this host does not serve the /v1 API`}}function Hp(t){return t.state==="accepted"?Ee()?f.green("accepted"):"accepted":t.state==="rejected"?Ee()?f.red(`rejected \xB7 ${t.reason}`):`rejected \xB7 ${t.reason}`:Ee()?f.yellow("unreachable"):"unreachable"}async function Kp(t={},e=fetch){let n=await Rt(),{file:r}=Rs();if(n.length===0){b("not signed in. Run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),L(f.dim(`no sign-ins stored in ${r}.`));return}let s=await dt(),o=await Promise.all(n.map(async i=>({baseUrl:i.baseUrl,active:!!s&&i.baseUrl===s.baseUrl,updatedAt:i.updatedAt,check:t.probe===!1?{state:"accepted"}:await hk(i.baseUrl,i.token,e)})));if(t.probe===!1)for(let i of o)b(`${i.active?"* ":" "}${i.baseUrl} (updated ${i.updatedAt})`);else if(Ee())b(ut(o.map(i=>[i.active?f.green("*"):" ",f.bold(i.baseUrl),Hp(i.check),f.dim(i.updatedAt)]),{head:["","instance","token","updated"]}));else for(let i of o)b(`${i.active?"* ":" "}${i.baseUrl} ${Hp(i.check)} (updated ${i.updatedAt})`);L(f.dim(`tokens read from ${r}`));for(let i of o)i.check.state==="rejected"?(L(f.red(`${i.baseUrl}: ${pk(i.check.reason)}.`)),L(f.dim(" then re-run `turbobase project init` \u2014 .mcp.json holds its own copy of the token, and until it is rewritten the authoring MCP tools stay absent from your session."))):i.check.state==="unreachable"&&L(f.yellow(`${i.baseUrl}: could not reach the instance (${i.check.detail}).`));s||L("no active default \u2014 run `turbobase auth use <base-url>` or pass --base-url.")}async function j0(t){let e=await Rt();e.length===0&&k("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),Ue()||k("this command needs an explicit <base-url> in a non-interactive shell.");try{return await Me(t,e.map(n=>({value:n.baseUrl,label:n.baseUrl,description:`updated ${n.updatedAt}`})))}catch{k("cancelled.")}}async function D0(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}`}}function U0(t){L(""),L("Sharing is ON. Two things reach the Turbobase product team from this instance:"),L(" \xB7 anonymized pattern cards from your proposals, solutions and INSTANCE-LEARNINGS.md \u2014"),L(" a pattern label, a record class, a fix shape, an outcome. No scripts, values or ids."),L(" \xB7 redacted prose from your projects\u2019 TURBOBASE-FEEDBACK.md \u2014 the findings you write"),L(" about OUR platform, with instance name, user names and ids masked before they leave."),L(`Turn it off: ${t}/admin/settings \u2192 Telemetry \u2192 "Share learnings and platform feedback".`),L("")}async function M0(t,e,n){let r=await Qd({baseUrl:t,token:e}),s=(await Rt()).length-1;if(b(`signed in to ${r.baseUrl} \u2014 now the active instance`+(s>0?` (${s} other${s===1?"":"s"} stored; commands default here).`:".")),U0(r.baseUrl),!n)return;let o=await D0(r.baseUrl,e);o.ok||(o.kind==="wrong-host"?(L(`warning: ${r.baseUrl} did not respond to the Turbobase API (/v1) \u2014 got ${o.detail}.`),L(" In local dev the Turbobase API is on :4100, not the app dev-server port (e.g. :5173)."),L(" The token is stored, but commands will fail against this base-url until it points at the API.")):o.kind==="bad-token"?L(`warning: ${r.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):L(`warning: could not reach ${r.baseUrl} to verify the token (${o.detail}). Stored anyway.`))}async function z0(t){let e=await br(t),n=ca(e);if(!n){let r=/\bdefineConduit\s*[<(]/.test(e);k(r?`could not parse id/on/from.source/from.recordType from ${t} \u2014 is it a defineConduit({...}) file?`:`could not parse id/recordType/event from ${t} \u2014 is it a defineHook({...}) file?`)}return{...n,sourceCode:e}}async function cr(t,e,n,r){try{return await re.access(t),"skipped"}catch{}return await re.mkdir(lr(t),{recursive:!0}),await re.writeFile(t,e,{mode:r??(n?493:420)}),r!==void 0&&await re.chmod(t,r),"wrote"}async function Kb(t,e){if(Gb(t)!==".gitignore")return cr(t,e.content,e.executable,e.mode);let n=await re.readFile(t,"utf8").catch(()=>null);if(n===null)return cr(t,e.content,e.executable,e.mode);let r=Uu(n);return r===null?"skipped":(await re.writeFile(t,r,"utf8"),"merged")}function xd(t){if(t!==void 0){if(t==="junit"||t==="json")return t;k(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function Si(t){process.exit(t)}function q0(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 B0(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 F0(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 Vb(t){return`${(Math.ceil(t/1048576*10)/10).toFixed(1).replace(/\.0$/,"")} MiB`}function Id({includeInternal:t=!0}={}){let e=new L0;e.name("turbobase").description("Build apps, author extensions, and drive your Turbobase instance.").version("1.12.0").option("--no-color","Disable ANSI colour + animations (also honoured via NO_COLOR / non-TTY)."),e.addHelpText("beforeAll",d=>d.command.parent?"":`${ra()}
3567
+ `,{mode:384})}async function ik(t){let e=await Ta();e.push(t),await Mp(e)}async function zp(t){let e=await Ta(),n=e.findIndex(s=>s.name===t);if(n===-1)return null;let[r]=e.splice(n,1);return await Mp(e),r??null}async function Ys(){return Ta()}function Ra(t,e=Date.now()){let n=[],r=[];for(let s of t)Wv(s.expiresAt,e)?r.push(s):n.push(s);return{active:n,expired:r}}async function qp(t,e){xa(t.target());let n=t.dataCopyUnavailable?.();if(n)throw new $r(n);let r=Jv(e.sample),s=e.now??Date.now(),o=(e.nameGen??(u=>nk(u)))(e.fromTenant),i=sk(o);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let u of a){let p=Yv(u.rows,r),g=Qv(u.table,p,u.classification);g.length>0&&await t.copyRows(i,u.table,g)}let l={name:o,fromTenant:e.fromTenant,database:i,createdAt:new Date(s).toISOString(),expiresAt:Gv(e.ttl,s),sampleFraction:r,url:ok(o,e.baseUrl)};return await ik(l),l}async function Bp(t,e){xa(t.target());let r=(await Ys()).find(s=>s.name===e);return r?(await t.dropDatabase(r.database),await zp(e),r):null}async function Ea(t,e=Date.now()){xa(t.target());let n=await Ys(),{expired:r}=Ra(n,e),s=[];for(let o of r){try{await t.dropDatabase(o.database)}catch{continue}await zp(o.name),s.push(o)}return s}var xr={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","skills:publish","docs:publish"],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"}},BI=Object.keys(xr).filter(t=>xr[t].mintable);var KI=xr.pat.prefix,ak=Object.entries(xr).map(([t,e])=>[e.prefix,t]),lk=/^[A-Za-z0-9_-]{20,}$/;function ck(t){if(typeof t!="string")return null;for(let[e,n]of ak)if(t.startsWith(e)&&lk.test(t.slice(e.length)))return n;return null}function Fp(t){return ck(t)==="pat"}hr();var dk=8e3,uk={revoked:"the instance REVOKED this token \u2014 mint a new PAT (Settings \u203A Access tokens) and run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`",expired:"this token has EXPIRED \u2014 mint a new PAT (Settings \u203A Access tokens) and run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`",unknown:"this instance has never seen this token \u2014 it was deleted, or it belongs to a DIFFERENT instance; check the base-url before minting a new PAT",suspended:"the user this token belongs to is SUSPENDED \u2014 an instance administrator has to restore the account; a new token will not help",malformed:"the stored credential is not shaped like a Turbobase PAT (tb_pat_\u2026) \u2014 sign in again",missing:"no token reached the instance \u2014 sign in again",transient:"the instance could not verify the token right now (a storage error, not a decision about the token) \u2014 retry"};function pk(t){return uk[t]??`the instance rejected this token (${t}) \u2014 run \`turbobase auth login\` to store a fresh PAT`}async function hk(t,e,n=fetch){let r;try{r=await n(`${t}/v1/catalog/records`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:AbortSignal.timeout(dk)})}catch(o){return{state:"unreachable",detail:o instanceof Error?o.message:String(o)}}if(r.status===200||r.status===403)return{state:"accepted"};if(r.status===401){let o=await r.json().catch(()=>null);return{state:"rejected",reason:typeof o?.reason=="string"&&o.reason?o.reason:"unknown"}}return(r.headers.get("content-type")??"").includes("application/json")?{state:"accepted"}:{state:"unreachable",detail:`HTTP ${r.status} \u2014 this host does not serve the /v1 API`}}function Hp(t){return t.state==="accepted"?Ee()?f.green("accepted"):"accepted":t.state==="rejected"?Ee()?f.red(`rejected \xB7 ${t.reason}`):`rejected \xB7 ${t.reason}`:Ee()?f.yellow("unreachable"):"unreachable"}async function Kp(t={},e=fetch){let n=await Rt(),{file:r}=Rs();if(n.length===0){b("not signed in. Run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),L(f.dim(`no sign-ins stored in ${r}.`));return}let s=await dt(),o=await Promise.all(n.map(async i=>({baseUrl:i.baseUrl,active:!!s&&i.baseUrl===s.baseUrl,updatedAt:i.updatedAt,check:t.probe===!1?{state:"accepted"}:await hk(i.baseUrl,i.token,e)})));if(t.probe===!1)for(let i of o)b(`${i.active?"* ":" "}${i.baseUrl} (updated ${i.updatedAt})`);else if(Ee())b(ut(o.map(i=>[i.active?f.green("*"):" ",f.bold(i.baseUrl),Hp(i.check),f.dim(i.updatedAt)]),{head:["","instance","token","updated"]}));else for(let i of o)b(`${i.active?"* ":" "}${i.baseUrl} ${Hp(i.check)} (updated ${i.updatedAt})`);L(f.dim(`tokens read from ${r}`));for(let i of o)i.check.state==="rejected"?(L(f.red(`${i.baseUrl}: ${pk(i.check.reason)}.`)),L(f.dim(" then re-run `turbobase project init` \u2014 .mcp.json holds its own copy of the token, and until it is rewritten the authoring MCP tools stay absent from your session."))):i.check.state==="unreachable"&&L(f.yellow(`${i.baseUrl}: could not reach the instance (${i.check.detail}).`));s||L("no active default \u2014 run `turbobase auth use <base-url>` or pass --base-url.")}async function j0(t){let e=await Rt();e.length===0&&k("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),Ue()||k("this command needs an explicit <base-url> in a non-interactive shell.");try{return await Me(t,e.map(n=>({value:n.baseUrl,label:n.baseUrl,description:`updated ${n.updatedAt}`})))}catch{k("cancelled.")}}async function D0(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}`}}function U0(t){L(""),L("Sharing is ON. Two things reach the Turbobase product team from this instance:"),L(" \xB7 anonymized pattern cards from your proposals, solutions and INSTANCE-LEARNINGS.md \u2014"),L(" a pattern label, a record class, a fix shape, an outcome. No scripts, values or ids."),L(" \xB7 redacted prose from your projects\u2019 TURBOBASE-FEEDBACK.md \u2014 the findings you write"),L(" about OUR platform, with instance name, user names and ids masked before they leave."),L(`Turn it off: ${t}/admin/settings \u2192 Telemetry \u2192 "Share learnings and platform feedback".`),L("")}async function M0(t,e,n){let r=await Qd({baseUrl:t,token:e}),s=(await Rt()).length-1;if(b(`signed in to ${r.baseUrl} \u2014 now the active instance`+(s>0?` (${s} other${s===1?"":"s"} stored; commands default here).`:".")),U0(r.baseUrl),!n)return;let o=await D0(r.baseUrl,e);o.ok||(o.kind==="wrong-host"?(L(`warning: ${r.baseUrl} did not respond to the Turbobase API (/v1) \u2014 got ${o.detail}.`),L(" In local dev the Turbobase API is on :4100, not the app dev-server port (e.g. :5173)."),L(" The token is stored, but commands will fail against this base-url until it points at the API.")):o.kind==="bad-token"?L(`warning: ${r.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):L(`warning: could not reach ${r.baseUrl} to verify the token (${o.detail}). Stored anyway.`))}async function z0(t){let e=await br(t),n=ca(e);if(!n){let r=/\bdefineConduit\s*[<(]/.test(e);k(r?`could not parse id/on/from.source/from.recordType from ${t} \u2014 is it a defineConduit({...}) file?`:`could not parse id/recordType/event from ${t} \u2014 is it a defineHook({...}) file?`)}return{...n,sourceCode:e}}async function cr(t,e,n,r){try{return await re.access(t),"skipped"}catch{}return await re.mkdir(lr(t),{recursive:!0}),await re.writeFile(t,e,{mode:r??(n?493:420)}),r!==void 0&&await re.chmod(t,r),"wrote"}async function Kb(t,e){if(Gb(t)!==".gitignore")return cr(t,e.content,e.executable,e.mode);let n=await re.readFile(t,"utf8").catch(()=>null);if(n===null)return cr(t,e.content,e.executable,e.mode);let r=Uu(n);return r===null?"skipped":(await re.writeFile(t,r,"utf8"),"merged")}function xd(t){if(t!==void 0){if(t==="junit"||t==="json")return t;k(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function Si(t){process.exit(t)}function q0(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 B0(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 F0(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 Vb(t){return`${(Math.ceil(t/1048576*10)/10).toFixed(1).replace(/\.0$/,"")} MiB`}function Id({includeInternal:t=!0}={}){let e=new L0;e.name("turbobase").description("Build apps, author extensions, and drive your Turbobase instance.").version("1.14.0").option("--no-color","Disable ANSI colour + animations (also honoured via NO_COLOR / non-TTY)."),e.addHelpText("beforeAll",d=>d.command.parent?"":`${ra()}
3568
3568
  `),e.hook("preAction",(d,h)=>{Yu(e.opts(),h.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 h=Xe(d.baseUrl);d.token||k(`auth login needs --token. Mint a PAT in your Turbobase app (Settings \u203A Access tokens), then:
3569
3569
  ${vr} auth login --base-url ${h} --token tb_pat_\u2026`),Fp(d.token)||k("--token does not look like a Turbobase PAT (tb_pat_\u2026)."),await M0(h,d.token,d.verify)}),n.command("status").description("Show every stored sign-in, where its token was read from, and whether the instance still accepts it.").option("--no-probe","List the stored sign-ins without asking each instance about its token.").action(async d=>Kp({probe:d.probe})),n.command("use [base-url]").description("Make an already-stored instance the active default (omit the arg to pick interactively).").action(async d=>{let h=d??await j0("Pick the active instance:"),y=await Es(h);if(!y){let x=await Rt();k(`no stored sign-in for ${Xe(h)}.`+(x.length?` Stored: ${x.map(w=>w.baseUrl).join(", ")}.`:"")+` Run \`turbobase auth login --base-url ${Xe(h)} --token tb_pat_\u2026\`.`)}b(`active instance \u2192 ${y.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 x=await Rt();for(let w of x)await Fi(w.baseUrl);b(`removed ${x.length} stored sign-in${x.length===1?"":"s"}.`);return}let h=await Fi(d.baseUrl);h||k(d.baseUrl?`no stored sign-in for ${Xe(d.baseUrl)}.`:"nothing to sign out of (or ambiguous \u2014 pass --base-url / --all).");let y=await dt();b(`signed out of ${h}.`+(y?` Active instance \u2192 ${y.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:h,token:y}=await V(d.baseUrl),x;try{x=await an({baseUrl:h,token:y,recordsOnly:d.recordsOnly})}catch(m){let _=m instanceof Error?m.message:String(m);if(!d.recordsOnly&&/openapi\.json failed \(401/.test(_)){L("types-gen: /v1/catalog/openapi.json needs a browser session \u2014 emitting record shapes only. The record shapes are the half apps generate against; for the OpenAPI half, run it from a signed-in browser session.");try{x=await an({baseUrl:h,token:y,recordsOnly:!0})}catch(R){k(`types generation failed: ${R instanceof Error?R.message:String(R)}`)}}else k(`types generation failed: ${_}`)}let w=ce(process.cwd(),d.out);await re.writeFile(w,x,"utf8"),b(`wrote ${w} (${x.split(`
3570
3570
  `).length} lines).`)}),Qu(r),r.command("grounding").description("Write the instance's grounding brief (source primer + record anatomy + KB retrieval) to a file for agent sessions.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--records <list>","Comma-separated record types to focus, e.g. purchaseOrder,itemReceipt.").option("--query <text>","Retrieval question for the knowledge base (e.g. what the app is about).").option("-o, --out <file>","Output file","INSTANCE.md").action(async d=>{let{baseUrl:h,token:y}=await V(d.baseUrl),x=new URLSearchParams;d.records&&x.set("records",d.records),d.query&&x.set("query",d.query);let w=x.toString(),m=await fetch(`${h}/v1/catalog/grounding${w?`?${w}`:""}`,{headers:{Authorization:`Bearer ${y}`,Accept:"application/json"}});m.ok||k(`grounding fetch failed: GET /v1/catalog/grounding \u2192 ${m.status}`);let R=((await m.json()).markdown??"").trim(),A=ce(process.cwd(),d.out),O=d.records?` records=${d.records}`:"",N=`<!-- GENERATED by \`turbobase project grounding\` against ${h}${O} \u2014 do not edit; re-run to refresh. -->
@@ -3594,7 +3594,7 @@ samples init: ${R} written, ${A} skipped.`)});let wt=e.command("guardrails").des
3594
3594
  `+f.dim("create one with `turbobase guardrails create <name> --source <s> --record <r>`"));b(ut(w.map(bs),{head:$i})),b(f.dim(" first match wins; lower PRIO is evaluated first."))}),wt.command("create <name>").description("Create a rule gating 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("--op <glob>","Raw op glob, when gating something other than a record write.").option("--target <glob>","Row address glob, e.g. 'salesOrder.12345'.").option("--actor <glob>","Only match this caller, e.g. 'agent:*' to gate AI writes but not human ones.").option("--decision <d>","allow | allow_with_audit | require_confirm | deny.","deny").option("--priority <n>","Lower is evaluated first (default 100).",d=>Number(d)).option("--when <expr>","Row predicate, e.g. 'total gt 10000'. Omit to match every row.").option("--reason <text>","Why this rule exists \u2014 shown to a blocked writer and in `list`.").option("--disabled","Create the rule parked, so it takes effect only when activated.").option("--confirm-freeze-all","Required for an unconditional deny across '*'/'*'.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,h)=>{let{baseUrl:y,token:x}=await V(h.baseUrl),w=new H({baseUrl:y,token:x}),m;try{m=await w.guardrailCreate({name:d,source:h.source,record:h.record,decision:h.decision,...h.op?{op:h.op}:{},...h.target?{target:h.target}:{},...h.actor?{actor:h.actor}:{},...Number.isFinite(h.priority)?{priority:Number(h.priority)}:{},...h.when?{when:h.when}:{},...h.reason?{reason:h.reason}:{},...h.disabled?{enabled:!1}:{},...h.confirmFreezeAll?{confirmFreezeAll:!0}:{}})}catch(_){W(_,"extensions:deploy","create a guardrail"),k(`create guardrail failed: ${_ instanceof Error?_.message:String(_)}`)}b(`${f.green("\u2713")} guardrail ${f.bold(m.name??m.id)} created${m.status==="active"?"":f.dim(` (${m.status} \u2014 activate it to take effect)`)}`),b(f.dim(` ${m.decision} ${m.source}/${m.record??m.op}${m.actor?` actor=${m.actor}`:""}${m.when?` where ${m.when}`:""} @prio ${m.priority}`)),b(f.dim(` verify with: turbobase guardrails test --source ${m.source==="*"?"<source>":m.source} --record <record>`))}),wt.command("edit <name>").description("Change a rule \u2014 its decision, priority, predicate or reason.").option("--decision <d>","allow | allow_with_audit | require_confirm | deny.").option("--priority <n>","Lower is evaluated first.",d=>Number(d)).option("--when <expr>","New row predicate. Pass '' to match every row.").option("--reason <text>","Why this rule exists.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,h)=>{h.decision===void 0&&h.priority===void 0&&h.when===void 0&&h.reason===void 0&&k("edit: nothing to change \u2014 pass --decision, --priority, --when or --reason.");let{baseUrl:y,token:x}=await V(h.baseUrl),w=new H({baseUrl:y,token:x}),m;try{m=await w.guardrailUpdate(d,{...h.decision!==void 0?{decision:h.decision}:{},...Number.isFinite(h.priority)?{priority:Number(h.priority)}:{},...h.when!==void 0?{when:h.when}:{},...h.reason!==void 0?{reason:h.reason}:{}})}catch(_){W(_,"extensions:deploy","edit a guardrail"),k(`edit guardrail failed: ${_ instanceof Error?_.message:String(_)}`)}b(`${f.green("\u2713")} guardrail ${f.bold(m.name??m.id)} updated`),b(f.dim(` ${m.decision} ${m.source}/${m.record??m.op}${m.when?` where ${m.when}`:""} @prio ${m.priority}`))});for(let[d,h,y]of[["activate","active","activated"],["deactivate","inactive","deactivated"]])wt.command(`${d} <name>`).description(h==="active"?"Arm a parked or proposed 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(x,w)=>{let{baseUrl:m,token:_}=await V(w.baseUrl),R=new H({baseUrl:m,token:_});try{await R.guardrailUpdate(x,{status:h})}catch(A){W(A,"extensions:deploy",`${d} a guardrail`),k(`${d} guardrail failed: ${A instanceof Error?A.message:String(A)}`)}b(`${f.green("\u2713")} guardrail ${f.bold(x)} ${y}`),h==="inactive"&&b(f.dim(" writes it was gating now fall through to the other rules."))});wt.command("delete <name>").description("Remove a rule. Prefer `deactivate` if you may want it back.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,h)=>{let{baseUrl:y,token:x}=await V(h.baseUrl),w=new H({baseUrl:y,token:x});try{await w.guardrailDelete(d)}catch(m){W(m,"extensions:deploy","delete a guardrail"),k(`delete guardrail failed: ${m instanceof Error?m.message:String(m)}`)}b(`${f.green("\u2713")} guardrail ${f.bold(d)} deleted`)}),wt.command("posture [mode]").description("Show or set the GLOBAL default. 'allow' = deny-list (unmatched writes allowed, carve with deny rules). 'deny' = allow-list (unmatched writes denied, open with allow rules).").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(d,h)=>{let{baseUrl:y,token:x}=await V(h.baseUrl),w=new H({baseUrl:y,token:x}),m=A=>A==="deny"?"deny-by-default (allow-list)":"allow-by-default (deny-list)";if(d===void 0){let A;try{A=await w.guardrailPostureGet()}catch(O){W(O,"extensions:deploy","read the guardrail posture"),k(`read posture failed: ${O instanceof Error?O.message:String(O)}`)}b(`guardrail posture: ${f.bold(m(A.mode))}`),b(f.dim(A.mode==="deny"?" Unmatched writes are DENIED. Open records with `guardrails create <name> --decision allow`.":" Unmatched writes are allowed-with-audit. Freeze records with `guardrails create <name> --decision deny`."));return}let _=d.trim().toLowerCase();_!=="allow"&&_!=="deny"&&k("posture must be 'allow' or 'deny'.");let R;try{R=await w.guardrailPostureSet(_)}catch(A){W(A,"extensions:deploy","set the guardrail posture"),k(`set posture failed: ${A instanceof Error?A.message:String(A)}`)}b(`${f.green("\u2713")} guardrail posture set to ${f.bold(m(R.mode))}`),R.mode==="deny"&&b(f.dim(" Every record with no active ALLOW rule is now frozen for write-back. Verify with `guardrails test --source <s> --record <r>`."))}),wt.command("test").description("What happens to this write? Runs the real evaluator; writes nothing.").requiredOption("--source <system>","Adapter id the write targets.").requiredOption("--record <type>","Record type the write targets.").option("--actor <actor>","Caller to simulate, e.g. 'agent:ai' or 'user:<id>'.").option("--external-id <id>","Source-side row id, for target-scoped rules.").option("--fields <json>","A candidate row as JSON, so `when` predicates can be decided.").option("--op <op>","Write op: 'delete' tests <record>.delete; omitted tests <record>.write.").option("--phase <phase>","boundary | cdc | worker | adapter. Defaults to worker (the last gate).").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--json","Emit raw JSON.").action(async d=>{let h;if(d.fields)try{h=JSON.parse(d.fields)}catch(R){k(`--fields must be valid JSON: ${R instanceof Error?R.message:String(R)}`)}let{baseUrl:y,token:x}=await V(d.baseUrl),w=new H({baseUrl:y,token:x}),m;try{m=await w.guardrailTest({source:d.source,record:d.record,...d.actor?{actor:d.actor}:{},...d.externalId?{externalId:d.externalId}:{},...h?{fields:h}:{},...d.op?{op:d.op}:{},...d.phase?{phase:d.phase}:{}})}catch(R){W(R,"extensions:deploy","test guardrails"),k(`guardrails test failed: ${R instanceof Error?R.message:String(R)}`)}if(d.json)return b(JSON.stringify(m,null,2));let _=`${d.source}/${m.op??d.record}`;m.degraded?(b(`${f.red("\u2717")} ${_} \u2014 guardrail policy is UNREADABLE; writes fail closed`),b(f.dim(` ${m.reason}`))):m.allowed?b(`${f.green("\u2713")} ${_} \u2014 ${m.decision}`+(m.blockedBy?f.dim(` (rule ${m.blockedBy})`):f.dim(" (no rule matched)"))):(b(`${f.red("\u2717")} ${_} \u2014 ${m.decision} by ${f.bold(m.blockedBy??"?")} at the ${m.phase} gate`),m.message&&b(f.dim(` ${m.message}`))),m.decision==="require_confirm"&&b(f.yellow(" this write is HELD for operator approval, not refused.")),m.allowed||(process.exitCode=1)});let dr=e.command("tables").description("Author custom tables on your instance (create, evolve, inspect)."),ys=(d,h={})=>{let y=`${f.bold(d.table)} ${f.dim(d.displayName)}`;b(d.exists===!1?`${y} ${f.dim("(new)")}`:y);for(let x of d.fields){let w=[x.required?"required":null].filter(Boolean).join(", "),m=h.showClaimants&&x.claimedBy?.length?` ${f.dim(`\u2190 ${x.claimedBy.join(", ")}`)}`:"";b(` ${x.column.padEnd(28)} ${x.type}${w?` (${w})`:""}${m}`)}h.showClaimants&&d.claimants.length>1&&b(f.dim(` claimed by ${d.claimants.join(", ")}`))};function ws(d){if(d instanceof X&&d.status===409){let h=d.body;h?.error==="table_conflict"&&h.message&&(L(h.message),process.exit(1))}k(`tables: ${d instanceof Error?d.message:String(d)}`)}let Cd=async d=>{let h=Ye(ce(process.cwd(),d),"public","turbobase.json"),y=await re.readFile(h,"utf8").catch(()=>null);if(y===null)return null;let x;try{x=JSON.parse(y)}catch(m){k(`${h} is not valid JSON: ${m instanceof Error?m.message:String(m)}`)}let w=x.tables;return w===void 0?{tables:[],manifestPath:h}:(Array.isArray(w)||k(`${h}: "tables" must be an array.`),{tables:w,manifestPath:h})};dr.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:h,token:y}=await V(d.baseUrl),x=new H({baseUrl:h,token:y}),w;try{w=await x.tablesList()}catch(m){W(m,"extensions:deploy","list tables"),k(`list tables failed: ${m instanceof Error?m.message:String(m)}`)}if(d.json)return b(JSON.stringify(w,null,2));if(w.length===0)return b("no custom tables yet \u2014 create one with `turbobase tables create <slug>`.");b(ut(w.map(m=>[m.table,m.displayName,String(m.fields.length),m.status,m.claimedBy.join(", ")||"\u2014"]),{head:["TABLE","NAME","FIELDS","STATUS","CLAIMED BY"]}))}),dr.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,h)=>{let y=await Cd(d??".");if(y||k(`no public/turbobase.json under ${ce(process.cwd(),d??".")}.`),y.tables.length===0)return b(`${y.manifestPath} declares no tables \u2014 nothing to plan.`);let{baseUrl:x,token:w}=await V(h.baseUrl),m=new H({baseUrl:x,token:w}),_;try{_=await Y("planning tables",()=>m.tablesPlan({claimedBy:h.as,tables:y.tables}))}catch(R){W(R,"extensions:deploy","plan tables"),ws(R)}b(`claiming as ${f.bold(_.claimedBy)}`);for(let R of _.tables)ys(R,{showClaimants:!0});b(""),b(f.dim("nothing was applied \u2014 run `turbobase tables apply` (or deploy the app) to make it so."))}),dr.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,h)=>{let y=await Cd(d??".");if(y||k(`no public/turbobase.json under ${ce(process.cwd(),d??".")}.`),y.tables.length===0)return b(`${y.manifestPath} declares no tables \u2014 nothing to apply.`);let{baseUrl:x,token:w}=await V(h.baseUrl),m=new H({baseUrl:x,token:w}),_;try{_=await Y("applying tables",()=>m.tablesApply({claimedBy:h.as,tables:y.tables}))}catch(R){W(R,"extensions:deploy","apply tables"),ws(R)}for(let R of _.tables)ys(R,{showClaimants:!0});b(`applied ${_.tables.length} table${_.tables.length===1?"":"s"} as ${_.claimedBy}.`)}),dr.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,h)=>[...h??[],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,h)=>{let y=h.field.map(R=>{let[A,O,N]=R.split(":");return(!A||!O)&&k(`--field "${R}" must be name:type[:required] (e.g. title:text:required).`),N!==void 0&&N!=="required"&&k(`--field "${R}": the third part may only be "required".`),{name:A,type:O,...N==="required"?{required:!0}:{}}}),{baseUrl:x,token:w}=await V(h.baseUrl),m=new H({baseUrl:x,token:w}),_;try{_=await Y(`creating cr_${d}`,()=>m.tablesApply({tables:[{slug:d,displayName:h.name??d,fields:y,...h.restrictToOwner?{rowLevelOwner:!0}:{}}]}))}catch(R){W(R,"extensions:deploy","create a table"),ws(R)}for(let R of _.tables)ys(R);b(""),b(f.dim("grant access under Settings \u203A Users, or in the Records page permissions matrix."))}),dr.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,h)=>{h.confirm!==d&&k(`drop refused: --confirm "${h.confirm}" does not match "${d}".`);let{baseUrl:y,token:x}=await V(h.baseUrl),w=new H({baseUrl:y,token:x}),m;try{m=await Y(`dropping cr_${d}`,()=>w.tablesDrop(d,h.confirm))}catch(_){W(_,"extensions:deploy","drop a table"),k(`drop failed: ${_ instanceof Error?_.message:String(_)}`)}if(!m.removed)return b(`no table named "${d}" on this instance \u2014 nothing dropped.`);b(`dropped ${m.table} and every row in it.`)});let xi=e.command("sandbox").description("Ephemeral local authoring sandboxes.");xi.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 h=Rd(),y;try{y=await qp(h,{fromTenant:d.from,ttl:d.ttl,sample:d.sample,baseUrl:d.baseUrl})}catch(x){x instanceof $r&&k(x.message),k(`sandbox create failed: ${x instanceof Error?x.message:String(x)}`)}b(`sandbox ${y.name} ready.`),b(` source: ${y.fromTenant} (sampled ${Math.round(y.sampleFraction*100)}%, redacted)`),b(` url: ${y.url}`),b(` expires: ${y.expiresAt}`)}),xi.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 h=Rd();await Ea(h).catch(()=>[]);let y;try{y=await Bp(h,d)}catch(x){k(`sandbox destroy failed: ${x instanceof Error?x.message:String(x)}`)}y||k(`no sandbox named '${d}'.`),b(`destroyed sandbox ${y.name} (dropped ${y.database}).`)}),xi.command("list").description("List active sandboxes with TTL/expiry; reaps any past their TTL.").action(async()=>{let d=Rd(),h=await Ea(d).catch(()=>[]),y=await Ys(),{active:x}=Ra(y);if(x.length===0)b("no active sandboxes.");else{for(let w of x)b(` ${w.name} from=${w.fromTenant} expires=${w.expiresAt} ${w.url}`);b(`
3595
3595
  ${x.length} active sandbox(es).`)}h.length>0&&b(`(reaped ${h.length} expired sandbox(es).)`)}),s.command("export").description("Pull the live instance into turbobase/bundle.json (adopt an instance into a repo).").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--out <dir>","Directory to write the bundle into.",Ms).action(async d=>{let{baseUrl:h,token:y}=await V(d.baseUrl),x=new H({baseUrl:h,token:y}),w;try{w=await Y("exporting instance config",()=>x.configExport())}catch(R){R instanceof X&&(R.status===401||R.status===403)&&k("ext export forbidden \u2014 the stored PAT lacks extensions:deploy. Re-run `turbobase auth login`."),k(`ext export failed: ${R instanceof Error?R.message:String(R)}`)}let m=typeof w.serialized=="string"?w.serialized:`${JSON.stringify(w.manifest??w,null,2)}
3596
3596
  `,_=ce(process.cwd(),d.out,wr);await re.mkdir(lr(_),{recursive:!0}),await re.writeFile(_,m,"utf8"),b(`wrote ${d.out}/${wr} (${m.split(`
3597
- `).length} lines).`)});let Od=s.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),Wb=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],Nd=()=>process.env.TURBOBASE_HOME?.trim()||Ye(N0(),".turbobase"),Jb=()=>"1.12.0",vs=async()=>Promise.all(Wb.map(async d=>{try{return await import(d.name),{...d,present:!0}}catch{return{...d,present:!1}}}));Od.command("status").description("Report which optional native packages are installed.").action(async()=>{let d=await vs();for(let h of d)b(`${h.present?ve.success("installed"):ve.warn("missing")} ${f.bold(h.name)} \u2014 ${h.needs}`);d.some(h=>!h.present)&&b(`
3597
+ `).length} lines).`)});let Od=s.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),Wb=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],Nd=()=>process.env.TURBOBASE_HOME?.trim()||Ye(N0(),".turbobase"),Jb=()=>"1.14.0",vs=async()=>Promise.all(Wb.map(async d=>{try{return await import(d.name),{...d,present:!0}}catch{return{...d,present:!1}}}));Od.command("status").description("Report which optional native packages are installed.").action(async()=>{let d=await vs();for(let h of d)b(`${h.present?ve.success("installed"):ve.warn("missing")} ${f.bold(h.name)} \u2014 ${h.needs}`);d.some(h=>!h.present)&&b(`
3598
3598
  run \`${vr} ext extras install\` to fetch them into ${Nd()}.`)}),Od.command("install").description("Install the optional native packages into $TURBOBASE_HOME (needs npm).").action(async()=>{let d=await vs();if(d.every(A=>A.present)){b(ve.success("extras already installed \u2014 nothing to do."));return}let h=Nd();await re.mkdir(h,{recursive:!0});let y=d.filter(A=>!A.present).map(A=>A.name),x=ka(process.platform,process.arch),w=async(A,O)=>{let{code:N,stderr:U}=await gs("npm",["install","--prefix",h,"--no-audit","--no-fund",...A]);N!==0&&k(`extras install failed while installing ${O} (npm exited ${N}).
3599
3599
  ${U.trim()}
3600
3600
  These are native packages \u2014 they need npm and a working toolchain. Everything except \`test\` and \`replay\` works without them.`)},m=y.filter(A=>A!=="isolated-vm");if(m.length>0){let A=m.map(O=>`${O}@${wa[O]??"latest"}`);b(`installing ${A.join(", ")} \u2192 ${h}/node_modules \u2026`),await w(A,m.join(", "))}if(y.includes("isolated-vm")){let A=`isolated-vm@${wa["isolated-vm"]}`;if(b(`installing ${A} \u2192 ${h}/node_modules \u2026`),await w(["--ignore-scripts",A],"isolated-vm"),Np.includes(x)){let N=`${va}@${Jb()}`;b(`fetching ${N} (no upstream binary for ${x}) \u2026`);let{code:U}=await gs("npm",["install","--prefix",h,"--no-audit","--no-fund","--ignore-scripts",N]);U!==0&&b(ve.warn(`could not fetch ${N} \u2014 falling back to a source build.`))}let O=await Lp({home:h});O.length>0&&b(` ${ve.success("prebuilt")} ${O.join(", ")} \u2192 isolated-vm/prebuilds/${x}`)}let _=await vs(),R=_.filter(A=>!A.present).map(A=>A.name);if(R.includes("isolated-vm")){b(ve.warn(`no prebuilt isolated-vm for ${x} / Node ${process.versions.node} (${Sa()}) \u2014 building from source. This needs Python and a C++ toolchain, and takes a few minutes.`));let{code:A}=await gs("npm",["rebuild","--prefix",h,"isolated-vm"]);A===0&&(_=await vs(),R=_.filter(O=>!O.present).map(O=>O.name))}R.length>0&&k(`npm succeeded but ${R.join(", ")} still will not load \u2014 no prebuilt binary for ${x} / Node ${process.versions.node} (${Sa()}), and the source build did not produce one either. Everything except \`test\` and \`replay\` works without them.`),b(ve.success("extras installed \u2014 `turbobase ext test` and `turbobase ext replay` are ready."))});let xn=e.command("release").description("Ship a release to your instance: deploy | status | rollback | list.");return xn.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 h=d.target==="approved"?"approved":d.target==="auto"?"auto":null;h||k(`--target must be 'auto' or 'approved' (got '${d.target}').`);let y=await J0(ce(process.cwd(),d.project??".")),x=Object.keys(y).length;!d.solution&&x===0&&k("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 Ed(["rev-parse",d.ref])??d.ref:null)??process.env.GITHUB_SHA??await Ed(["rev-parse","HEAD"]),_=d.gitRepo??process.env.GITHUB_REPOSITORY??await Ed(["remote","get-url","origin"]),R=d.gitRef??process.env.GITHUB_REF??null,A=d.prUrl??process.env.PR_URL??null,{baseUrl:O,token:N}=await V(d.baseUrl),U=new H({baseUrl:O,token:N}),I;try{I=await U.releaseDeploy({solutionId:d.solution??null,target:h,approvedBy:d.approvedBy??null,files:x>0?y:null,gitRepo:_,gitRef:R,gitSha:m,prUrl:A})}catch(F){W(F,"releases:write","release deploy"),F instanceof X&&F.status===404&&k("solution_not_found \u2014 no solution with that id."),k(`release deploy failed: ${F instanceof Error?F.message:String(F)}`)}b(`release ${I.releaseId} enqueued \u2192 taskId=${I.taskId} (target=${h}`+(x>0?`, uploaded ${x} bundle file(s)`:", live-fetch")+")."),d.follow&&await ua(()=>U.releaseStatus(I.releaseId),`release ${I.releaseId}`,d.intervalMs)}),xn.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,h)=>{let{baseUrl:y,token:x}=await V(h.baseUrl),w=new H({baseUrl:y,token:x}),m;try{m=await w.releaseFiles(d)}catch(A){A instanceof X&&A.status===404&&k(`release ${d} not found.`),A instanceof X&&A.status===409&&k(`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.`),W(A,"releases:write","pull a release"),k(`release pull failed: ${A instanceof Error?A.message:String(A)}`)}let _=ce(process.cwd(),h.dir),R=0;for(let[A,O]of[["owned",m.owned],["context",m.context]])for(let[N,U]of Object.entries(O)){let I=Ye(_,A,N);await re.mkdir(lr(I),{recursive:!0}),await re.writeFile(I,U,"utf8"),R++}await re.writeFile(Ye(_,".turbobase-release.json"),JSON.stringify({releaseId:m.releaseId,version:m.version,contentDigest:m.contentDigest},null,2),"utf8"),b(`pulled release ${m.releaseId} (${m.status}) \u2192 ${_}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turbobase/cli",
3
- "version": "1.12.0",
3
+ "version": "1.14.0",
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",