@turbobase/cli 1.34.2 → 1.36.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.
|
@@ -2061,7 +2061,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
2061
2061
|
`}var IU,hl,mT=O(()=>{Oo();IU=10*1024*1024,hl=class{constructor(e){this._maxBufferSize=e?.maxBufferSize??IU}append(e){if((this._buffer?.length??0)+e.length>this._maxBufferSize)throw this.clear(),new Error(`ReadBuffer exceeded maximum size of ${this._maxBufferSize} bytes`);this._buffer=this._buffer?Buffer.concat([this._buffer,e]):e}readMessage(){if(!this._buffer)return null;let e=this._buffer.indexOf(`
|
|
2062
2062
|
`);if(e===-1)return null;let r=this._buffer.toString("utf8",0,e).replace(/\r$/,"");return this._buffer=this._buffer.subarray(e+1),OU(r)}clear(){this._buffer=void 0}}});import fT from"node:process";var ml,gT=O(()=>{mT();ml=class{constructor(e=fT.stdin,r=fT.stdout,n){this._stdin=e,this._stdout=r,this._started=!1,this._ondata=o=>{try{this._readBuffer.append(o),this.processReadBuffer()}catch(s){this.onerror?.(s),this.close().catch(()=>{})}},this._onerror=o=>{this.onerror?.(o)},this._readBuffer=new hl({maxBufferSize:n?.maxBufferSize})}async start(){if(this._started)throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror)}processReadBuffer(){for(;;)try{let e=this._readBuffer.readMessage();if(e===null)break;this.onmessage?.(e)}catch(e){this.onerror?.(e)}}async close(){this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror),this._stdin.listenerCount("data")===0&&this._stdin.pause(),this._readBuffer.clear(),this.onclose?.()}send(e){return new Promise(r=>{let n=hT(e);this._stdout.write(n)?r():this._stdout.once("drain",r)})}}});var yT,bT=O(()=>{yT=[]});var RT={};es(RT,{AUTHORING_MCP_SERVER_NAME:()=>xT,AUTHORING_MCP_SERVER_VERSION:()=>kT,AUTHORING_RESOURCES:()=>UU,AUTHORING_TOOLS:()=>MU,AUTHORING_TOOLS_PUBLIC:()=>TT,buildAuthoringMcpServer:()=>$T,resolveSamplesDir:()=>ST,runAuthoringMcpServer:()=>qU});import{promises as NU}from"node:fs";import{isAbsolute as zU,resolve as fl,sep as LU}from"node:path";function vT(){return _T??=import("../mcp/grounding.js").then(e=>e,()=>null),_T}function ST(t,e){if(e===void 0)return fl(process.cwd(),t??"samples");let r=fl(e);if(t===void 0)return r;if(zU(t))return null;let n=fl(r,t);return n===r||n.startsWith(r+LU)?n:null}function We(t){return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}async function DU(t,e={}){let r=e.fetchImpl??fetch;try{let n=await r(`${t.baseUrl.replace(/\/+$/,"")}/v1/extensions`,{headers:{Authorization:`Bearer ${t.token}`,Accept:"application/json"}});return n.ok?((await n.json()).extensions??[]).map(s=>s.hookId).filter(s=>typeof s=="string"):null}catch{return null}}async function $T(t={}){async function e(){if(t.resolveTenant)return t.resolveTenant();let m=await Qt(t.baseUrl);if(!m)throw new Error("not signed in \u2014 run `turbobase auth login` (or set a single stored instance).");return{baseUrl:m.baseUrl,token:m.token}}let r=t.grounding??{},n=await vT(),o=null;if(n)try{let m=await e();o={surface:"mcp-authoring",baseUrl:m.baseUrl,token:m.token}}catch{o=null}let s=n&&o?await n.loadActiveSources(o,r):[],i={...r,loadActiveSourcesImpl:async()=>s},a=[],c=[];if(n&&o){try{a=await n.listMcpPromptSkills(o,i)}catch{a=[]}try{c=(await n.listRecipesForSurface(o,i)).map(m=>({id:m.id,record_type:m.record_type,intent:m.intent}))}catch{c=[]}}let l=o?await DU(o,r):null,u=new pl({name:xT,version:kT},n?{instructions:n.renderAuthoringInstructions({baseUrl:t.displayBaseUrl??o?.baseUrl??t.baseUrl??"this instance",sources:s,recipeIntents:c,promptNames:a.map(m=>m.id),deployedExtensions:l,docsToken:r.docsToken??n.docsPublicToken()})}:void 0);if(n&&o){let m=o;for(let h of a)u.registerPrompt(h.id,{title:h.name,description:h.description},async()=>{let w=await n.getMcpPromptSkill(m,h.id,r);if(!w)throw new Error(`unknown prompt: ${h.id}`);return{description:w.description,messages:[{role:"user",content:{type:"text",text:(w.prompts??[]).join(`
|
|
2063
2063
|
|
|
2064
|
-
`)}}]}})}u.registerTool("catalog_get",{title:"Get the instance catalog",description:"Fetch the instance's record-type catalog (/v1/catalog/records).",inputSchema:{}},async()=>{let{baseUrl:m,token:h}=await e(),w=await fetch(`${m}/v1/catalog/records`,{headers:{Authorization:`Bearer ${h}`,Accept:"application/json"}});if(!w.ok)throw new Error(`catalog fetch failed: ${w.status}`);return We(await w.json())}),u.registerTool("record_anatomy",{title:"Record anatomy + display contract",description:"Fetch ONE record type in depth (/v1/catalog/records/:type): anatomy markdown (field meanings, sublists, gotchas, relations), authored recipe display contracts (columns/labels/formats), semantic column metadata and the record's plain-English description. Read this BEFORE building UI over a record \u2014 column names alone routinely mislead (e.g. tran_id is the human-facing document number, entity_id has a display twin entity_name). Also reports `unpopulatedColumns`: columns that EXIST but carry no value in any sampled row on this instance (currency_name is the classic \u2014 currency_id holds the data). Rendering one of those produces a blank column while faithfully following the display contract, so check it before choosing what to show. And reports `lines`: the record's LINE tables with their columns, including line-level custom fields (custcol_*). Check the lines before concluding a link is absent \u2014 a transaction routinely carries a reference on its LINES while the header field is NULL, and reading only the header reports a real link as missing.",inputSchema:{recordType:A.string().describe('The catalog recordType, e.g. "purchaseOrder" or "customrecord_inbound_shipment".'),source:A.string().optional().describe('Which connector publishes this recordType, e.g. "netsuite" or "stripe". 29 record types are published by more than one source ("customer" by six) and each is a different shape behind the same name. Omit it and the catalog collapse rule picks one; the answer names its choice in `source`.')}},async({recordType:m,source:h})=>{let{baseUrl:w,token:S}=await e(),R=h?`?source=${encodeURIComponent(h)}`:"",P=await fetch(`${w}/v1/catalog/records/${encodeURIComponent(m)}${R}`,{headers:{Authorization:`Bearer ${S}`,Accept:"application/json"}});if(P.status===404)throw new Error(`unknown record type "${m}" \u2014 list them with catalog_get.`);if(!P.ok)throw new Error(`record anatomy fetch failed: ${P.status}`);return We(await P.json())}),u.registerTool("common_columns",{title:"Columns common to several record types",description:"Given SEVERAL record types, return the columns present on ALL of them (safe to select in one shared query shape) and the columns present on only some, with exactly which types lack each. Use this BEFORE writing a query that spans record types \u2014 transaction tables are NOT uniform (memo, total, created_from_id and even pg_row_id are missing from some), and PostgREST rejects an unknown column with 42703 and fails the ENTIRE request, so one wrong name loses the whole table rather than one field. Record types the caller cannot read come back in unknownRecordTypes \u2014 if that is non-empty the intersection covers fewer types than you asked for.",inputSchema:{recordTypes:A.array(A.string()).min(1).describe('Catalog recordTypes, e.g. ["purchaseOrder","vendorBill","itemReceipt"].'),source:A.string().optional().describe("Pin every requested type to ONE connector, e.g. \"netsuite\". Without it each name collapses by the catalog rule, so an intersection can silently mix two sources' tables for the same name. The response's `resolvedSources` names what it used.")}},async({recordTypes:m,source:h})=>{let{baseUrl:w,token:S}=await e(),R=new URLSearchParams({types:m.join(",")});h&&R.set("source",h);let P=await fetch(`${w}/v1/catalog/columns?${R}`,{headers:{Authorization:`Bearer ${S}`,Accept:"application/json"}});if(P.status===404)throw new Error(`none of those record types resolved: ${m.join(", ")} \u2014 list them with catalog_get.`);if(!P.ok)throw new Error(`common columns fetch failed: ${P.status}`);return We(await P.json())}),u.registerTool("catalog_census",{title:"Count the customer's customizations",description:'Count the whole customization catalog: every object by kind AND by whether it can be changed (editable / partner_owned / turbobase_managed / unknown), plus which classes were never collected and why. Call this BEFORE search_catalog when the question is "how much is there" / "what is this account made of" / "how much of this is even ours to change". Takes no arguments.',inputSchema:{}},async()=>{let{baseUrl:m,token:h}=await e(),w=await fetch(`${m}/v1/catalog/census`,{headers:{Authorization:`Bearer ${h}`,Accept:"application/json"}});if(!w.ok)throw new Error(`catalog census failed: ${w.status}`);return We(await w.json())}),u.registerTool("search_catalog",{title:"Search the customization catalog",description:'Survey the customer\'s customization catalog by free text \u2014 scripts, custom records, fields, lists, workflows, roles, saved searches, and installed bundles. Matches ids, names AND the generated plain-English explanations, so ask what you actually want ("which object handles rebates?"), not just an exact id. Returns compact {kind, externalId, name, summary, modifiability} hits plus the TRUE match total, so you can see when the page truncated; pass a hit to explain_object for the full definition + prose. A `partner_owned` object CANNOT be edited by this account however well you understand it \u2014 check that before planning a change. Search this before building over a custom record: its definition and explanation are the spec.',inputSchema:{query:A.string().describe('Free text, e.g. "inbound shipment" or "customscript_recalc".'),kind:A.enum(wT).optional().describe("Narrow to one catalog kind (omit to search all)."),limit:A.number().int().min(1).max(200).optional().describe("Max hits (default 50, max 200). The result reports the true total either way.")}},async({query:m,kind:h,limit:w})=>{let{baseUrl:S,token:R}=await e(),P=new URLSearchParams({q:m});h&&P.set("kind",h),w!==void 0&&P.set("limit",String(w));let z=await fetch(`${S}/v1/catalog/objects?${P}`,{headers:{Authorization:`Bearer ${R}`,Accept:"application/json"}});if(!z.ok)throw new Error(`catalog object search failed: ${z.status}`);return We(await z.json())}),u.registerTool("explain_object",{title:"Explain one catalog object",description:"Fetch ONE customization-catalog object in depth: its captured definition (or script source, or a bundle\u2019s membership), human label, the generated plain-English explanation (tldr + markdown) of what it is for, and its OWNERSHIP \u2014 `modifiability` plus a one-line reason saying whether this account can change it at all. Address it by the {kind, externalId} a search_catalog hit returns. For a FIELD it also reports `selectTarget`: where a List/Record field ACTUALLY points, resolved to a mirrored table plus the filter needed to read it correctly. Do not infer a target from the field name \u2014 a carrier-named field can point at `entity` (vendors), and building on the wrong guess resolves nothing while looking like an empty table.",inputSchema:{kind:A.enum(wT),externalId:A.string().describe('The source scriptid, e.g. "customrecord_inbound_shipment".')}},async({kind:m,externalId:h})=>{let{baseUrl:w,token:S}=await e(),R=await fetch(`${w}/v1/catalog/objects/${encodeURIComponent(m)}/${encodeURIComponent(h)}`,{headers:{Authorization:`Bearer ${S}`,Accept:"application/json"}});if(R.status===404)throw new Error(`unknown object ${m}:${h} \u2014 find it with search_catalog.`);if(!R.ok)throw new Error(`catalog object fetch failed: ${R.status}`);return We(await R.json())}),u.registerTool("search_documentation",{title:"Search the knowledge base",description:'Search this instance\'s published knowledge base \u2014 internal documentation: processes, policies, playbooks, how-tos. Route questions about how the ORGANISATION works here ("what\'s our receiving process", "how do we handle returns"), and ALWAYS search it before building UI over a custom record \u2014 the KB is the only spec a custom record has. Returns up to `limit` {slug, title, snippet} hits; pass a slug to read_document for the full text. Published release only \u2014 drafts are never returned.',inputSchema:{query:A.string().describe('Words or a phrase, e.g. "inbound shipment receiving".'),limit:A.number().int().min(1).max(25).optional().describe("Max results (default 8).")}},async({query:m,limit:h})=>{let{baseUrl:w,token:S}=await e(),R=new URLSearchParams({q:m});h!==void 0&&R.set("limit",String(h));let P=await fetch(`${w}/v1/kb/search?${R}`,{headers:{Authorization:`Bearer ${S}`,Accept:"application/json"}});if(!P.ok)throw new Error(`kb search failed: ${P.status}`);return We(await P.json())}),u.registerTool("read_document",{title:"Read a knowledge-base document",description:"Read one published knowledge-base document in full by its slug (from a search_documentation hit). Returns {found, slug, title, body_markdown}. Published release only \u2014 unpublished / admin-only drafts are never returned.",inputSchema:{slug:A.string().describe("The document slug, e.g. one returned by search_documentation.")}},async({slug:m})=>{let{baseUrl:h,token:w}=await e(),S=await fetch(`${h}/v1/kb/document/${encodeURIComponent(m)}`,{headers:{Authorization:`Bearer ${w}`,Accept:"application/json"}});if(!S.ok&&S.status!==404)throw new Error(`kb document fetch failed: ${S.status}`);return We(await S.json())}),u.registerTool("generate_types",{title:"Generate types.ts",description:"Generate the instance's types.ts (record shapes + PostgREST ops). Returns the source.",inputSchema:{recordsOnly:A.boolean().optional()}},async({recordsOnly:m})=>{let{baseUrl:h,token:w}=await e();return{content:[{type:"text",text:await On({baseUrl:h,token:w,recordsOnly:m})}]}}),u.registerTool("scaffold_hook",{title:"Scaffold a hook",description:"Generate a hook file body (does not write to disk; returns path + content).",inputSchema:{recordType:A.string(),event:A.string().optional(),template:A.enum(["require-field","enforce-format","blank"]).optional(),field:A.string().optional(),regex:A.string().optional(),source:A.string().optional(),id:A.string().optional()}},async m=>{let h=gr("hook",{recordType:m.recordType,event:m.event,template:m.template,field:m.field,regex:m.regex,source:m.source,id:m.id});return We({path:h.relativePath,content:h.content})}),u.registerTool("scaffold_conduit",{title:"Scaffold a conduit",description:"Generate a conduit file body that bridges a canonical record from one source into another (does not write to disk; returns path + content).",inputSchema:{source:A.string().describe('Origin source the record arrives from, e.g. "shopify".'),recordType:A.string().describe('Canonical record type the conduit triggers on, e.g. "order".'),to:A.string().describe('Target source the conduit bridges into, e.g. "netsuite".'),toRecordType:A.string().optional().describe("Target record type (defaults to the trigger record type)."),event:A.enum(["after_canonicalize_inbound","after_sync_success_outbound"]).optional().describe("Cross-source lifecycle event (default after_canonicalize_inbound)."),id:A.string().optional()}},async m=>{let h=gr("conduit",{recordType:m.recordType,source:m.source,to:m.to,toRecordType:m.toRecordType,event:m.event,id:m.id});return We({path:h.relativePath,content:h.content})}),u.registerTool("scaffold_process",{title:"Scaffold a process",description:"Generate a process file body \u2014 an ordered sequence of record writes an app invokes and the platform runs on the server (does not write to disk; returns path + content).",inputSchema:{recordType:A.string().describe('Record type the first step writes, e.g. "purchaseOrder".'),then:A.string().optional().describe('Record type of a second step that waits for the first, e.g. "vendorBill".'),onFailure:A.enum(["stop","rollback"]).optional().describe("What the engine does when a step fails (default stop)."),id:A.string().optional()}},async m=>{let h=gr("process",{recordType:m.recordType,then:m.then,onFailure:m.onFailure,id:m.id});return We({path:h.relativePath,content:h.content})}),u.registerTool("author_conduit",{title:"Draft and check a conduit",description:'Ask the instance to WRITE a conduit from a plain-English intent and check it before staging it for review \u2014 the cross-source counterpart to the hook the healing agent drafts. Returns {status, intentId, id?, reason?}: "written" staged a proposal, "rejected_by_validation" says why it could not (including a target system this instance can only read from). Nothing is deployed \u2014 an administrator applies the staged bridge from the Conduits tab. Prefer scaffold_conduit when you intend to write the transform yourself.',inputSchema:{intent:A.string().describe(`What the bridge should do, in the operator's words, e.g. "when a shopify order is paid, raise the matching NetSuite sales order".`),fromSource:A.string().describe('Origin source the record arrives from, e.g. "shopify".'),fromRecordType:A.string().describe('Canonical record type the bridge triggers on, e.g. "order".'),toSource:A.string().describe('Target source the bridge writes into, e.g. "netsuite".'),toRecordType:A.string().describe('Canonical record type written on the target, e.g. "salesOrder".'),intentId:A.string().optional().describe("Correlation id for the audit trail. The instance mints one when omitted."),columns:A.array(A.string()).optional().describe("Narrow the target columns the transform may write. Defaults to the target record's own.")}},async m=>{let{baseUrl:h,token:w}=await e(),S=new G({baseUrl:h,token:w});return We(await S.authorConduit({intent:m.intent,from:{source:m.fromSource,recordType:m.fromRecordType},to:{source:m.toSource,recordType:m.toRecordType},...m.intentId?{intentId:m.intentId}:{},...m.columns?.length?{columns:m.columns}:{}}))}),u.registerTool("mock_test",{title:"Dry-run an extension against samples",description:"Bundle the given source through the production sandbox and run it against samples/ fixtures.",inputSchema:{source:A.string().describe("The extension TS source."),samplesDir:A.string().optional().describe('Samples directory (default "samples").')}},async({source:m,samplesDir:h})=>{let w=ST(h,t.samplesRoot);if(w===null)return{content:[{type:"text",text:JSON.stringify({error:"samples_dir_refused",message:jU})}],isError:!0};let S;try{S=await ds(w)}catch(z){if(t.samplesRoot!==void 0)return{content:[{type:"text",text:JSON.stringify({error:"samples_unreadable",message:"the samples directory could not be read"})}],isError:!0};throw z}let R=await oa(m,S.map(z=>z.fixture)),P=R.filter(z=>!z.pass).length;return We({total:R.length,passed:R.length-P,results:R})}),u.registerTool("deploy",{title:"Deploy an extension",description:"Deploy an extension source to the instance (POST /v1/extensions).",inputSchema:{id:A.string(),recordType:A.string(),event:A.string(),source:A.string().describe('Adapter id or "standalone".'),sourceCode:A.string().describe("The verbatim extension TS."),extensionKind:A.string().optional()}},async m=>{let{baseUrl:h,token:w}=await e(),R=await new G({baseUrl:h,token:w}).deploy({id:m.id,recordType:m.recordType,event:m.event,source:m.source,sourceCode:m.sourceCode,extensionKind:m.extensionKind});return We(R)}),u.registerTool("tail_logs",{title:"Tail an extension's logs",description:"Read an extension's log ring buffer (GET /v1/extensions/:id/logs).",inputSchema:{id:A.string(),limit:A.number().int().positive().optional()}},async({id:m,limit:h})=>{let{baseUrl:w,token:S}=await e(),R=new G({baseUrl:w,token:S});return We({id:m,logs:await R.logs(m,h)})}),u.registerTool("apps_list",{title:"List hosted apps",description:"Every hosted app on this instance with its slug, whether it's deployed, and its latest source revision. `sourceRev: null` means the app has no source of record yet (deployed dist only) \u2014 pull will 404 until something is pushed. GET /v1/apps.",inputSchema:{}},async()=>{let{baseUrl:m,token:h}=await e(),w=new G({baseUrl:m,token:h});return We(await w.appsList())}),u.registerTool("apps_create",{title:"Create a hosted app",description:"Create a bare hosted-app row so source can be pushed and a build shipped to it \u2014 the author-led path that needs no proposal. Creates the registration only; it serves nothing until a build is shipped. POST /v1/apps.",inputSchema:{slug:A.string().describe("URL slug for the app (lowercased; served at /a/<slug>)."),name:A.string().optional().describe("Display name (defaults to the slug)."),description:A.string().optional().describe("Short description.")}},async({slug:m,name:h,description:w})=>{let{baseUrl:S,token:R}=await e(),P=new G({baseUrl:S,token:R});return We(await P.appsCreate({slug:m,name:h,description:w}))}),u.registerTool("apps_pull",{title:"Pull a hosted app's source",description:"Fetch the app's latest source tree as {path \u2192 contents} READ-ONLY \u2014 write it to disk, refine it, then apps_push it back. Returns 404 when the app has no source revision yet. NEVER deploys. GET /v1/apps/:id/pull.",inputSchema:{app:A.string().describe("App slug or id.")}},async({app:m})=>{let{baseUrl:h,token:w}=await e(),S=new G({baseUrl:h,token:w});return We(await S.appsPull(m))}),u.registerTool("apps_push",{title:"Push a hosted app's source",description:"Record an edited source tree as the app's NEW source revision, so the instance holds the source of record rather than one laptop. Send the whole tree ({path \u2192 contents}, relative POSIX paths, no node_modules/dist/.git). SOURCE ONLY \u2014 this does not build and does not change what is live; run `turbobase apps deploy <slug>` for that. POST /v1/apps/:id/push.",inputSchema:{app:A.string().describe("App slug or id."),files:A.record(A.string()).describe("The full source tree: relative path \u2192 file contents (text only)."),note:A.string().optional().describe("Short note stored with the revision.")}},async({app:m,files:h,note:w})=>{let{baseUrl:S,token:R}=await e(),P=new G({baseUrl:S,token:R});return We(await P.appsPush(m,h,{note:w}))});let p=A.object({name:A.string().describe("Field name (lowercase identifier)."),type:A.enum(["text","number","date","bool","select"]).describe("Customer-facing type, NOT a Postgres type."),required:A.boolean().optional()}),b=A.object({slug:A.string().describe("Table slug; the physical table is cr_<slug>."),displayName:A.string().optional(),fields:A.array(p).describe("At least one field."),rowLevelOwner:A.boolean().optional().describe("Restrict non-admins to rows they created.")});return u.registerTool("tables_list",{title:"List custom tables",description:"The instance's customer-authored tables, their fields, and which apps claim each one. Read the `column` on each field \u2014 that is the actual Postgres column an app SELECTs (cf_sa_<name>), which is not the same string as the field name. GET /v1/tables.",inputSchema:{}},async()=>{let{baseUrl:m,token:h}=await e(),w=new G({baseUrl:m,token:h});return We(await w.tablesList())}),u.registerTool("tables_plan",{title:"Preview a table declaration",description:"What these table declarations WOULD do, without doing it. Several apps may claim one table and the instance merges their declarations, so this reports the merged shape and \u2014 the reason to call it \u2014 fails with a conflict report when another claimant already declares one of your field names at a different type. Writes NOTHING. POST /v1/tables/plan.",inputSchema:{tables:A.array(b),claimedBy:A.string().optional().describe("`cli` or `app:<slug>`. `console` is reserved for the in-product builder.")}},async({tables:m,claimedBy:h})=>{let{baseUrl:w,token:S}=await e(),R=new G({baseUrl:w,token:S});return We(await R.tablesPlan({tables:m,claimedBy:h}))}),u.registerTool("tables_apply",{title:"Create or evolve custom tables",description:"Record these table declarations and reconcile them into real Postgres tables \u2014 RLS, realtime, and the write path included. Additive: an existing table gains the fields you declare and keeps the ones other claimants declared. Never drops a column, so removing a field from your declaration releases your claim on it rather than deleting data. Prefer declaring tables in the app manifest (public/turbobase.json) so the deploy applies them; use this when authoring outside an app. POST /v1/tables/apply.",inputSchema:{tables:A.array(b),claimedBy:A.string().optional().describe("`cli` or `app:<slug>`. `console` is reserved for the in-product builder.")}},async({tables:m,claimedBy:h})=>{let{baseUrl:w,token:S}=await e(),R=new G({baseUrl:w,token:S});return We(await R.tablesApply({tables:m,claimedBy:h}))}),u.registerResource("catalog","catalog:///",{title:"Instance catalog",description:"The instance record-type catalog.",mimeType:"application/json"},async m=>{let{baseUrl:h,token:w}=await e(),R=await(await fetch(`${h}/v1/catalog/records`,{headers:{Authorization:`Bearer ${w}`,Accept:"application/json"}})).text();return{contents:[{uri:m.href,mimeType:"application/json",text:R}]}}),u.registerResource("types","types://all",{title:"Generated types",description:"The instance types.ts source.",mimeType:"text/plain"},async m=>{let{baseUrl:h,token:w}=await e(),S=await On({baseUrl:h,token:w});return{contents:[{uri:m.href,mimeType:"text/plain",text:S}]}}),u.registerResource("samples","samples:///",{title:"Sample fixtures",description:"The local samples/ dry-run fixtures.",mimeType:"application/json"},async m=>{let h=fl(process.cwd(),"samples"),w=[];try{w=(await NU.readdir(h)).filter(R=>R.endsWith(".json"))}catch{w=[]}let S=await ds(h);return{contents:[{uri:m.href,mimeType:"application/json",text:JSON.stringify({files:w,samples:S.map(R=>R.fixture)},null,2)}]}}),u.registerResource("recipes","recipes:///",{title:"Intent recipes",description:"Intent recipes (columns / stats / actions per record type + intent), filtered to the instance's installed sources.",mimeType:"application/json"},async m=>{let h=await vT(),w=[];if(h){let{baseUrl:S,token:R}=await e();w=await h.listRecipesForSurface({surface:"mcp-authoring",baseUrl:S,token:R},r)}return{contents:[{uri:m.href,mimeType:"application/json",text:JSON.stringify({recipes:w})}]}}),u}async function qU(t={}){let e=await $T(t),r=new ml;await e.connect(r)}var wT,_T,xT,kT,jU,TT,MU,UU,ET=O(()=>{pT();gT();In();tu();cs();er();pu();la();bT();wT=["script","field","record","list","workflow","role","saved_search","bundle","form","dataset"];xT="turbobase-authoring",kT="0.1.0";jU="samplesDir must be a relative path inside the samples directory this server serves; absolute paths and paths that leave it are refused over the network.";TT=["catalog_get","record_anatomy","common_columns","search_documentation","read_document","search_catalog","catalog_census","explain_object","generate_types","scaffold_hook","scaffold_conduit","scaffold_process","author_conduit","mock_test","deploy","tail_logs","apps_list","apps_create","apps_pull","apps_push","tables_list","tables_plan","tables_apply"],MU=[...TT,...yT],UU=["catalog:///","types://all","samples:///","recipes:///"]});import{spawn as hg}from"node:child_process";import{promises as _e}from"node:fs";import{homedir as BU}from"node:os";import{basename as CT,dirname as Mr,join as Ot,relative as yl,resolve as ye,sep as pg}from"node:path";var Ig=xl(Cg(),1),{program:dq,createCommand:pq,createArgument:hq,createOption:mq,CommanderError:fq,InvalidArgumentError:gq,InvalidOptionArgumentError:yq,Command:Og,Argument:bq,Option:wq,Help:_q}=Ig.default;tu();cs();er();pu();na();import{promises as Oy}from"node:fs";var i0=/^[a-z][a-z0-9_]+$/;function Ey(t,e){if(typeof t!="string"||!i0.test(t))throw new Error(`${e} ${JSON.stringify(t)} must match /^[a-z][a-z0-9_]+$/ (NS scriptid form). This restriction is what lets reconcile safely splice it as a SQL identifier; refusing to emit DDL.`);return t}var a0="customrecord",c0="customlist";function l0(t){return t.startsWith("customrecord_")?t.slice(13):t}function u0(t){return t.startsWith("customlist_")?t.slice(11):t}function d0(t){return Ey(t,"custom_record scriptId"),`cr_${l0(t)}`}function p0(t){return Ey(t,"custom_list scriptId"),`cl_${u0(t)}`}function Py(t){return t.startsWith(a0)?d0(t):t.startsWith(c0)?p0(t):null}var h0={salesOrder:{table:"order"},invoice:{table:"invoice"},itemFulfillment:{table:"order_fulfillment"},customerPayment:{table:"customer_payment"},cashSale:{table:"cash_sale"},cashRefund:{table:"cash_refund"},opportunity:{table:"opportunity"},estimate:{table:"estimate"},customerDeposit:{table:"customer_deposit"},creditMemo:{table:"credit_memo"},customer:{table:"entity",typeColumn:"entity_type",typeValue:"customer"},vendor:{table:"entity",typeColumn:"entity_type",typeValue:"vendor"},contact:{table:"entity",typeColumn:"entity_type",typeValue:"contact"},employee:{table:"entity",typeColumn:"entity_type",typeValue:"employee"},partner:{table:"entity",typeColumn:"entity_type",typeValue:"partner"},inventoryItem:{table:"item",typeColumn:"item_type",typeValue:"inventoryItem"},serviceSaleItem:{table:"item",typeColumn:"item_type",typeValue:"serviceSaleItem"},nonInventoryResaleItem:{table:"item",typeColumn:"item_type",typeValue:"nonInventoryResaleItem"},kitItem:{table:"item",typeColumn:"item_type",typeValue:"kitItem"},assemblyItem:{table:"item",typeColumn:"item_type",typeValue:"assemblyItem"},lotNumberedInventoryItem:{table:"item",typeColumn:"item_type",typeValue:"lotNumberedInventoryItem"},serializedInventoryItem:{table:"item",typeColumn:"item_type",typeValue:"serializedInventoryItem"},lotNumberedAssemblyItem:{table:"item",typeColumn:"item_type",typeValue:"lotNumberedAssemblyItem"},serializedAssemblyItem:{table:"item",typeColumn:"item_type",typeValue:"serializedAssemblyItem"},nonInventorySaleItem:{table:"item",typeColumn:"item_type",typeValue:"nonInventorySaleItem"},otherChargeSaleItem:{table:"item",typeColumn:"item_type",typeValue:"otherChargeSaleItem"},discountItem:{table:"item",typeColumn:"item_type",typeValue:"discountItem"},location:{table:"location"},department:{table:"department"},classification:{table:"classification"},vendorBill:{table:"vendor_bill"},vendorPayment:{table:"vendor_payment"},purchaseOrder:{table:"purchase_order"},journalEntry:{table:"journal_entry"},timeBill:{table:"time_bill"},expenseReport:{table:"expense_report"},itemReceipt:{table:"item_receipt"},vendorReturnAuthorization:{table:"vendor_return_authorization"},transferOrder:{table:"transfer_order"},check:{table:"check"},customerRefund:{table:"customer_refund"},project:{table:"project"},projectTask:{table:"work_item",typeColumn:"item_type",typeValue:"project_task"},supportCase:{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},caseMessage:{table:"activity",typeColumn:"activity_type",typeValue:"case_message_outgoing",alsoAddresses:["case_message_incoming"]},shippingItem:{table:"shipping_item"},unitsType:{table:"units_type"},returnAuthorization:{table:"return_authorization"},inventoryAdjustment:{table:"inventory_adjustment"},vendorCredit:{table:"vendor_credit"},deposit:{table:"deposit"},account:{table:"account"},channel_message:{table:"activity",typeColumn:"activity_type",typeValue:"message"},social_post:{table:"activity",typeColumn:"activity_type",typeValue:"social_post"},reaction:{table:"reaction"},esignSubmission:{table:"esign_submission"},esignSubmitter:{table:"esign_submitter"},workflow_run:{table:"automation_run"},printer:{table:"printer"},printJob:{table:"print_job"},label:{table:"label"},call:{table:"activity",typeColumn:"activity_type",typeValue:"call"},quote:{table:"estimate"},tag:{table:"label"},request:{table:"request"}};function Ay(t,e){let r=e?f0[`${e}:${t}`]:void 0;if(r)return r;let n=h0[t];if(n)return n;let o=Py(t);return o?{table:o}:void 0}var m0={accountingPeriod:"accounting_period"};var f0={...Object.fromEntries(Object.entries(m0).map(([t,e])=>[`standalone:${t}`,{table:e}])),"google_workspace:doc":{table:"memory_document",typeColumn:"kind",typeValue:"doc"},"google_workspace:email":{table:"memory_document",typeColumn:"kind",typeValue:"email"},"google_workspace:file":{table:"memory_document",typeColumn:"kind",typeValue:"file"},"google_workspace:sheet":{table:"memory_document",typeColumn:"kind",typeValue:"sheet"},"google_workspace:slide":{table:"memory_document",typeColumn:"kind",typeValue:"slide"},"salesforce:account":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"salesforce:case":{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},"salesforce:lead":{table:"entity",typeColumn:"entity_type",typeValue:"lead"},"salesforce:task":{table:"activity",typeColumn:"activity_type",typeValue:"task"},"salesforce:event":{table:"activity",typeColumn:"activity_type",typeValue:"event"},"intercom:admin":{table:"entity",typeColumn:"entity_type",typeValue:"employee"},"intercom:company":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"hubspot:company":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"hubspot:owner":{table:"entity",typeColumn:"entity_type",typeValue:"employee"},"intercom:conversation":{table:"work_item",typeColumn:"item_type",typeValue:"conversation"},"intercom:ticket":{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},"github:issue":{table:"work_item",typeColumn:"item_type",typeValue:"issue"},"github:pull_request":{table:"work_item",typeColumn:"item_type",typeValue:"pull_request"},"github:comment":{table:"activity",typeColumn:"activity_type",typeValue:"comment"},"zendesk:organization":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"getsales:company":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"xero:supplier":{table:"entity",typeColumn:"entity_type",typeValue:"vendor"},"zoho_crm:account":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"zoho_crm:lead":{table:"entity",typeColumn:"entity_type",typeValue:"lead"},"zoho_crm:case":{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},"slack:user":{table:"entity",typeColumn:"entity_type",typeValue:"contact"},"slack:channel":{table:"channel"},"slack:reaction":{table:"reaction"},"quickbooks:bill":{table:"vendor_bill"},"quickbooks:payment":{table:"customer_payment"},"quickbooks:billPayment":{table:"vendor_payment"},"quickbooks:item":{table:"item"},"zoho_crm:task":{table:"activity",typeColumn:"activity_type",typeValue:"task"},"zoho_crm:event":{table:"activity",typeColumn:"activity_type",typeValue:"event"},"zoho_crm:product":{table:"item",typeColumn:"item_type",typeValue:"inventoryItem"},"xero:bill":{table:"vendor_bill"},"xero:billPayment":{table:"vendor_payment"},"xero:creditNote":{table:"credit_memo"},"xero:item":{table:"item"},"xero:manualJournal":{table:"journal_entry"},"xero:payment":{table:"customer_payment"},"xero:receiveMoney":{table:"deposit"},"xero:spendMoney":{table:"check"},"xero:supplierCreditNote":{table:"vendor_credit"},"azure_blob:file":{table:"source_file"}};function Cy(t,e){let r=Ay(t,e);return r?`${r.table}|${r.typeColumn??"-"}|${r.typeValue??"-"}`:void 0}function Iy(t,e,r){if(e===t)return"same-type-merge";let n=r?.fromSource,o=r?.toSource;if(n&&o&&n!==o){let s=Cy(t,n),i=Cy(e,o);if(s!==void 0&&s===i)return"same-type-merge"}return"different-type-mint"}var Ny=4;function hu(t){if(!/\bdefineConduit\s*[<(]/.test(t))throw new Error("not a conduit file \u2014 replay drives a defineConduit({...}) module");let e=po(t,"id"),r=po(t,"on"),n=po(t,"source"),o=po(t,"recordType"),s=[];for(let c of t.matchAll(/\bto\s*:\s*\{([^}]*)\}/g)){let l=po(c[1]??"","source"),u=po(c[1]??"","recordType");l&&u&&s.push({source:l,recordType:u})}let i=s[0],a=/cycleBudget\s*:\s*(\d+)/.exec(t);if(!e||!n||!o||!i)throw new Error("could not parse id/from.{source,recordType}/to.{source,recordType} \u2014 is it a reference-shape defineConduit({...})?");return{id:e,on:r,fromSource:n,fromRecordType:o,toSource:i.source,toRecordType:i.recordType,targets:s,cycleBudget:a?Number(a[1]):Ny}}function po(t,e){let r=new RegExp(`${e}\\s*:\\s*(['"\`])([^'"\`]+)\\1`).exec(t);return r?r[2]:void 0}function g0(t){return{recordType:t.recordType,event:t.event??"after_canonicalize_inbound",direction:"inbound",origin:"system",source:t.fromSource,payload:t.payload??{},tenantConfig:t.tenantConfig??{},metadata:{externalId:t.externalId,lines:t.lines??[]},bindings:t.bindings??[],atlasLinks:t.atlasLinks??{}}}async function zy(t,e,r="fixture",n={}){let o=e.name??r,s=[],i;try{i=hu(t)}catch(l){return{fixtureName:o,conduit:b0(),guards:s,whenPassed:!1,atlasResolutions:[],wouldWriteBindings:[],targets:[],logs:[],error:l instanceof Error?l.message:String(l)}}let a=g0(e),c=Object.entries(e.atlasLinks??{}).map(([l,u])=>({key:l,resolved:u}));try{let l=await ra(t,{resolveDir:n.resolveDir}),p=!!(await us(l,a,{member:"when",timeoutMs:ta})).whenResult;s.push({name:"from.when",status:p?"passed":"failed",detail:p?"gate returned true (or no gate) \u2014 conduit fires":"gate returned false \u2014 conduit short-circuits (dispatch records no ledger row)"});let b=e.hop??0;if(s.push({name:"cycle-budget",status:"skipped-offline",detail:`hop=${b} vs cycleBudget=${i.cycleBudget} is computable, but the chain's true hop count requires live conduit_event state \u2014 requires live sandbox state (skipped in fixture mode)`}),s.push({name:"opposite-direction-loop",status:"skipped-offline",detail:"reads committed conduit_event rows for the reverse leg \u2014 requires live sandbox state (skipped in fixture mode)"}),s.push({name:"idempotency",status:"skipped-offline",detail:"INSERT conduit_event ON CONFLICT (idempotency_key) \u2014 requires live sandbox state (skipped in fixture mode)"}),!p)return{fixtureName:o,conduit:i,guards:s,whenPassed:p,atlasResolutions:c,wouldWriteBindings:[],targets:[],logs:[]};let m=[],h=[],w=[];for(let R=0;R<i.targets.length;R+=1){let P=i.targets[R],z=await us(l,{...a,materialized:w.map(Ur=>({...Ur}))},{member:"transform",stepIndex:R,timeoutMs:ta});m.push(...z.effects.logs);let B=z.effects.result;if(!B||typeof B!="object")throw new Error(`conduit ${i.id} transform${i.targets.length>1?` (target ${R+1})`:""} returned no result (expected { patch, createdFrom? })`);let K=B,Ge=Iy(i.fromRecordType,P.recordType,{fromSource:i.fromSource,toSource:P.source}),{wouldWriteBindings:Je,masterAction:Jt}=y0(i,P,Ge,e);h.push({to:P,transformResult:K,applyBranch:Ge,wouldWriteBindings:Je,masterAction:Jt}),w.push({source:P.source,recordType:P.recordType,masterTable:`<${P.recordType} master, dry-run>`,masterRowId:`<dry-run target ${R+1}>`,externalId:null})}let S=h[0];return{fixtureName:o,conduit:i,guards:s,whenPassed:p,atlasResolutions:c,transformResult:S.transformResult,applyBranch:S.applyBranch,wouldWriteBindings:S.wouldWriteBindings,masterAction:S.masterAction,targets:h,logs:m}}catch(l){return{fixtureName:o,conduit:i,guards:s,whenPassed:!1,atlasResolutions:c,wouldWriteBindings:[],targets:[],logs:[],error:l instanceof Error?l.message:String(l)}}}function y0(t,e,r,n){return r==="same-type-merge"?{masterAction:`would MERGE the patch onto the existing ${t.fromRecordType} master (firing source '${t.fromSource}', external_id='${n.externalId}') and flip originating_system='hybrid'`,wouldWriteBindings:[{source:t.fromSource,externalId:n.externalId,role:"origin",why:"firing source \u2014 its identity already on-row; backfilled into txn_source_link"},{source:e.source,externalId:null,role:"replica",why:"target source bound to the SAME master; external_id null until writeback mints it"}]}:{masterAction:`would MINT a new ${e.recordType} master (source_system='${e.source}', originating_system='hybrid', external_id=null) + stamp created_from lineage`,wouldWriteBindings:[{source:e.source,externalId:null,role:"origin",why:"this conduit CREATED the new master, so the target is its origin; external_id null until writeback mints it"}]}}function b0(){return{id:"",fromSource:"",fromRecordType:"",toSource:"",toRecordType:"",targets:[],cycleBudget:Ny}}async function Ly(t){return Promise.reject(new Error("`replay --against sandbox` requires a running sandbox (TURBOBASE_SANDBOX=1 + a local DB). No local Postgres / k3d in this environment \u2014 deferred to a DB-backed run. Use `replay --fixture <file>` for the offline transform/apply-branch dry-run."))}async function jy(t){let e=await Oy.readFile(t,"utf8");return JSON.parse(e)}function Dy(t){return Oy.readFile(t,"utf8")}function My(t,e){let r=t.conduit;if(t.error)return`replay ${t.fixtureName}: ERROR \u2014 ${t.error}`;let n=t.targets.length>1;if(!e){if(!t.whenPassed)return`replay ${t.fixtureName} [${r.id}]: WHEN-FAILED (conduit short-circuited)`;if(n){let a=t.targets.reduce((c,l)=>c+l.wouldWriteBindings.length,0);return`replay ${t.fixtureName} [${r.id}]: OK \u2014 ${t.targets.length} targets in sequence (${a} binding(s) would be written)`}return`replay ${t.fixtureName} [${r.id}]: OK \u2014 ${t.applyBranch} (${t.wouldWriteBindings.length} binding(s) would be written)`}let o=[];o.push(`\u2501\u2501 replay ${t.fixtureName} \u2501\u2501`),o.push(`conduit: ${r.id}`);let s=(r.targets.length>0?r.targets:[{source:r.toSource,recordType:r.toRecordType}]).map(a=>`${a.source}.${a.recordType}`).join(" \u2192 ");o.push(`route: ${r.fromSource}.${r.fromRecordType} \u2192 ${s}${r.on?` on=${r.on}`:""}`),o.push(""),o.push("guards (dispatch order):");for(let a of t.guards){let c=a.status==="passed"?"\u2713":a.status==="failed"?"\u2717":"\u2298";o.push(` ${c} ${a.name} [${a.status}] \u2014 ${a.detail}`)}if(o.push(""),o.push("atlas.resolve (used by transform):"),t.atlasResolutions.length===0)o.push(" (none declared \u2014 every ctx.atlas.resolve(...) resolves to null in fixture mode)");else for(let a of t.atlasResolutions)o.push(` ${a.key} \u2192 ${a.resolved===null?"null (no Atlas link \u2014 noted)":a.resolved}`);if(!t.whenPassed)return o.push(""),o.push("transform: SKIPPED (from.when gate did not fire)."),o.join(`
|
|
2064
|
+
`)}}]}})}u.registerTool("catalog_get",{title:"Get the instance catalog",description:"Fetch the instance's record-type catalog (/v1/catalog/records).",inputSchema:{}},async()=>{let{baseUrl:m,token:h}=await e(),w=await fetch(`${m}/v1/catalog/records`,{headers:{Authorization:`Bearer ${h}`,Accept:"application/json"}});if(!w.ok)throw new Error(`catalog fetch failed: ${w.status}`);return We(await w.json())}),u.registerTool("record_anatomy",{title:"Record anatomy + display contract",description:"Fetch ONE record type in depth (/v1/catalog/records/:type): anatomy markdown (field meanings, sublists, gotchas, relations), authored recipe display contracts (columns/labels/formats), semantic column metadata and the record's plain-English description. Read this BEFORE building UI over a record \u2014 column names alone routinely mislead (e.g. tran_id is the human-facing document number, entity_id has a display twin entity_name). Also reports `unpopulatedColumns`: columns that EXIST but carry no value in any sampled row on this instance (currency_name is the classic \u2014 currency_id holds the data). Rendering one of those produces a blank column while faithfully following the display contract, so check it before choosing what to show. And reports `lines`: the record's LINE tables with their columns, including line-level custom fields (custcol_*). Check the lines before concluding a link is absent \u2014 a transaction routinely carries a reference on its LINES while the header field is NULL, and reading only the header reports a real link as missing.",inputSchema:{recordType:A.string().describe('The catalog recordType, e.g. "purchaseOrder" or "customrecord_inbound_shipment".'),source:A.string().optional().describe('Which connector publishes this recordType, e.g. "netsuite" or "stripe". 29 record types are published by more than one source ("customer" by six) and each is a different shape behind the same name. Omit it and the catalog collapse rule picks one; the answer names its choice in `source`.')}},async({recordType:m,source:h})=>{let{baseUrl:w,token:S}=await e(),R=h?`?source=${encodeURIComponent(h)}`:"",P=await fetch(`${w}/v1/catalog/records/${encodeURIComponent(m)}${R}`,{headers:{Authorization:`Bearer ${S}`,Accept:"application/json"}});if(P.status===404)throw new Error(`unknown record type "${m}" \u2014 list them with catalog_get.`);if(!P.ok)throw new Error(`record anatomy fetch failed: ${P.status}`);return We(await P.json())}),u.registerTool("common_columns",{title:"Columns common to several record types",description:"Given SEVERAL record types, return the columns present on ALL of them (safe to select in one shared query shape) and the columns present on only some, with exactly which types lack each. Use this BEFORE writing a query that spans record types \u2014 transaction tables are NOT uniform (memo, total, created_from_id and even pg_row_id are missing from some), and PostgREST rejects an unknown column with 42703 and fails the ENTIRE request, so one wrong name loses the whole table rather than one field. Record types the caller cannot read come back in unknownRecordTypes \u2014 if that is non-empty the intersection covers fewer types than you asked for.",inputSchema:{recordTypes:A.array(A.string()).min(1).describe('Catalog recordTypes, e.g. ["purchaseOrder","vendorBill","itemReceipt"].'),source:A.string().optional().describe("Pin every requested type to ONE connector, e.g. \"netsuite\". Without it each name collapses by the catalog rule, so an intersection can silently mix two sources' tables for the same name. The response's `resolvedSources` names what it used.")}},async({recordTypes:m,source:h})=>{let{baseUrl:w,token:S}=await e(),R=new URLSearchParams({types:m.join(",")});h&&R.set("source",h);let P=await fetch(`${w}/v1/catalog/columns?${R}`,{headers:{Authorization:`Bearer ${S}`,Accept:"application/json"}});if(P.status===404)throw new Error(`none of those record types resolved: ${m.join(", ")} \u2014 list them with catalog_get.`);if(!P.ok)throw new Error(`common columns fetch failed: ${P.status}`);return We(await P.json())}),u.registerTool("catalog_census",{title:"Count the customer's customizations",description:'Count the whole customization catalog: every object by kind AND by whether it can be changed (editable / partner_owned / turbobase_managed / unknown), plus which classes were never collected and why. Call this BEFORE search_catalog when the question is "how much is there" / "what is this account made of" / "how much of this is even ours to change". Takes no arguments.',inputSchema:{}},async()=>{let{baseUrl:m,token:h}=await e(),w=await fetch(`${m}/v1/catalog/census`,{headers:{Authorization:`Bearer ${h}`,Accept:"application/json"}});if(!w.ok)throw new Error(`catalog census failed: ${w.status}`);return We(await w.json())}),u.registerTool("search_catalog",{title:"Search the customization catalog",description:'Survey the customer\'s customization catalog by free text \u2014 scripts, custom records, fields, lists, workflows, roles, saved searches, and installed bundles. Matches ids, names AND the generated plain-English explanations, so ask what you actually want ("which object handles rebates?"), not just an exact id. Returns compact {kind, externalId, name, summary, modifiability} hits plus the TRUE match total, so you can see when the page truncated; pass a hit to explain_object for the full definition + prose. A `partner_owned` object CANNOT be edited by this account however well you understand it \u2014 check that before planning a change. Search this before building over a custom record: its definition and explanation are the spec.',inputSchema:{query:A.string().describe('Free text, e.g. "inbound shipment" or "customscript_recalc".'),kind:A.enum(wT).optional().describe("Narrow to one catalog kind (omit to search all)."),limit:A.number().int().min(1).max(200).optional().describe("Max hits (default 50, max 200). The result reports the true total either way.")}},async({query:m,kind:h,limit:w})=>{let{baseUrl:S,token:R}=await e(),P=new URLSearchParams({q:m});h&&P.set("kind",h),w!==void 0&&P.set("limit",String(w));let z=await fetch(`${S}/v1/catalog/objects?${P}`,{headers:{Authorization:`Bearer ${R}`,Accept:"application/json"}});if(!z.ok)throw new Error(`catalog object search failed: ${z.status}`);return We(await z.json())}),u.registerTool("explain_object",{title:"Explain one catalog object",description:"Fetch ONE customization-catalog object in depth: its captured definition (or script source, or a bundle\u2019s membership), human label, the generated plain-English explanation (tldr + markdown) of what it is for, and its OWNERSHIP \u2014 `modifiability` plus a one-line reason saying whether this account can change it at all. Address it by the {kind, externalId} a search_catalog hit returns. For a FIELD it also reports `selectTarget`: where a List/Record field ACTUALLY points, resolved to a mirrored table plus the filter needed to read it correctly. Do not infer a target from the field name \u2014 a carrier-named field can point at `entity` (vendors), and building on the wrong guess resolves nothing while looking like an empty table.",inputSchema:{kind:A.enum(wT),externalId:A.string().describe('The source scriptid, e.g. "customrecord_inbound_shipment".')}},async({kind:m,externalId:h})=>{let{baseUrl:w,token:S}=await e(),R=await fetch(`${w}/v1/catalog/objects/${encodeURIComponent(m)}/${encodeURIComponent(h)}`,{headers:{Authorization:`Bearer ${S}`,Accept:"application/json"}});if(R.status===404)throw new Error(`unknown object ${m}:${h} \u2014 find it with search_catalog.`);if(!R.ok)throw new Error(`catalog object fetch failed: ${R.status}`);return We(await R.json())}),u.registerTool("search_documentation",{title:"Search the knowledge base",description:'Search this instance\'s published knowledge base \u2014 internal documentation: processes, policies, playbooks, how-tos. Route questions about how the ORGANISATION works here ("what\'s our receiving process", "how do we handle returns"), and ALWAYS search it before building UI over a custom record \u2014 the KB is the only spec a custom record has. Returns up to `limit` {slug, title, snippet} hits; pass a slug to read_document for the full text. Published release only \u2014 drafts are never returned.',inputSchema:{query:A.string().describe('Words or a phrase, e.g. "inbound shipment receiving".'),limit:A.number().int().min(1).max(25).optional().describe("Max results (default 8).")}},async({query:m,limit:h})=>{let{baseUrl:w,token:S}=await e(),R=new URLSearchParams({q:m});h!==void 0&&R.set("limit",String(h));let P=await fetch(`${w}/v1/kb/search?${R}`,{headers:{Authorization:`Bearer ${S}`,Accept:"application/json"}});if(!P.ok)throw new Error(`kb search failed: ${P.status}`);return We(await P.json())}),u.registerTool("read_document",{title:"Read a knowledge-base document",description:"Read one published knowledge-base document in full by its slug (from a search_documentation hit). Returns {found, slug, title, body_markdown}. Published release only \u2014 unpublished / admin-only drafts are never returned.",inputSchema:{slug:A.string().describe("The document slug, e.g. one returned by search_documentation.")}},async({slug:m})=>{let{baseUrl:h,token:w}=await e(),S=await fetch(`${h}/v1/kb/document/${encodeURIComponent(m)}`,{headers:{Authorization:`Bearer ${w}`,Accept:"application/json"}});if(!S.ok&&S.status!==404)throw new Error(`kb document fetch failed: ${S.status}`);return We(await S.json())}),u.registerTool("generate_types",{title:"Generate types.ts",description:"Generate the instance's types.ts (record shapes + PostgREST ops). Returns the source.",inputSchema:{recordsOnly:A.boolean().optional()}},async({recordsOnly:m})=>{let{baseUrl:h,token:w}=await e();return{content:[{type:"text",text:await On({baseUrl:h,token:w,recordsOnly:m})}]}}),u.registerTool("scaffold_hook",{title:"Scaffold a hook",description:"Generate a hook file body (does not write to disk; returns path + content).",inputSchema:{recordType:A.string(),event:A.string().optional(),template:A.enum(["require-field","enforce-format","blank"]).optional(),field:A.string().optional(),regex:A.string().optional(),source:A.string().optional(),id:A.string().optional()}},async m=>{let h=gr("hook",{recordType:m.recordType,event:m.event,template:m.template,field:m.field,regex:m.regex,source:m.source,id:m.id});return We({path:h.relativePath,content:h.content})}),u.registerTool("scaffold_conduit",{title:"Scaffold a conduit",description:"Generate a conduit file body that bridges a canonical record from one source into another (does not write to disk; returns path + content).",inputSchema:{source:A.string().describe('Origin source the record arrives from, e.g. "shopify".'),recordType:A.string().describe('Canonical record type the conduit triggers on, e.g. "order".'),to:A.string().describe('Target source the conduit bridges into, e.g. "netsuite".'),toRecordType:A.string().optional().describe("Target record type (defaults to the trigger record type)."),event:A.enum(["after_canonicalize_inbound","after_sync_success_outbound"]).optional().describe("Cross-source lifecycle event (default after_canonicalize_inbound)."),id:A.string().optional()}},async m=>{let h=gr("conduit",{recordType:m.recordType,source:m.source,to:m.to,toRecordType:m.toRecordType,event:m.event,id:m.id});return We({path:h.relativePath,content:h.content})}),u.registerTool("scaffold_process",{title:"Scaffold a process",description:"Generate a process file body \u2014 an ordered sequence of record writes an app invokes and the platform runs on the server (does not write to disk; returns path + content).",inputSchema:{recordType:A.string().describe('Record type the first step writes, e.g. "purchaseOrder".'),then:A.string().optional().describe('Record type of a second step that waits for the first, e.g. "vendorBill".'),onFailure:A.enum(["stop","rollback"]).optional().describe("What the engine does when a step fails (default stop)."),id:A.string().optional()}},async m=>{let h=gr("process",{recordType:m.recordType,then:m.then,onFailure:m.onFailure,id:m.id});return We({path:h.relativePath,content:h.content})}),u.registerTool("author_conduit",{title:"Draft and check a conduit",description:'Ask the instance to WRITE a conduit from a plain-English intent and check it before staging it for review \u2014 the cross-source counterpart to the hook the healing agent drafts. Returns {status, intentId, id?, reason?}: "written" staged a proposal, "rejected_by_validation" says why it could not (including a target system this instance can only read from). Nothing is deployed \u2014 an administrator applies the staged bridge from the Conduits tab. Prefer scaffold_conduit when you intend to write the transform yourself.',inputSchema:{intent:A.string().describe(`What the bridge should do, in the operator's words, e.g. "when a shopify order is paid, raise the matching NetSuite sales order".`),fromSource:A.string().describe('Origin source the record arrives from, e.g. "shopify".'),fromRecordType:A.string().describe('Canonical record type the bridge triggers on, e.g. "order".'),toSource:A.string().describe('Target source the bridge writes into, e.g. "netsuite".'),toRecordType:A.string().describe('Canonical record type written on the target, e.g. "salesOrder".'),intentId:A.string().optional().describe("Correlation id for the audit trail. The instance mints one when omitted."),columns:A.array(A.string()).optional().describe("Narrow the target columns the transform may write. Defaults to the target record's own.")}},async m=>{let{baseUrl:h,token:w}=await e(),S=new G({baseUrl:h,token:w});return We(await S.authorConduit({intent:m.intent,from:{source:m.fromSource,recordType:m.fromRecordType},to:{source:m.toSource,recordType:m.toRecordType},...m.intentId?{intentId:m.intentId}:{},...m.columns?.length?{columns:m.columns}:{}}))}),u.registerTool("mock_test",{title:"Dry-run an extension against samples",description:"Bundle the given source through the production sandbox and run it against samples/ fixtures.",inputSchema:{source:A.string().describe("The extension TS source."),samplesDir:A.string().optional().describe('Samples directory (default "samples").')}},async({source:m,samplesDir:h})=>{let w=ST(h,t.samplesRoot);if(w===null)return{content:[{type:"text",text:JSON.stringify({error:"samples_dir_refused",message:jU})}],isError:!0};let S;try{S=await ds(w)}catch(z){if(t.samplesRoot!==void 0)return{content:[{type:"text",text:JSON.stringify({error:"samples_unreadable",message:"the samples directory could not be read"})}],isError:!0};throw z}let R=await oa(m,S.map(z=>z.fixture)),P=R.filter(z=>!z.pass).length;return We({total:R.length,passed:R.length-P,results:R})}),u.registerTool("deploy",{title:"Deploy an extension",description:"Deploy an extension source to the instance (POST /v1/extensions).",inputSchema:{id:A.string(),recordType:A.string(),event:A.string(),source:A.string().describe('Adapter id or "standalone".'),sourceCode:A.string().describe("The verbatim extension TS."),extensionKind:A.string().optional()}},async m=>{let{baseUrl:h,token:w}=await e(),R=await new G({baseUrl:h,token:w}).deploy({id:m.id,recordType:m.recordType,event:m.event,source:m.source,sourceCode:m.sourceCode,extensionKind:m.extensionKind});return We(R)}),u.registerTool("tail_logs",{title:"Tail an extension's logs",description:"Read an extension's log ring buffer (GET /v1/extensions/:id/logs).",inputSchema:{id:A.string(),limit:A.number().int().positive().optional()}},async({id:m,limit:h})=>{let{baseUrl:w,token:S}=await e(),R=new G({baseUrl:w,token:S});return We({id:m,logs:await R.logs(m,h)})}),u.registerTool("apps_list",{title:"List hosted apps",description:"Every hosted app on this instance with its slug, whether it's deployed, and its latest source revision. `sourceRev: null` means the app has no source of record yet (deployed dist only) \u2014 pull will 404 until something is pushed. GET /v1/apps.",inputSchema:{}},async()=>{let{baseUrl:m,token:h}=await e(),w=new G({baseUrl:m,token:h});return We(await w.appsList())}),u.registerTool("apps_create",{title:"Create a hosted app",description:"Create a bare hosted-app row so source can be pushed and a build shipped to it \u2014 the author-led path that needs no proposal. Creates the registration only; it serves nothing until a build is shipped. POST /v1/apps.",inputSchema:{slug:A.string().describe("URL slug for the app (lowercased; served at /a/<slug>)."),name:A.string().optional().describe("Display name (defaults to the slug)."),description:A.string().optional().describe("Short description.")}},async({slug:m,name:h,description:w})=>{let{baseUrl:S,token:R}=await e(),P=new G({baseUrl:S,token:R});return We(await P.appsCreate({slug:m,name:h,description:w}))}),u.registerTool("apps_pull",{title:"Pull a hosted app's source",description:"Fetch the app's latest source tree as {path \u2192 contents} READ-ONLY \u2014 write it to disk, refine it, then apps_push it back. Returns 404 when the app has no source revision yet. NEVER deploys. GET /v1/apps/:id/pull.",inputSchema:{app:A.string().describe("App slug or id.")}},async({app:m})=>{let{baseUrl:h,token:w}=await e(),S=new G({baseUrl:h,token:w});return We(await S.appsPull(m))}),u.registerTool("apps_push",{title:"Push a hosted app's source",description:"Record an edited source tree as the app's NEW source revision, so the instance holds the source of record rather than one laptop. Send the whole tree ({path \u2192 contents}, relative POSIX paths, no node_modules/dist/.git). SOURCE ONLY \u2014 this does not build and does not change what is live; run `turbobase apps deploy <slug>` for that. POST /v1/apps/:id/push.",inputSchema:{app:A.string().describe("App slug or id."),files:A.record(A.string()).describe("The full source tree: relative path \u2192 file contents (text only)."),note:A.string().optional().describe("Short note stored with the revision.")}},async({app:m,files:h,note:w})=>{let{baseUrl:S,token:R}=await e(),P=new G({baseUrl:S,token:R});return We(await P.appsPush(m,h,{note:w}))});let p=A.object({name:A.string().describe("Field name (lowercase identifier)."),type:A.enum(["text","number","date","bool","select"]).describe("Customer-facing type, NOT a Postgres type."),required:A.boolean().optional()}),b=A.object({slug:A.string().describe("Table slug; the physical table is cr_<slug>."),displayName:A.string().optional(),fields:A.array(p).describe("At least one field."),rowLevelOwner:A.boolean().optional().describe("Restrict non-admins to rows they created.")});return u.registerTool("tables_list",{title:"List custom tables",description:"The instance's customer-authored tables, their fields, and which apps claim each one. Read the `column` on each field \u2014 that is the actual Postgres column an app SELECTs (cf_sa_<name>), which is not the same string as the field name. GET /v1/tables.",inputSchema:{}},async()=>{let{baseUrl:m,token:h}=await e(),w=new G({baseUrl:m,token:h});return We(await w.tablesList())}),u.registerTool("tables_plan",{title:"Preview a table declaration",description:"What these table declarations WOULD do, without doing it. Several apps may claim one table and the instance merges their declarations, so this reports the merged shape and \u2014 the reason to call it \u2014 fails with a conflict report when another claimant already declares one of your field names at a different type. Writes NOTHING. POST /v1/tables/plan.",inputSchema:{tables:A.array(b),claimedBy:A.string().optional().describe("`cli` or `app:<slug>`. `console` is reserved for the in-product builder.")}},async({tables:m,claimedBy:h})=>{let{baseUrl:w,token:S}=await e(),R=new G({baseUrl:w,token:S});return We(await R.tablesPlan({tables:m,claimedBy:h}))}),u.registerTool("tables_apply",{title:"Create or evolve custom tables",description:"Record these table declarations and reconcile them into real Postgres tables \u2014 RLS, realtime, and the write path included. Additive: an existing table gains the fields you declare and keeps the ones other claimants declared. Never drops a column, so removing a field from your declaration releases your claim on it rather than deleting data. Prefer declaring tables in the app manifest (public/turbobase.json) so the deploy applies them; use this when authoring outside an app. POST /v1/tables/apply.",inputSchema:{tables:A.array(b),claimedBy:A.string().optional().describe("`cli` or `app:<slug>`. `console` is reserved for the in-product builder.")}},async({tables:m,claimedBy:h})=>{let{baseUrl:w,token:S}=await e(),R=new G({baseUrl:w,token:S});return We(await R.tablesApply({tables:m,claimedBy:h}))}),u.registerResource("catalog","catalog:///",{title:"Instance catalog",description:"The instance record-type catalog.",mimeType:"application/json"},async m=>{let{baseUrl:h,token:w}=await e(),R=await(await fetch(`${h}/v1/catalog/records`,{headers:{Authorization:`Bearer ${w}`,Accept:"application/json"}})).text();return{contents:[{uri:m.href,mimeType:"application/json",text:R}]}}),u.registerResource("types","types://all",{title:"Generated types",description:"The instance types.ts source.",mimeType:"text/plain"},async m=>{let{baseUrl:h,token:w}=await e(),S=await On({baseUrl:h,token:w});return{contents:[{uri:m.href,mimeType:"text/plain",text:S}]}}),u.registerResource("samples","samples:///",{title:"Sample fixtures",description:"The local samples/ dry-run fixtures.",mimeType:"application/json"},async m=>{let h=fl(process.cwd(),"samples"),w=[];try{w=(await NU.readdir(h)).filter(R=>R.endsWith(".json"))}catch{w=[]}let S=await ds(h);return{contents:[{uri:m.href,mimeType:"application/json",text:JSON.stringify({files:w,samples:S.map(R=>R.fixture)},null,2)}]}}),u.registerResource("recipes","recipes:///",{title:"Intent recipes",description:"Intent recipes (columns / stats / actions per record type + intent), filtered to the instance's installed sources.",mimeType:"application/json"},async m=>{let h=await vT(),w=[];if(h){let{baseUrl:S,token:R}=await e();w=await h.listRecipesForSurface({surface:"mcp-authoring",baseUrl:S,token:R},r)}return{contents:[{uri:m.href,mimeType:"application/json",text:JSON.stringify({recipes:w})}]}}),u}async function qU(t={}){let e=await $T(t),r=new ml;await e.connect(r)}var wT,_T,xT,kT,jU,TT,MU,UU,ET=O(()=>{pT();gT();In();tu();cs();er();pu();la();bT();wT=["script","field","record","list","workflow","role","saved_search","bundle","form","dataset"];xT="turbobase-authoring",kT="0.1.0";jU="samplesDir must be a relative path inside the samples directory this server serves; absolute paths and paths that leave it are refused over the network.";TT=["catalog_get","record_anatomy","common_columns","search_documentation","read_document","search_catalog","catalog_census","explain_object","generate_types","scaffold_hook","scaffold_conduit","scaffold_process","author_conduit","mock_test","deploy","tail_logs","apps_list","apps_create","apps_pull","apps_push","tables_list","tables_plan","tables_apply"],MU=[...TT,...yT],UU=["catalog:///","types://all","samples:///","recipes:///"]});import{spawn as hg}from"node:child_process";import{promises as _e}from"node:fs";import{homedir as BU}from"node:os";import{basename as CT,dirname as Mr,join as Ot,relative as yl,resolve as ye,sep as pg}from"node:path";var Ig=xl(Cg(),1),{program:dq,createCommand:pq,createArgument:hq,createOption:mq,CommanderError:fq,InvalidArgumentError:gq,InvalidOptionArgumentError:yq,Command:Og,Argument:bq,Option:wq,Help:_q}=Ig.default;tu();cs();er();pu();na();import{promises as Oy}from"node:fs";var i0=/^[a-z][a-z0-9_]+$/;function Ey(t,e){if(typeof t!="string"||!i0.test(t))throw new Error(`${e} ${JSON.stringify(t)} must match /^[a-z][a-z0-9_]+$/ (NS scriptid form). This restriction is what lets reconcile safely splice it as a SQL identifier; refusing to emit DDL.`);return t}var a0="customrecord",c0="customlist";function l0(t){return t.startsWith("customrecord_")?t.slice(13):t}function u0(t){return t.startsWith("customlist_")?t.slice(11):t}function d0(t){return Ey(t,"custom_record scriptId"),`cr_${l0(t)}`}function p0(t){return Ey(t,"custom_list scriptId"),`cl_${u0(t)}`}function Py(t){return t.startsWith(a0)?d0(t):t.startsWith(c0)?p0(t):null}var h0={salesOrder:{table:"order"},invoice:{table:"invoice"},itemFulfillment:{table:"order_fulfillment"},customerPayment:{table:"customer_payment"},cashSale:{table:"cash_sale"},cashRefund:{table:"cash_refund"},opportunity:{table:"opportunity"},estimate:{table:"estimate"},customerDeposit:{table:"customer_deposit"},creditMemo:{table:"credit_memo"},customer:{table:"entity",typeColumn:"entity_type",typeValue:"customer"},vendor:{table:"entity",typeColumn:"entity_type",typeValue:"vendor"},contact:{table:"entity",typeColumn:"entity_type",typeValue:"contact"},employee:{table:"entity",typeColumn:"entity_type",typeValue:"employee"},partner:{table:"entity",typeColumn:"entity_type",typeValue:"partner"},inventoryItem:{table:"item",typeColumn:"item_type",typeValue:"inventoryItem"},serviceSaleItem:{table:"item",typeColumn:"item_type",typeValue:"serviceSaleItem"},nonInventoryResaleItem:{table:"item",typeColumn:"item_type",typeValue:"nonInventoryResaleItem"},kitItem:{table:"item",typeColumn:"item_type",typeValue:"kitItem"},assemblyItem:{table:"item",typeColumn:"item_type",typeValue:"assemblyItem"},lotNumberedInventoryItem:{table:"item",typeColumn:"item_type",typeValue:"lotNumberedInventoryItem"},serializedInventoryItem:{table:"item",typeColumn:"item_type",typeValue:"serializedInventoryItem"},lotNumberedAssemblyItem:{table:"item",typeColumn:"item_type",typeValue:"lotNumberedAssemblyItem"},serializedAssemblyItem:{table:"item",typeColumn:"item_type",typeValue:"serializedAssemblyItem"},nonInventorySaleItem:{table:"item",typeColumn:"item_type",typeValue:"nonInventorySaleItem"},otherChargeSaleItem:{table:"item",typeColumn:"item_type",typeValue:"otherChargeSaleItem"},discountItem:{table:"item",typeColumn:"item_type",typeValue:"discountItem"},location:{table:"location"},department:{table:"department"},classification:{table:"classification"},vendorBill:{table:"vendor_bill"},vendorPayment:{table:"vendor_payment"},purchaseOrder:{table:"purchase_order"},journalEntry:{table:"journal_entry"},timeBill:{table:"time_bill"},expenseReport:{table:"expense_report"},itemReceipt:{table:"item_receipt"},vendorReturnAuthorization:{table:"vendor_return_authorization"},transferOrder:{table:"transfer_order"},check:{table:"check"},customerRefund:{table:"customer_refund"},project:{table:"project"},projectTask:{table:"work_item",typeColumn:"item_type",typeValue:"project_task"},supportCase:{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},caseMessage:{table:"activity",typeColumn:"activity_type",typeValue:"case_message_outgoing",alsoAddresses:["case_message_incoming"]},shippingItem:{table:"shipping_item"},unitsType:{table:"units_type"},returnAuthorization:{table:"return_authorization"},inventoryAdjustment:{table:"inventory_adjustment"},vendorCredit:{table:"vendor_credit"},deposit:{table:"deposit"},account:{table:"account"},channel_message:{table:"activity",typeColumn:"activity_type",typeValue:"message"},social_post:{table:"activity",typeColumn:"activity_type",typeValue:"social_post"},reaction:{table:"reaction"},esignSubmission:{table:"esign_submission"},esignSubmitter:{table:"esign_submitter"},workflow_run:{table:"automation_run"},printer:{table:"printer"},printJob:{table:"print_job"},label:{table:"label"},call:{table:"activity",typeColumn:"activity_type",typeValue:"call"},quote:{table:"estimate"},tag:{table:"label"},request:{table:"request"}};function Ay(t,e){let r=e?f0[`${e}:${t}`]:void 0;if(r)return r;let n=h0[t];if(n)return n;let o=Py(t);return o?{table:o}:void 0}var m0={accountingPeriod:"accounting_period",printer:"printer"};var f0={...Object.fromEntries(Object.entries(m0).map(([t,e])=>[`standalone:${t}`,{table:e}])),"google_workspace:doc":{table:"memory_document",typeColumn:"kind",typeValue:"doc"},"google_workspace:email":{table:"memory_document",typeColumn:"kind",typeValue:"email"},"google_workspace:file":{table:"memory_document",typeColumn:"kind",typeValue:"file"},"google_workspace:sheet":{table:"memory_document",typeColumn:"kind",typeValue:"sheet"},"google_workspace:slide":{table:"memory_document",typeColumn:"kind",typeValue:"slide"},"salesforce:account":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"salesforce:case":{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},"salesforce:lead":{table:"entity",typeColumn:"entity_type",typeValue:"lead"},"salesforce:task":{table:"activity",typeColumn:"activity_type",typeValue:"task"},"salesforce:event":{table:"activity",typeColumn:"activity_type",typeValue:"event"},"intercom:admin":{table:"entity",typeColumn:"entity_type",typeValue:"employee"},"intercom:company":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"hubspot:company":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"hubspot:owner":{table:"entity",typeColumn:"entity_type",typeValue:"employee"},"intercom:conversation":{table:"work_item",typeColumn:"item_type",typeValue:"conversation"},"intercom:ticket":{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},"github:issue":{table:"work_item",typeColumn:"item_type",typeValue:"issue"},"github:pull_request":{table:"work_item",typeColumn:"item_type",typeValue:"pull_request"},"github:comment":{table:"activity",typeColumn:"activity_type",typeValue:"comment"},"zendesk:organization":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"getsales:company":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"xero:supplier":{table:"entity",typeColumn:"entity_type",typeValue:"vendor"},"zoho_crm:account":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"zoho_crm:lead":{table:"entity",typeColumn:"entity_type",typeValue:"lead"},"zoho_crm:case":{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},"slack:user":{table:"entity",typeColumn:"entity_type",typeValue:"contact"},"slack:channel":{table:"channel"},"slack:reaction":{table:"reaction"},"quickbooks:bill":{table:"vendor_bill"},"quickbooks:payment":{table:"customer_payment"},"quickbooks:billPayment":{table:"vendor_payment"},"quickbooks:item":{table:"item"},"zoho_crm:task":{table:"activity",typeColumn:"activity_type",typeValue:"task"},"zoho_crm:event":{table:"activity",typeColumn:"activity_type",typeValue:"event"},"zoho_crm:product":{table:"item",typeColumn:"item_type",typeValue:"inventoryItem"},"xero:bill":{table:"vendor_bill"},"xero:billPayment":{table:"vendor_payment"},"xero:creditNote":{table:"credit_memo"},"xero:item":{table:"item"},"xero:manualJournal":{table:"journal_entry"},"xero:payment":{table:"customer_payment"},"xero:receiveMoney":{table:"deposit"},"xero:spendMoney":{table:"check"},"xero:supplierCreditNote":{table:"vendor_credit"},"azure_blob:file":{table:"source_file"}};function Cy(t,e){let r=Ay(t,e);return r?`${r.table}|${r.typeColumn??"-"}|${r.typeValue??"-"}`:void 0}function Iy(t,e,r){if(e===t)return"same-type-merge";let n=r?.fromSource,o=r?.toSource;if(n&&o&&n!==o){let s=Cy(t,n),i=Cy(e,o);if(s!==void 0&&s===i)return"same-type-merge"}return"different-type-mint"}var Ny=4;function hu(t){if(!/\bdefineConduit\s*[<(]/.test(t))throw new Error("not a conduit file \u2014 replay drives a defineConduit({...}) module");let e=po(t,"id"),r=po(t,"on"),n=po(t,"source"),o=po(t,"recordType"),s=[];for(let c of t.matchAll(/\bto\s*:\s*\{([^}]*)\}/g)){let l=po(c[1]??"","source"),u=po(c[1]??"","recordType");l&&u&&s.push({source:l,recordType:u})}let i=s[0],a=/cycleBudget\s*:\s*(\d+)/.exec(t);if(!e||!n||!o||!i)throw new Error("could not parse id/from.{source,recordType}/to.{source,recordType} \u2014 is it a reference-shape defineConduit({...})?");return{id:e,on:r,fromSource:n,fromRecordType:o,toSource:i.source,toRecordType:i.recordType,targets:s,cycleBudget:a?Number(a[1]):Ny}}function po(t,e){let r=new RegExp(`${e}\\s*:\\s*(['"\`])([^'"\`]+)\\1`).exec(t);return r?r[2]:void 0}function g0(t){return{recordType:t.recordType,event:t.event??"after_canonicalize_inbound",direction:"inbound",origin:"system",source:t.fromSource,payload:t.payload??{},tenantConfig:t.tenantConfig??{},metadata:{externalId:t.externalId,lines:t.lines??[]},bindings:t.bindings??[],atlasLinks:t.atlasLinks??{}}}async function zy(t,e,r="fixture",n={}){let o=e.name??r,s=[],i;try{i=hu(t)}catch(l){return{fixtureName:o,conduit:b0(),guards:s,whenPassed:!1,atlasResolutions:[],wouldWriteBindings:[],targets:[],logs:[],error:l instanceof Error?l.message:String(l)}}let a=g0(e),c=Object.entries(e.atlasLinks??{}).map(([l,u])=>({key:l,resolved:u}));try{let l=await ra(t,{resolveDir:n.resolveDir}),p=!!(await us(l,a,{member:"when",timeoutMs:ta})).whenResult;s.push({name:"from.when",status:p?"passed":"failed",detail:p?"gate returned true (or no gate) \u2014 conduit fires":"gate returned false \u2014 conduit short-circuits (dispatch records no ledger row)"});let b=e.hop??0;if(s.push({name:"cycle-budget",status:"skipped-offline",detail:`hop=${b} vs cycleBudget=${i.cycleBudget} is computable, but the chain's true hop count requires live conduit_event state \u2014 requires live sandbox state (skipped in fixture mode)`}),s.push({name:"opposite-direction-loop",status:"skipped-offline",detail:"reads committed conduit_event rows for the reverse leg \u2014 requires live sandbox state (skipped in fixture mode)"}),s.push({name:"idempotency",status:"skipped-offline",detail:"INSERT conduit_event ON CONFLICT (idempotency_key) \u2014 requires live sandbox state (skipped in fixture mode)"}),!p)return{fixtureName:o,conduit:i,guards:s,whenPassed:p,atlasResolutions:c,wouldWriteBindings:[],targets:[],logs:[]};let m=[],h=[],w=[];for(let R=0;R<i.targets.length;R+=1){let P=i.targets[R],z=await us(l,{...a,materialized:w.map(Ur=>({...Ur}))},{member:"transform",stepIndex:R,timeoutMs:ta});m.push(...z.effects.logs);let B=z.effects.result;if(!B||typeof B!="object")throw new Error(`conduit ${i.id} transform${i.targets.length>1?` (target ${R+1})`:""} returned no result (expected { patch, createdFrom? })`);let K=B,Ge=Iy(i.fromRecordType,P.recordType,{fromSource:i.fromSource,toSource:P.source}),{wouldWriteBindings:Je,masterAction:Jt}=y0(i,P,Ge,e);h.push({to:P,transformResult:K,applyBranch:Ge,wouldWriteBindings:Je,masterAction:Jt}),w.push({source:P.source,recordType:P.recordType,masterTable:`<${P.recordType} master, dry-run>`,masterRowId:`<dry-run target ${R+1}>`,externalId:null})}let S=h[0];return{fixtureName:o,conduit:i,guards:s,whenPassed:p,atlasResolutions:c,transformResult:S.transformResult,applyBranch:S.applyBranch,wouldWriteBindings:S.wouldWriteBindings,masterAction:S.masterAction,targets:h,logs:m}}catch(l){return{fixtureName:o,conduit:i,guards:s,whenPassed:!1,atlasResolutions:c,wouldWriteBindings:[],targets:[],logs:[],error:l instanceof Error?l.message:String(l)}}}function y0(t,e,r,n){return r==="same-type-merge"?{masterAction:`would MERGE the patch onto the existing ${t.fromRecordType} master (firing source '${t.fromSource}', external_id='${n.externalId}') and flip originating_system='hybrid'`,wouldWriteBindings:[{source:t.fromSource,externalId:n.externalId,role:"origin",why:"firing source \u2014 its identity already on-row; backfilled into txn_source_link"},{source:e.source,externalId:null,role:"replica",why:"target source bound to the SAME master; external_id null until writeback mints it"}]}:{masterAction:`would MINT a new ${e.recordType} master (source_system='${e.source}', originating_system='hybrid', external_id=null) + stamp created_from lineage`,wouldWriteBindings:[{source:e.source,externalId:null,role:"origin",why:"this conduit CREATED the new master, so the target is its origin; external_id null until writeback mints it"}]}}function b0(){return{id:"",fromSource:"",fromRecordType:"",toSource:"",toRecordType:"",targets:[],cycleBudget:Ny}}async function Ly(t){return Promise.reject(new Error("`replay --against sandbox` requires a running sandbox (TURBOBASE_SANDBOX=1 + a local DB). No local Postgres / k3d in this environment \u2014 deferred to a DB-backed run. Use `replay --fixture <file>` for the offline transform/apply-branch dry-run."))}async function jy(t){let e=await Oy.readFile(t,"utf8");return JSON.parse(e)}function Dy(t){return Oy.readFile(t,"utf8")}function My(t,e){let r=t.conduit;if(t.error)return`replay ${t.fixtureName}: ERROR \u2014 ${t.error}`;let n=t.targets.length>1;if(!e){if(!t.whenPassed)return`replay ${t.fixtureName} [${r.id}]: WHEN-FAILED (conduit short-circuited)`;if(n){let a=t.targets.reduce((c,l)=>c+l.wouldWriteBindings.length,0);return`replay ${t.fixtureName} [${r.id}]: OK \u2014 ${t.targets.length} targets in sequence (${a} binding(s) would be written)`}return`replay ${t.fixtureName} [${r.id}]: OK \u2014 ${t.applyBranch} (${t.wouldWriteBindings.length} binding(s) would be written)`}let o=[];o.push(`\u2501\u2501 replay ${t.fixtureName} \u2501\u2501`),o.push(`conduit: ${r.id}`);let s=(r.targets.length>0?r.targets:[{source:r.toSource,recordType:r.toRecordType}]).map(a=>`${a.source}.${a.recordType}`).join(" \u2192 ");o.push(`route: ${r.fromSource}.${r.fromRecordType} \u2192 ${s}${r.on?` on=${r.on}`:""}`),o.push(""),o.push("guards (dispatch order):");for(let a of t.guards){let c=a.status==="passed"?"\u2713":a.status==="failed"?"\u2717":"\u2298";o.push(` ${c} ${a.name} [${a.status}] \u2014 ${a.detail}`)}if(o.push(""),o.push("atlas.resolve (used by transform):"),t.atlasResolutions.length===0)o.push(" (none declared \u2014 every ctx.atlas.resolve(...) resolves to null in fixture mode)");else for(let a of t.atlasResolutions)o.push(` ${a.key} \u2192 ${a.resolved===null?"null (no Atlas link \u2014 noted)":a.resolved}`);if(!t.whenPassed)return o.push(""),o.push("transform: SKIPPED (from.when gate did not fire)."),o.join(`
|
|
2065
2065
|
`);let i=t.targets.length>0?t.targets:[{to:{source:r.toSource,recordType:r.toRecordType},transformResult:t.transformResult??{patch:{}},applyBranch:t.applyBranch??"different-type-mint",wouldWriteBindings:t.wouldWriteBindings,masterAction:t.masterAction??""}];if(i.forEach((a,c)=>{let l=n?` (target ${c+1}/${i.length}: ${a.to.source}.${a.to.recordType})`:"";o.push(""),o.push(`transform${l} \u2192 patch:`),o.push(w0(JSON.stringify(a.transformResult.patch??{},null,2)," ")),a.transformResult.createdFrom&&o.push(` createdFrom: ${JSON.stringify(a.transformResult.createdFrom)}`),o.push(""),o.push(`apply-branch${l}: ${a.applyBranch}`),o.push(` ${a.masterAction}`),o.push(""),o.push(`bindings that WOULD be written (txn_source_link)${l}:`);for(let u of a.wouldWriteBindings)o.push(` ${u.source} role=${u.role} external_id=${u.externalId??"null"} \u2014 ${u.why}`)}),t.logs.length>0){o.push(""),o.push("conduit logs:");for(let a of t.logs)o.push(` [${a.level}] ${a.message}${a.meta?` ${JSON.stringify(a.meta)}`:""}`)}return o.join(`
|
|
2066
2066
|
`)}function w0(t,e){return t.split(`
|
|
2067
2067
|
`).map(r=>e+r).join(`
|
|
@@ -3860,7 +3860,7 @@ ${o.map(ZP).join(`
|
|
|
3860
3860
|
`))}async function uw(t,e,r=_o){let n=[...new Set(t.map(a=>a.trim()).filter(Boolean))];n.length===0&&v("name at least one skill slug. `turbobase skills list` shows them.");let o=await vo(e,r);if(!await _a(`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 se("publishing",()=>o.skillsPublish(n))}catch(a){te(a,"skills:publish","publish a skill"),v(`publish failed: ${a instanceof Error?a.message:String(a)}`)}_(f.green(`published ${i.published.length} skill(s) \u2014 live now.`)),i.unknown.length>0&&(q(f.yellow(`no such skill: ${i.unknown.join(", ")}`)),process.exitCode=1),q(f.dim(`release v${i.version.version_no} \xB7 undo with: turbobase skills unpublish ${i.published.join(" ")}`))}async function dw(t,e,r=_o){let n=[...new Set(t.map(a=>a.trim()).filter(Boolean))];n.length===0&&v("name at least one skill slug. `turbobase skills list` shows them.");let o=await vo(e,r);if(!await _a(`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 se("unpublishing",()=>o.skillsUnpublish(n))}catch(a){te(a,"skills:publish","unpublish a skill"),v(`unpublish failed: ${a instanceof Error?a.message:String(a)}`)}_(f.green(`unpublished ${i.unpublished.length} skill(s) \u2014 retracted now.`)),q(f.dim(`release v${i.version.version_no} \xB7 re-publish with: turbobase skills publish ${i.unpublished.join(" ")}`)),i.unknown.length>0&&(q(f.yellow(`no such skill: ${i.unknown.join(", ")}`)),process.exitCode=1)}async function pw(t,e,r=_o){let n=Number(t);(!Number.isInteger(n)||n<1)&&v(`--version must be a release number (got '${t}'). \`turbobase skills releases\` lists them.`);let o=await vo(e,r);if(await _a(`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 se("rolling back",()=>o.skillsRollback(n));_(f.green(`rolled back to v${i.version.version_no}.`))}catch(i){te(i,"skills:publish","roll back a skills release"),v(`rollback failed: ${i instanceof Error?i.message:String(i)}`)}}async function hw(t,e=_o){let r=await vo(t,e),n;try{n=await se("reading the releases",()=>r.skillsReleases())}catch(o){te(o,"skills:publish","read the skills releases"),v(`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?f.green("*"):" ",i=o.release_note?` \u2014 ${o.release_note}`:"";_(`${s} v${o.version_no} ${o.skill_count} skill(s) ${f.dim(o.published_at)}${o.published_by?f.dim(` by ${o.published_by}`):""}${f.dim(i)}`)}}async function mw(t,e,r=_o){let n=[...new Set((t??[]).map(l=>l.trim()).filter(Boolean))];n.length===0&&v("name at least one skill to delete. `turbobase skills list` shows the slugs.");let o=await vo(e,r);if(!await _a(n.length===1?`Delete the skill '${n[0]}'?`:`Delete ${n.length} skills?
|
|
3861
3861
|
${n.join(`
|
|
3862
3862
|
`)}`,"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 se(`deleting ${l}`,()=>o.skillsDelete(l)))}catch(u){if(u instanceof le&&u.status===404){a.push(l);continue}i.length>0&&q(f.dim(`deleted before the error: ${i.map(p=>p.slug).join(", ")}`)),te(u,"skills:publish","delete a skill"),v(`delete failed on '${l}': ${u instanceof Error?u.message:String(u)}`)}i.length>0&&_(f.green(`deleted ${i.length} skill(s).`));let c=i.filter(l=>l.wasPublished).map(l=>l.slug);c.length>0&&q(f.yellow(`Published until now, so no longer injected: ${c.join(", ")}`)),a.length>0&&(q(f.yellow(`no such skill: ${a.join(", ")}`)),process.exitCode=1)}import{promises as fw}from"node:fs";import{join as CA,relative as IA,resolve as OA,sep as NA}from"node:path";var zA=new Set(["node_modules","dist","build","coverage",".git",".claude",".turbobase",".next",".svelte-kit",".vercel",".cache"]),LA=new Set([".mcp.json",".env"]);function jA(t){return LA.has(t)||t.startsWith(".env.")&&t!==".env.example"}function DA(t){return Buffer.compare(Buffer.from(t.toString("utf8"),"utf8"),t)===0}function MA(t){return t.includes(0)}async function gw(t){let e=OA(t),r={},n=[],o=[],s=[];async function i(a){let c=await fw.readdir(a,{withFileTypes:!0});for(let l of c){let u=CA(a,l.name);if(zA.has(l.name))continue;if(l.isDirectory()){await i(u);continue}if(!l.isFile())continue;let p=IA(e,u).split(NA).join("/");if(jA(l.name)){o.push(p);continue}let b=await fw.readFile(u);if(!DA(b)){n.push(p);continue}MA(b)&&s.push(p),r[p]=b.toString("utf8")}}return await i(e),{files:r,skipped:n,secrets:o,unstorable:s}}import{randomBytes as UA}from"node:crypto";var qA="/_tb/app-assets",BA=16,FA=/^[0-9a-f]{32}$/;function yw(){return UA(BA).toString("hex")}function ZA(t){return FA.test(t)}function bw(t){if(!ZA(t))throw new Error(`not a build id: ${t}`);return`${qA}/${t}/`}var ww=7;zu();var Lu=50*1024*1024;import va from"node:fs/promises";import _s from"node:path";var ju={esbuild:"0.28.1","isolated-vm":"6.1.2"},Du="@turbobase/isolated-vm-prebuilds";function Mu(t,e){return`${t}-${e}`}function Uu(t=process.versions){return`abi${t.modules}`}var _w=["darwin-x64","win32-arm64"];async function vw(t){let e=t.platform??process.platform,r=t.arch??process.arch,n=Mu(e,r),o=_s.join(t.home,"node_modules"),s=_s.join(o,...Du.split("/"),"prebuilds",n),i=_s.join(o,"isolated-vm","prebuilds",n),a;try{a=await va.readdir(s)}catch{return[]}let c=[];await va.mkdir(i,{recursive:!0});for(let l of a){if(!l.endsWith(".node"))continue;let u=_s.join(i,l);try{await va.copyFile(_s.join(s,l),u,va.constants?.COPYFILE_EXCL??1),c.push(l)}catch{}}return c}import{homedir as VA}from"node:os";import{join as xw}from"node:path";import{promises as qu}from"node:fs";function Sw(){let t=process.env.TURBOBASE_HOME??xw(VA(),".turbobase");return{dir:t,file:xw(t,"sandboxes.json")}}var HA={s:1e3,m:6e4,h:36e5,d:864e5};function KA(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*HA[e[2]]}function WA(t,e=Date.now()){return new Date(e+KA(t)).toISOString()}function GA(t,e=Date.now()){let r=Date.parse(t);return Number.isNaN(r)?!0:r<=e}function JA(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 YA(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 XA="[REDACTED]",vs=class extends Error{constructor(e){super(e),this.name="SandboxDataCopyUnavailableError"}},Bu=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 QA(t,e,r){let n={};for(let[o,s]of Object.entries(e)){let i=r[o];if(i===void 0)throw new Bu(t,o,"column has no PII classification (unknown = unsafe)");i==="pii"?n[o]=s==null?s:XA:n[o]=s}return n}function eC(t,e,r){return e.map(n=>QA(t,n,r))}var kw=new Set(["localhost","127.0.0.1","::1","0.0.0.0"]),tC=["oraclecloud.com","us-ashburn-1","amazonaws.com","rds.","supabase.co","turbobase.app","turbobase.io"];function rC(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 Fu(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 tC)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=rC(t);if(!kw.has(r))throw new Error(`local-only guard: target host '${r}' is not a recognised local Postgres (${[...kw].join(", ")}). \`turbobase sandbox create\` is LOCAL-ONLY.`)}function nC(t,e=oC){return`sbx-${t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,24)||"tenant"}-${e()}`}function oC(){return Math.floor(Math.random()*16777215).toString(16).padStart(6,"0")}function sC(t){return`turbobase_${t.replace(/[^a-z0-9]+/gi,"_").toLowerCase()}`}function iC(t,e){return`${(e??process.env.TURBOBASE_SANDBOX_BASE_URL??"http://localhost:5173").replace(/\/+$/,"")}/?sandbox=${encodeURIComponent(t)}`}async function Zu(){let{file:t}=Sw();try{let e=await qu.readFile(t,"utf8"),r=JSON.parse(e);return Array.isArray(r)?r:[]}catch(e){if(e.code==="ENOENT")return[];throw e}}async function $w(t){let{dir:e,file:r}=Sw();await qu.mkdir(e,{recursive:!0}),await qu.writeFile(r,`${JSON.stringify(t,null,2)}
|
|
3863
|
-
`,{mode:384})}async function aC(t){let e=await Zu();e.push(t),await $w(e)}async function Tw(t){let e=await Zu(),r=e.findIndex(o=>o.name===t);if(r===-1)return null;let[n]=e.splice(r,1);return await $w(e),n??null}async function xa(){return Zu()}function Vu(t,e=Date.now()){let r=[],n=[];for(let o of t)GA(o.expiresAt,e)?n.push(o):r.push(o);return{active:r,expired:n}}async function Rw(t,e){Fu(t.target());let r=t.dataCopyUnavailable?.();if(r)throw new vs(r);let n=JA(e.sample),o=e.now??Date.now(),s=(e.nameGen??(l=>nC(l)))(e.fromTenant),i=sC(s);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let l of a){let u=YA(l.rows,n),p=eC(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:WA(e.ttl,o),sampleFraction:n,url:iC(s,e.baseUrl)};return await aC(c),c}async function Ew(t,e){Fu(t.target());let n=(await xa()).find(o=>o.name===e);return n?(await t.dropDatabase(n.database),await Tw(e),n):null}async function Hu(t,e=Date.now()){Fu(t.target());let r=await xa(),{expired:n}=Vu(r,e),o=[];for(let s of n){try{await t.dropDatabase(s.database)}catch{continue}await Tw(s.name),o.push(s)}return o}var xs={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","skills:publish","docs:publish","mcp:data","mcp:write"],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"}},k2=Object.keys(xs).filter(t=>xs[t].mintable);var T2=xs.pat.prefix,cC=Object.entries(xs).map(([t,e])=>[e.prefix,t]),lC=/^[A-Za-z0-9_-]{20,}$/;function uC(t){if(typeof t!="string")return null;for(let[e,r]of cC)if(t.startsWith(e)&&lC.test(t.slice(e.length)))return r;return null}function Pw(t){return uC(t)==="pat"}cs();var dC=8e3,pC={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 hC(t){return pC[t]??`the instance rejected this token (${t}) \u2014 run \`turbobase auth login\` to store a fresh PAT`}async function mC(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(dC)})}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 Aw(t){return t.state==="accepted"?nt()?f.green("accepted"):"accepted":t.state==="rejected"?nt()?f.red(`rejected \xB7 ${t.reason}`):`rejected \xB7 ${t.reason}`:nt()?f.yellow("unreachable"):"unreachable"}async function Cw(t={},e=fetch){let r=await Ar(),{file:n}=Gi();if(r.length===0){_("not signed in. Run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),q(f.dim(`no sign-ins stored in ${n}.`));return}let o=await Qt(),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 mC(i.baseUrl,i.token,e)})));if(t.probe===!1)for(let i of s)_(`${i.active?"* ":" "}${i.baseUrl} (updated ${i.updatedAt})`);else if(nt())_(tr(s.map(i=>[i.active?f.green("*"):" ",f.bold(i.baseUrl),Aw(i.check),f.dim(i.updatedAt)]),{head:["","instance","token","updated"]}));else for(let i of s)_(`${i.active?"* ":" "}${i.baseUrl} ${Aw(i.check)} (updated ${i.updatedAt})`);q(f.dim(`tokens read from ${n}`));for(let i of s)i.check.state==="rejected"?(q(f.red(`${i.baseUrl}: ${hC(i.check.reason)}.`)),q(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"&&q(f.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 FU(t){let e=await Ar();e.length===0&&v("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),it()||v("this command needs an explicit <base-url> in a non-interactive shell.");try{return await at(t,e.map(r=>({value:r.baseUrl,label:r.baseUrl,description:`updated ${r.updatedAt}`})))}catch{v("cancelled.")}}async function ZU(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 VU(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 HU(t,e,r){let n=await gy({baseUrl:t,token:e}),o=(await Ar()).length-1;if(_(`signed in to ${n.baseUrl} \u2014 now the active instance`+(o>0?` (${o} other${o===1?"":"s"} stored; commands default here).`:".")),VU(n.baseUrl),!r)return;let s=await ZU(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 KU(t){let e=await uu(await ps(t),Mr(t)),r=Su(e);return r||v(`${t}: ${$u(e)}`),{...r,sourceCode:e}}async function eo(t,e,r,n){try{return await _e.access(t),"skipped"}catch{}return await _e.mkdir(Mr(t),{recursive:!0}),await _e.writeFile(t,e,{mode:n??(r?493:420)}),n!==void 0&&await _e.chmod(t,n),"wrote"}async function PT(t,e){if(CT(t)!==".gitignore")return eo(t,e.content,e.executable,e.mode);let r=await _e.readFile(t,"utf8").catch(()=>null);if(r===null)return eo(t,e.content,e.executable,e.mode);let n=gb(r);return n===null?"skipped":(await _e.writeFile(t,n,"utf8"),"merged")}function cg(t){if(t!==void 0){if(t==="junit"||t==="json")return t;v(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function gl(t){process.exit(t)}function WU(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 GU(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 JU(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 AT(t){return`${(Math.ceil(t/1048576*10)/10).toFixed(1).replace(/\.0$/,"")} MiB`}function mg({includeInternal:t=!0}={}){let e=new Og;e.name("turbobase").description("Build apps, author extensions, and drive your Turbobase instance.").version("1.34.2").option("--no-color","Disable ANSI colour + animations (also honoured via NO_COLOR / non-TTY)."),e.addHelpText("beforeAll",d=>d.command.parent?"":`${yu()}
|
|
3863
|
+
`,{mode:384})}async function aC(t){let e=await Zu();e.push(t),await $w(e)}async function Tw(t){let e=await Zu(),r=e.findIndex(o=>o.name===t);if(r===-1)return null;let[n]=e.splice(r,1);return await $w(e),n??null}async function xa(){return Zu()}function Vu(t,e=Date.now()){let r=[],n=[];for(let o of t)GA(o.expiresAt,e)?n.push(o):r.push(o);return{active:r,expired:n}}async function Rw(t,e){Fu(t.target());let r=t.dataCopyUnavailable?.();if(r)throw new vs(r);let n=JA(e.sample),o=e.now??Date.now(),s=(e.nameGen??(l=>nC(l)))(e.fromTenant),i=sC(s);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let l of a){let u=YA(l.rows,n),p=eC(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:WA(e.ttl,o),sampleFraction:n,url:iC(s,e.baseUrl)};return await aC(c),c}async function Ew(t,e){Fu(t.target());let n=(await xa()).find(o=>o.name===e);return n?(await t.dropDatabase(n.database),await Tw(e),n):null}async function Hu(t,e=Date.now()){Fu(t.target());let r=await xa(),{expired:n}=Vu(r,e),o=[];for(let s of n){try{await t.dropDatabase(s.database)}catch{continue}await Tw(s.name),o.push(s)}return o}var xs={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","skills:publish","docs:publish","mcp:data","mcp:write"],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"}},k2=Object.keys(xs).filter(t=>xs[t].mintable);var T2=xs.pat.prefix,cC=Object.entries(xs).map(([t,e])=>[e.prefix,t]),lC=/^[A-Za-z0-9_-]{20,}$/;function uC(t){if(typeof t!="string")return null;for(let[e,r]of cC)if(t.startsWith(e)&&lC.test(t.slice(e.length)))return r;return null}function Pw(t){return uC(t)==="pat"}cs();var dC=8e3,pC={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 hC(t){return pC[t]??`the instance rejected this token (${t}) \u2014 run \`turbobase auth login\` to store a fresh PAT`}async function mC(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(dC)})}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 Aw(t){return t.state==="accepted"?nt()?f.green("accepted"):"accepted":t.state==="rejected"?nt()?f.red(`rejected \xB7 ${t.reason}`):`rejected \xB7 ${t.reason}`:nt()?f.yellow("unreachable"):"unreachable"}async function Cw(t={},e=fetch){let r=await Ar(),{file:n}=Gi();if(r.length===0){_("not signed in. Run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),q(f.dim(`no sign-ins stored in ${n}.`));return}let o=await Qt(),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 mC(i.baseUrl,i.token,e)})));if(t.probe===!1)for(let i of s)_(`${i.active?"* ":" "}${i.baseUrl} (updated ${i.updatedAt})`);else if(nt())_(tr(s.map(i=>[i.active?f.green("*"):" ",f.bold(i.baseUrl),Aw(i.check),f.dim(i.updatedAt)]),{head:["","instance","token","updated"]}));else for(let i of s)_(`${i.active?"* ":" "}${i.baseUrl} ${Aw(i.check)} (updated ${i.updatedAt})`);q(f.dim(`tokens read from ${n}`));for(let i of s)i.check.state==="rejected"?(q(f.red(`${i.baseUrl}: ${hC(i.check.reason)}.`)),q(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"&&q(f.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 FU(t){let e=await Ar();e.length===0&&v("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),it()||v("this command needs an explicit <base-url> in a non-interactive shell.");try{return await at(t,e.map(r=>({value:r.baseUrl,label:r.baseUrl,description:`updated ${r.updatedAt}`})))}catch{v("cancelled.")}}async function ZU(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 VU(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 HU(t,e,r){let n=await gy({baseUrl:t,token:e}),o=(await Ar()).length-1;if(_(`signed in to ${n.baseUrl} \u2014 now the active instance`+(o>0?` (${o} other${o===1?"":"s"} stored; commands default here).`:".")),VU(n.baseUrl),!r)return;let s=await ZU(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 KU(t){let e=await uu(await ps(t),Mr(t)),r=Su(e);return r||v(`${t}: ${$u(e)}`),{...r,sourceCode:e}}async function eo(t,e,r,n){try{return await _e.access(t),"skipped"}catch{}return await _e.mkdir(Mr(t),{recursive:!0}),await _e.writeFile(t,e,{mode:n??(r?493:420)}),n!==void 0&&await _e.chmod(t,n),"wrote"}async function PT(t,e){if(CT(t)!==".gitignore")return eo(t,e.content,e.executable,e.mode);let r=await _e.readFile(t,"utf8").catch(()=>null);if(r===null)return eo(t,e.content,e.executable,e.mode);let n=gb(r);return n===null?"skipped":(await _e.writeFile(t,n,"utf8"),"merged")}function cg(t){if(t!==void 0){if(t==="junit"||t==="json")return t;v(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function gl(t){process.exit(t)}function WU(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 GU(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 JU(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 AT(t){return`${(Math.ceil(t/1048576*10)/10).toFixed(1).replace(/\.0$/,"")} MiB`}function mg({includeInternal:t=!0}={}){let e=new Og;e.name("turbobase").description("Build apps, author extensions, and drive your Turbobase instance.").version("1.36.0").option("--no-color","Disable ANSI colour + animations (also honoured via NO_COLOR / non-TTY)."),e.addHelpText("beforeAll",d=>d.command.parent?"":`${yu()}
|
|
3864
3864
|
`),e.hook("preAction",(d,g)=>{Pb(e.opts(),g.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 g=Mt(d.baseUrl);d.token||v(`auth login needs --token. Mint a PAT in your Turbobase app (Settings \u203A Access tokens), then:
|
|
3865
3865
|
${ys} auth login --base-url ${g} --token tb_pat_\u2026`),Pw(d.token)||v("--token does not look like a Turbobase PAT (tb_pat_\u2026)."),await HU(g,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=>Cw({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 g=d??await FU("Pick the active instance:"),x=await Ji(g);if(!x){let T=await Ar();v(`no stored sign-in for ${Mt(g)}.`+(T.length?` Stored: ${T.map(k=>k.baseUrl).join(", ")}.`:"")+` Run \`turbobase auth login --base-url ${Mt(g)} --token tb_pat_\u2026\`.`)}_(`active instance \u2192 ${x.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 Ar();for(let k of T)await ou(k.baseUrl);_(`removed ${T.length} stored sign-in${T.length===1?"":"s"}.`);return}let g=await ou(d.baseUrl);g||v(d.baseUrl?`no stored sign-in for ${Mt(d.baseUrl)}.`:"nothing to sign out of (or ambiguous \u2014 pass --base-url / --all).");let x=await Qt();_(`signed out of ${g}.`+(x?` Active instance \u2192 ${x.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:g,token:x}=await Q(d.baseUrl),T;try{T=await On({baseUrl:g,token:x,recordsOnly:d.recordsOnly})}catch(y){let $=y instanceof Error?y.message:String(y);if(!d.recordsOnly&&/openapi\.json failed \(401/.test($)){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 On({baseUrl:g,token:x,recordsOnly:!0})}catch(E){v(`types generation failed: ${E instanceof Error?E.message:String(E)}`)}}else v(`types generation failed: ${$}`)}let k=ye(process.cwd(),d.out);await _e.writeFile(k,T,"utf8"),_(`wrote ${k} (${T.split(`
|
|
3866
3866
|
`).length} lines).`)}),Ib(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:g,token:x}=await Q(d.baseUrl),T=new URLSearchParams;d.records&&T.set("records",d.records),d.query&&T.set("query",d.query);let k=T.toString(),y=await fetch(`${g}/v1/catalog/grounding${k?`?${k}`:""}`,{headers:{Authorization:`Bearer ${x}`,Accept:"application/json"}});y.ok||v(`grounding fetch failed: GET /v1/catalog/grounding \u2192 ${y.status}`);let E=((await y.json()).markdown??"").trim(),N=ye(process.cwd(),d.out),L=d.records?` records=${d.records}`:"",M=`<!-- GENERATED by \`turbobase project grounding\` against ${g}${L} \u2014 do not edit; re-run to refresh. -->
|
|
@@ -3890,7 +3890,7 @@ samples init: ${E} written, ${N} skipped.`)});let dr=e.command("guardrails").des
|
|
|
3890
3890
|
`+f.dim("create one with `turbobase guardrails create <name> --source <s> --record <r>`"));_(tr(k.map(bl),{head:IT})),_(f.dim(" first match wins; lower PRIO is evaluated first."))}),dr.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,g)=>{let{baseUrl:x,token:T}=await Q(g.baseUrl),k=new G({baseUrl:x,token:T}),y;try{y=await k.guardrailCreate({name:d,source:g.source,record:g.record,decision:g.decision,...g.op?{op:g.op}:{},...g.target?{target:g.target}:{},...g.actor?{actor:g.actor}:{},...Number.isFinite(g.priority)?{priority:Number(g.priority)}:{},...g.when?{when:g.when}:{},...g.reason?{reason:g.reason}:{},...g.disabled?{enabled:!1}:{},...g.confirmFreezeAll?{confirmFreezeAll:!0}:{}})}catch($){te($,"extensions:deploy","create a guardrail"),v(`create guardrail failed: ${$ instanceof Error?$.message:String($)}`)}_(`${f.green("\u2713")} guardrail ${f.bold(y.name??y.id)} created${y.status==="active"?"":f.dim(` (${y.status} \u2014 activate it to take effect)`)}`),_(f.dim(` ${y.decision} ${y.source}/${y.record??y.op}${y.actor?` actor=${y.actor}`:""}${y.when?` where ${y.when}`:""} @prio ${y.priority}`)),_(f.dim(` verify with: turbobase guardrails test --source ${y.source==="*"?"<source>":y.source} --record <record>`))}),dr.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,g)=>{g.decision===void 0&&g.priority===void 0&&g.when===void 0&&g.reason===void 0&&v("edit: nothing to change \u2014 pass --decision, --priority, --when or --reason.");let{baseUrl:x,token:T}=await Q(g.baseUrl),k=new G({baseUrl:x,token:T}),y;try{y=await k.guardrailUpdate(d,{...g.decision!==void 0?{decision:g.decision}:{},...Number.isFinite(g.priority)?{priority:Number(g.priority)}:{},...g.when!==void 0?{when:g.when}:{},...g.reason!==void 0?{reason:g.reason}:{}})}catch($){te($,"extensions:deploy","edit a guardrail"),v(`edit guardrail failed: ${$ instanceof Error?$.message:String($)}`)}_(`${f.green("\u2713")} guardrail ${f.bold(y.name??y.id)} updated`),_(f.dim(` ${y.decision} ${y.source}/${y.record??y.op}${y.when?` where ${y.when}`:""} @prio ${y.priority}`))});for(let[d,g,x]of[["activate","active","activated"],["deactivate","inactive","deactivated"]])dr.command(`${d} <name>`).description(g==="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,k)=>{let{baseUrl:y,token:$}=await Q(k.baseUrl),E=new G({baseUrl:y,token:$});try{await E.guardrailUpdate(T,{status:g})}catch(N){te(N,"extensions:deploy",`${d} a guardrail`),v(`${d} guardrail failed: ${N instanceof Error?N.message:String(N)}`)}_(`${f.green("\u2713")} guardrail ${f.bold(T)} ${x}`),g==="inactive"&&_(f.dim(" writes it was gating now fall through to the other rules."))});dr.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,g)=>{let{baseUrl:x,token:T}=await Q(g.baseUrl),k=new G({baseUrl:x,token:T});try{await k.guardrailDelete(d)}catch(y){te(y,"extensions:deploy","delete a guardrail"),v(`delete guardrail failed: ${y instanceof Error?y.message:String(y)}`)}_(`${f.green("\u2713")} guardrail ${f.bold(d)} deleted`)}),dr.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,g)=>{let{baseUrl:x,token:T}=await Q(g.baseUrl),k=new G({baseUrl:x,token:T}),y=N=>N==="deny"?"deny-by-default (allow-list)":"allow-by-default (deny-list)";if(d===void 0){let N;try{N=await k.guardrailPostureGet()}catch(L){te(L,"extensions:deploy","read the guardrail posture"),v(`read posture failed: ${L instanceof Error?L.message:String(L)}`)}_(`guardrail posture: ${f.bold(y(N.mode))}`),_(f.dim(N.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"&&v("posture must be 'allow' or 'deny'.");let E;try{E=await k.guardrailPostureSet($)}catch(N){te(N,"extensions:deploy","set the guardrail posture"),v(`set posture failed: ${N instanceof Error?N.message:String(N)}`)}_(`${f.green("\u2713")} guardrail posture set to ${f.bold(y(E.mode))}`),E.mode==="deny"&&_(f.dim(" Every record with no active ALLOW rule is now frozen for write-back. Verify with `guardrails test --source <s> --record <r>`."))}),dr.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 g;if(d.fields)try{g=JSON.parse(d.fields)}catch(E){v(`--fields must be valid JSON: ${E instanceof Error?E.message:String(E)}`)}let{baseUrl:x,token:T}=await Q(d.baseUrl),k=new G({baseUrl:x,token:T}),y;try{y=await k.guardrailTest({source:d.source,record:d.record,...d.actor?{actor:d.actor}:{},...d.externalId?{externalId:d.externalId}:{},...g?{fields:g}:{},...d.op?{op:d.op}:{},...d.phase?{phase:d.phase}:{}})}catch(E){te(E,"extensions:deploy","test guardrails"),v(`guardrails test failed: ${E instanceof Error?E.message:String(E)}`)}if(d.json)return _(JSON.stringify(y,null,2));let $=`${d.source}/${y.op??d.record}`;y.degraded?(_(`${f.red("\u2717")} ${$} \u2014 guardrail policy is UNREADABLE; writes fail closed`),_(f.dim(` ${y.reason}`))):y.allowed?_(`${f.green("\u2713")} ${$} \u2014 ${y.decision}`+(y.blockedBy?f.dim(` (rule ${y.blockedBy})`):f.dim(" (no rule matched)"))):(_(`${f.red("\u2717")} ${$} \u2014 ${y.decision} by ${f.bold(y.blockedBy??"?")} at the ${y.phase} gate`),y.message&&_(f.dim(` ${y.message}`))),y.decision==="require_confirm"&&_(f.yellow(" this write is HELD for operator approval, not refused.")),y.allowed||(process.exitCode=1)});let Xo=e.command("tables").description("Author custom tables on your instance (create, evolve, inspect)."),Ei=(d,g={})=>{let x=`${f.bold(d.table)} ${f.dim(d.displayName)}`;_(d.exists===!1?`${x} ${f.dim("(new)")}`:x);for(let T of d.fields){let k=[T.required?"required":null].filter(Boolean).join(", "),y=g.showClaimants&&T.claimedBy?.length?` ${f.dim(`\u2190 ${T.claimedBy.join(", ")}`)}`:"";_(` ${T.column.padEnd(28)} ${T.type}${k?` (${k})`:""}${y}`)}g.showClaimants&&d.claimants.length>1&&_(f.dim(` claimed by ${d.claimants.join(", ")}`))};function Pi(d){if(d instanceof le&&d.status===409){let g=d.body;g?.error==="table_conflict"&&g.message&&(q(g.message),process.exit(1))}v(`tables: ${d instanceof Error?d.message:String(d)}`)}let fg=async d=>{let g=Ot(ye(process.cwd(),d),"public","turbobase.json"),x=await _e.readFile(g,"utf8").catch(()=>null);if(x===null)return null;let T;try{T=JSON.parse(x)}catch(y){v(`${g} is not valid JSON: ${y instanceof Error?y.message:String(y)}`)}let k=T.tables;return k===void 0?{tables:[],manifestPath:g}:(Array.isArray(k)||v(`${g}: "tables" must be an array.`),{tables:k,manifestPath:g})};Xo.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:g,token:x}=await Q(d.baseUrl),T=new G({baseUrl:g,token:x}),k;try{k=await T.tablesList()}catch(y){te(y,"extensions:deploy","list tables"),v(`list tables failed: ${y instanceof Error?y.message:String(y)}`)}if(d.json)return _(JSON.stringify(k,null,2));if(k.length===0)return _("no custom tables yet \u2014 create one with `turbobase tables create <slug>`.");_(tr(k.map(y=>[y.table,y.displayName,String(y.fields.length),y.status,y.claimedBy.join(", ")||"\u2014"]),{head:["TABLE","NAME","FIELDS","STATUS","CLAIMED BY"]}))}),Xo.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,g)=>{let x=await fg(d??".");if(x||v(`no public/turbobase.json under ${ye(process.cwd(),d??".")}.`),x.tables.length===0)return _(`${x.manifestPath} declares no tables \u2014 nothing to plan.`);let{baseUrl:T,token:k}=await Q(g.baseUrl),y=new G({baseUrl:T,token:k}),$;try{$=await se("planning tables",()=>y.tablesPlan({claimedBy:g.as,tables:x.tables}))}catch(E){te(E,"extensions:deploy","plan tables"),Pi(E)}_(`claiming as ${f.bold($.claimedBy)}`);for(let E of $.tables)Ei(E,{showClaimants:!0});_(""),_(f.dim("nothing was applied \u2014 run `turbobase tables apply` (or deploy the app) to make it so."))}),Xo.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,g)=>{let x=await fg(d??".");if(x||v(`no public/turbobase.json under ${ye(process.cwd(),d??".")}.`),x.tables.length===0)return _(`${x.manifestPath} declares no tables \u2014 nothing to apply.`);let{baseUrl:T,token:k}=await Q(g.baseUrl),y=new G({baseUrl:T,token:k}),$;try{$=await se("applying tables",()=>y.tablesApply({claimedBy:g.as,tables:x.tables}))}catch(E){te(E,"extensions:deploy","apply tables"),Pi(E)}for(let E of $.tables)Ei(E,{showClaimants:!0});_(`applied ${$.tables.length} table${$.tables.length===1?"":"s"} as ${$.claimedBy}.`)}),Xo.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,g)=>[...g??[],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,g)=>{let x=g.field.map(E=>{let[N,L,M]=E.split(":");return(!N||!L)&&v(`--field "${E}" must be name:type[:required] (e.g. title:text:required).`),M!==void 0&&M!=="required"&&v(`--field "${E}": the third part may only be "required".`),{name:N,type:L,...M==="required"?{required:!0}:{}}}),{baseUrl:T,token:k}=await Q(g.baseUrl),y=new G({baseUrl:T,token:k}),$;try{$=await se(`creating cr_${d}`,()=>y.tablesApply({tables:[{slug:d,displayName:g.name??d,fields:x,...g.restrictToOwner?{rowLevelOwner:!0}:{}}]}))}catch(E){te(E,"extensions:deploy","create a table"),Pi(E)}for(let E of $.tables)Ei(E);_(""),_(f.dim("grant access under Settings \u203A Users, or in the Records page permissions matrix."))}),Xo.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,g)=>{g.confirm!==d&&v(`drop refused: --confirm "${g.confirm}" does not match "${d}".`);let{baseUrl:x,token:T}=await Q(g.baseUrl),k=new G({baseUrl:x,token:T}),y;try{y=await se(`dropping cr_${d}`,()=>k.tablesDrop(d,g.confirm))}catch($){te($,"extensions:deploy","drop a table"),v(`drop failed: ${$ instanceof Error?$.message:String($)}`)}if(!y.removed)return _(`no table named "${d}" on this instance \u2014 nothing dropped.`);_(`dropped ${y.table} and every row in it.`)});let wl=e.command("sandbox").description("Ephemeral local authoring sandboxes.");wl.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 g=ug(),x;try{x=await Rw(g,{fromTenant:d.from,ttl:d.ttl,sample:d.sample,baseUrl:d.baseUrl})}catch(T){T instanceof vs&&v(T.message),v(`sandbox create failed: ${T instanceof Error?T.message:String(T)}`)}_(`sandbox ${x.name} ready.`),_(` source: ${x.fromTenant} (sampled ${Math.round(x.sampleFraction*100)}%, redacted)`),_(` url: ${x.url}`),_(` expires: ${x.expiresAt}`)}),wl.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 g=ug();await Hu(g).catch(()=>[]);let x;try{x=await Ew(g,d)}catch(T){v(`sandbox destroy failed: ${T instanceof Error?T.message:String(T)}`)}x||v(`no sandbox named '${d}'.`),_(`destroyed sandbox ${x.name} (dropped ${x.database}).`)}),wl.command("list").description("List active sandboxes with TTL/expiry; reaps any past their TTL.").action(async()=>{let d=ug(),g=await Hu(d).catch(()=>[]),x=await xa(),{active:T}=Vu(x);if(T.length===0)_("no active sandboxes.");else{for(let k of T)_(` ${k.name} from=${k.fromTenant} expires=${k.expiresAt} ${k.url}`);_(`
|
|
3891
3891
|
${T.length} active sandbox(es).`)}g.length>0&&_(`(reaped ${g.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.",da).action(async d=>{let{baseUrl:g,token:x}=await Q(d.baseUrl),T=new G({baseUrl:g,token:x}),k;try{k=await se("exporting instance config",()=>T.configExport())}catch(E){E instanceof le&&(E.status===401||E.status===403)&&v("ext export forbidden \u2014 the stored PAT lacks extensions:deploy. Re-run `turbobase auth login`."),v(`ext export failed: ${E instanceof Error?E.message:String(E)}`)}let y=typeof k.serialized=="string"?k.serialized:`${JSON.stringify(k.manifest??k,null,2)}
|
|
3892
3892
|
`,$=ye(process.cwd(),d.out,fs);await _e.mkdir(Mr($),{recursive:!0}),await _e.writeFile($,y,"utf8"),_(`wrote ${d.out}/${fs} (${y.split(`
|
|
3893
|
-
`).length} lines).`)});let gg=o.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),OT=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],yg=()=>process.env.TURBOBASE_HOME?.trim()||Ot(BU(),".turbobase"),NT=()=>"1.
|
|
3893
|
+
`).length} lines).`)});let gg=o.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),OT=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],yg=()=>process.env.TURBOBASE_HOME?.trim()||Ot(BU(),".turbobase"),NT=()=>"1.36.0",Ai=async()=>Promise.all(OT.map(async d=>{try{return await import(d.name),{...d,present:!0}}catch{return{...d,present:!1}}}));gg.command("status").description("Report which optional native packages are installed.").action(async()=>{let d=await Ai();for(let g of d)_(`${g.present?ze.success("installed"):ze.warn("missing")} ${f.bold(g.name)} \u2014 ${g.needs}`);d.some(g=>!g.present)&&_(`
|
|
3894
3894
|
run \`${ys} ext extras install\` to fetch them into ${yg()}.`)}),gg.command("install").description("Install the optional native packages into $TURBOBASE_HOME (needs npm).").action(async()=>{let d=await Ai();if(d.every(N=>N.present)){_(ze.success("extras already installed \u2014 nothing to do."));return}let g=yg();await _e.mkdir(g,{recursive:!0});let x=d.filter(N=>!N.present).map(N=>N.name),T=Mu(process.platform,process.arch),k=async(N,L)=>{let{code:M,stderr:re}=await Ri("npm",["install","--prefix",g,"--no-audit","--no-fund",...N]);M!==0&&v(`extras install failed while installing ${L} (npm exited ${M}).
|
|
3895
3895
|
${re.trim()}
|
|
3896
3896
|
These are native packages \u2014 they need npm and a working toolchain. Everything except \`test\` and \`replay\` works without them.`)},y=x.filter(N=>N!=="isolated-vm");if(y.length>0){let N=y.map(L=>`${L}@${ju[L]??"latest"}`);_(`installing ${N.join(", ")} \u2192 ${g}/node_modules \u2026`),await k(N,y.join(", "))}if(x.includes("isolated-vm")){let N=`isolated-vm@${ju["isolated-vm"]}`;if(_(`installing ${N} \u2192 ${g}/node_modules \u2026`),await k(["--ignore-scripts",N],"isolated-vm"),_w.includes(T)){let M=`${Du}@${NT()}`;_(`fetching ${M} (no upstream binary for ${T}) \u2026`);let{code:re}=await Ri("npm",["install","--prefix",g,"--no-audit","--no-fund","--ignore-scripts",M]);re!==0&&_(ze.warn(`could not fetch ${M} \u2014 falling back to a source build.`))}let L=await vw({home:g});L.length>0&&_(` ${ze.success("prebuilt")} ${L.join(", ")} \u2192 isolated-vm/prebuilds/${T}`)}let $=await Ai(),E=$.filter(N=>!N.present).map(N=>N.name);if(E.includes("isolated-vm")){_(ze.warn(`no prebuilt isolated-vm for ${T} / Node ${process.versions.node} (${Uu()}) \u2014 building from source. This needs Python and a C++ toolchain, and takes a few minutes.`));let{code:N}=await Ri("npm",["rebuild","--prefix",g,"isolated-vm"]);N===0&&($=await Ai(),E=$.filter(L=>!L.present).map(L=>L.name))}E.length>0&&v(`npm succeeded but ${E.join(", ")} still will not load \u2014 no prebuilt binary for ${T} / Node ${process.versions.node} (${Uu()}), and the source build did not produce one either. Everything except \`test\` and \`replay\` works without them.`),_(ze.success("extras installed \u2014 `turbobase ext test` and `turbobase ext replay` are ready."))});let to=e.command("release").description("Ship a release to your instance: deploy | status | rollback | list.");return to.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 g=d.target==="approved"?"approved":d.target==="auto"?"auto":null;g||v(`--target must be 'auto' or 'approved' (got '${d.target}').`);let x=await rq(ye(process.cwd(),d.project??".")),T=Object.keys(x).length;!d.solution&&T===0&&v("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 dg(["rev-parse",d.ref])??d.ref:null)??process.env.GITHUB_SHA??await dg(["rev-parse","HEAD"]),$=d.gitRepo??process.env.GITHUB_REPOSITORY??await dg(["remote","get-url","origin"]),E=d.gitRef??process.env.GITHUB_REF??null,N=d.prUrl??process.env.PR_URL??null,{baseUrl:L,token:M}=await Q(d.baseUrl),re=new G({baseUrl:L,token:M}),U;try{U=await re.releaseDeploy({solutionId:d.solution??null,target:g,approvedBy:d.approvedBy??null,files:T>0?x:null,gitRepo:$,gitRef:E,gitSha:y,prUrl:N})}catch(ne){te(ne,"releases:write","release deploy"),ne instanceof le&&ne.status===404&&v("solution_not_found \u2014 no solution with that id."),v(`release deploy failed: ${ne instanceof Error?ne.message:String(ne)}`)}_(`release ${U.releaseId} enqueued \u2192 taskId=${U.taskId} (target=${g}`+(T>0?`, uploaded ${T} bundle file(s)`:", live-fetch")+")."),d.follow&&await Ru(()=>re.releaseStatus(U.releaseId),`release ${U.releaseId}`,d.intervalMs)}),to.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,g)=>{let{baseUrl:x,token:T}=await Q(g.baseUrl),k=new G({baseUrl:x,token:T}),y;try{y=await k.releaseFiles(d)}catch(N){N instanceof le&&N.status===404&&v(`release ${d} not found.`),N instanceof le&&N.status===409&&v(`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.`),te(N,"releases:write","pull a release"),v(`release pull failed: ${N instanceof Error?N.message:String(N)}`)}let $=ye(process.cwd(),g.dir),E=0;for(let[N,L]of[["owned",y.owned],["context",y.context]])for(let[M,re]of Object.entries(L)){let U=Ot($,N,M);await _e.mkdir(Mr(U),{recursive:!0}),await _e.writeFile(U,re,"utf8"),E++}await _e.writeFile(Ot($,".turbobase-release.json"),JSON.stringify({releaseId:y.releaseId,version:y.version,contentDigest:y.contentDigest},null,2),"utf8"),_(`pulled release ${y.releaseId} (${y.status}) \u2192 ${$}
|
package/dist/turbobase.js
CHANGED
|
@@ -2014,7 +2014,7 @@ export { Label };
|
|
|
2014
2014
|
`}var sA,Ai,vb=j(()=>{Jr();sA=10*1024*1024,Ai=class{constructor(e){this._maxBufferSize=e?.maxBufferSize??sA}append(e){if((this._buffer?.length??0)+e.length>this._maxBufferSize)throw this.clear(),new Error(`ReadBuffer exceeded maximum size of ${this._maxBufferSize} bytes`);this._buffer=this._buffer?Buffer.concat([this._buffer,e]):e}readMessage(){if(!this._buffer)return null;let e=this._buffer.indexOf(`
|
|
2015
2015
|
`);if(e===-1)return null;let r=this._buffer.toString("utf8",0,e).replace(/\r$/,"");return this._buffer=this._buffer.subarray(e+1),iA(r)}clear(){this._buffer=void 0}}});import _b from"node:process";var Ci,Sb=j(()=>{vb();Ci=class{constructor(e=_b.stdin,r=_b.stdout,n){this._stdin=e,this._stdout=r,this._started=!1,this._ondata=o=>{try{this._readBuffer.append(o),this.processReadBuffer()}catch(s){this.onerror?.(s),this.close().catch(()=>{})}},this._onerror=o=>{this.onerror?.(o)},this._readBuffer=new Ai({maxBufferSize:n?.maxBufferSize})}async start(){if(this._started)throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror)}processReadBuffer(){for(;;)try{let e=this._readBuffer.readMessage();if(e===null)break;this.onmessage?.(e)}catch(e){this.onerror?.(e)}}async close(){this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror),this._stdin.listenerCount("data")===0&&this._stdin.pause(),this._readBuffer.clear(),this.onclose?.()}send(e){return new Promise(r=>{let n=wb(e);this._stdout.write(n)?r():this._stdout.once("drain",r)})}}});var kb,Tb=j(()=>{kb=[]});var Ob={};Yd(Ob,{AUTHORING_MCP_SERVER_NAME:()=>Eb,AUTHORING_MCP_SERVER_VERSION:()=>Pb,AUTHORING_RESOURCES:()=>mA,AUTHORING_TOOLS:()=>pA,AUTHORING_TOOLS_PUBLIC:()=>Ib,buildAuthoringMcpServer:()=>Cb,resolveSamplesDir:()=>Ab,runAuthoringMcpServer:()=>hA});import{promises as aA}from"node:fs";import{isAbsolute as lA,resolve as Ii,sep as cA}from"node:path";import{z as M}from"zod";function $b(){return Rb??=import("../mcp/grounding.js").then(e=>e,()=>null),Rb}function Ab(t,e){if(e===void 0)return Ii(process.cwd(),t??"samples");let r=Ii(e);if(t===void 0)return r;if(lA(t))return null;let n=Ii(r,t);return n===r||n.startsWith(r+cA)?n:null}function Te(t){return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}async function uA(t,e={}){let r=e.fetchImpl??fetch;try{let n=await r(`${t.baseUrl.replace(/\/+$/,"")}/v1/extensions`,{headers:{Authorization:`Bearer ${t.token}`,Accept:"application/json"}});return n.ok?((await n.json()).extensions??[]).map(s=>s.hookId).filter(s=>typeof s=="string"):null}catch{return null}}async function Cb(t={}){async function e(){if(t.resolveTenant)return t.resolveTenant();let y=await mt(t.baseUrl);if(!y)throw new Error("not signed in \u2014 run `turbobase auth login` (or set a single stored instance).");return{baseUrl:y.baseUrl,token:y.token}}let r=t.grounding??{},n=await $b(),o=null;if(n)try{let y=await e();o={surface:"mcp-authoring",baseUrl:y.baseUrl,token:y.token}}catch{o=null}let s=n&&o?await n.loadActiveSources(o,r):[],i={...r,loadActiveSourcesImpl:async()=>s},a=[],l=[];if(n&&o){try{a=await n.listMcpPromptSkills(o,i)}catch{a=[]}try{l=(await n.listRecipesForSurface(o,i)).map(y=>({id:y.id,record_type:y.record_type,intent:y.intent}))}catch{l=[]}}let d=o?await uA(o,r):null,p=new Pi({name:Eb,version:Pb},n?{instructions:n.renderAuthoringInstructions({baseUrl:t.displayBaseUrl??o?.baseUrl??t.baseUrl??"this instance",sources:s,recipeIntents:l,promptNames:a.map(y=>y.id),deployedExtensions:d,docsToken:r.docsToken??n.docsPublicToken()})}:void 0);if(n&&o){let y=o;for(let k of a)p.registerPrompt(k.id,{title:k.name,description:k.description},async()=>{let R=await n.getMcpPromptSkill(y,k.id,r);if(!R)throw new Error(`unknown prompt: ${k.id}`);return{description:R.description,messages:[{role:"user",content:{type:"text",text:(R.prompts??[]).join(`
|
|
2016
2016
|
|
|
2017
|
-
`)}}]}})}p.registerTool("catalog_get",{title:"Get the instance catalog",description:"Fetch the instance's record-type catalog (/v1/catalog/records).",inputSchema:{}},async()=>{let{baseUrl:y,token:k}=await e(),R=await fetch(`${y}/v1/catalog/records`,{headers:{Authorization:`Bearer ${k}`,Accept:"application/json"}});if(!R.ok)throw new Error(`catalog fetch failed: ${R.status}`);return Te(await R.json())}),p.registerTool("record_anatomy",{title:"Record anatomy + display contract",description:"Fetch ONE record type in depth (/v1/catalog/records/:type): anatomy markdown (field meanings, sublists, gotchas, relations), authored recipe display contracts (columns/labels/formats), semantic column metadata and the record's plain-English description. Read this BEFORE building UI over a record \u2014 column names alone routinely mislead (e.g. tran_id is the human-facing document number, entity_id has a display twin entity_name). Also reports `unpopulatedColumns`: columns that EXIST but carry no value in any sampled row on this instance (currency_name is the classic \u2014 currency_id holds the data). Rendering one of those produces a blank column while faithfully following the display contract, so check it before choosing what to show. And reports `lines`: the record's LINE tables with their columns, including line-level custom fields (custcol_*). Check the lines before concluding a link is absent \u2014 a transaction routinely carries a reference on its LINES while the header field is NULL, and reading only the header reports a real link as missing.",inputSchema:{recordType:M.string().describe('The catalog recordType, e.g. "purchaseOrder" or "customrecord_inbound_shipment".'),source:M.string().optional().describe('Which connector publishes this recordType, e.g. "netsuite" or "stripe". 29 record types are published by more than one source ("customer" by six) and each is a different shape behind the same name. Omit it and the catalog collapse rule picks one; the answer names its choice in `source`.')}},async({recordType:y,source:k})=>{let{baseUrl:R,token:$}=await e(),P=k?`?source=${encodeURIComponent(k)}`:"",I=await fetch(`${R}/v1/catalog/records/${encodeURIComponent(y)}${P}`,{headers:{Authorization:`Bearer ${$}`,Accept:"application/json"}});if(I.status===404)throw new Error(`unknown record type "${y}" \u2014 list them with catalog_get.`);if(!I.ok)throw new Error(`record anatomy fetch failed: ${I.status}`);return Te(await I.json())}),p.registerTool("common_columns",{title:"Columns common to several record types",description:"Given SEVERAL record types, return the columns present on ALL of them (safe to select in one shared query shape) and the columns present on only some, with exactly which types lack each. Use this BEFORE writing a query that spans record types \u2014 transaction tables are NOT uniform (memo, total, created_from_id and even pg_row_id are missing from some), and PostgREST rejects an unknown column with 42703 and fails the ENTIRE request, so one wrong name loses the whole table rather than one field. Record types the caller cannot read come back in unknownRecordTypes \u2014 if that is non-empty the intersection covers fewer types than you asked for.",inputSchema:{recordTypes:M.array(M.string()).min(1).describe('Catalog recordTypes, e.g. ["purchaseOrder","vendorBill","itemReceipt"].'),source:M.string().optional().describe("Pin every requested type to ONE connector, e.g. \"netsuite\". Without it each name collapses by the catalog rule, so an intersection can silently mix two sources' tables for the same name. The response's `resolvedSources` names what it used.")}},async({recordTypes:y,source:k})=>{let{baseUrl:R,token:$}=await e(),P=new URLSearchParams({types:y.join(",")});k&&P.set("source",k);let I=await fetch(`${R}/v1/catalog/columns?${P}`,{headers:{Authorization:`Bearer ${$}`,Accept:"application/json"}});if(I.status===404)throw new Error(`none of those record types resolved: ${y.join(", ")} \u2014 list them with catalog_get.`);if(!I.ok)throw new Error(`common columns fetch failed: ${I.status}`);return Te(await I.json())}),p.registerTool("catalog_census",{title:"Count the customer's customizations",description:'Count the whole customization catalog: every object by kind AND by whether it can be changed (editable / partner_owned / turbobase_managed / unknown), plus which classes were never collected and why. Call this BEFORE search_catalog when the question is "how much is there" / "what is this account made of" / "how much of this is even ours to change". Takes no arguments.',inputSchema:{}},async()=>{let{baseUrl:y,token:k}=await e(),R=await fetch(`${y}/v1/catalog/census`,{headers:{Authorization:`Bearer ${k}`,Accept:"application/json"}});if(!R.ok)throw new Error(`catalog census failed: ${R.status}`);return Te(await R.json())}),p.registerTool("search_catalog",{title:"Search the customization catalog",description:'Survey the customer\'s customization catalog by free text \u2014 scripts, custom records, fields, lists, workflows, roles, saved searches, and installed bundles. Matches ids, names AND the generated plain-English explanations, so ask what you actually want ("which object handles rebates?"), not just an exact id. Returns compact {kind, externalId, name, summary, modifiability} hits plus the TRUE match total, so you can see when the page truncated; pass a hit to explain_object for the full definition + prose. A `partner_owned` object CANNOT be edited by this account however well you understand it \u2014 check that before planning a change. Search this before building over a custom record: its definition and explanation are the spec.',inputSchema:{query:M.string().describe('Free text, e.g. "inbound shipment" or "customscript_recalc".'),kind:M.enum(xb).optional().describe("Narrow to one catalog kind (omit to search all)."),limit:M.number().int().min(1).max(200).optional().describe("Max hits (default 50, max 200). The result reports the true total either way.")}},async({query:y,kind:k,limit:R})=>{let{baseUrl:$,token:P}=await e(),I=new URLSearchParams({q:y});k&&I.set("kind",k),R!==void 0&&I.set("limit",String(R));let D=await fetch(`${$}/v1/catalog/objects?${I}`,{headers:{Authorization:`Bearer ${P}`,Accept:"application/json"}});if(!D.ok)throw new Error(`catalog object search failed: ${D.status}`);return Te(await D.json())}),p.registerTool("explain_object",{title:"Explain one catalog object",description:"Fetch ONE customization-catalog object in depth: its captured definition (or script source, or a bundle\u2019s membership), human label, the generated plain-English explanation (tldr + markdown) of what it is for, and its OWNERSHIP \u2014 `modifiability` plus a one-line reason saying whether this account can change it at all. Address it by the {kind, externalId} a search_catalog hit returns. For a FIELD it also reports `selectTarget`: where a List/Record field ACTUALLY points, resolved to a mirrored table plus the filter needed to read it correctly. Do not infer a target from the field name \u2014 a carrier-named field can point at `entity` (vendors), and building on the wrong guess resolves nothing while looking like an empty table.",inputSchema:{kind:M.enum(xb),externalId:M.string().describe('The source scriptid, e.g. "customrecord_inbound_shipment".')}},async({kind:y,externalId:k})=>{let{baseUrl:R,token:$}=await e(),P=await fetch(`${R}/v1/catalog/objects/${encodeURIComponent(y)}/${encodeURIComponent(k)}`,{headers:{Authorization:`Bearer ${$}`,Accept:"application/json"}});if(P.status===404)throw new Error(`unknown object ${y}:${k} \u2014 find it with search_catalog.`);if(!P.ok)throw new Error(`catalog object fetch failed: ${P.status}`);return Te(await P.json())}),p.registerTool("search_documentation",{title:"Search the knowledge base",description:'Search this instance\'s published knowledge base \u2014 internal documentation: processes, policies, playbooks, how-tos. Route questions about how the ORGANISATION works here ("what\'s our receiving process", "how do we handle returns"), and ALWAYS search it before building UI over a custom record \u2014 the KB is the only spec a custom record has. Returns up to `limit` {slug, title, snippet} hits; pass a slug to read_document for the full text. Published release only \u2014 drafts are never returned.',inputSchema:{query:M.string().describe('Words or a phrase, e.g. "inbound shipment receiving".'),limit:M.number().int().min(1).max(25).optional().describe("Max results (default 8).")}},async({query:y,limit:k})=>{let{baseUrl:R,token:$}=await e(),P=new URLSearchParams({q:y});k!==void 0&&P.set("limit",String(k));let I=await fetch(`${R}/v1/kb/search?${P}`,{headers:{Authorization:`Bearer ${$}`,Accept:"application/json"}});if(!I.ok)throw new Error(`kb search failed: ${I.status}`);return Te(await I.json())}),p.registerTool("read_document",{title:"Read a knowledge-base document",description:"Read one published knowledge-base document in full by its slug (from a search_documentation hit). Returns {found, slug, title, body_markdown}. Published release only \u2014 unpublished / admin-only drafts are never returned.",inputSchema:{slug:M.string().describe("The document slug, e.g. one returned by search_documentation.")}},async({slug:y})=>{let{baseUrl:k,token:R}=await e(),$=await fetch(`${k}/v1/kb/document/${encodeURIComponent(y)}`,{headers:{Authorization:`Bearer ${R}`,Accept:"application/json"}});if(!$.ok&&$.status!==404)throw new Error(`kb document fetch failed: ${$.status}`);return Te(await $.json())}),p.registerTool("generate_types",{title:"Generate types.ts",description:"Generate the instance's types.ts (record shapes + PostgREST ops). Returns the source.",inputSchema:{recordsOnly:M.boolean().optional()}},async({recordsOnly:y})=>{let{baseUrl:k,token:R}=await e();return{content:[{type:"text",text:await mr({baseUrl:k,token:R,recordsOnly:y})}]}}),p.registerTool("scaffold_hook",{title:"Scaffold a hook",description:"Generate a hook file body (does not write to disk; returns path + content).",inputSchema:{recordType:M.string(),event:M.string().optional(),template:M.enum(["require-field","enforce-format","blank"]).optional(),field:M.string().optional(),regex:M.string().optional(),source:M.string().optional(),id:M.string().optional()}},async y=>{let k=Rt("hook",{recordType:y.recordType,event:y.event,template:y.template,field:y.field,regex:y.regex,source:y.source,id:y.id});return Te({path:k.relativePath,content:k.content})}),p.registerTool("scaffold_conduit",{title:"Scaffold a conduit",description:"Generate a conduit file body that bridges a canonical record from one source into another (does not write to disk; returns path + content).",inputSchema:{source:M.string().describe('Origin source the record arrives from, e.g. "shopify".'),recordType:M.string().describe('Canonical record type the conduit triggers on, e.g. "order".'),to:M.string().describe('Target source the conduit bridges into, e.g. "netsuite".'),toRecordType:M.string().optional().describe("Target record type (defaults to the trigger record type)."),event:M.enum(["after_canonicalize_inbound","after_sync_success_outbound"]).optional().describe("Cross-source lifecycle event (default after_canonicalize_inbound)."),id:M.string().optional()}},async y=>{let k=Rt("conduit",{recordType:y.recordType,source:y.source,to:y.to,toRecordType:y.toRecordType,event:y.event,id:y.id});return Te({path:k.relativePath,content:k.content})}),p.registerTool("scaffold_process",{title:"Scaffold a process",description:"Generate a process file body \u2014 an ordered sequence of record writes an app invokes and the platform runs on the server (does not write to disk; returns path + content).",inputSchema:{recordType:M.string().describe('Record type the first step writes, e.g. "purchaseOrder".'),then:M.string().optional().describe('Record type of a second step that waits for the first, e.g. "vendorBill".'),onFailure:M.enum(["stop","rollback"]).optional().describe("What the engine does when a step fails (default stop)."),id:M.string().optional()}},async y=>{let k=Rt("process",{recordType:y.recordType,then:y.then,onFailure:y.onFailure,id:y.id});return Te({path:k.relativePath,content:k.content})}),p.registerTool("author_conduit",{title:"Draft and check a conduit",description:'Ask the instance to WRITE a conduit from a plain-English intent and check it before staging it for review \u2014 the cross-source counterpart to the hook the healing agent drafts. Returns {status, intentId, id?, reason?}: "written" staged a proposal, "rejected_by_validation" says why it could not (including a target system this instance can only read from). Nothing is deployed \u2014 an administrator applies the staged bridge from the Conduits tab. Prefer scaffold_conduit when you intend to write the transform yourself.',inputSchema:{intent:M.string().describe(`What the bridge should do, in the operator's words, e.g. "when a shopify order is paid, raise the matching NetSuite sales order".`),fromSource:M.string().describe('Origin source the record arrives from, e.g. "shopify".'),fromRecordType:M.string().describe('Canonical record type the bridge triggers on, e.g. "order".'),toSource:M.string().describe('Target source the bridge writes into, e.g. "netsuite".'),toRecordType:M.string().describe('Canonical record type written on the target, e.g. "salesOrder".'),intentId:M.string().optional().describe("Correlation id for the audit trail. The instance mints one when omitted."),columns:M.array(M.string()).optional().describe("Narrow the target columns the transform may write. Defaults to the target record's own.")}},async y=>{let{baseUrl:k,token:R}=await e(),$=new q({baseUrl:k,token:R});return Te(await $.authorConduit({intent:y.intent,from:{source:y.fromSource,recordType:y.fromRecordType},to:{source:y.toSource,recordType:y.toRecordType},...y.intentId?{intentId:y.intentId}:{},...y.columns?.length?{columns:y.columns}:{}}))}),p.registerTool("mock_test",{title:"Dry-run an extension against samples",description:"Bundle the given source through the production sandbox and run it against samples/ fixtures.",inputSchema:{source:M.string().describe("The extension TS source."),samplesDir:M.string().optional().describe('Samples directory (default "samples").')}},async({source:y,samplesDir:k})=>{let R=Ab(k,t.samplesRoot);if(R===null)return{content:[{type:"text",text:JSON.stringify({error:"samples_dir_refused",message:dA})}],isError:!0};let $;try{$=await _n(R)}catch(D){if(t.samplesRoot!==void 0)return{content:[{type:"text",text:JSON.stringify({error:"samples_unreadable",message:"the samples directory could not be read"})}],isError:!0};throw D}let P=await Ho(y,$.map(D=>D.fixture)),I=P.filter(D=>!D.pass).length;return Te({total:P.length,passed:P.length-I,results:P})}),p.registerTool("deploy",{title:"Deploy an extension",description:"Deploy an extension source to the instance (POST /v1/extensions).",inputSchema:{id:M.string(),recordType:M.string(),event:M.string(),source:M.string().describe('Adapter id or "standalone".'),sourceCode:M.string().describe("The verbatim extension TS."),extensionKind:M.string().optional()}},async y=>{let{baseUrl:k,token:R}=await e(),P=await new q({baseUrl:k,token:R}).deploy({id:y.id,recordType:y.recordType,event:y.event,source:y.source,sourceCode:y.sourceCode,extensionKind:y.extensionKind});return Te(P)}),p.registerTool("tail_logs",{title:"Tail an extension's logs",description:"Read an extension's log ring buffer (GET /v1/extensions/:id/logs).",inputSchema:{id:M.string(),limit:M.number().int().positive().optional()}},async({id:y,limit:k})=>{let{baseUrl:R,token:$}=await e(),P=new q({baseUrl:R,token:$});return Te({id:y,logs:await P.logs(y,k)})}),p.registerTool("apps_list",{title:"List hosted apps",description:"Every hosted app on this instance with its slug, whether it's deployed, and its latest source revision. `sourceRev: null` means the app has no source of record yet (deployed dist only) \u2014 pull will 404 until something is pushed. GET /v1/apps.",inputSchema:{}},async()=>{let{baseUrl:y,token:k}=await e(),R=new q({baseUrl:y,token:k});return Te(await R.appsList())}),p.registerTool("apps_create",{title:"Create a hosted app",description:"Create a bare hosted-app row so source can be pushed and a build shipped to it \u2014 the author-led path that needs no proposal. Creates the registration only; it serves nothing until a build is shipped. POST /v1/apps.",inputSchema:{slug:M.string().describe("URL slug for the app (lowercased; served at /a/<slug>)."),name:M.string().optional().describe("Display name (defaults to the slug)."),description:M.string().optional().describe("Short description.")}},async({slug:y,name:k,description:R})=>{let{baseUrl:$,token:P}=await e(),I=new q({baseUrl:$,token:P});return Te(await I.appsCreate({slug:y,name:k,description:R}))}),p.registerTool("apps_pull",{title:"Pull a hosted app's source",description:"Fetch the app's latest source tree as {path \u2192 contents} READ-ONLY \u2014 write it to disk, refine it, then apps_push it back. Returns 404 when the app has no source revision yet. NEVER deploys. GET /v1/apps/:id/pull.",inputSchema:{app:M.string().describe("App slug or id.")}},async({app:y})=>{let{baseUrl:k,token:R}=await e(),$=new q({baseUrl:k,token:R});return Te(await $.appsPull(y))}),p.registerTool("apps_push",{title:"Push a hosted app's source",description:"Record an edited source tree as the app's NEW source revision, so the instance holds the source of record rather than one laptop. Send the whole tree ({path \u2192 contents}, relative POSIX paths, no node_modules/dist/.git). SOURCE ONLY \u2014 this does not build and does not change what is live; run `turbobase apps deploy <slug>` for that. POST /v1/apps/:id/push.",inputSchema:{app:M.string().describe("App slug or id."),files:M.record(M.string()).describe("The full source tree: relative path \u2192 file contents (text only)."),note:M.string().optional().describe("Short note stored with the revision.")}},async({app:y,files:k,note:R})=>{let{baseUrl:$,token:P}=await e(),I=new q({baseUrl:$,token:P});return Te(await I.appsPush(y,k,{note:R}))});let f=M.object({name:M.string().describe("Field name (lowercase identifier)."),type:M.enum(["text","number","date","bool","select"]).describe("Customer-facing type, NOT a Postgres type."),required:M.boolean().optional()}),_=M.object({slug:M.string().describe("Table slug; the physical table is cr_<slug>."),displayName:M.string().optional(),fields:M.array(f).describe("At least one field."),rowLevelOwner:M.boolean().optional().describe("Restrict non-admins to rows they created.")});return p.registerTool("tables_list",{title:"List custom tables",description:"The instance's customer-authored tables, their fields, and which apps claim each one. Read the `column` on each field \u2014 that is the actual Postgres column an app SELECTs (cf_sa_<name>), which is not the same string as the field name. GET /v1/tables.",inputSchema:{}},async()=>{let{baseUrl:y,token:k}=await e(),R=new q({baseUrl:y,token:k});return Te(await R.tablesList())}),p.registerTool("tables_plan",{title:"Preview a table declaration",description:"What these table declarations WOULD do, without doing it. Several apps may claim one table and the instance merges their declarations, so this reports the merged shape and \u2014 the reason to call it \u2014 fails with a conflict report when another claimant already declares one of your field names at a different type. Writes NOTHING. POST /v1/tables/plan.",inputSchema:{tables:M.array(_),claimedBy:M.string().optional().describe("`cli` or `app:<slug>`. `console` is reserved for the in-product builder.")}},async({tables:y,claimedBy:k})=>{let{baseUrl:R,token:$}=await e(),P=new q({baseUrl:R,token:$});return Te(await P.tablesPlan({tables:y,claimedBy:k}))}),p.registerTool("tables_apply",{title:"Create or evolve custom tables",description:"Record these table declarations and reconcile them into real Postgres tables \u2014 RLS, realtime, and the write path included. Additive: an existing table gains the fields you declare and keeps the ones other claimants declared. Never drops a column, so removing a field from your declaration releases your claim on it rather than deleting data. Prefer declaring tables in the app manifest (public/turbobase.json) so the deploy applies them; use this when authoring outside an app. POST /v1/tables/apply.",inputSchema:{tables:M.array(_),claimedBy:M.string().optional().describe("`cli` or `app:<slug>`. `console` is reserved for the in-product builder.")}},async({tables:y,claimedBy:k})=>{let{baseUrl:R,token:$}=await e(),P=new q({baseUrl:R,token:$});return Te(await P.tablesApply({tables:y,claimedBy:k}))}),p.registerResource("catalog","catalog:///",{title:"Instance catalog",description:"The instance record-type catalog.",mimeType:"application/json"},async y=>{let{baseUrl:k,token:R}=await e(),P=await(await fetch(`${k}/v1/catalog/records`,{headers:{Authorization:`Bearer ${R}`,Accept:"application/json"}})).text();return{contents:[{uri:y.href,mimeType:"application/json",text:P}]}}),p.registerResource("types","types://all",{title:"Generated types",description:"The instance types.ts source.",mimeType:"text/plain"},async y=>{let{baseUrl:k,token:R}=await e(),$=await mr({baseUrl:k,token:R});return{contents:[{uri:y.href,mimeType:"text/plain",text:$}]}}),p.registerResource("samples","samples:///",{title:"Sample fixtures",description:"The local samples/ dry-run fixtures.",mimeType:"application/json"},async y=>{let k=Ii(process.cwd(),"samples"),R=[];try{R=(await aA.readdir(k)).filter(P=>P.endsWith(".json"))}catch{R=[]}let $=await _n(k);return{contents:[{uri:y.href,mimeType:"application/json",text:JSON.stringify({files:R,samples:$.map(P=>P.fixture)},null,2)}]}}),p.registerResource("recipes","recipes:///",{title:"Intent recipes",description:"Intent recipes (columns / stats / actions per record type + intent), filtered to the instance's installed sources.",mimeType:"application/json"},async y=>{let k=await $b(),R=[];if(k){let{baseUrl:$,token:P}=await e();R=await k.listRecipesForSurface({surface:"mcp-authoring",baseUrl:$,token:P},r)}return{contents:[{uri:y.href,mimeType:"application/json",text:JSON.stringify({recipes:R})}]}}),p}async function hA(t={}){let e=await Cb(t),r=new Ci;await e.connect(r)}var xb,Rb,Eb,Pb,dA,Ib,pA,mA,Nb=j(()=>{bb();Sb();Zi();bn();ht();ca();Jo();Tb();xb=["script","field","record","list","workflow","role","saved_search","bundle","form","dataset"];Eb="turbobase-authoring",Pb="0.1.0";dA="samplesDir must be a relative path inside the samples directory this server serves; absolute paths and paths that leave it are refused over the network.";Ib=["catalog_get","record_anatomy","common_columns","search_documentation","read_document","search_catalog","catalog_census","explain_object","generate_types","scaffold_hook","scaffold_conduit","scaffold_process","author_conduit","mock_test","deploy","tail_logs","apps_list","apps_create","apps_pull","apps_push","tables_list","tables_plan","tables_apply"],pA=[...Ib,...kb],mA=["catalog:///","types://all","samples:///","recipes:///"]});Zi();bn();ht();ca();import{spawn as Kd}from"node:child_process";import{promises as ae}from"node:fs";import{homedir as fA}from"node:os";import{basename as Db,dirname as zt,join as et,relative as Ni,resolve as se,sep as Hd}from"node:path";import{Command as gA}from"commander";Fo();import{promises as Du}from"node:fs";var qw=/^[a-z][a-z0-9_]+$/;function Iu(t,e){if(typeof t!="string"||!qw.test(t))throw new Error(`${e} ${JSON.stringify(t)} must match /^[a-z][a-z0-9_]+$/ (NS scriptid form). This restriction is what lets reconcile safely splice it as a SQL identifier; refusing to emit DDL.`);return t}var zw="customrecord",Bw="customlist";function Fw(t){return t.startsWith("customrecord_")?t.slice(13):t}function Hw(t){return t.startsWith("customlist_")?t.slice(11):t}function Kw(t){return Iu(t,"custom_record scriptId"),`cr_${Fw(t)}`}function Vw(t){return Iu(t,"custom_list scriptId"),`cl_${Hw(t)}`}function Ou(t){return t.startsWith(zw)?Kw(t):t.startsWith(Bw)?Vw(t):null}var Gw={salesOrder:{table:"order"},invoice:{table:"invoice"},itemFulfillment:{table:"order_fulfillment"},customerPayment:{table:"customer_payment"},cashSale:{table:"cash_sale"},cashRefund:{table:"cash_refund"},opportunity:{table:"opportunity"},estimate:{table:"estimate"},customerDeposit:{table:"customer_deposit"},creditMemo:{table:"credit_memo"},customer:{table:"entity",typeColumn:"entity_type",typeValue:"customer"},vendor:{table:"entity",typeColumn:"entity_type",typeValue:"vendor"},contact:{table:"entity",typeColumn:"entity_type",typeValue:"contact"},employee:{table:"entity",typeColumn:"entity_type",typeValue:"employee"},partner:{table:"entity",typeColumn:"entity_type",typeValue:"partner"},inventoryItem:{table:"item",typeColumn:"item_type",typeValue:"inventoryItem"},serviceSaleItem:{table:"item",typeColumn:"item_type",typeValue:"serviceSaleItem"},nonInventoryResaleItem:{table:"item",typeColumn:"item_type",typeValue:"nonInventoryResaleItem"},kitItem:{table:"item",typeColumn:"item_type",typeValue:"kitItem"},assemblyItem:{table:"item",typeColumn:"item_type",typeValue:"assemblyItem"},lotNumberedInventoryItem:{table:"item",typeColumn:"item_type",typeValue:"lotNumberedInventoryItem"},serializedInventoryItem:{table:"item",typeColumn:"item_type",typeValue:"serializedInventoryItem"},lotNumberedAssemblyItem:{table:"item",typeColumn:"item_type",typeValue:"lotNumberedAssemblyItem"},serializedAssemblyItem:{table:"item",typeColumn:"item_type",typeValue:"serializedAssemblyItem"},nonInventorySaleItem:{table:"item",typeColumn:"item_type",typeValue:"nonInventorySaleItem"},otherChargeSaleItem:{table:"item",typeColumn:"item_type",typeValue:"otherChargeSaleItem"},discountItem:{table:"item",typeColumn:"item_type",typeValue:"discountItem"},location:{table:"location"},department:{table:"department"},classification:{table:"classification"},vendorBill:{table:"vendor_bill"},vendorPayment:{table:"vendor_payment"},purchaseOrder:{table:"purchase_order"},journalEntry:{table:"journal_entry"},timeBill:{table:"time_bill"},expenseReport:{table:"expense_report"},itemReceipt:{table:"item_receipt"},vendorReturnAuthorization:{table:"vendor_return_authorization"},transferOrder:{table:"transfer_order"},check:{table:"check"},customerRefund:{table:"customer_refund"},project:{table:"project"},projectTask:{table:"work_item",typeColumn:"item_type",typeValue:"project_task"},supportCase:{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},caseMessage:{table:"activity",typeColumn:"activity_type",typeValue:"case_message_outgoing",alsoAddresses:["case_message_incoming"]},shippingItem:{table:"shipping_item"},unitsType:{table:"units_type"},returnAuthorization:{table:"return_authorization"},inventoryAdjustment:{table:"inventory_adjustment"},vendorCredit:{table:"vendor_credit"},deposit:{table:"deposit"},account:{table:"account"},channel_message:{table:"activity",typeColumn:"activity_type",typeValue:"message"},social_post:{table:"activity",typeColumn:"activity_type",typeValue:"social_post"},reaction:{table:"reaction"},esignSubmission:{table:"esign_submission"},esignSubmitter:{table:"esign_submitter"},workflow_run:{table:"automation_run"},printer:{table:"printer"},printJob:{table:"print_job"},label:{table:"label"},call:{table:"activity",typeColumn:"activity_type",typeValue:"call"},quote:{table:"estimate"},tag:{table:"label"},request:{table:"request"}};function Nu(t,e){let r=e?Jw[`${e}:${t}`]:void 0;if(r)return r;let n=Gw[t];if(n)return n;let o=Ou(t);return o?{table:o}:void 0}var Ww={accountingPeriod:"accounting_period"};var Jw={...Object.fromEntries(Object.entries(Ww).map(([t,e])=>[`standalone:${t}`,{table:e}])),"google_workspace:doc":{table:"memory_document",typeColumn:"kind",typeValue:"doc"},"google_workspace:email":{table:"memory_document",typeColumn:"kind",typeValue:"email"},"google_workspace:file":{table:"memory_document",typeColumn:"kind",typeValue:"file"},"google_workspace:sheet":{table:"memory_document",typeColumn:"kind",typeValue:"sheet"},"google_workspace:slide":{table:"memory_document",typeColumn:"kind",typeValue:"slide"},"salesforce:account":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"salesforce:case":{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},"salesforce:lead":{table:"entity",typeColumn:"entity_type",typeValue:"lead"},"salesforce:task":{table:"activity",typeColumn:"activity_type",typeValue:"task"},"salesforce:event":{table:"activity",typeColumn:"activity_type",typeValue:"event"},"intercom:admin":{table:"entity",typeColumn:"entity_type",typeValue:"employee"},"intercom:company":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"hubspot:company":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"hubspot:owner":{table:"entity",typeColumn:"entity_type",typeValue:"employee"},"intercom:conversation":{table:"work_item",typeColumn:"item_type",typeValue:"conversation"},"intercom:ticket":{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},"github:issue":{table:"work_item",typeColumn:"item_type",typeValue:"issue"},"github:pull_request":{table:"work_item",typeColumn:"item_type",typeValue:"pull_request"},"github:comment":{table:"activity",typeColumn:"activity_type",typeValue:"comment"},"zendesk:organization":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"getsales:company":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"xero:supplier":{table:"entity",typeColumn:"entity_type",typeValue:"vendor"},"zoho_crm:account":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"zoho_crm:lead":{table:"entity",typeColumn:"entity_type",typeValue:"lead"},"zoho_crm:case":{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},"slack:user":{table:"entity",typeColumn:"entity_type",typeValue:"contact"},"slack:channel":{table:"channel"},"slack:reaction":{table:"reaction"},"quickbooks:bill":{table:"vendor_bill"},"quickbooks:payment":{table:"customer_payment"},"quickbooks:billPayment":{table:"vendor_payment"},"quickbooks:item":{table:"item"},"zoho_crm:task":{table:"activity",typeColumn:"activity_type",typeValue:"task"},"zoho_crm:event":{table:"activity",typeColumn:"activity_type",typeValue:"event"},"zoho_crm:product":{table:"item",typeColumn:"item_type",typeValue:"inventoryItem"},"xero:bill":{table:"vendor_bill"},"xero:billPayment":{table:"vendor_payment"},"xero:creditNote":{table:"credit_memo"},"xero:item":{table:"item"},"xero:manualJournal":{table:"journal_entry"},"xero:payment":{table:"customer_payment"},"xero:receiveMoney":{table:"deposit"},"xero:spendMoney":{table:"check"},"xero:supplierCreditNote":{table:"vendor_credit"},"azure_blob:file":{table:"source_file"}};function Lu(t,e){let r=Nu(t,e);return r?`${r.table}|${r.typeColumn??"-"}|${r.typeValue??"-"}`:void 0}function ju(t,e,r){if(e===t)return"same-type-merge";let n=r?.fromSource,o=r?.toSource;if(n&&o&&n!==o){let s=Lu(t,n),i=Lu(e,o);if(s!==void 0&&s===i)return"same-type-merge"}return"different-type-mint"}var Uu=4;function da(t){if(!/\bdefineConduit\s*[<(]/.test(t))throw new Error("not a conduit file \u2014 replay drives a defineConduit({...}) module");let e=Ir(t,"id"),r=Ir(t,"on"),n=Ir(t,"source"),o=Ir(t,"recordType"),s=[];for(let l of t.matchAll(/\bto\s*:\s*\{([^}]*)\}/g)){let d=Ir(l[1]??"","source"),p=Ir(l[1]??"","recordType");d&&p&&s.push({source:d,recordType:p})}let i=s[0],a=/cycleBudget\s*:\s*(\d+)/.exec(t);if(!e||!n||!o||!i)throw new Error("could not parse id/from.{source,recordType}/to.{source,recordType} \u2014 is it a reference-shape defineConduit({...})?");return{id:e,on:r,fromSource:n,fromRecordType:o,toSource:i.source,toRecordType:i.recordType,targets:s,cycleBudget:a?Number(a[1]):Uu}}function Ir(t,e){let r=new RegExp(`${e}\\s*:\\s*(['"\`])([^'"\`]+)\\1`).exec(t);return r?r[2]:void 0}function Yw(t){return{recordType:t.recordType,event:t.event??"after_canonicalize_inbound",direction:"inbound",origin:"system",source:t.fromSource,payload:t.payload??{},tenantConfig:t.tenantConfig??{},metadata:{externalId:t.externalId,lines:t.lines??[]},bindings:t.bindings??[],atlasLinks:t.atlasLinks??{}}}async function Mu(t,e,r="fixture",n={}){let o=e.name??r,s=[],i;try{i=da(t)}catch(d){return{fixtureName:o,conduit:Zw(),guards:s,whenPassed:!1,atlasResolutions:[],wouldWriteBindings:[],targets:[],logs:[],error:d instanceof Error?d.message:String(d)}}let a=Yw(e),l=Object.entries(e.atlasLinks??{}).map(([d,p])=>({key:d,resolved:p}));try{let d=await Bo(t,{resolveDir:n.resolveDir}),f=!!(await vn(d,a,{member:"when",timeoutMs:zo})).whenResult;s.push({name:"from.when",status:f?"passed":"failed",detail:f?"gate returned true (or no gate) \u2014 conduit fires":"gate returned false \u2014 conduit short-circuits (dispatch records no ledger row)"});let _=e.hop??0;if(s.push({name:"cycle-budget",status:"skipped-offline",detail:`hop=${_} vs cycleBudget=${i.cycleBudget} is computable, but the chain's true hop count requires live conduit_event state \u2014 requires live sandbox state (skipped in fixture mode)`}),s.push({name:"opposite-direction-loop",status:"skipped-offline",detail:"reads committed conduit_event rows for the reverse leg \u2014 requires live sandbox state (skipped in fixture mode)"}),s.push({name:"idempotency",status:"skipped-offline",detail:"INSERT conduit_event ON CONFLICT (idempotency_key) \u2014 requires live sandbox state (skipped in fixture mode)"}),!f)return{fixtureName:o,conduit:i,guards:s,whenPassed:f,atlasResolutions:l,wouldWriteBindings:[],targets:[],logs:[]};let y=[],k=[],R=[];for(let P=0;P<i.targets.length;P+=1){let I=i.targets[P],D=await vn(d,{...a,materialized:R.map(Ft=>({...Ft}))},{member:"transform",stepIndex:P,timeoutMs:zo});y.push(...D.effects.logs);let Y=D.effects.result;if(!Y||typeof Y!="object")throw new Error(`conduit ${i.id} transform${i.targets.length>1?` (target ${P+1})`:""} returned no result (expected { patch, createdFrom? })`);let F=Y,Oe=ju(i.fromRecordType,I.recordType,{fromSource:i.fromSource,toSource:I.source}),{wouldWriteBindings:Ce,masterAction:Bt}=Xw(i,I,Oe,e);k.push({to:I,transformResult:F,applyBranch:Oe,wouldWriteBindings:Ce,masterAction:Bt}),R.push({source:I.source,recordType:I.recordType,masterTable:`<${I.recordType} master, dry-run>`,masterRowId:`<dry-run target ${P+1}>`,externalId:null})}let $=k[0];return{fixtureName:o,conduit:i,guards:s,whenPassed:f,atlasResolutions:l,transformResult:$.transformResult,applyBranch:$.applyBranch,wouldWriteBindings:$.wouldWriteBindings,masterAction:$.masterAction,targets:k,logs:y}}catch(d){return{fixtureName:o,conduit:i,guards:s,whenPassed:!1,atlasResolutions:l,wouldWriteBindings:[],targets:[],logs:[],error:d instanceof Error?d.message:String(d)}}}function Xw(t,e,r,n){return r==="same-type-merge"?{masterAction:`would MERGE the patch onto the existing ${t.fromRecordType} master (firing source '${t.fromSource}', external_id='${n.externalId}') and flip originating_system='hybrid'`,wouldWriteBindings:[{source:t.fromSource,externalId:n.externalId,role:"origin",why:"firing source \u2014 its identity already on-row; backfilled into txn_source_link"},{source:e.source,externalId:null,role:"replica",why:"target source bound to the SAME master; external_id null until writeback mints it"}]}:{masterAction:`would MINT a new ${e.recordType} master (source_system='${e.source}', originating_system='hybrid', external_id=null) + stamp created_from lineage`,wouldWriteBindings:[{source:e.source,externalId:null,role:"origin",why:"this conduit CREATED the new master, so the target is its origin; external_id null until writeback mints it"}]}}function Zw(){return{id:"",fromSource:"",fromRecordType:"",toSource:"",toRecordType:"",targets:[],cycleBudget:Uu}}async function qu(t){return Promise.reject(new Error("`replay --against sandbox` requires a running sandbox (TURBOBASE_SANDBOX=1 + a local DB). No local Postgres / k3d in this environment \u2014 deferred to a DB-backed run. Use `replay --fixture <file>` for the offline transform/apply-branch dry-run."))}async function zu(t){let e=await Du.readFile(t,"utf8");return JSON.parse(e)}function Bu(t){return Du.readFile(t,"utf8")}function Fu(t,e){let r=t.conduit;if(t.error)return`replay ${t.fixtureName}: ERROR \u2014 ${t.error}`;let n=t.targets.length>1;if(!e){if(!t.whenPassed)return`replay ${t.fixtureName} [${r.id}]: WHEN-FAILED (conduit short-circuited)`;if(n){let a=t.targets.reduce((l,d)=>l+d.wouldWriteBindings.length,0);return`replay ${t.fixtureName} [${r.id}]: OK \u2014 ${t.targets.length} targets in sequence (${a} binding(s) would be written)`}return`replay ${t.fixtureName} [${r.id}]: OK \u2014 ${t.applyBranch} (${t.wouldWriteBindings.length} binding(s) would be written)`}let o=[];o.push(`\u2501\u2501 replay ${t.fixtureName} \u2501\u2501`),o.push(`conduit: ${r.id}`);let s=(r.targets.length>0?r.targets:[{source:r.toSource,recordType:r.toRecordType}]).map(a=>`${a.source}.${a.recordType}`).join(" \u2192 ");o.push(`route: ${r.fromSource}.${r.fromRecordType} \u2192 ${s}${r.on?` on=${r.on}`:""}`),o.push(""),o.push("guards (dispatch order):");for(let a of t.guards){let l=a.status==="passed"?"\u2713":a.status==="failed"?"\u2717":"\u2298";o.push(` ${l} ${a.name} [${a.status}] \u2014 ${a.detail}`)}if(o.push(""),o.push("atlas.resolve (used by transform):"),t.atlasResolutions.length===0)o.push(" (none declared \u2014 every ctx.atlas.resolve(...) resolves to null in fixture mode)");else for(let a of t.atlasResolutions)o.push(` ${a.key} \u2192 ${a.resolved===null?"null (no Atlas link \u2014 noted)":a.resolved}`);if(!t.whenPassed)return o.push(""),o.push("transform: SKIPPED (from.when gate did not fire)."),o.join(`
|
|
2017
|
+
`)}}]}})}p.registerTool("catalog_get",{title:"Get the instance catalog",description:"Fetch the instance's record-type catalog (/v1/catalog/records).",inputSchema:{}},async()=>{let{baseUrl:y,token:k}=await e(),R=await fetch(`${y}/v1/catalog/records`,{headers:{Authorization:`Bearer ${k}`,Accept:"application/json"}});if(!R.ok)throw new Error(`catalog fetch failed: ${R.status}`);return Te(await R.json())}),p.registerTool("record_anatomy",{title:"Record anatomy + display contract",description:"Fetch ONE record type in depth (/v1/catalog/records/:type): anatomy markdown (field meanings, sublists, gotchas, relations), authored recipe display contracts (columns/labels/formats), semantic column metadata and the record's plain-English description. Read this BEFORE building UI over a record \u2014 column names alone routinely mislead (e.g. tran_id is the human-facing document number, entity_id has a display twin entity_name). Also reports `unpopulatedColumns`: columns that EXIST but carry no value in any sampled row on this instance (currency_name is the classic \u2014 currency_id holds the data). Rendering one of those produces a blank column while faithfully following the display contract, so check it before choosing what to show. And reports `lines`: the record's LINE tables with their columns, including line-level custom fields (custcol_*). Check the lines before concluding a link is absent \u2014 a transaction routinely carries a reference on its LINES while the header field is NULL, and reading only the header reports a real link as missing.",inputSchema:{recordType:M.string().describe('The catalog recordType, e.g. "purchaseOrder" or "customrecord_inbound_shipment".'),source:M.string().optional().describe('Which connector publishes this recordType, e.g. "netsuite" or "stripe". 29 record types are published by more than one source ("customer" by six) and each is a different shape behind the same name. Omit it and the catalog collapse rule picks one; the answer names its choice in `source`.')}},async({recordType:y,source:k})=>{let{baseUrl:R,token:$}=await e(),P=k?`?source=${encodeURIComponent(k)}`:"",I=await fetch(`${R}/v1/catalog/records/${encodeURIComponent(y)}${P}`,{headers:{Authorization:`Bearer ${$}`,Accept:"application/json"}});if(I.status===404)throw new Error(`unknown record type "${y}" \u2014 list them with catalog_get.`);if(!I.ok)throw new Error(`record anatomy fetch failed: ${I.status}`);return Te(await I.json())}),p.registerTool("common_columns",{title:"Columns common to several record types",description:"Given SEVERAL record types, return the columns present on ALL of them (safe to select in one shared query shape) and the columns present on only some, with exactly which types lack each. Use this BEFORE writing a query that spans record types \u2014 transaction tables are NOT uniform (memo, total, created_from_id and even pg_row_id are missing from some), and PostgREST rejects an unknown column with 42703 and fails the ENTIRE request, so one wrong name loses the whole table rather than one field. Record types the caller cannot read come back in unknownRecordTypes \u2014 if that is non-empty the intersection covers fewer types than you asked for.",inputSchema:{recordTypes:M.array(M.string()).min(1).describe('Catalog recordTypes, e.g. ["purchaseOrder","vendorBill","itemReceipt"].'),source:M.string().optional().describe("Pin every requested type to ONE connector, e.g. \"netsuite\". Without it each name collapses by the catalog rule, so an intersection can silently mix two sources' tables for the same name. The response's `resolvedSources` names what it used.")}},async({recordTypes:y,source:k})=>{let{baseUrl:R,token:$}=await e(),P=new URLSearchParams({types:y.join(",")});k&&P.set("source",k);let I=await fetch(`${R}/v1/catalog/columns?${P}`,{headers:{Authorization:`Bearer ${$}`,Accept:"application/json"}});if(I.status===404)throw new Error(`none of those record types resolved: ${y.join(", ")} \u2014 list them with catalog_get.`);if(!I.ok)throw new Error(`common columns fetch failed: ${I.status}`);return Te(await I.json())}),p.registerTool("catalog_census",{title:"Count the customer's customizations",description:'Count the whole customization catalog: every object by kind AND by whether it can be changed (editable / partner_owned / turbobase_managed / unknown), plus which classes were never collected and why. Call this BEFORE search_catalog when the question is "how much is there" / "what is this account made of" / "how much of this is even ours to change". Takes no arguments.',inputSchema:{}},async()=>{let{baseUrl:y,token:k}=await e(),R=await fetch(`${y}/v1/catalog/census`,{headers:{Authorization:`Bearer ${k}`,Accept:"application/json"}});if(!R.ok)throw new Error(`catalog census failed: ${R.status}`);return Te(await R.json())}),p.registerTool("search_catalog",{title:"Search the customization catalog",description:'Survey the customer\'s customization catalog by free text \u2014 scripts, custom records, fields, lists, workflows, roles, saved searches, and installed bundles. Matches ids, names AND the generated plain-English explanations, so ask what you actually want ("which object handles rebates?"), not just an exact id. Returns compact {kind, externalId, name, summary, modifiability} hits plus the TRUE match total, so you can see when the page truncated; pass a hit to explain_object for the full definition + prose. A `partner_owned` object CANNOT be edited by this account however well you understand it \u2014 check that before planning a change. Search this before building over a custom record: its definition and explanation are the spec.',inputSchema:{query:M.string().describe('Free text, e.g. "inbound shipment" or "customscript_recalc".'),kind:M.enum(xb).optional().describe("Narrow to one catalog kind (omit to search all)."),limit:M.number().int().min(1).max(200).optional().describe("Max hits (default 50, max 200). The result reports the true total either way.")}},async({query:y,kind:k,limit:R})=>{let{baseUrl:$,token:P}=await e(),I=new URLSearchParams({q:y});k&&I.set("kind",k),R!==void 0&&I.set("limit",String(R));let D=await fetch(`${$}/v1/catalog/objects?${I}`,{headers:{Authorization:`Bearer ${P}`,Accept:"application/json"}});if(!D.ok)throw new Error(`catalog object search failed: ${D.status}`);return Te(await D.json())}),p.registerTool("explain_object",{title:"Explain one catalog object",description:"Fetch ONE customization-catalog object in depth: its captured definition (or script source, or a bundle\u2019s membership), human label, the generated plain-English explanation (tldr + markdown) of what it is for, and its OWNERSHIP \u2014 `modifiability` plus a one-line reason saying whether this account can change it at all. Address it by the {kind, externalId} a search_catalog hit returns. For a FIELD it also reports `selectTarget`: where a List/Record field ACTUALLY points, resolved to a mirrored table plus the filter needed to read it correctly. Do not infer a target from the field name \u2014 a carrier-named field can point at `entity` (vendors), and building on the wrong guess resolves nothing while looking like an empty table.",inputSchema:{kind:M.enum(xb),externalId:M.string().describe('The source scriptid, e.g. "customrecord_inbound_shipment".')}},async({kind:y,externalId:k})=>{let{baseUrl:R,token:$}=await e(),P=await fetch(`${R}/v1/catalog/objects/${encodeURIComponent(y)}/${encodeURIComponent(k)}`,{headers:{Authorization:`Bearer ${$}`,Accept:"application/json"}});if(P.status===404)throw new Error(`unknown object ${y}:${k} \u2014 find it with search_catalog.`);if(!P.ok)throw new Error(`catalog object fetch failed: ${P.status}`);return Te(await P.json())}),p.registerTool("search_documentation",{title:"Search the knowledge base",description:'Search this instance\'s published knowledge base \u2014 internal documentation: processes, policies, playbooks, how-tos. Route questions about how the ORGANISATION works here ("what\'s our receiving process", "how do we handle returns"), and ALWAYS search it before building UI over a custom record \u2014 the KB is the only spec a custom record has. Returns up to `limit` {slug, title, snippet} hits; pass a slug to read_document for the full text. Published release only \u2014 drafts are never returned.',inputSchema:{query:M.string().describe('Words or a phrase, e.g. "inbound shipment receiving".'),limit:M.number().int().min(1).max(25).optional().describe("Max results (default 8).")}},async({query:y,limit:k})=>{let{baseUrl:R,token:$}=await e(),P=new URLSearchParams({q:y});k!==void 0&&P.set("limit",String(k));let I=await fetch(`${R}/v1/kb/search?${P}`,{headers:{Authorization:`Bearer ${$}`,Accept:"application/json"}});if(!I.ok)throw new Error(`kb search failed: ${I.status}`);return Te(await I.json())}),p.registerTool("read_document",{title:"Read a knowledge-base document",description:"Read one published knowledge-base document in full by its slug (from a search_documentation hit). Returns {found, slug, title, body_markdown}. Published release only \u2014 unpublished / admin-only drafts are never returned.",inputSchema:{slug:M.string().describe("The document slug, e.g. one returned by search_documentation.")}},async({slug:y})=>{let{baseUrl:k,token:R}=await e(),$=await fetch(`${k}/v1/kb/document/${encodeURIComponent(y)}`,{headers:{Authorization:`Bearer ${R}`,Accept:"application/json"}});if(!$.ok&&$.status!==404)throw new Error(`kb document fetch failed: ${$.status}`);return Te(await $.json())}),p.registerTool("generate_types",{title:"Generate types.ts",description:"Generate the instance's types.ts (record shapes + PostgREST ops). Returns the source.",inputSchema:{recordsOnly:M.boolean().optional()}},async({recordsOnly:y})=>{let{baseUrl:k,token:R}=await e();return{content:[{type:"text",text:await mr({baseUrl:k,token:R,recordsOnly:y})}]}}),p.registerTool("scaffold_hook",{title:"Scaffold a hook",description:"Generate a hook file body (does not write to disk; returns path + content).",inputSchema:{recordType:M.string(),event:M.string().optional(),template:M.enum(["require-field","enforce-format","blank"]).optional(),field:M.string().optional(),regex:M.string().optional(),source:M.string().optional(),id:M.string().optional()}},async y=>{let k=Rt("hook",{recordType:y.recordType,event:y.event,template:y.template,field:y.field,regex:y.regex,source:y.source,id:y.id});return Te({path:k.relativePath,content:k.content})}),p.registerTool("scaffold_conduit",{title:"Scaffold a conduit",description:"Generate a conduit file body that bridges a canonical record from one source into another (does not write to disk; returns path + content).",inputSchema:{source:M.string().describe('Origin source the record arrives from, e.g. "shopify".'),recordType:M.string().describe('Canonical record type the conduit triggers on, e.g. "order".'),to:M.string().describe('Target source the conduit bridges into, e.g. "netsuite".'),toRecordType:M.string().optional().describe("Target record type (defaults to the trigger record type)."),event:M.enum(["after_canonicalize_inbound","after_sync_success_outbound"]).optional().describe("Cross-source lifecycle event (default after_canonicalize_inbound)."),id:M.string().optional()}},async y=>{let k=Rt("conduit",{recordType:y.recordType,source:y.source,to:y.to,toRecordType:y.toRecordType,event:y.event,id:y.id});return Te({path:k.relativePath,content:k.content})}),p.registerTool("scaffold_process",{title:"Scaffold a process",description:"Generate a process file body \u2014 an ordered sequence of record writes an app invokes and the platform runs on the server (does not write to disk; returns path + content).",inputSchema:{recordType:M.string().describe('Record type the first step writes, e.g. "purchaseOrder".'),then:M.string().optional().describe('Record type of a second step that waits for the first, e.g. "vendorBill".'),onFailure:M.enum(["stop","rollback"]).optional().describe("What the engine does when a step fails (default stop)."),id:M.string().optional()}},async y=>{let k=Rt("process",{recordType:y.recordType,then:y.then,onFailure:y.onFailure,id:y.id});return Te({path:k.relativePath,content:k.content})}),p.registerTool("author_conduit",{title:"Draft and check a conduit",description:'Ask the instance to WRITE a conduit from a plain-English intent and check it before staging it for review \u2014 the cross-source counterpart to the hook the healing agent drafts. Returns {status, intentId, id?, reason?}: "written" staged a proposal, "rejected_by_validation" says why it could not (including a target system this instance can only read from). Nothing is deployed \u2014 an administrator applies the staged bridge from the Conduits tab. Prefer scaffold_conduit when you intend to write the transform yourself.',inputSchema:{intent:M.string().describe(`What the bridge should do, in the operator's words, e.g. "when a shopify order is paid, raise the matching NetSuite sales order".`),fromSource:M.string().describe('Origin source the record arrives from, e.g. "shopify".'),fromRecordType:M.string().describe('Canonical record type the bridge triggers on, e.g. "order".'),toSource:M.string().describe('Target source the bridge writes into, e.g. "netsuite".'),toRecordType:M.string().describe('Canonical record type written on the target, e.g. "salesOrder".'),intentId:M.string().optional().describe("Correlation id for the audit trail. The instance mints one when omitted."),columns:M.array(M.string()).optional().describe("Narrow the target columns the transform may write. Defaults to the target record's own.")}},async y=>{let{baseUrl:k,token:R}=await e(),$=new q({baseUrl:k,token:R});return Te(await $.authorConduit({intent:y.intent,from:{source:y.fromSource,recordType:y.fromRecordType},to:{source:y.toSource,recordType:y.toRecordType},...y.intentId?{intentId:y.intentId}:{},...y.columns?.length?{columns:y.columns}:{}}))}),p.registerTool("mock_test",{title:"Dry-run an extension against samples",description:"Bundle the given source through the production sandbox and run it against samples/ fixtures.",inputSchema:{source:M.string().describe("The extension TS source."),samplesDir:M.string().optional().describe('Samples directory (default "samples").')}},async({source:y,samplesDir:k})=>{let R=Ab(k,t.samplesRoot);if(R===null)return{content:[{type:"text",text:JSON.stringify({error:"samples_dir_refused",message:dA})}],isError:!0};let $;try{$=await _n(R)}catch(D){if(t.samplesRoot!==void 0)return{content:[{type:"text",text:JSON.stringify({error:"samples_unreadable",message:"the samples directory could not be read"})}],isError:!0};throw D}let P=await Ho(y,$.map(D=>D.fixture)),I=P.filter(D=>!D.pass).length;return Te({total:P.length,passed:P.length-I,results:P})}),p.registerTool("deploy",{title:"Deploy an extension",description:"Deploy an extension source to the instance (POST /v1/extensions).",inputSchema:{id:M.string(),recordType:M.string(),event:M.string(),source:M.string().describe('Adapter id or "standalone".'),sourceCode:M.string().describe("The verbatim extension TS."),extensionKind:M.string().optional()}},async y=>{let{baseUrl:k,token:R}=await e(),P=await new q({baseUrl:k,token:R}).deploy({id:y.id,recordType:y.recordType,event:y.event,source:y.source,sourceCode:y.sourceCode,extensionKind:y.extensionKind});return Te(P)}),p.registerTool("tail_logs",{title:"Tail an extension's logs",description:"Read an extension's log ring buffer (GET /v1/extensions/:id/logs).",inputSchema:{id:M.string(),limit:M.number().int().positive().optional()}},async({id:y,limit:k})=>{let{baseUrl:R,token:$}=await e(),P=new q({baseUrl:R,token:$});return Te({id:y,logs:await P.logs(y,k)})}),p.registerTool("apps_list",{title:"List hosted apps",description:"Every hosted app on this instance with its slug, whether it's deployed, and its latest source revision. `sourceRev: null` means the app has no source of record yet (deployed dist only) \u2014 pull will 404 until something is pushed. GET /v1/apps.",inputSchema:{}},async()=>{let{baseUrl:y,token:k}=await e(),R=new q({baseUrl:y,token:k});return Te(await R.appsList())}),p.registerTool("apps_create",{title:"Create a hosted app",description:"Create a bare hosted-app row so source can be pushed and a build shipped to it \u2014 the author-led path that needs no proposal. Creates the registration only; it serves nothing until a build is shipped. POST /v1/apps.",inputSchema:{slug:M.string().describe("URL slug for the app (lowercased; served at /a/<slug>)."),name:M.string().optional().describe("Display name (defaults to the slug)."),description:M.string().optional().describe("Short description.")}},async({slug:y,name:k,description:R})=>{let{baseUrl:$,token:P}=await e(),I=new q({baseUrl:$,token:P});return Te(await I.appsCreate({slug:y,name:k,description:R}))}),p.registerTool("apps_pull",{title:"Pull a hosted app's source",description:"Fetch the app's latest source tree as {path \u2192 contents} READ-ONLY \u2014 write it to disk, refine it, then apps_push it back. Returns 404 when the app has no source revision yet. NEVER deploys. GET /v1/apps/:id/pull.",inputSchema:{app:M.string().describe("App slug or id.")}},async({app:y})=>{let{baseUrl:k,token:R}=await e(),$=new q({baseUrl:k,token:R});return Te(await $.appsPull(y))}),p.registerTool("apps_push",{title:"Push a hosted app's source",description:"Record an edited source tree as the app's NEW source revision, so the instance holds the source of record rather than one laptop. Send the whole tree ({path \u2192 contents}, relative POSIX paths, no node_modules/dist/.git). SOURCE ONLY \u2014 this does not build and does not change what is live; run `turbobase apps deploy <slug>` for that. POST /v1/apps/:id/push.",inputSchema:{app:M.string().describe("App slug or id."),files:M.record(M.string()).describe("The full source tree: relative path \u2192 file contents (text only)."),note:M.string().optional().describe("Short note stored with the revision.")}},async({app:y,files:k,note:R})=>{let{baseUrl:$,token:P}=await e(),I=new q({baseUrl:$,token:P});return Te(await I.appsPush(y,k,{note:R}))});let f=M.object({name:M.string().describe("Field name (lowercase identifier)."),type:M.enum(["text","number","date","bool","select"]).describe("Customer-facing type, NOT a Postgres type."),required:M.boolean().optional()}),_=M.object({slug:M.string().describe("Table slug; the physical table is cr_<slug>."),displayName:M.string().optional(),fields:M.array(f).describe("At least one field."),rowLevelOwner:M.boolean().optional().describe("Restrict non-admins to rows they created.")});return p.registerTool("tables_list",{title:"List custom tables",description:"The instance's customer-authored tables, their fields, and which apps claim each one. Read the `column` on each field \u2014 that is the actual Postgres column an app SELECTs (cf_sa_<name>), which is not the same string as the field name. GET /v1/tables.",inputSchema:{}},async()=>{let{baseUrl:y,token:k}=await e(),R=new q({baseUrl:y,token:k});return Te(await R.tablesList())}),p.registerTool("tables_plan",{title:"Preview a table declaration",description:"What these table declarations WOULD do, without doing it. Several apps may claim one table and the instance merges their declarations, so this reports the merged shape and \u2014 the reason to call it \u2014 fails with a conflict report when another claimant already declares one of your field names at a different type. Writes NOTHING. POST /v1/tables/plan.",inputSchema:{tables:M.array(_),claimedBy:M.string().optional().describe("`cli` or `app:<slug>`. `console` is reserved for the in-product builder.")}},async({tables:y,claimedBy:k})=>{let{baseUrl:R,token:$}=await e(),P=new q({baseUrl:R,token:$});return Te(await P.tablesPlan({tables:y,claimedBy:k}))}),p.registerTool("tables_apply",{title:"Create or evolve custom tables",description:"Record these table declarations and reconcile them into real Postgres tables \u2014 RLS, realtime, and the write path included. Additive: an existing table gains the fields you declare and keeps the ones other claimants declared. Never drops a column, so removing a field from your declaration releases your claim on it rather than deleting data. Prefer declaring tables in the app manifest (public/turbobase.json) so the deploy applies them; use this when authoring outside an app. POST /v1/tables/apply.",inputSchema:{tables:M.array(_),claimedBy:M.string().optional().describe("`cli` or `app:<slug>`. `console` is reserved for the in-product builder.")}},async({tables:y,claimedBy:k})=>{let{baseUrl:R,token:$}=await e(),P=new q({baseUrl:R,token:$});return Te(await P.tablesApply({tables:y,claimedBy:k}))}),p.registerResource("catalog","catalog:///",{title:"Instance catalog",description:"The instance record-type catalog.",mimeType:"application/json"},async y=>{let{baseUrl:k,token:R}=await e(),P=await(await fetch(`${k}/v1/catalog/records`,{headers:{Authorization:`Bearer ${R}`,Accept:"application/json"}})).text();return{contents:[{uri:y.href,mimeType:"application/json",text:P}]}}),p.registerResource("types","types://all",{title:"Generated types",description:"The instance types.ts source.",mimeType:"text/plain"},async y=>{let{baseUrl:k,token:R}=await e(),$=await mr({baseUrl:k,token:R});return{contents:[{uri:y.href,mimeType:"text/plain",text:$}]}}),p.registerResource("samples","samples:///",{title:"Sample fixtures",description:"The local samples/ dry-run fixtures.",mimeType:"application/json"},async y=>{let k=Ii(process.cwd(),"samples"),R=[];try{R=(await aA.readdir(k)).filter(P=>P.endsWith(".json"))}catch{R=[]}let $=await _n(k);return{contents:[{uri:y.href,mimeType:"application/json",text:JSON.stringify({files:R,samples:$.map(P=>P.fixture)},null,2)}]}}),p.registerResource("recipes","recipes:///",{title:"Intent recipes",description:"Intent recipes (columns / stats / actions per record type + intent), filtered to the instance's installed sources.",mimeType:"application/json"},async y=>{let k=await $b(),R=[];if(k){let{baseUrl:$,token:P}=await e();R=await k.listRecipesForSurface({surface:"mcp-authoring",baseUrl:$,token:P},r)}return{contents:[{uri:y.href,mimeType:"application/json",text:JSON.stringify({recipes:R})}]}}),p}async function hA(t={}){let e=await Cb(t),r=new Ci;await e.connect(r)}var xb,Rb,Eb,Pb,dA,Ib,pA,mA,Nb=j(()=>{bb();Sb();Zi();bn();ht();ca();Jo();Tb();xb=["script","field","record","list","workflow","role","saved_search","bundle","form","dataset"];Eb="turbobase-authoring",Pb="0.1.0";dA="samplesDir must be a relative path inside the samples directory this server serves; absolute paths and paths that leave it are refused over the network.";Ib=["catalog_get","record_anatomy","common_columns","search_documentation","read_document","search_catalog","catalog_census","explain_object","generate_types","scaffold_hook","scaffold_conduit","scaffold_process","author_conduit","mock_test","deploy","tail_logs","apps_list","apps_create","apps_pull","apps_push","tables_list","tables_plan","tables_apply"],pA=[...Ib,...kb],mA=["catalog:///","types://all","samples:///","recipes:///"]});Zi();bn();ht();ca();import{spawn as Kd}from"node:child_process";import{promises as ae}from"node:fs";import{homedir as fA}from"node:os";import{basename as Db,dirname as zt,join as et,relative as Ni,resolve as se,sep as Hd}from"node:path";import{Command as gA}from"commander";Fo();import{promises as Du}from"node:fs";var qw=/^[a-z][a-z0-9_]+$/;function Iu(t,e){if(typeof t!="string"||!qw.test(t))throw new Error(`${e} ${JSON.stringify(t)} must match /^[a-z][a-z0-9_]+$/ (NS scriptid form). This restriction is what lets reconcile safely splice it as a SQL identifier; refusing to emit DDL.`);return t}var zw="customrecord",Bw="customlist";function Fw(t){return t.startsWith("customrecord_")?t.slice(13):t}function Hw(t){return t.startsWith("customlist_")?t.slice(11):t}function Kw(t){return Iu(t,"custom_record scriptId"),`cr_${Fw(t)}`}function Vw(t){return Iu(t,"custom_list scriptId"),`cl_${Hw(t)}`}function Ou(t){return t.startsWith(zw)?Kw(t):t.startsWith(Bw)?Vw(t):null}var Gw={salesOrder:{table:"order"},invoice:{table:"invoice"},itemFulfillment:{table:"order_fulfillment"},customerPayment:{table:"customer_payment"},cashSale:{table:"cash_sale"},cashRefund:{table:"cash_refund"},opportunity:{table:"opportunity"},estimate:{table:"estimate"},customerDeposit:{table:"customer_deposit"},creditMemo:{table:"credit_memo"},customer:{table:"entity",typeColumn:"entity_type",typeValue:"customer"},vendor:{table:"entity",typeColumn:"entity_type",typeValue:"vendor"},contact:{table:"entity",typeColumn:"entity_type",typeValue:"contact"},employee:{table:"entity",typeColumn:"entity_type",typeValue:"employee"},partner:{table:"entity",typeColumn:"entity_type",typeValue:"partner"},inventoryItem:{table:"item",typeColumn:"item_type",typeValue:"inventoryItem"},serviceSaleItem:{table:"item",typeColumn:"item_type",typeValue:"serviceSaleItem"},nonInventoryResaleItem:{table:"item",typeColumn:"item_type",typeValue:"nonInventoryResaleItem"},kitItem:{table:"item",typeColumn:"item_type",typeValue:"kitItem"},assemblyItem:{table:"item",typeColumn:"item_type",typeValue:"assemblyItem"},lotNumberedInventoryItem:{table:"item",typeColumn:"item_type",typeValue:"lotNumberedInventoryItem"},serializedInventoryItem:{table:"item",typeColumn:"item_type",typeValue:"serializedInventoryItem"},lotNumberedAssemblyItem:{table:"item",typeColumn:"item_type",typeValue:"lotNumberedAssemblyItem"},serializedAssemblyItem:{table:"item",typeColumn:"item_type",typeValue:"serializedAssemblyItem"},nonInventorySaleItem:{table:"item",typeColumn:"item_type",typeValue:"nonInventorySaleItem"},otherChargeSaleItem:{table:"item",typeColumn:"item_type",typeValue:"otherChargeSaleItem"},discountItem:{table:"item",typeColumn:"item_type",typeValue:"discountItem"},location:{table:"location"},department:{table:"department"},classification:{table:"classification"},vendorBill:{table:"vendor_bill"},vendorPayment:{table:"vendor_payment"},purchaseOrder:{table:"purchase_order"},journalEntry:{table:"journal_entry"},timeBill:{table:"time_bill"},expenseReport:{table:"expense_report"},itemReceipt:{table:"item_receipt"},vendorReturnAuthorization:{table:"vendor_return_authorization"},transferOrder:{table:"transfer_order"},check:{table:"check"},customerRefund:{table:"customer_refund"},project:{table:"project"},projectTask:{table:"work_item",typeColumn:"item_type",typeValue:"project_task"},supportCase:{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},caseMessage:{table:"activity",typeColumn:"activity_type",typeValue:"case_message_outgoing",alsoAddresses:["case_message_incoming"]},shippingItem:{table:"shipping_item"},unitsType:{table:"units_type"},returnAuthorization:{table:"return_authorization"},inventoryAdjustment:{table:"inventory_adjustment"},vendorCredit:{table:"vendor_credit"},deposit:{table:"deposit"},account:{table:"account"},channel_message:{table:"activity",typeColumn:"activity_type",typeValue:"message"},social_post:{table:"activity",typeColumn:"activity_type",typeValue:"social_post"},reaction:{table:"reaction"},esignSubmission:{table:"esign_submission"},esignSubmitter:{table:"esign_submitter"},workflow_run:{table:"automation_run"},printer:{table:"printer"},printJob:{table:"print_job"},label:{table:"label"},call:{table:"activity",typeColumn:"activity_type",typeValue:"call"},quote:{table:"estimate"},tag:{table:"label"},request:{table:"request"}};function Nu(t,e){let r=e?Jw[`${e}:${t}`]:void 0;if(r)return r;let n=Gw[t];if(n)return n;let o=Ou(t);return o?{table:o}:void 0}var Ww={accountingPeriod:"accounting_period",printer:"printer"};var Jw={...Object.fromEntries(Object.entries(Ww).map(([t,e])=>[`standalone:${t}`,{table:e}])),"google_workspace:doc":{table:"memory_document",typeColumn:"kind",typeValue:"doc"},"google_workspace:email":{table:"memory_document",typeColumn:"kind",typeValue:"email"},"google_workspace:file":{table:"memory_document",typeColumn:"kind",typeValue:"file"},"google_workspace:sheet":{table:"memory_document",typeColumn:"kind",typeValue:"sheet"},"google_workspace:slide":{table:"memory_document",typeColumn:"kind",typeValue:"slide"},"salesforce:account":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"salesforce:case":{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},"salesforce:lead":{table:"entity",typeColumn:"entity_type",typeValue:"lead"},"salesforce:task":{table:"activity",typeColumn:"activity_type",typeValue:"task"},"salesforce:event":{table:"activity",typeColumn:"activity_type",typeValue:"event"},"intercom:admin":{table:"entity",typeColumn:"entity_type",typeValue:"employee"},"intercom:company":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"hubspot:company":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"hubspot:owner":{table:"entity",typeColumn:"entity_type",typeValue:"employee"},"intercom:conversation":{table:"work_item",typeColumn:"item_type",typeValue:"conversation"},"intercom:ticket":{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},"github:issue":{table:"work_item",typeColumn:"item_type",typeValue:"issue"},"github:pull_request":{table:"work_item",typeColumn:"item_type",typeValue:"pull_request"},"github:comment":{table:"activity",typeColumn:"activity_type",typeValue:"comment"},"zendesk:organization":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"getsales:company":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"xero:supplier":{table:"entity",typeColumn:"entity_type",typeValue:"vendor"},"zoho_crm:account":{table:"entity",typeColumn:"entity_type",typeValue:"customer"},"zoho_crm:lead":{table:"entity",typeColumn:"entity_type",typeValue:"lead"},"zoho_crm:case":{table:"work_item",typeColumn:"item_type",typeValue:"ticket"},"slack:user":{table:"entity",typeColumn:"entity_type",typeValue:"contact"},"slack:channel":{table:"channel"},"slack:reaction":{table:"reaction"},"quickbooks:bill":{table:"vendor_bill"},"quickbooks:payment":{table:"customer_payment"},"quickbooks:billPayment":{table:"vendor_payment"},"quickbooks:item":{table:"item"},"zoho_crm:task":{table:"activity",typeColumn:"activity_type",typeValue:"task"},"zoho_crm:event":{table:"activity",typeColumn:"activity_type",typeValue:"event"},"zoho_crm:product":{table:"item",typeColumn:"item_type",typeValue:"inventoryItem"},"xero:bill":{table:"vendor_bill"},"xero:billPayment":{table:"vendor_payment"},"xero:creditNote":{table:"credit_memo"},"xero:item":{table:"item"},"xero:manualJournal":{table:"journal_entry"},"xero:payment":{table:"customer_payment"},"xero:receiveMoney":{table:"deposit"},"xero:spendMoney":{table:"check"},"xero:supplierCreditNote":{table:"vendor_credit"},"azure_blob:file":{table:"source_file"}};function Lu(t,e){let r=Nu(t,e);return r?`${r.table}|${r.typeColumn??"-"}|${r.typeValue??"-"}`:void 0}function ju(t,e,r){if(e===t)return"same-type-merge";let n=r?.fromSource,o=r?.toSource;if(n&&o&&n!==o){let s=Lu(t,n),i=Lu(e,o);if(s!==void 0&&s===i)return"same-type-merge"}return"different-type-mint"}var Uu=4;function da(t){if(!/\bdefineConduit\s*[<(]/.test(t))throw new Error("not a conduit file \u2014 replay drives a defineConduit({...}) module");let e=Ir(t,"id"),r=Ir(t,"on"),n=Ir(t,"source"),o=Ir(t,"recordType"),s=[];for(let l of t.matchAll(/\bto\s*:\s*\{([^}]*)\}/g)){let d=Ir(l[1]??"","source"),p=Ir(l[1]??"","recordType");d&&p&&s.push({source:d,recordType:p})}let i=s[0],a=/cycleBudget\s*:\s*(\d+)/.exec(t);if(!e||!n||!o||!i)throw new Error("could not parse id/from.{source,recordType}/to.{source,recordType} \u2014 is it a reference-shape defineConduit({...})?");return{id:e,on:r,fromSource:n,fromRecordType:o,toSource:i.source,toRecordType:i.recordType,targets:s,cycleBudget:a?Number(a[1]):Uu}}function Ir(t,e){let r=new RegExp(`${e}\\s*:\\s*(['"\`])([^'"\`]+)\\1`).exec(t);return r?r[2]:void 0}function Yw(t){return{recordType:t.recordType,event:t.event??"after_canonicalize_inbound",direction:"inbound",origin:"system",source:t.fromSource,payload:t.payload??{},tenantConfig:t.tenantConfig??{},metadata:{externalId:t.externalId,lines:t.lines??[]},bindings:t.bindings??[],atlasLinks:t.atlasLinks??{}}}async function Mu(t,e,r="fixture",n={}){let o=e.name??r,s=[],i;try{i=da(t)}catch(d){return{fixtureName:o,conduit:Zw(),guards:s,whenPassed:!1,atlasResolutions:[],wouldWriteBindings:[],targets:[],logs:[],error:d instanceof Error?d.message:String(d)}}let a=Yw(e),l=Object.entries(e.atlasLinks??{}).map(([d,p])=>({key:d,resolved:p}));try{let d=await Bo(t,{resolveDir:n.resolveDir}),f=!!(await vn(d,a,{member:"when",timeoutMs:zo})).whenResult;s.push({name:"from.when",status:f?"passed":"failed",detail:f?"gate returned true (or no gate) \u2014 conduit fires":"gate returned false \u2014 conduit short-circuits (dispatch records no ledger row)"});let _=e.hop??0;if(s.push({name:"cycle-budget",status:"skipped-offline",detail:`hop=${_} vs cycleBudget=${i.cycleBudget} is computable, but the chain's true hop count requires live conduit_event state \u2014 requires live sandbox state (skipped in fixture mode)`}),s.push({name:"opposite-direction-loop",status:"skipped-offline",detail:"reads committed conduit_event rows for the reverse leg \u2014 requires live sandbox state (skipped in fixture mode)"}),s.push({name:"idempotency",status:"skipped-offline",detail:"INSERT conduit_event ON CONFLICT (idempotency_key) \u2014 requires live sandbox state (skipped in fixture mode)"}),!f)return{fixtureName:o,conduit:i,guards:s,whenPassed:f,atlasResolutions:l,wouldWriteBindings:[],targets:[],logs:[]};let y=[],k=[],R=[];for(let P=0;P<i.targets.length;P+=1){let I=i.targets[P],D=await vn(d,{...a,materialized:R.map(Ft=>({...Ft}))},{member:"transform",stepIndex:P,timeoutMs:zo});y.push(...D.effects.logs);let Y=D.effects.result;if(!Y||typeof Y!="object")throw new Error(`conduit ${i.id} transform${i.targets.length>1?` (target ${P+1})`:""} returned no result (expected { patch, createdFrom? })`);let F=Y,Oe=ju(i.fromRecordType,I.recordType,{fromSource:i.fromSource,toSource:I.source}),{wouldWriteBindings:Ce,masterAction:Bt}=Xw(i,I,Oe,e);k.push({to:I,transformResult:F,applyBranch:Oe,wouldWriteBindings:Ce,masterAction:Bt}),R.push({source:I.source,recordType:I.recordType,masterTable:`<${I.recordType} master, dry-run>`,masterRowId:`<dry-run target ${P+1}>`,externalId:null})}let $=k[0];return{fixtureName:o,conduit:i,guards:s,whenPassed:f,atlasResolutions:l,transformResult:$.transformResult,applyBranch:$.applyBranch,wouldWriteBindings:$.wouldWriteBindings,masterAction:$.masterAction,targets:k,logs:y}}catch(d){return{fixtureName:o,conduit:i,guards:s,whenPassed:!1,atlasResolutions:l,wouldWriteBindings:[],targets:[],logs:[],error:d instanceof Error?d.message:String(d)}}}function Xw(t,e,r,n){return r==="same-type-merge"?{masterAction:`would MERGE the patch onto the existing ${t.fromRecordType} master (firing source '${t.fromSource}', external_id='${n.externalId}') and flip originating_system='hybrid'`,wouldWriteBindings:[{source:t.fromSource,externalId:n.externalId,role:"origin",why:"firing source \u2014 its identity already on-row; backfilled into txn_source_link"},{source:e.source,externalId:null,role:"replica",why:"target source bound to the SAME master; external_id null until writeback mints it"}]}:{masterAction:`would MINT a new ${e.recordType} master (source_system='${e.source}', originating_system='hybrid', external_id=null) + stamp created_from lineage`,wouldWriteBindings:[{source:e.source,externalId:null,role:"origin",why:"this conduit CREATED the new master, so the target is its origin; external_id null until writeback mints it"}]}}function Zw(){return{id:"",fromSource:"",fromRecordType:"",toSource:"",toRecordType:"",targets:[],cycleBudget:Uu}}async function qu(t){return Promise.reject(new Error("`replay --against sandbox` requires a running sandbox (TURBOBASE_SANDBOX=1 + a local DB). No local Postgres / k3d in this environment \u2014 deferred to a DB-backed run. Use `replay --fixture <file>` for the offline transform/apply-branch dry-run."))}async function zu(t){let e=await Du.readFile(t,"utf8");return JSON.parse(e)}function Bu(t){return Du.readFile(t,"utf8")}function Fu(t,e){let r=t.conduit;if(t.error)return`replay ${t.fixtureName}: ERROR \u2014 ${t.error}`;let n=t.targets.length>1;if(!e){if(!t.whenPassed)return`replay ${t.fixtureName} [${r.id}]: WHEN-FAILED (conduit short-circuited)`;if(n){let a=t.targets.reduce((l,d)=>l+d.wouldWriteBindings.length,0);return`replay ${t.fixtureName} [${r.id}]: OK \u2014 ${t.targets.length} targets in sequence (${a} binding(s) would be written)`}return`replay ${t.fixtureName} [${r.id}]: OK \u2014 ${t.applyBranch} (${t.wouldWriteBindings.length} binding(s) would be written)`}let o=[];o.push(`\u2501\u2501 replay ${t.fixtureName} \u2501\u2501`),o.push(`conduit: ${r.id}`);let s=(r.targets.length>0?r.targets:[{source:r.toSource,recordType:r.toRecordType}]).map(a=>`${a.source}.${a.recordType}`).join(" \u2192 ");o.push(`route: ${r.fromSource}.${r.fromRecordType} \u2192 ${s}${r.on?` on=${r.on}`:""}`),o.push(""),o.push("guards (dispatch order):");for(let a of t.guards){let l=a.status==="passed"?"\u2713":a.status==="failed"?"\u2717":"\u2298";o.push(` ${l} ${a.name} [${a.status}] \u2014 ${a.detail}`)}if(o.push(""),o.push("atlas.resolve (used by transform):"),t.atlasResolutions.length===0)o.push(" (none declared \u2014 every ctx.atlas.resolve(...) resolves to null in fixture mode)");else for(let a of t.atlasResolutions)o.push(` ${a.key} \u2192 ${a.resolved===null?"null (no Atlas link \u2014 noted)":a.resolved}`);if(!t.whenPassed)return o.push(""),o.push("transform: SKIPPED (from.when gate did not fire)."),o.join(`
|
|
2018
2018
|
`);let i=t.targets.length>0?t.targets:[{to:{source:r.toSource,recordType:r.toRecordType},transformResult:t.transformResult??{patch:{}},applyBranch:t.applyBranch??"different-type-mint",wouldWriteBindings:t.wouldWriteBindings,masterAction:t.masterAction??""}];if(i.forEach((a,l)=>{let d=n?` (target ${l+1}/${i.length}: ${a.to.source}.${a.to.recordType})`:"";o.push(""),o.push(`transform${d} \u2192 patch:`),o.push(Qw(JSON.stringify(a.transformResult.patch??{},null,2)," ")),a.transformResult.createdFrom&&o.push(` createdFrom: ${JSON.stringify(a.transformResult.createdFrom)}`),o.push(""),o.push(`apply-branch${d}: ${a.applyBranch}`),o.push(` ${a.masterAction}`),o.push(""),o.push(`bindings that WOULD be written (txn_source_link)${d}:`);for(let p of a.wouldWriteBindings)o.push(` ${p.source} role=${p.role} external_id=${p.externalId??"null"} \u2014 ${p.why}`)}),t.logs.length>0){o.push(""),o.push("conduit logs:");for(let a of t.logs)o.push(` [${a.level}] ${a.message}${a.meta?` ${JSON.stringify(a.meta)}`:""}`)}return o.join(`
|
|
2019
2019
|
`)}function Qw(t,e){return t.split(`
|
|
2020
2020
|
`).map(r=>e+r).join(`
|
|
@@ -3813,7 +3813,7 @@ ${o.map(T_).join(`
|
|
|
3813
3813
|
`))}async function hm(t,e,r=qr){let n=[...new Set(t.map(a=>a.trim()).filter(Boolean))];n.length===0&&w("name at least one skill slug. `turbobase skills list` shows them.");let o=await zr(e,r);if(!await is(`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 J("publishing",()=>o.skillsPublish(n))}catch(a){V(a,"skills:publish","publish a skill"),w(`publish failed: ${a instanceof Error?a.message:String(a)}`)}b(m.green(`published ${i.published.length} skill(s) \u2014 live now.`)),i.unknown.length>0&&(L(m.yellow(`no such skill: ${i.unknown.join(", ")}`)),process.exitCode=1),L(m.dim(`release v${i.version.version_no} \xB7 undo with: turbobase skills unpublish ${i.published.join(" ")}`))}async function fm(t,e,r=qr){let n=[...new Set(t.map(a=>a.trim()).filter(Boolean))];n.length===0&&w("name at least one skill slug. `turbobase skills list` shows them.");let o=await zr(e,r);if(!await is(`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 J("unpublishing",()=>o.skillsUnpublish(n))}catch(a){V(a,"skills:publish","unpublish a skill"),w(`unpublish failed: ${a instanceof Error?a.message:String(a)}`)}b(m.green(`unpublished ${i.unpublished.length} skill(s) \u2014 retracted now.`)),L(m.dim(`release v${i.version.version_no} \xB7 re-publish with: turbobase skills publish ${i.unpublished.join(" ")}`)),i.unknown.length>0&&(L(m.yellow(`no such skill: ${i.unknown.join(", ")}`)),process.exitCode=1)}async function gm(t,e,r=qr){let n=Number(t);(!Number.isInteger(n)||n<1)&&w(`--version must be a release number (got '${t}'). \`turbobase skills releases\` lists them.`);let o=await zr(e,r);if(await is(`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 J("rolling back",()=>o.skillsRollback(n));b(m.green(`rolled back to v${i.version.version_no}.`))}catch(i){V(i,"skills:publish","roll back a skills release"),w(`rollback failed: ${i instanceof Error?i.message:String(i)}`)}}async function ym(t,e=qr){let r=await zr(t,e),n;try{n=await J("reading the releases",()=>r.skillsReleases())}catch(o){V(o,"skills:publish","read the skills releases"),w(`could not read the releases: ${o instanceof Error?o.message:String(o)}`)}if(n.items.length===0){L("no releases yet.");return}for(let o of n.items){let s=o.is_current?m.green("*"):" ",i=o.release_note?` \u2014 ${o.release_note}`:"";b(`${s} v${o.version_no} ${o.skill_count} skill(s) ${m.dim(o.published_at)}${o.published_by?m.dim(` by ${o.published_by}`):""}${m.dim(i)}`)}}async function bm(t,e,r=qr){let n=[...new Set((t??[]).map(d=>d.trim()).filter(Boolean))];n.length===0&&w("name at least one skill to delete. `turbobase skills list` shows the slugs.");let o=await zr(e,r);if(!await is(n.length===1?`Delete the skill '${n[0]}'?`:`Delete ${n.length} skills?
|
|
3814
3814
|
${n.join(`
|
|
3815
3815
|
`)}`,"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 d of n)try{i.push(await J(`deleting ${d}`,()=>o.skillsDelete(d)))}catch(p){if(p instanceof Q&&p.status===404){a.push(d);continue}i.length>0&&L(m.dim(`deleted before the error: ${i.map(f=>f.slug).join(", ")}`)),V(p,"skills:publish","delete a skill"),w(`delete failed on '${d}': ${p instanceof Error?p.message:String(p)}`)}i.length>0&&b(m.green(`deleted ${i.length} skill(s).`));let l=i.filter(d=>d.wasPublished).map(d=>d.slug);l.length>0&&L(m.yellow(`Published until now, so no longer injected: ${l.join(", ")}`)),a.length>0&&(L(m.yellow(`no such skill: ${a.join(", ")}`)),process.exitCode=1)}import{promises as wm}from"node:fs";import{join as uS,relative as pS,resolve as mS,sep as hS}from"node:path";var fS=new Set(["node_modules","dist","build","coverage",".git",".claude",".turbobase",".next",".svelte-kit",".vercel",".cache"]),gS=new Set([".mcp.json",".env"]);function yS(t){return gS.has(t)||t.startsWith(".env.")&&t!==".env.example"}function bS(t){return Buffer.compare(Buffer.from(t.toString("utf8"),"utf8"),t)===0}function wS(t){return t.includes(0)}async function vm(t){let e=mS(t),r={},n=[],o=[],s=[];async function i(a){let l=await wm.readdir(a,{withFileTypes:!0});for(let d of l){let p=uS(a,d.name);if(fS.has(d.name))continue;if(d.isDirectory()){await i(p);continue}if(!d.isFile())continue;let f=pS(e,p).split(hS).join("/");if(yS(d.name)){o.push(f);continue}let _=await wm.readFile(p);if(!bS(_)){n.push(f);continue}wS(_)&&s.push(f),r[f]=_.toString("utf8")}}return await i(e),{files:r,skipped:n,secrets:o,unstorable:s}}import{randomBytes as vS}from"node:crypto";var _S="/_tb/app-assets",SS=16,kS=/^[0-9a-f]{32}$/;function _m(){return vS(SS).toString("hex")}function TS(t){return kS.test(t)}function Sm(t){if(!TS(t))throw new Error(`not a build id: ${t}`);return`${_S}/${t}/`}var km=7;Ia();var Oa=50*1024*1024;import as from"node:fs/promises";import An from"node:path";var Na={esbuild:"0.28.1","isolated-vm":"6.1.2"},La="@turbobase/isolated-vm-prebuilds";function ja(t,e){return`${t}-${e}`}function Da(t=process.versions){return`abi${t.modules}`}var Tm=["darwin-x64","win32-arm64"];async function xm(t){let e=t.platform??process.platform,r=t.arch??process.arch,n=ja(e,r),o=An.join(t.home,"node_modules"),s=An.join(o,...La.split("/"),"prebuilds",n),i=An.join(o,"isolated-vm","prebuilds",n),a;try{a=await as.readdir(s)}catch{return[]}let l=[];await as.mkdir(i,{recursive:!0});for(let d of a){if(!d.endsWith(".node"))continue;let p=An.join(i,d);try{await as.copyFile(An.join(s,d),p,as.constants?.COPYFILE_EXCL??1),l.push(d)}catch{}}return l}import{homedir as xS}from"node:os";import{join as Rm}from"node:path";import{promises as Ua}from"node:fs";function Em(){let t=process.env.TURBOBASE_HOME??Rm(xS(),".turbobase");return{dir:t,file:Rm(t,"sandboxes.json")}}var RS={s:1e3,m:6e4,h:36e5,d:864e5};function $S(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*RS[e[2]]}function ES(t,e=Date.now()){return new Date(e+$S(t)).toISOString()}function PS(t,e=Date.now()){let r=Date.parse(t);return Number.isNaN(r)?!0:r<=e}function AS(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 CS(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 IS="[REDACTED]",Cn=class extends Error{constructor(e){super(e),this.name="SandboxDataCopyUnavailableError"}},Ma=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 OS(t,e,r){let n={};for(let[o,s]of Object.entries(e)){let i=r[o];if(i===void 0)throw new Ma(t,o,"column has no PII classification (unknown = unsafe)");i==="pii"?n[o]=s==null?s:IS:n[o]=s}return n}function NS(t,e,r){return e.map(n=>OS(t,n,r))}var $m=new Set(["localhost","127.0.0.1","::1","0.0.0.0"]),LS=["oraclecloud.com","us-ashburn-1","amazonaws.com","rds.","supabase.co","turbobase.app","turbobase.io"];function jS(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 qa(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 LS)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=jS(t);if(!$m.has(r))throw new Error(`local-only guard: target host '${r}' is not a recognised local Postgres (${[...$m].join(", ")}). \`turbobase sandbox create\` is LOCAL-ONLY.`)}function DS(t,e=US){return`sbx-${t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,24)||"tenant"}-${e()}`}function US(){return Math.floor(Math.random()*16777215).toString(16).padStart(6,"0")}function MS(t){return`turbobase_${t.replace(/[^a-z0-9]+/gi,"_").toLowerCase()}`}function qS(t,e){return`${(e??process.env.TURBOBASE_SANDBOX_BASE_URL??"http://localhost:5173").replace(/\/+$/,"")}/?sandbox=${encodeURIComponent(t)}`}async function za(){let{file:t}=Em();try{let e=await Ua.readFile(t,"utf8"),r=JSON.parse(e);return Array.isArray(r)?r:[]}catch(e){if(e.code==="ENOENT")return[];throw e}}async function Pm(t){let{dir:e,file:r}=Em();await Ua.mkdir(e,{recursive:!0}),await Ua.writeFile(r,`${JSON.stringify(t,null,2)}
|
|
3816
|
-
`,{mode:384})}async function zS(t){let e=await za();e.push(t),await Pm(e)}async function Am(t){let e=await za(),r=e.findIndex(o=>o.name===t);if(r===-1)return null;let[n]=e.splice(r,1);return await Pm(e),n??null}async function ls(){return za()}function Ba(t,e=Date.now()){let r=[],n=[];for(let o of t)PS(o.expiresAt,e)?n.push(o):r.push(o);return{active:r,expired:n}}async function Cm(t,e){qa(t.target());let r=t.dataCopyUnavailable?.();if(r)throw new Cn(r);let n=AS(e.sample),o=e.now??Date.now(),s=(e.nameGen??(d=>DS(d)))(e.fromTenant),i=MS(s);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let d of a){let p=CS(d.rows,n),f=NS(d.table,p,d.classification);f.length>0&&await t.copyRows(i,d.table,f)}let l={name:s,fromTenant:e.fromTenant,database:i,createdAt:new Date(o).toISOString(),expiresAt:ES(e.ttl,o),sampleFraction:n,url:qS(s,e.baseUrl)};return await zS(l),l}async function Im(t,e){qa(t.target());let n=(await ls()).find(o=>o.name===e);return n?(await t.dropDatabase(n.database),await Am(e),n):null}async function Fa(t,e=Date.now()){qa(t.target());let r=await ls(),{expired:n}=Ba(r,e),o=[];for(let s of n){try{await t.dropDatabase(s.database)}catch{continue}await Am(s.name),o.push(s)}return o}var In={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","skills:publish","docs:publish","mcp:data","mcp:write"],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"}},lN=Object.keys(In).filter(t=>In[t].mintable);var uN=In.pat.prefix,BS=Object.entries(In).map(([t,e])=>[e.prefix,t]),FS=/^[A-Za-z0-9_-]{20,}$/;function HS(t){if(typeof t!="string")return null;for(let[e,r]of BS)if(t.startsWith(e)&&FS.test(t.slice(e.length)))return r;return null}function Om(t){return HS(t)==="pat"}bn();var KS=8e3,VS={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 GS(t){return VS[t]??`the instance rejected this token (${t}) \u2014 run \`turbobase auth login\` to store a fresh PAT`}async function WS(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(KS)})}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 Nm(t){return t.state==="accepted"?Ie()?m.green("accepted"):"accepted":t.state==="rejected"?Ie()?m.red(`rejected \xB7 ${t.reason}`):`rejected \xB7 ${t.reason}`:Ie()?m.yellow("unreachable"):"unreachable"}async function Lm(t={},e=fetch){let r=await Ot(),{file:n}=Lo();if(r.length===0){b("not signed in. Run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),L(m.dim(`no sign-ins stored in ${n}.`));return}let o=await mt(),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 WS(i.baseUrl,i.token,e)})));if(t.probe===!1)for(let i of s)b(`${i.active?"* ":" "}${i.baseUrl} (updated ${i.updatedAt})`);else if(Ie())b(ft(s.map(i=>[i.active?m.green("*"):" ",m.bold(i.baseUrl),Nm(i.check),m.dim(i.updatedAt)]),{head:["","instance","token","updated"]}));else for(let i of s)b(`${i.active?"* ":" "}${i.baseUrl} ${Nm(i.check)} (updated ${i.updatedAt})`);L(m.dim(`tokens read from ${n}`));for(let i of s)i.check.state==="rejected"?(L(m.red(`${i.baseUrl}: ${GS(i.check.reason)}.`)),L(m.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(m.yellow(`${i.baseUrl}: could not reach the instance (${i.check.detail}).`));o||L("no active default \u2014 run `turbobase auth use <base-url>` or pass --base-url.")}async function yA(t){let e=await Ot();e.length===0&&w("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),Ne()||w("this command needs an explicit <base-url> in a non-interactive shell.");try{return await Le(t,e.map(r=>({value:r.baseUrl,label:r.baseUrl,description:`updated ${r.updatedAt}`})))}catch{w("cancelled.")}}async function bA(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 wA(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 vA(t,e,r){let n=await vu({baseUrl:t,token:e}),o=(await Ot()).length-1;if(b(`signed in to ${n.baseUrl} \u2014 now the active instance`+(o>0?` (${o} other${o===1?"":"s"} stored; commands default here).`:".")),wA(n.baseUrl),!r)return;let s=await bA(n.baseUrl,e);s.ok||(s.kind==="wrong-host"?(L(`warning: ${n.baseUrl} did not respond to the Turbobase API (/v1) \u2014 got ${s.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.")):s.kind==="bad-token"?L(`warning: ${n.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):L(`warning: could not reach ${n.baseUrl} to verify the token (${s.detail}). Stored anyway.`))}async function _A(t){let e=await aa(await Sn(t),zt(t)),r=_a(e);return r||w(`${t}: ${Sa(e)}`),{...r,sourceCode:e}}async function Pr(t,e,r,n){try{return await ae.access(t),"skipped"}catch{}return await ae.mkdir(zt(t),{recursive:!0}),await ae.writeFile(t,e,{mode:n??(r?493:420)}),n!==void 0&&await ae.chmod(t,n),"wrote"}async function Lb(t,e){if(Db(t)!==".gitignore")return Pr(t,e.content,e.executable,e.mode);let r=await ae.readFile(t,"utf8").catch(()=>null);if(r===null)return Pr(t,e.content,e.executable,e.mode);let n=vp(r);return n===null?"skipped":(await ae.writeFile(t,n,"utf8"),"merged")}function qd(t){if(t!==void 0){if(t==="junit"||t==="json")return t;w(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function Oi(t){process.exit(t)}function SA(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 kA(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 TA(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 jb(t){return`${(Math.ceil(t/1048576*10)/10).toFixed(1).replace(/\.0$/,"")} MiB`}function Vd({includeInternal:t=!0}={}){let e=new gA;e.name("turbobase").description("Build apps, author extensions, and drive your Turbobase instance.").version("1.34.2").option("--no-color","Disable ANSI colour + animations (also honoured via NO_COLOR / non-TTY)."),e.addHelpText("beforeAll",u=>u.command.parent?"":`${ha()}
|
|
3816
|
+
`,{mode:384})}async function zS(t){let e=await za();e.push(t),await Pm(e)}async function Am(t){let e=await za(),r=e.findIndex(o=>o.name===t);if(r===-1)return null;let[n]=e.splice(r,1);return await Pm(e),n??null}async function ls(){return za()}function Ba(t,e=Date.now()){let r=[],n=[];for(let o of t)PS(o.expiresAt,e)?n.push(o):r.push(o);return{active:r,expired:n}}async function Cm(t,e){qa(t.target());let r=t.dataCopyUnavailable?.();if(r)throw new Cn(r);let n=AS(e.sample),o=e.now??Date.now(),s=(e.nameGen??(d=>DS(d)))(e.fromTenant),i=MS(s);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let d of a){let p=CS(d.rows,n),f=NS(d.table,p,d.classification);f.length>0&&await t.copyRows(i,d.table,f)}let l={name:s,fromTenant:e.fromTenant,database:i,createdAt:new Date(o).toISOString(),expiresAt:ES(e.ttl,o),sampleFraction:n,url:qS(s,e.baseUrl)};return await zS(l),l}async function Im(t,e){qa(t.target());let n=(await ls()).find(o=>o.name===e);return n?(await t.dropDatabase(n.database),await Am(e),n):null}async function Fa(t,e=Date.now()){qa(t.target());let r=await ls(),{expired:n}=Ba(r,e),o=[];for(let s of n){try{await t.dropDatabase(s.database)}catch{continue}await Am(s.name),o.push(s)}return o}var In={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","skills:publish","docs:publish","mcp:data","mcp:write"],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"}},lN=Object.keys(In).filter(t=>In[t].mintable);var uN=In.pat.prefix,BS=Object.entries(In).map(([t,e])=>[e.prefix,t]),FS=/^[A-Za-z0-9_-]{20,}$/;function HS(t){if(typeof t!="string")return null;for(let[e,r]of BS)if(t.startsWith(e)&&FS.test(t.slice(e.length)))return r;return null}function Om(t){return HS(t)==="pat"}bn();var KS=8e3,VS={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 GS(t){return VS[t]??`the instance rejected this token (${t}) \u2014 run \`turbobase auth login\` to store a fresh PAT`}async function WS(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(KS)})}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 Nm(t){return t.state==="accepted"?Ie()?m.green("accepted"):"accepted":t.state==="rejected"?Ie()?m.red(`rejected \xB7 ${t.reason}`):`rejected \xB7 ${t.reason}`:Ie()?m.yellow("unreachable"):"unreachable"}async function Lm(t={},e=fetch){let r=await Ot(),{file:n}=Lo();if(r.length===0){b("not signed in. Run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),L(m.dim(`no sign-ins stored in ${n}.`));return}let o=await mt(),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 WS(i.baseUrl,i.token,e)})));if(t.probe===!1)for(let i of s)b(`${i.active?"* ":" "}${i.baseUrl} (updated ${i.updatedAt})`);else if(Ie())b(ft(s.map(i=>[i.active?m.green("*"):" ",m.bold(i.baseUrl),Nm(i.check),m.dim(i.updatedAt)]),{head:["","instance","token","updated"]}));else for(let i of s)b(`${i.active?"* ":" "}${i.baseUrl} ${Nm(i.check)} (updated ${i.updatedAt})`);L(m.dim(`tokens read from ${n}`));for(let i of s)i.check.state==="rejected"?(L(m.red(`${i.baseUrl}: ${GS(i.check.reason)}.`)),L(m.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(m.yellow(`${i.baseUrl}: could not reach the instance (${i.check.detail}).`));o||L("no active default \u2014 run `turbobase auth use <base-url>` or pass --base-url.")}async function yA(t){let e=await Ot();e.length===0&&w("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),Ne()||w("this command needs an explicit <base-url> in a non-interactive shell.");try{return await Le(t,e.map(r=>({value:r.baseUrl,label:r.baseUrl,description:`updated ${r.updatedAt}`})))}catch{w("cancelled.")}}async function bA(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 wA(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 vA(t,e,r){let n=await vu({baseUrl:t,token:e}),o=(await Ot()).length-1;if(b(`signed in to ${n.baseUrl} \u2014 now the active instance`+(o>0?` (${o} other${o===1?"":"s"} stored; commands default here).`:".")),wA(n.baseUrl),!r)return;let s=await bA(n.baseUrl,e);s.ok||(s.kind==="wrong-host"?(L(`warning: ${n.baseUrl} did not respond to the Turbobase API (/v1) \u2014 got ${s.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.")):s.kind==="bad-token"?L(`warning: ${n.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):L(`warning: could not reach ${n.baseUrl} to verify the token (${s.detail}). Stored anyway.`))}async function _A(t){let e=await aa(await Sn(t),zt(t)),r=_a(e);return r||w(`${t}: ${Sa(e)}`),{...r,sourceCode:e}}async function Pr(t,e,r,n){try{return await ae.access(t),"skipped"}catch{}return await ae.mkdir(zt(t),{recursive:!0}),await ae.writeFile(t,e,{mode:n??(r?493:420)}),n!==void 0&&await ae.chmod(t,n),"wrote"}async function Lb(t,e){if(Db(t)!==".gitignore")return Pr(t,e.content,e.executable,e.mode);let r=await ae.readFile(t,"utf8").catch(()=>null);if(r===null)return Pr(t,e.content,e.executable,e.mode);let n=vp(r);return n===null?"skipped":(await ae.writeFile(t,n,"utf8"),"merged")}function qd(t){if(t!==void 0){if(t==="junit"||t==="json")return t;w(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function Oi(t){process.exit(t)}function SA(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 kA(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 TA(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 jb(t){return`${(Math.ceil(t/1048576*10)/10).toFixed(1).replace(/\.0$/,"")} MiB`}function Vd({includeInternal:t=!0}={}){let e=new gA;e.name("turbobase").description("Build apps, author extensions, and drive your Turbobase instance.").version("1.36.0").option("--no-color","Disable ANSI colour + animations (also honoured via NO_COLOR / non-TTY)."),e.addHelpText("beforeAll",u=>u.command.parent?"":`${ha()}
|
|
3817
3817
|
`),e.hook("preAction",(u,h)=>{Op(e.opts(),h.opts())});let r=e.command("auth").description("Sign in to an instance and manage stored credentials."),n=e.command("project").description("Set up the local project and the files an agent session reads."),o=e.command("ext").description("Author, test, ship, and observe extensions \u2014 hooks and conduits."),s=e.command("kb").description("Write into the instance's knowledge base."),i=e.command("skills").description("Write standing rules into the instance's agent skills.");r.command("login").description("Sign in to an instance by pasting a personal access token (PAT), stored in ~/.turbobase/credentials.").requiredOption("--base-url <url>","Instance base URL, e.g. https://acme.turbobase.app").option("--token <tb_pat_\u2026>","The PAT to store. Mint one in your app under Settings \u203A Access tokens.").option("--no-verify","Skip the post-login connectivity/token probe.").action(async u=>{let h=rt(u.baseUrl);u.token||w(`auth login needs --token. Mint a PAT in your Turbobase app (Settings \u203A Access tokens), then:
|
|
3818
3818
|
${$n} auth login --base-url ${h} --token tb_pat_\u2026`),Om(u.token)||w("--token does not look like a Turbobase PAT (tb_pat_\u2026)."),await vA(h,u.token,u.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 u=>Lm({probe:u.probe})),r.command("use [base-url]").description("Make an already-stored instance the active default (omit the arg to pick interactively).").action(async u=>{let h=u??await yA("Pick the active instance:"),v=await jo(h);if(!v){let x=await Ot();w(`no stored sign-in for ${rt(h)}.`+(x.length?` Stored: ${x.map(S=>S.baseUrl).join(", ")}.`:"")+` Run \`turbobase auth login --base-url ${rt(h)} --token tb_pat_\u2026\`.`)}b(`active instance \u2192 ${v.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 u=>{if(u.all){let x=await Ot();for(let S of x)await ta(S.baseUrl);b(`removed ${x.length} stored sign-in${x.length===1?"":"s"}.`);return}let h=await ta(u.baseUrl);h||w(u.baseUrl?`no stored sign-in for ${rt(u.baseUrl)}.`:"nothing to sign out of (or ambiguous \u2014 pass --base-url / --all).");let v=await mt();b(`signed out of ${h}.`+(v?` Active instance \u2192 ${v.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 u=>{let{baseUrl:h,token:v}=await H(u.baseUrl),x;try{x=await mr({baseUrl:h,token:v,recordsOnly:u.recordsOnly})}catch(g){let T=g instanceof Error?g.message:String(g);if(!u.recordsOnly&&/openapi\.json failed \(401/.test(T)){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 mr({baseUrl:h,token:v,recordsOnly:!0})}catch(E){w(`types generation failed: ${E instanceof Error?E.message:String(E)}`)}}else w(`types generation failed: ${T}`)}let S=se(process.cwd(),u.out);await ae.writeFile(S,x,"utf8"),b(`wrote ${S} (${x.split(`
|
|
3819
3819
|
`).length} lines).`)}),jp(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 u=>{let{baseUrl:h,token:v}=await H(u.baseUrl),x=new URLSearchParams;u.records&&x.set("records",u.records),u.query&&x.set("query",u.query);let S=x.toString(),g=await fetch(`${h}/v1/catalog/grounding${S?`?${S}`:""}`,{headers:{Authorization:`Bearer ${v}`,Accept:"application/json"}});g.ok||w(`grounding fetch failed: GET /v1/catalog/grounding \u2192 ${g.status}`);let E=((await g.json()).markdown??"").trim(),A=se(process.cwd(),u.out),C=u.records?` records=${u.records}`:"",O=`<!-- GENERATED by \`turbobase project grounding\` against ${h}${C} \u2014 do not edit; re-run to refresh. -->
|
|
@@ -3843,7 +3843,7 @@ samples init: ${E} written, ${A} skipped.`)});let Tt=e.command("guardrails").des
|
|
|
3843
3843
|
`+m.dim("create one with `turbobase guardrails create <name> --source <s> --record <r>`"));b(ft(S.map(Li),{head:Ub})),b(m.dim(" first match wins; lower PRIO is evaluated first."))}),Tt.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).",u=>Number(u)).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(u,h)=>{let{baseUrl:v,token:x}=await H(h.baseUrl),S=new q({baseUrl:v,token:x}),g;try{g=await S.guardrailCreate({name:u,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(T){V(T,"extensions:deploy","create a guardrail"),w(`create guardrail failed: ${T instanceof Error?T.message:String(T)}`)}b(`${m.green("\u2713")} guardrail ${m.bold(g.name??g.id)} created${g.status==="active"?"":m.dim(` (${g.status} \u2014 activate it to take effect)`)}`),b(m.dim(` ${g.decision} ${g.source}/${g.record??g.op}${g.actor?` actor=${g.actor}`:""}${g.when?` where ${g.when}`:""} @prio ${g.priority}`)),b(m.dim(` verify with: turbobase guardrails test --source ${g.source==="*"?"<source>":g.source} --record <record>`))}),Tt.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.",u=>Number(u)).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(u,h)=>{h.decision===void 0&&h.priority===void 0&&h.when===void 0&&h.reason===void 0&&w("edit: nothing to change \u2014 pass --decision, --priority, --when or --reason.");let{baseUrl:v,token:x}=await H(h.baseUrl),S=new q({baseUrl:v,token:x}),g;try{g=await S.guardrailUpdate(u,{...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(T){V(T,"extensions:deploy","edit a guardrail"),w(`edit guardrail failed: ${T instanceof Error?T.message:String(T)}`)}b(`${m.green("\u2713")} guardrail ${m.bold(g.name??g.id)} updated`),b(m.dim(` ${g.decision} ${g.source}/${g.record??g.op}${g.when?` where ${g.when}`:""} @prio ${g.priority}`))});for(let[u,h,v]of[["activate","active","activated"],["deactivate","inactive","deactivated"]])Tt.command(`${u} <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,S)=>{let{baseUrl:g,token:T}=await H(S.baseUrl),E=new q({baseUrl:g,token:T});try{await E.guardrailUpdate(x,{status:h})}catch(A){V(A,"extensions:deploy",`${u} a guardrail`),w(`${u} guardrail failed: ${A instanceof Error?A.message:String(A)}`)}b(`${m.green("\u2713")} guardrail ${m.bold(x)} ${v}`),h==="inactive"&&b(m.dim(" writes it was gating now fall through to the other rules."))});Tt.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(u,h)=>{let{baseUrl:v,token:x}=await H(h.baseUrl),S=new q({baseUrl:v,token:x});try{await S.guardrailDelete(u)}catch(g){V(g,"extensions:deploy","delete a guardrail"),w(`delete guardrail failed: ${g instanceof Error?g.message:String(g)}`)}b(`${m.green("\u2713")} guardrail ${m.bold(u)} deleted`)}),Tt.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(u,h)=>{let{baseUrl:v,token:x}=await H(h.baseUrl),S=new q({baseUrl:v,token:x}),g=A=>A==="deny"?"deny-by-default (allow-list)":"allow-by-default (deny-list)";if(u===void 0){let A;try{A=await S.guardrailPostureGet()}catch(C){V(C,"extensions:deploy","read the guardrail posture"),w(`read posture failed: ${C instanceof Error?C.message:String(C)}`)}b(`guardrail posture: ${m.bold(g(A.mode))}`),b(m.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 T=u.trim().toLowerCase();T!=="allow"&&T!=="deny"&&w("posture must be 'allow' or 'deny'.");let E;try{E=await S.guardrailPostureSet(T)}catch(A){V(A,"extensions:deploy","set the guardrail posture"),w(`set posture failed: ${A instanceof Error?A.message:String(A)}`)}b(`${m.green("\u2713")} guardrail posture set to ${m.bold(g(E.mode))}`),E.mode==="deny"&&b(m.dim(" Every record with no active ALLOW rule is now frozen for write-back. Verify with `guardrails test --source <s> --record <r>`."))}),Tt.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 u=>{let h;if(u.fields)try{h=JSON.parse(u.fields)}catch(E){w(`--fields must be valid JSON: ${E instanceof Error?E.message:String(E)}`)}let{baseUrl:v,token:x}=await H(u.baseUrl),S=new q({baseUrl:v,token:x}),g;try{g=await S.guardrailTest({source:u.source,record:u.record,...u.actor?{actor:u.actor}:{},...u.externalId?{externalId:u.externalId}:{},...h?{fields:h}:{},...u.op?{op:u.op}:{},...u.phase?{phase:u.phase}:{}})}catch(E){V(E,"extensions:deploy","test guardrails"),w(`guardrails test failed: ${E instanceof Error?E.message:String(E)}`)}if(u.json)return b(JSON.stringify(g,null,2));let T=`${u.source}/${g.op??u.record}`;g.degraded?(b(`${m.red("\u2717")} ${T} \u2014 guardrail policy is UNREADABLE; writes fail closed`),b(m.dim(` ${g.reason}`))):g.allowed?b(`${m.green("\u2713")} ${T} \u2014 ${g.decision}`+(g.blockedBy?m.dim(` (rule ${g.blockedBy})`):m.dim(" (no rule matched)"))):(b(`${m.red("\u2717")} ${T} \u2014 ${g.decision} by ${m.bold(g.blockedBy??"?")} at the ${g.phase} gate`),g.message&&b(m.dim(` ${g.message}`))),g.decision==="require_confirm"&&b(m.yellow(" this write is HELD for operator approval, not refused.")),g.allowed||(process.exitCode=1)});let fn=e.command("tables").description("Author custom tables on your instance (create, evolve, inspect)."),xo=(u,h={})=>{let v=`${m.bold(u.table)} ${m.dim(u.displayName)}`;b(u.exists===!1?`${v} ${m.dim("(new)")}`:v);for(let x of u.fields){let S=[x.required?"required":null].filter(Boolean).join(", "),g=h.showClaimants&&x.claimedBy?.length?` ${m.dim(`\u2190 ${x.claimedBy.join(", ")}`)}`:"";b(` ${x.column.padEnd(28)} ${x.type}${S?` (${S})`:""}${g}`)}h.showClaimants&&u.claimants.length>1&&b(m.dim(` claimed by ${u.claimants.join(", ")}`))};function Ro(u){if(u instanceof Q&&u.status===409){let h=u.body;h?.error==="table_conflict"&&h.message&&(L(h.message),process.exit(1))}w(`tables: ${u instanceof Error?u.message:String(u)}`)}let Gd=async u=>{let h=et(se(process.cwd(),u),"public","turbobase.json"),v=await ae.readFile(h,"utf8").catch(()=>null);if(v===null)return null;let x;try{x=JSON.parse(v)}catch(g){w(`${h} is not valid JSON: ${g instanceof Error?g.message:String(g)}`)}let S=x.tables;return S===void 0?{tables:[],manifestPath:h}:(Array.isArray(S)||w(`${h}: "tables" must be an array.`),{tables:S,manifestPath:h})};fn.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 u=>{let{baseUrl:h,token:v}=await H(u.baseUrl),x=new q({baseUrl:h,token:v}),S;try{S=await x.tablesList()}catch(g){V(g,"extensions:deploy","list tables"),w(`list tables failed: ${g instanceof Error?g.message:String(g)}`)}if(u.json)return b(JSON.stringify(S,null,2));if(S.length===0)return b("no custom tables yet \u2014 create one with `turbobase tables create <slug>`.");b(ft(S.map(g=>[g.table,g.displayName,String(g.fields.length),g.status,g.claimedBy.join(", ")||"\u2014"]),{head:["TABLE","NAME","FIELDS","STATUS","CLAIMED BY"]}))}),fn.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(u,h)=>{let v=await Gd(u??".");if(v||w(`no public/turbobase.json under ${se(process.cwd(),u??".")}.`),v.tables.length===0)return b(`${v.manifestPath} declares no tables \u2014 nothing to plan.`);let{baseUrl:x,token:S}=await H(h.baseUrl),g=new q({baseUrl:x,token:S}),T;try{T=await J("planning tables",()=>g.tablesPlan({claimedBy:h.as,tables:v.tables}))}catch(E){V(E,"extensions:deploy","plan tables"),Ro(E)}b(`claiming as ${m.bold(T.claimedBy)}`);for(let E of T.tables)xo(E,{showClaimants:!0});b(""),b(m.dim("nothing was applied \u2014 run `turbobase tables apply` (or deploy the app) to make it so."))}),fn.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(u,h)=>{let v=await Gd(u??".");if(v||w(`no public/turbobase.json under ${se(process.cwd(),u??".")}.`),v.tables.length===0)return b(`${v.manifestPath} declares no tables \u2014 nothing to apply.`);let{baseUrl:x,token:S}=await H(h.baseUrl),g=new q({baseUrl:x,token:S}),T;try{T=await J("applying tables",()=>g.tablesApply({claimedBy:h.as,tables:v.tables}))}catch(E){V(E,"extensions:deploy","apply tables"),Ro(E)}for(let E of T.tables)xo(E,{showClaimants:!0});b(`applied ${T.tables.length} table${T.tables.length===1?"":"s"} as ${T.claimedBy}.`)}),fn.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.",(u,h)=>[...h??[],u],[]).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(u,h)=>{let v=h.field.map(E=>{let[A,C,O]=E.split(":");return(!A||!C)&&w(`--field "${E}" must be name:type[:required] (e.g. title:text:required).`),O!==void 0&&O!=="required"&&w(`--field "${E}": the third part may only be "required".`),{name:A,type:C,...O==="required"?{required:!0}:{}}}),{baseUrl:x,token:S}=await H(h.baseUrl),g=new q({baseUrl:x,token:S}),T;try{T=await J(`creating cr_${u}`,()=>g.tablesApply({tables:[{slug:u,displayName:h.name??u,fields:v,...h.restrictToOwner?{rowLevelOwner:!0}:{}}]}))}catch(E){V(E,"extensions:deploy","create a table"),Ro(E)}for(let E of T.tables)xo(E);b(""),b(m.dim("grant access under Settings \u203A Users, or in the Records page permissions matrix."))}),fn.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(u,h)=>{h.confirm!==u&&w(`drop refused: --confirm "${h.confirm}" does not match "${u}".`);let{baseUrl:v,token:x}=await H(h.baseUrl),S=new q({baseUrl:v,token:x}),g;try{g=await J(`dropping cr_${u}`,()=>S.tablesDrop(u,h.confirm))}catch(T){V(T,"extensions:deploy","drop a table"),w(`drop failed: ${T instanceof Error?T.message:String(T)}`)}if(!g.removed)return b(`no table named "${u}" on this instance \u2014 nothing dropped.`);b(`dropped ${g.table} and every row in it.`)});let ji=e.command("sandbox").description("Ephemeral local authoring sandboxes.");ji.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 u=>{process.env.TURBOBASE_SANDBOX="1";let h=Bd(),v;try{v=await Cm(h,{fromTenant:u.from,ttl:u.ttl,sample:u.sample,baseUrl:u.baseUrl})}catch(x){x instanceof Cn&&w(x.message),w(`sandbox create failed: ${x instanceof Error?x.message:String(x)}`)}b(`sandbox ${v.name} ready.`),b(` source: ${v.fromTenant} (sampled ${Math.round(v.sampleFraction*100)}%, redacted)`),b(` url: ${v.url}`),b(` expires: ${v.expiresAt}`)}),ji.command("destroy").argument("<name>","The sandbox name to tear down.").description("Drop a sandbox database and remove it from the registry.").action(async u=>{let h=Bd();await Fa(h).catch(()=>[]);let v;try{v=await Im(h,u)}catch(x){w(`sandbox destroy failed: ${x instanceof Error?x.message:String(x)}`)}v||w(`no sandbox named '${u}'.`),b(`destroyed sandbox ${v.name} (dropped ${v.database}).`)}),ji.command("list").description("List active sandboxes with TTL/expiry; reaps any past their TTL.").action(async()=>{let u=Bd(),h=await Fa(u).catch(()=>[]),v=await ls(),{active:x}=Ba(v);if(x.length===0)b("no active sandboxes.");else{for(let S of x)b(` ${S.name} from=${S.fromTenant} expires=${S.expiresAt} ${S.url}`);b(`
|
|
3844
3844
|
${x.length} active sandbox(es).`)}h.length>0&&b(`(reaped ${h.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.",Xo).action(async u=>{let{baseUrl:h,token:v}=await H(u.baseUrl),x=new q({baseUrl:h,token:v}),S;try{S=await J("exporting instance config",()=>x.configExport())}catch(E){E instanceof Q&&(E.status===401||E.status===403)&&w("ext export forbidden \u2014 the stored PAT lacks extensions:deploy. Re-run `turbobase auth login`."),w(`ext export failed: ${E instanceof Error?E.message:String(E)}`)}let g=typeof S.serialized=="string"?S.serialized:`${JSON.stringify(S.manifest??S,null,2)}
|
|
3845
3845
|
`,T=se(process.cwd(),u.out,xn);await ae.mkdir(zt(T),{recursive:!0}),await ae.writeFile(T,g,"utf8"),b(`wrote ${u.out}/${xn} (${g.split(`
|
|
3846
|
-
`).length} lines).`)});let Wd=o.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),Mb=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],Jd=()=>process.env.TURBOBASE_HOME?.trim()||et(fA(),".turbobase"),qb=()=>"1.
|
|
3846
|
+
`).length} lines).`)});let Wd=o.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),Mb=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],Jd=()=>process.env.TURBOBASE_HOME?.trim()||et(fA(),".turbobase"),qb=()=>"1.36.0",$o=async()=>Promise.all(Mb.map(async u=>{try{return await import(u.name),{...u,present:!0}}catch{return{...u,present:!1}}}));Wd.command("status").description("Report which optional native packages are installed.").action(async()=>{let u=await $o();for(let h of u)b(`${h.present?ve.success("installed"):ve.warn("missing")} ${m.bold(h.name)} \u2014 ${h.needs}`);u.some(h=>!h.present)&&b(`
|
|
3847
3847
|
run \`${$n} ext extras install\` to fetch them into ${Jd()}.`)}),Wd.command("install").description("Install the optional native packages into $TURBOBASE_HOME (needs npm).").action(async()=>{let u=await $o();if(u.every(A=>A.present)){b(ve.success("extras already installed \u2014 nothing to do."));return}let h=Jd();await ae.mkdir(h,{recursive:!0});let v=u.filter(A=>!A.present).map(A=>A.name),x=ja(process.platform,process.arch),S=async(A,C)=>{let{code:O,stderr:G}=await To("npm",["install","--prefix",h,"--no-audit","--no-fund",...A]);O!==0&&w(`extras install failed while installing ${C} (npm exited ${O}).
|
|
3848
3848
|
${G.trim()}
|
|
3849
3849
|
These are native packages \u2014 they need npm and a working toolchain. Everything except \`test\` and \`replay\` works without them.`)},g=v.filter(A=>A!=="isolated-vm");if(g.length>0){let A=g.map(C=>`${C}@${Na[C]??"latest"}`);b(`installing ${A.join(", ")} \u2192 ${h}/node_modules \u2026`),await S(A,g.join(", "))}if(v.includes("isolated-vm")){let A=`isolated-vm@${Na["isolated-vm"]}`;if(b(`installing ${A} \u2192 ${h}/node_modules \u2026`),await S(["--ignore-scripts",A],"isolated-vm"),Tm.includes(x)){let O=`${La}@${qb()}`;b(`fetching ${O} (no upstream binary for ${x}) \u2026`);let{code:G}=await To("npm",["install","--prefix",h,"--no-audit","--no-fund","--ignore-scripts",O]);G!==0&&b(ve.warn(`could not fetch ${O} \u2014 falling back to a source build.`))}let C=await xm({home:h});C.length>0&&b(` ${ve.success("prebuilt")} ${C.join(", ")} \u2192 isolated-vm/prebuilds/${x}`)}let T=await $o(),E=T.filter(A=>!A.present).map(A=>A.name);if(E.includes("isolated-vm")){b(ve.warn(`no prebuilt isolated-vm for ${x} / Node ${process.versions.node} (${Da()}) \u2014 building from source. This needs Python and a C++ toolchain, and takes a few minutes.`));let{code:A}=await To("npm",["rebuild","--prefix",h,"isolated-vm"]);A===0&&(T=await $o(),E=T.filter(C=>!C.present).map(C=>C.name))}E.length>0&&w(`npm succeeded but ${E.join(", ")} still will not load \u2014 no prebuilt binary for ${x} / Node ${process.versions.node} (${Da()}), 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 Ar=e.command("release").description("Ship a release to your instance: deploy | status | rollback | list.");return Ar.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 u=>{let h=u.target==="approved"?"approved":u.target==="auto"?"auto":null;h||w(`--target must be 'auto' or 'approved' (got '${u.target}').`);let v=await AA(se(process.cwd(),u.project??".")),x=Object.keys(v).length;!u.solution&&x===0&&w("release deploy needs --solution <id> (live-fetch) or an SDF project (a src/ dir under --project, default cwd) to upload.");let g=(u.ref?await Fd(["rev-parse",u.ref])??u.ref:null)??process.env.GITHUB_SHA??await Fd(["rev-parse","HEAD"]),T=u.gitRepo??process.env.GITHUB_REPOSITORY??await Fd(["remote","get-url","origin"]),E=u.gitRef??process.env.GITHUB_REF??null,A=u.prUrl??process.env.PR_URL??null,{baseUrl:C,token:O}=await H(u.baseUrl),G=new q({baseUrl:C,token:O}),N;try{N=await G.releaseDeploy({solutionId:u.solution??null,target:h,approvedBy:u.approvedBy??null,files:x>0?v:null,gitRepo:T,gitRef:E,gitSha:g,prUrl:A})}catch(W){V(W,"releases:write","release deploy"),W instanceof Q&&W.status===404&&w("solution_not_found \u2014 no solution with that id."),w(`release deploy failed: ${W instanceof Error?W.message:String(W)}`)}b(`release ${N.releaseId} enqueued \u2192 taskId=${N.taskId} (target=${h}`+(x>0?`, uploaded ${x} bundle file(s)`:", live-fetch")+")."),u.follow&&await Ta(()=>G.releaseStatus(N.releaseId),`release ${N.releaseId}`,u.intervalMs)}),Ar.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(u,h)=>{let{baseUrl:v,token:x}=await H(h.baseUrl),S=new q({baseUrl:v,token:x}),g;try{g=await S.releaseFiles(u)}catch(A){A instanceof Q&&A.status===404&&w(`release ${u} not found.`),A instanceof Q&&A.status===409&&w(`no built project was retained for release ${u}. Files are kept for a FAILED attempt only \u2014 a shipped bundle is recoverable from the account.`),V(A,"releases:write","pull a release"),w(`release pull failed: ${A instanceof Error?A.message:String(A)}`)}let T=se(process.cwd(),h.dir),E=0;for(let[A,C]of[["owned",g.owned],["context",g.context]])for(let[O,G]of Object.entries(C)){let N=et(T,A,O);await ae.mkdir(zt(N),{recursive:!0}),await ae.writeFile(N,G,"utf8"),E++}await ae.writeFile(et(T,".turbobase-release.json"),JSON.stringify({releaseId:g.releaseId,version:g.version,contentDigest:g.contentDigest},null,2),"utf8"),b(`pulled release ${g.releaseId} (${g.status}) \u2192 ${T}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@turbobase/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.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",
|