@tangle-network/sandbox-cli 0.12.0-develop.20260724231304.911a687 → 0.12.1-develop.20260725201418.71254fb
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import{createRequire as e}from"node:module";import"dotenv/config";import{Command as t}from"commander";import{createHash as n,randomBytes as r,randomUUID as i}from"node:crypto";import a from"chalk";import*as o from"node:fs";import{chmodSync as s,existsSync as c,mkdirSync as l,readFileSync as u,renameSync as d,writeFileSync as f}from"node:fs";import*as p from"node:path";import{basename as ee,dirname as te,extname as ne,join as m,resolve as re}from"node:path";import{AuthError as ie,NetworkError as h,NotFoundError as ae,QuotaError as oe,Sandbox as g,ServerError as _,StateError as v,TimeoutError as se,ValidationError as ce,createConfidentialSandbox as le,generateAttestationNonce as ue,parseSSEStream as de}from"@tangle-network/sandbox";import*as fe from"node:os";import{homedir as pe,tmpdir as me}from"node:os";import{execFileSync as he,spawn as ge}from"node:child_process";import{HubClient as _e,HubSdkError as ve}from"@tangle-network/hub-sdk";import ye from"ora";import{CONTENT_COMPLETION_KEY as be,CONTENT_PROMPT_KEY as xe,CONTENT_TOOL_ARGS_KEY as Se,CONTENT_TOOL_NAME_KEY as Ce,CONTENT_TOOL_RESULT_KEY as we,TANGLE_SUBJECT_KEY as Te,asContentField as y,asContentString as Ee}from"@tangle-network/agent-core/telemetry";import{createMcpServer as De}from"@tangle-network/sandbox/agent";import{readFile as Oe}from"node:fs/promises";import{fileURLToPath as ke}from"node:url";import Ae from"ws";import{supervise as je,workerFromBackend as Me}from"@tangle-network/agent-runtime/loops";import{createIntelligenceClient as Ne}from"@tangle-network/sandbox/intelligence";function Pe(e){if(e.startsWith(`github:`)){let[t,n]=e.slice(7).split(`@`,2),r=t.split(`/`).filter(Boolean);if(r.length<3)throw Error(`invalid github ref "${e}": expected github:<owner>/<repo>/<path>[@<ref>]`);let i=`${r[0]}/${r[1]}`,a=r.slice(2).join(`/`);return{kind:`github`,repository:i,path:a,...n?{ref:n}:{},name:ee(a)}}let t;try{t=u(e,`utf8`)}catch{throw Error(`cannot read skill/tool "${e}": not a readable file and not a github: ref`)}return{kind:`inline`,name:ee(e),content:t}}function Fe(e){let t=(e.skills??[]).map(Pe),n=(e.tools??[]).map(Pe),r=t.length>0||n.length>0;if(!(!e.model&&!e.systemPrompt&&!r))return{...e.model?{model:{default:e.model}}:{},...e.systemPrompt?{prompt:{systemPrompt:e.systemPrompt}}:{},...r?{resources:{...t.length>0?{skills:t}:{},...n.length>0?{tools:n}:{}}}:{}}}async function Ie(e){let t;if(e.startsWith(`github:`)){let[n,r]=e.slice(7).split(`@`,2),i=n.split(`/`).filter(Boolean);if(i.length<3)throw Error(`invalid --profile github ref "${e}": expected github:<owner>/<repo>/<path>[@<ref>]`);let a=r??`main`;t=await We(`https://raw.githubusercontent.com/${i[0]}/${i[1]}/${a}/${i.slice(2).join(`/`)}`)}else t=/^https?:\/\//.test(e)?await We(e):u(e,`utf8`);let n;try{n=JSON.parse(t)}catch{throw Error(`profile at "${e}" is not valid JSON`)}return Be(n,e),n}const Le=[`prompt`,`model`,`permissions`,`tools`,`mcp`,`connections`,`subagents`,`resources`,`hooks`,`modes`,`confidential`,`extensions`],Re=new Set([...Le,`name`,`description`,`version`,`tags`,`metadata`]),ze=[`files`,`tools`,`skills`,`agents`,`commands`];function Be(e,t){if(!e||typeof e!=`object`||Array.isArray(e))throw Error(`profile at "${t}" is not an object`);let n=Object.keys(e),r=n.some(e=>Le.includes(e)),i=n.includes(`name`)&&n.every(e=>Re.has(e));if(!r&&!i)throw Error(`"${t}" does not look like an agent profile: no recognized profile keys (model, prompt, resources, tools, connections, …). Got: ${n.slice(0,6).join(`, `)||`(empty object)`}.`);Ve(e,t)}function Ve(e,t){let n=e.resources;if(!n||typeof n!=`object`)return;for(let e of ze){let r=n[e];if(r!==void 0){if(!Array.isArray(r))throw Error(`profile at "${t}" is malformed: resources.${e} must be an array`);for(let n of r)Ue(e===`files`?He(n):n,t)}}let r=n.instructions;r!==void 0&&typeof r!=`string`&&Ue(r,t)}function He(e){return e&&typeof e==`object`?e.resource:e}function Ue(e,t){let n=e&&typeof e==`object`?e.kind:void 0;if(n!==`inline`&&n!==`github`)throw Error(`profile at "${t}" has an invalid resource ref kind ${JSON.stringify(n)}: expected "inline" or "github"`)}async function We(e){let t=await fetch(e);if(!t.ok)throw Error(`failed to fetch ${e} (${t.status})`);return t.text()}async function Ge(e){let t=e.profileSource?await Ie(e.profileSource):void 0,n=Fe(e);return t?n?{...t,...n,...n.resources?{resources:{...t.resources,...n.resources}}:{}}:t:n}const Ke=p.join(fe.homedir(),`.tangle`),qe=p.join(Ke,`credentials.json`),Je=`tangle-sandbox-cli`,Ye=`TANGLE_ALLOW_PLAINTEXT_CREDENTIALS`;var Xe=class extends Error{constructor(e,t){super(`Credentials file at ${e} is corrupted and cannot be parsed. Inspect or remove it manually before retrying — refusing to overwrite it automatically.`),this.filePath=e,this.name=`CredentialsFileCorruptedError`,t&&(this.cause=t)}},Ze=class extends Error{constructor(){super(`Could not store credential in the OS keychain, and the plaintext fallback is not enabled. Install a keychain provider (macOS Keychain or libsecret/secret-tool on Linux), or set ${Ye}=1 to opt into a plaintext credentials.json with mode 0600.`),this.name=`KeychainUnavailableError`}};function Qe(e){let t=nt(e);if(t)return{value:t,source:`keychain`};let n=at(e);return n?{value:n,source:`file`}:{source:`none`}}function $e(e,t){if(rt(e,t))return st(e),`keychain`;if(!tt())throw new Ze;return ot(e,t),`file`}function et(e){it(e),st(e)}function tt(){let e=process.env[Ye];if(!e)return!1;let t=e.trim().toLowerCase();return t===`1`||t===`true`||t===`yes`}function nt(e){if(process.platform===`darwin`)try{return he(`security`,[`find-generic-password`,`-s`,Je,`-a`,dt(e),`-w`],{encoding:`utf8`,stdio:[`ignore`,`pipe`,`ignore`]}).trim()}catch{return}if(process.platform===`linux`)try{return he(`secret-tool`,[`lookup`,`service`,Je,`account`,dt(e)],{encoding:`utf8`,stdio:[`ignore`,`pipe`,`ignore`]}).trim()}catch{return}}function rt(e,t){if(process.platform===`darwin`)try{return he(`security`,[`add-generic-password`,`-U`,`-s`,Je,`-a`,dt(e),`-w`,t],{stdio:[`ignore`,`ignore`,`ignore`]}),!0}catch{return!1}if(process.platform===`linux`)try{return he(`secret-tool`,[`store`,`--label=Tangle Sandbox CLI`,`service`,Je,`account`,dt(e)],{input:t,stdio:[`pipe`,`ignore`,`ignore`]}),!0}catch{return!1}return!1}function it(e){if(process.platform===`darwin`){try{he(`security`,[`delete-generic-password`,`-s`,Je,`-a`,dt(e)],{stdio:[`ignore`,`ignore`,`ignore`]})}catch{}return}if(process.platform===`linux`)try{he(`secret-tool`,[`clear`,`service`,Je,`account`,dt(e)],{stdio:[`ignore`,`ignore`,`ignore`]})}catch{}}function at(e){return ct()[e]}function ot(e,t){let n=ct();n[e]=t,lt(n)}function st(e){let t;try{t=ct()}catch(e){if(e instanceof Xe)return;throw e}e in t&&(delete t[e],lt(t))}function ct(){let e;try{e=o.readFileSync(qe,`utf8`)}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return{};throw e}try{let t=JSON.parse(e);if(typeof t!=`object`||!t||Array.isArray(t))throw new Xe(qe);let n={};for(let[e,r]of Object.entries(t))typeof r==`string`&&(n[e]=r);return n}catch(e){throw e instanceof Xe?e:new Xe(qe,e instanceof Error?e:void 0)}}function lt(e){if(ut(),Object.keys(e).length===0){o.existsSync(qe)&&o.unlinkSync(qe);return}let t=`${qe}.${process.pid}.tmp`;o.writeFileSync(t,`${JSON.stringify(e,null,2)}\n`,{mode:384}),o.renameSync(t,qe)}function ut(){if(!o.existsSync(Ke)){o.mkdirSync(Ke,{mode:448,recursive:!0});return}if(process.platform!==`win32`)try{(o.statSync(Ke).mode&511)!=448&&o.chmodSync(Ke,448)}catch{}}function dt(e){return`profile:${e}`}const ft=p.join(fe.homedir(),`.tangle`),pt=p.join(ft,`credentials`),mt=p.join(ft,`config.json`),b=`default`;function ht(){o.existsSync(ft)||o.mkdirSync(ft,{mode:448,recursive:!0})}function gt(e,t){ht();let n=`${e}.${process.pid}.tmp`;o.writeFileSync(n,t,{mode:384}),o.renameSync(n,e)}function _t(){try{if(o.existsSync(pt)){let e=o.readFileSync(pt,`utf-8`).trim();return vt(e)?e:e.match(/api_key\s*=\s*(\S+)/)?.[1]}}catch{}}function vt(e){return e.startsWith(`sk_`)||e.startsWith(`sk-tan-`)}function yt(){try{o.existsSync(pt)&&o.unlinkSync(pt)}catch{}}function x(){return zt(Rt())}function bt(e){let t=Bt(x(),e);gt(mt,`${JSON.stringify(t,null,2)}\n`)}function S(e){return Vt(e||process.env.TANGLE_PROFILE||process.env.SANDBOX_PROFILE||x().activeProfile||b)}function xt(e){bt({activeProfile:Vt(e)})}function St(){return process.env.TANGLE_SANDBOX||x().currentSandbox||void 0}function Ct(e){bt({currentSandbox:e})}function wt(){let e=x();delete e.currentSandbox,gt(mt,`${JSON.stringify(e,null,2)}\n`)}function Tt(){let e=x().deviceId;if(e&&/^[A-Za-z0-9_.-]{1,64}$/.test(e))return e;let t=fe.hostname().replace(/[^A-Za-z0-9_.-]/g,`-`).replace(/^-+|-+$/g,``).slice(0,32),n=r(4).toString(`hex`),i=`${t?`${t}-`:`device-`}${n}`;return bt({deviceId:i}),i}function Et(e){let t=e||St();if(!t)throw Error("No sandbox specified. Pass it positionally, set a default with `tangle use <id>`, pass `--box <id>`, or set TANGLE_SANDBOX.");return t}function Dt(){let e=x(),t=S(),n=new Set([b,...Object.keys(e.profiles??{})]);return e.activeProfile&&n.add(Vt(e.activeProfile)),[...n].map(e=>{let n=Qe(e),r=e===b?_t():void 0,i=n.source===`none`?r?`legacy-file`:`none`:n.source;return{name:e,active:e===t,hasApiKey:n.source!==`none`||!!r,baseUrl:Mt(void 0,e),apiKeySource:i}}).sort((e,t)=>e.name.localeCompare(t.name))}function Ot(e){let t=S(e);return{name:t,active:t===S(),apiKey:C(void 0,t),baseUrl:Mt(void 0,t),credentialSource:jt(void 0,t)}}function kt(e,t){let n=Vt(e),r=x(),i=Lt(n,r),a={},o=t.baseUrl??i.baseUrl;o&&(a.baseUrl=o);let s=t.activeTeamId??i.activeTeamId;s&&(a.activeTeamId=s);let c=t.activeTeamName??i.activeTeamName;c&&(a.activeTeamName=c);let l={...r.profiles??{}};a.baseUrl||a.activeTeamId||a.activeTeamName?l[n]=a:delete l[n];let u;return t.apiKey&&(u=$e(n,t.apiKey),n===b&&yt()),bt({profiles:Object.keys(l).length>0?l:{}}),u}function At(e){let t=S(e),n={...x().profiles??{}},r=n[t];if(r){let e={...r,apiKey:void 0};e.baseUrl||e.activeTeamId||e.activeTeamName?n[t]=e:delete n[t]}bt({profiles:n}),et(t),t===b&&yt()}function C(e,t){if(e)return e;let n=process.env.TANGLE_API_KEY||process.env.SANDBOX_API_KEY;if(n)return n;let r=S(t),i=Qe(r);if(i.value)return i.value;if(r===b)return _t()}function jt(e,t){if(e)return`flag`;if(process.env.TANGLE_API_KEY||process.env.SANDBOX_API_KEY)return`env`;let n=S(t),r=Qe(n);return r.source===`none`?n===b&&_t()?`legacy-file`:`none`:r.source}function Mt(e,t){if(e)return e;let n=process.env.TANGLE_BASE_URL||process.env.SANDBOX_BASE_URL;if(n)return n;let r=S(t),i=x(),a=Lt(r,i);return a.baseUrl?a.baseUrl:r===b&&i.baseUrl?i.baseUrl:`https://sandbox.tangle.tools`}function Nt(e){if(e)return e.replace(/\/v1\/hub\/?$/,``)}function w(e){let t=S(e.profile),n=C(e.apiKey,t);if(!n)throw Error(`No API key found for profile '${t}'. Set TANGLE_API_KEY or run: tangle auth login${t===b?``:` --profile ${t}`}`);return{apiKey:n,baseUrl:Mt(e.baseUrl,t),timeout:e.timeout??3e4,profile:t,...Pt(t)}}function Pt(e){let t=x(),n=Lt(S(e),t);return{activeTeamId:n.activeTeamId,activeTeamName:n.activeTeamName}}function Ft(e,t){kt(S(t),{activeTeamId:e.id,activeTeamName:e.name})}function It(e){let t=S(e),n=x(),r={...n.profiles??{}},i={baseUrl:Lt(t,n).baseUrl};i.baseUrl?r[t]=i:delete r[t],bt({profiles:r})}function Lt(e,t=x()){let n=Vt(e);return{...n===b?{baseUrl:t.baseUrl,activeTeamId:t.profiles?.[b]?.activeTeamId,activeTeamName:t.profiles?.[b]?.activeTeamName}:{},...t.profiles?.[n]??{}}}function Rt(){try{if(o.existsSync(mt)){let e=o.readFileSync(mt,`utf-8`);return JSON.parse(e)}}catch{}return{}}function zt(e){let t=!1,n={};e.apiKey&&($e(b,e.apiKey),yt(),t=!0);for(let[r,i]of Object.entries(e.profiles??{})){i.apiKey&&($e(r,i.apiKey),t=!0);let e={};i.baseUrl&&(e.baseUrl=i.baseUrl),i.activeTeamId&&(e.activeTeamId=i.activeTeamId),i.activeTeamName&&(e.activeTeamName=i.activeTeamName),Object.keys(e).length>0&&(n[r]=e)}let r={...e,apiKey:void 0,profiles:Object.keys(n).length>0?n:void 0};return t&>(mt,`${JSON.stringify(r,null,2)}\n`),r}function Bt(e,t){let n=t.profiles===void 0?{...e.profiles??{}}:Object.fromEntries(Object.entries(t.profiles).filter(([,e])=>!!(e.apiKey||e.baseUrl||e.activeTeamId||e.activeTeamName)));return{...e,...t,profiles:Object.keys(n).length>0?n:void 0}}function Vt(e){let t=e.trim().toLowerCase();if(!t)throw Error(`Profile name cannot be empty`);if(!/^[a-z0-9][a-z0-9._-]*$/.test(t))throw Error(`Profile names may only contain lowercase letters, numbers, dots, underscores, and hyphens`);return t}let T=null,E=null;function D(e){if(e)return T&&E&&E.apiKey===e.apiKey&&E.baseUrl===e.baseUrl&&E.timeout===e.timeout?T:(T=new g({apiKey:e.apiKey,baseUrl:e.baseUrl,timeoutMs:e.timeout}),E=e,T);if(T)return T;let t=w({});return T=new g({apiKey:t.apiKey,baseUrl:t.baseUrl,timeoutMs:t.timeout}),E=t,T}function Ht(){T=null,E=null}async function Ut(e,t){let n=t.trim();if(n.length===0)throw Error(`Connection reference is empty.`);if(n.startsWith(`hubconn_`))return n;let{connections:r}=await e.connections.list();return Wt(r,n)}function Wt(e,t){let n=t.trim();if(n.length===0)throw Error(`Connection reference is empty.`);let r=e.find(e=>e.id===n);if(r)return r.id;let i=n.indexOf(`:`),a=(i===-1?n:n.slice(0,i)).trim().toLowerCase(),o=i===-1?null:n.slice(i+1).trim(),s=e.filter(e=>e.status!==`revoked`&&e.providerId.toLowerCase()===a);if(o&&o.length>0){let e=o.toLowerCase();s=s.filter(t=>(t.accountDisplay??``).toLowerCase()===e||t.displayName.toLowerCase()===e)}if(s.length===1)return s[0].id;throw s.length===0?Error(`No hub connection matches "${t}". List your connections with \`tangle hub connections\`.`):Error(Gt(t,s))}function Gt(e,t){let n=t.map(e=>`- ${e.id} (provider: ${e.providerId}, account: ${e.accountDisplay??e.displayName})`).join(`
|
|
2
2
|
`);return`Hub connection reference is ambiguous: "${e}" matches ${t.length} connections. Use a connection id or provider:account instead.
|
|
3
|
-
`+n}function Kt(e,t){return[...t,e]}async function qt(e,t){let n=e.map(e=>{let t=e.trim(),n=t.indexOf(`:`);return n===-1?{raw:e,ref:null,tail:null}:{raw:e,ref:t.slice(0,n).trim(),tail:t.slice(n+1)}});if(!n.some(e=>e.ref!==null&&e.ref!==`*`&&!e.ref.startsWith(`hubconn_`)))return[...e];let{connections:r}=await(await t()).connections.list();return n.map(e=>e.ref===null||e.ref===`*`||e.ref.startsWith(`hubconn_`)?e.raw:`${Wt(r,e.ref)}:${e.tail}`)}function Jt(e){let t=new Map,n=[];for(let r of e){let e=r.trim(),i=e.indexOf(`:`);if(i===-1)throw Error(`Invalid --connection "${r}": expected <connectionId>:<capability>[,<capability>]. List connection ids with \`tangle hub connections\` and capability paths with \`tangle hub tools search <keyword>\`.`);let a=e.slice(0,i).trim();if(a.length===0)throw Error(`Invalid --connection "${r}": connection id is empty. Use <connectionId>:<capability> — list ids with \`tangle hub connections\`.`);let o=e.slice(i+1).split(`,`).map(e=>e.trim()).filter(e=>e.length>0);if(o.length===0)throw Error(`Invalid --connection "${r}": grant at least one capability, e.g. ${a}:gmail.messages.list. Discover paths with \`tangle hub tools search <keyword>\`.`);if(o.includes(`*`)&&o.length>1)throw Error(`Invalid --connection "${r}": "*" grants all capabilities and can't be combined with specific paths. Use <connectionId>:* alone, or list explicit paths.`);if(a===`*`&&!(o.length===1&&o[0]===`*`))throw Error(`Invalid --connection "${r}": a "*" connection is only valid as "*:*" (all connections, all capabilities). Name a connection id for specific paths.`);let s=t.get(a);s===void 0&&(s=new Set,t.set(a,s),n.push(a));for(let e of o)s.add(e)}return n.map(e=>{let n=t.get(e),r=n?[...n]:[];if(r.includes(`*`)&&r.length>1)throw Error(`Invalid --connection for "${e}": "*" can't be combined with specific capability paths across repeated --connection flags.`);return{connectionId:e,capabilities:r}})}function Yt(e){return e==null?a.dim(`-`):typeof e==`boolean`?e?a.green(`yes`):a.red(`no`):e instanceof Date?Zt(e):typeof e==`string`&&Xt(e)?Zt(new Date(e)):String(e)}function Xt(e){return/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(e)}function Zt(e){let t=Date.now()-e.getTime();if(t<6e4)return`just now`;if(t<36e5)return`${Math.floor(t/6e4)} min ago`;if(t<864e5){let e=Math.floor(t/36e5);return`${e} hour${e>1?`s`:``} ago`}let n=Math.floor(t/864e5);return`${n} day${n>1?`s`:``} ago`}function Qt(e){switch(e){case`running`:return a.green(e);case`pending`:case`provisioning`:return a.yellow(e);case`stopped`:return a.gray(e);case`failed`:case`deleted`:return a.red(e);default:return e}}function O(e,t){if(e.length===0){console.log(a.dim(`No items found.`));return}let n=t.map(t=>{let n=t.header.length,r=Math.max(...e.map(e=>Yt(e[t.key]).length));return t.width??Math.max(n,r)+2}),r=t.map((e,t)=>a.bold(e.header.padEnd(n[t]))).join(``);console.log(r);for(let r of e){let e=t.map((e,t)=>{let i=Yt(r[e.key]);return e.key===`status`&&(i=Qt(String(r[e.key]))),i.padEnd(n[t])}).join(``);console.log(e)}}function k(e){console.log(JSON.stringify(e,null,2))}function A(e){console.log(a.green(`✓`),e)}function j(e){console.error(a.red(`✗`),e)}function $t(e){console.log(a.yellow(`!`),e)}function M(e){console.log(a.blue(`→`),e)}const en=new Set;function N(e){let t=ye({text:e,color:`cyan`});return en.add(t),t}function tn(){for(let e of en)e.isSpinning&&e.stop();en.clear()}function P(e,t=0){let n=` `.repeat(t);for(let[t,r]of Object.entries(e))r!=null&&console.log(`${n}${a.dim(`${t}:`)} ${Yt(r)}`)}function nn(e){if(console.log(),console.log(a.bold(`Sandbox Details`)),console.log(a.dim(`─`.repeat(40))),P({ID:e.id,Name:e.name,Status:Qt(e.status),Created:e.createdAt,Expires:e.expiresAt}),e.connection){if(console.log(),console.log(a.bold(`Connection`)),console.log(a.dim(`─`.repeat(40))),e.connection.ssh){let{ssh:t}=e.connection,n=process.platform===`win32`?`NUL`:`/dev/null`,r=`ssh -o ProxyCommand="${t.proxyCommand}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=${n} -o GlobalKnownHostsFile=${n} -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=4 -o TCPKeepAlive=yes ${t.username}@localhost -p ${t.port}`;P({SSH:process.platform===`win32`?`$env:TANGLE_SSH_PROXY_AUTH_TOKEN='<token>'; ${r}`:`TANGLE_SSH_PROXY_AUTH_TOKEN='<token>' ${r}`})}e.connection.webTerminalUrl&&P({"Web Terminal":e.connection.webTerminalUrl}),e.connection.runtimeUrl&&P({"API URL":e.connection.runtimeUrl})}console.log()}function F(e,t){tn(),t?k({error:e.message}):j(e.message),process.exit(1)}function I(e){k(e)}function L(e,t){if(t.length===0){console.log(a.dim(`No items found.`));return}let n=e.map((e,n)=>{let r=Math.max(...t.map(e=>String(e[n]??``).length));return Math.max(e.length,r)+2});console.log(e.map((e,t)=>a.bold(e.padEnd(n[t]))).join(``));for(let e of t)console.log(e.map((e,t)=>String(e??``).padEnd(n[t])).join(``))}function R(e,t=!1){tn(),t&&(k(rn(e)),process.exit(dn(e)));let n=an(e);console.error(a.red(`Error:`),n),process.exit(dn(e))}function rn(e){return e instanceof ve?{error:{code:e.code,message:e.message,...typeof e.status==`number`?{status:e.status}:{},...e.details===void 0?{}:{details:e.details}}}:un(e)?{error:{code:e.code,message:e.message,status:e.status,...e.details===void 0?{}:{details:e.details}}}:{error:{message:an(e)}}}function an(e){if(e instanceof ie)return`Authentication failed. Run 'tangle auth login' to authenticate.`;if(e instanceof ae)return`Resource not found. Check the ID and try again.`;if(e instanceof oe)return`Quota exceeded. Upgrade your plan or wait for quota reset.`;if(e instanceof ce)return`Invalid input: ${e.message}`;if(e instanceof v)return`Invalid state: ${e.message}`;if(e instanceof se)return`Request timed out after ${e.timeoutMs} ms. Retry, or re-run with a longer --timeout <ms> on commands that accept it (e.g. create, resume).`;if(e instanceof h)return`Network error. Check your connection and try again.`;if(e instanceof _)return`${e.status?`HTTP ${e.status}`:`server error`}: ${e.message}`;if(e instanceof ve){let t=typeof e.status==`number`?` (HTTP ${e.status})`:``;return e.code===`HUB_CONNECTION_MISSING`?on(e.code,t,e.message,e.details):e.code===`HUB_APPROVAL_REQUIRED`?cn(e.code,t,e.message):e.code===`HUB_UNAUTHENTICATED`?ln(e.code,t,e.message):`${e.code}${t}: ${e.message}`}return un(e)?e.code===`HUB_CONNECTION_MISSING`?on(e.code,` (HTTP ${e.status})`,e.message,e.details):e.code===`HUB_APPROVAL_REQUIRED`?cn(e.code,` (HTTP ${e.status})`,e.message):e.code===`HUB_UNAUTHENTICATED`?ln(e.code,` (HTTP ${e.status})`,e.message):`${e.code} (HTTP ${e.status}): ${e.message}`:e instanceof Error?e.message:String(e)}function on(e,t,n,r){let i=sn(r);return`${e}${t}: ${n}${i?`. Run: tangle hub connect ${i}`:``}`}function sn(e){if(!e||typeof e!=`object`)return;let t=e.provider;return typeof t==`string`&&t.length>0?t:void 0}function cn(e,t,n){return`${e}${t}: ${n}. Run the command again with --auto-approve, or run: tangle hub resume <approval-id> --accept. See: tangle hub resume --help`}function ln(e,t,n){return`${e}${t}: ${n}. The CLI auto-mints a short-lived Hub key from your login session, so this usually means that session has expired — run 'tangle auth login' to re-authenticate. To use a credential directly instead, set TANGLE_API_KEY, SANDBOX_API_KEY, or TANGLE_HUB_CAPABILITY_TOKEN (and pass --no-hub-key to skip auto-minting).`}function un(e){if(!(e instanceof Error))return!1;let t=e;return typeof t.code==`string`&&t.code.startsWith(`HUB_`)&&typeof t.status==`number`}function dn(e){return e instanceof ce?2:1}function fn(e,t){return n(`sha256`).update(`${e}${t}`).digest(`hex`).slice(0,32)}function pn(e){return`${e}:hub`}function mn(e){let{value:t}=Qe(pn(e));if(!t)return null;try{let e=JSON.parse(t);if(typeof e.key==`string`&&typeof e.expiresAt==`string`&&typeof e.platformUrl==`string`&&typeof e.sessionFingerprint==`string`)return{key:e.key,expiresAt:e.expiresAt,platformUrl:e.platformUrl,sessionFingerprint:e.sessionFingerprint}}catch{}return null}function hn(e,t){try{$e(pn(e),JSON.stringify(t))}catch{}}async function gn(e,t,n){let r=`${e.replace(/\/+$/,``)}/auth/cli/hub-key`,i;try{i=await fetch(r,{method:`POST`,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${t}`},body:JSON.stringify({device_id:n})})}catch(e){throw Error(`Could not reach ${r} to mint a Hub key: ${e instanceof Error?e.message:String(e)}`)}let a=await i.text(),o;try{o=JSON.parse(a)}catch{o=null}if(!i.ok||!o?.success){let e=typeof o?.error?.message==`string`?o.error.message:`HTTP ${i.status}`;throw Error(`Hub key minting failed: ${e}. Your sandbox session may have expired — run \`tangle auth login\`.`)}let s=o.data;if(typeof s?.api_key!=`string`||typeof s?.expires_at!=`string`||typeof s?.platform_url!=`string`)throw Error(`Hub key minting returned an unexpected response shape.`);return{key:s.api_key,expiresAt:s.expires_at,platformUrl:s.platform_url}}var _n=class{cache;fingerprint;constructor(e,t,n,r){this.profile=e,this.sandboxBaseUrl=t,this.sessionToken=n,this.deviceId=r,this.fingerprint=fn(t,n);let i=mn(e);this.cache=i&&i.sessionFingerprint===this.fingerprint?i:null}isFresh(e){return e!==null&&Date.parse(e.expiresAt)-Date.now()>6e4}async ensure(e=!1){if(!e&&this.isFresh(this.cache))return this.cache;let t={...await gn(this.sandboxBaseUrl,this.sessionToken,this.deviceId),sessionFingerprint:this.fingerprint};return this.cache=t,hn(this.profile,t),t}};function vn(e){if(!e)return!1;let t=e.trim().toLowerCase();return t===`1`||t===`true`||t===`yes`}function yn(e,t){if(e.baseUrl)return e.baseUrl;let n=Nt(process.env.TANGLE_HUB_URL);if(n)return n;let r=mn(t);return r?r.platformUrl:`https://id.tangle.tools`}function bn(e,t){let n=S(),r=C(e.apiKey,n),i=jt(e.apiKey,n),a=process.env.TANGLE_HUB_CAPABILITY_TOKEN?.trim()||void 0;if(a&&i===`env`)throw Error(`Set exactly one of TANGLE_API_KEY/SANDBOX_API_KEY or TANGLE_HUB_CAPABILITY_TOKEN, not both`);if(i===`flag`&&r)return{kind:`static`,apiKey:r,baseUrl:yn(e,n)};if(i===`env`&&r)return{kind:`static`,apiKey:a??r,baseUrl:yn(e,n)};if(a)return{kind:`static`,apiKey:a,baseUrl:yn(e,n)};if(!r)throw Error(`No API key found for profile '${n}'. Set TANGLE_API_KEY or run: tangle auth login${n===`default`?``:` --profile ${n}`}`);return t?{kind:`static`,apiKey:r,baseUrl:yn(e,n)}:{kind:`managed`,profile:n,sandboxBaseUrl:Mt(void 0,n),sessionToken:r,deviceId:Tt(),baseUrlOverride:e.baseUrl}}async function z(e={}){let t=bn(e,e.hubKey===!1||vn(process.env.TANGLE_SKIP_HUB_KEY));if(t.kind===`static`)return new _e({baseUrl:t.baseUrl,apiKey:t.apiKey});let n=new _n(t.profile,t.sandboxBaseUrl,t.sessionToken,t.deviceId),r=await n.ensure();return new _e({baseUrl:t.baseUrlOverride??r.platformUrl,authHeaders:async()=>({Authorization:`Bearer ${(await n.ensure()).key}`}),fetch:async(e,t)=>{let r=await fetch(e,t);if(r.status!==401)return r;let i=await n.ensure(!0),a=new Headers(t?.headers);return a.set(`Authorization`,`Bearer ${i.key}`),fetch(e,{...t,headers:a})}})}function xn(e){return e instanceof Date?e.toISOString():String(e)}function Sn(e,t){return`Sandbox name is ambiguous: ${e}. Use a sandbox id instead.\n${t.map(e=>`- ${e.id} (status: ${e.status}, created: ${xn(e.createdAt)})`).join(`
|
|
3
|
+
`+n}function Kt(e,t){return[...t,e]}async function qt(e,t){let n=e.map(e=>{let t=e.trim(),n=t.indexOf(`:`);return n===-1?{raw:e,ref:null,tail:null}:{raw:e,ref:t.slice(0,n).trim(),tail:t.slice(n+1)}});if(!n.some(e=>e.ref!==null&&e.ref!==`*`&&!e.ref.startsWith(`hubconn_`)))return[...e];let{connections:r}=await(await t()).connections.list();return n.map(e=>e.ref===null||e.ref===`*`||e.ref.startsWith(`hubconn_`)?e.raw:`${Wt(r,e.ref)}:${e.tail}`)}function Jt(e){let t=new Map,n=[];for(let r of e){let e=r.trim(),i=e.indexOf(`:`);if(i===-1)throw Error(`Invalid --connection "${r}": expected <connectionId>:<capability>[,<capability>]. List connection ids with \`tangle hub connections\` and capability paths with \`tangle hub tools search <keyword>\`.`);let a=e.slice(0,i).trim();if(a.length===0)throw Error(`Invalid --connection "${r}": connection id is empty. Use <connectionId>:<capability> — list ids with \`tangle hub connections\`.`);let o=e.slice(i+1).split(`,`).map(e=>e.trim()).filter(e=>e.length>0);if(o.length===0)throw Error(`Invalid --connection "${r}": grant at least one capability, e.g. ${a}:gmail.messages.list. Discover paths with \`tangle hub tools search <keyword>\`.`);if(o.includes(`*`)&&o.length>1)throw Error(`Invalid --connection "${r}": "*" grants all capabilities and can't be combined with specific paths. Use <connectionId>:* alone, or list explicit paths.`);if(a===`*`&&!(o.length===1&&o[0]===`*`))throw Error(`Invalid --connection "${r}": a "*" connection is only valid as "*:*" (all connections, all capabilities). Name a connection id for specific paths.`);let s=t.get(a);s===void 0&&(s=new Set,t.set(a,s),n.push(a));for(let e of o)s.add(e)}return n.map(e=>{let n=t.get(e),r=n?[...n]:[];if(r.includes(`*`)&&r.length>1)throw Error(`Invalid --connection for "${e}": "*" can't be combined with specific capability paths across repeated --connection flags.`);return{connectionId:e,capabilities:r}})}function Yt(e){return e==null?a.dim(`-`):typeof e==`boolean`?e?a.green(`yes`):a.red(`no`):e instanceof Date?Zt(e):typeof e==`string`&&Xt(e)?Zt(new Date(e)):String(e)}function Xt(e){return/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(e)}function Zt(e){let t=Date.now()-e.getTime();if(t<6e4)return`just now`;if(t<36e5)return`${Math.floor(t/6e4)} min ago`;if(t<864e5){let e=Math.floor(t/36e5);return`${e} hour${e>1?`s`:``} ago`}let n=Math.floor(t/864e5);return`${n} day${n>1?`s`:``} ago`}function Qt(e){switch(e){case`running`:return a.green(e);case`pending`:case`provisioning`:return a.yellow(e);case`stopped`:return a.gray(e);case`failed`:case`deleted`:return a.red(e);default:return e}}function O(e,t){if(e.length===0){console.log(a.dim(`No items found.`));return}let n=t.map(t=>{let n=t.header.length,r=Math.max(...e.map(e=>Yt(e[t.key]).length));return t.width??Math.max(n,r)+2}),r=t.map((e,t)=>a.bold(e.header.padEnd(n[t]))).join(``);console.log(r);for(let r of e){let e=t.map((e,t)=>{let i=Yt(r[e.key]);return e.key===`status`&&(i=Qt(String(r[e.key]))),i.padEnd(n[t])}).join(``);console.log(e)}}function k(e){console.log(JSON.stringify(e,null,2))}function A(e){console.log(a.green(`✓`),e)}function j(e){console.error(a.red(`✗`),e)}function $t(e){console.log(a.yellow(`!`),e)}function M(e){console.log(a.blue(`→`),e)}const en=new Set;function N(e){let t=ye({text:e,color:`cyan`});return en.add(t),t}function tn(){for(let e of en)e.isSpinning&&e.stop();en.clear()}function P(e,t=0){let n=` `.repeat(t);for(let[t,r]of Object.entries(e))r!=null&&console.log(`${n}${a.dim(`${t}:`)} ${Yt(r)}`)}function nn(e){if(console.log(),console.log(a.bold(`Sandbox Details`)),console.log(a.dim(`─`.repeat(40))),P({ID:e.id,Name:e.name,Status:Qt(e.status),Created:e.createdAt,Expires:e.expiresAt}),e.connection){if(console.log(),console.log(a.bold(`Connection`)),console.log(a.dim(`─`.repeat(40))),e.connection.ssh){let{ssh:t}=e.connection,n=process.platform===`win32`?`NUL`:`/dev/null`,r=`ssh -o ProxyCommand="${t.proxyCommand}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=${n} -o GlobalKnownHostsFile=${n} -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=4 -o TCPKeepAlive=yes ${t.username}@localhost -p ${t.port}`;P({SSH:process.platform===`win32`?`$env:TANGLE_SSH_PROXY_AUTH_TOKEN='<token>'; ${r}`:`TANGLE_SSH_PROXY_AUTH_TOKEN='<token>' ${r}`})}e.connection.webTerminalUrl&&P({"Web Terminal":e.connection.webTerminalUrl}),e.connection.runtimeUrl&&P({"API URL":e.connection.runtimeUrl})}console.log()}function F(e,t){tn(),t?k({error:e.message}):j(e.message),process.exit(1)}function I(e){k(e)}function L(e,t){if(t.length===0){console.log(a.dim(`No items found.`));return}let n=e.map((e,n)=>{let r=Math.max(...t.map(e=>String(e[n]??``).length));return Math.max(e.length,r)+2});console.log(e.map((e,t)=>a.bold(e.padEnd(n[t]))).join(``));for(let e of t)console.log(e.map((e,t)=>String(e??``).padEnd(n[t])).join(``))}function R(e,t=!1){tn(),t&&(k(rn(e)),process.exit(dn(e)));let n=an(e);console.error(a.red(`Error:`),n),process.exit(dn(e))}function rn(e){return e instanceof ve?{error:{code:e.code,message:e.message,...typeof e.status==`number`?{status:e.status}:{},...e.details===void 0?{}:{details:e.details}}}:un(e)?{error:{code:e.code,message:e.message,status:e.status,...e.details===void 0?{}:{details:e.details}}}:{error:{message:an(e)}}}function an(e){if(e instanceof ie)return`Authentication failed. Run 'tangle auth login' to authenticate.`;if(e instanceof ae)return`Resource not found. Check the ID and try again.`;if(e instanceof oe)return`Quota exceeded. Upgrade your plan or wait for quota reset.`;if(e instanceof ce)return`Invalid input: ${e.message}`;if(e instanceof v)return`Invalid state: ${e.message}`;if(e instanceof se)return`Request timed out after ${e.timeoutMs} ms. Retry, or re-run with a longer --timeout <ms> on commands that accept it (e.g. create, resume).`;if(e instanceof h)return`Network error. Check your connection and try again.`;if(e instanceof _)return`${e.status?`HTTP ${e.status}`:`server error`}: ${e.message}`;if(e instanceof ve){let t=typeof e.status==`number`?` (HTTP ${e.status})`:``;return e.code===`HUB_CONNECTION_MISSING`?on(e.code,t,e.message,e.details):e.code===`HUB_APPROVAL_REQUIRED`?cn(e.code,t,e.message):e.code===`HUB_UNAUTHENTICATED`?ln(e.code,t,e.message):`${e.code}${t}: ${e.message}`}return un(e)?e.code===`HUB_CONNECTION_MISSING`?on(e.code,` (HTTP ${e.status})`,e.message,e.details):e.code===`HUB_APPROVAL_REQUIRED`?cn(e.code,` (HTTP ${e.status})`,e.message):e.code===`HUB_UNAUTHENTICATED`?ln(e.code,` (HTTP ${e.status})`,e.message):`${e.code} (HTTP ${e.status}): ${e.message}`:e instanceof Error?e.message:String(e)}function on(e,t,n,r){let i=sn(r);return`${e}${t}: ${n}${i?`. Run: tangle hub connect ${i}`:``}`}function sn(e){if(!e||typeof e!=`object`)return;let t=e.provider;return typeof t==`string`&&t.length>0?t:void 0}function cn(e,t,n){return`${e}${t}: ${n}. Run the command again with --auto-approve, or run: tangle hub resume <approval-id> --accept. See: tangle hub resume --help`}function ln(e,t,n){return`${e}${t}: ${n}. The CLI auto-mints a short-lived Hub key from your login session, so this usually means that session has expired — run 'tangle auth login' to re-authenticate. To use a credential directly instead, set TANGLE_API_KEY, SANDBOX_API_KEY, or TANGLE_HUB_CAPABILITY_TOKEN (and pass --no-hub-key to skip auto-minting).`}function un(e){if(!(e instanceof Error))return!1;let t=e;return typeof t.code==`string`&&t.code.startsWith(`HUB_`)&&typeof t.status==`number`}function dn(e){return e instanceof ce?2:1}function fn(e,t){return n(`sha256`).update(`${e}\0${t}`).digest(`hex`).slice(0,32)}function pn(e){return`${e}:hub`}function mn(e){let{value:t}=Qe(pn(e));if(!t)return null;try{let e=JSON.parse(t);if(typeof e.key==`string`&&typeof e.expiresAt==`string`&&typeof e.platformUrl==`string`&&typeof e.sessionFingerprint==`string`)return{key:e.key,expiresAt:e.expiresAt,platformUrl:e.platformUrl,sessionFingerprint:e.sessionFingerprint}}catch{}return null}function hn(e,t){try{$e(pn(e),JSON.stringify(t))}catch{}}async function gn(e,t,n){let r=`${e.replace(/\/+$/,``)}/auth/cli/hub-key`,i;try{i=await fetch(r,{method:`POST`,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${t}`},body:JSON.stringify({device_id:n})})}catch(e){throw Error(`Could not reach ${r} to mint a Hub key: ${e instanceof Error?e.message:String(e)}`)}let a=await i.text(),o;try{o=JSON.parse(a)}catch{o=null}if(!i.ok||!o?.success){let e=typeof o?.error?.message==`string`?o.error.message:`HTTP ${i.status}`;throw Error(`Hub key minting failed: ${e}. Your sandbox session may have expired — run \`tangle auth login\`.`)}let s=o.data;if(typeof s?.api_key!=`string`||typeof s?.expires_at!=`string`||typeof s?.platform_url!=`string`)throw Error(`Hub key minting returned an unexpected response shape.`);return{key:s.api_key,expiresAt:s.expires_at,platformUrl:s.platform_url}}var _n=class{cache;fingerprint;constructor(e,t,n,r){this.profile=e,this.sandboxBaseUrl=t,this.sessionToken=n,this.deviceId=r,this.fingerprint=fn(t,n);let i=mn(e);this.cache=i&&i.sessionFingerprint===this.fingerprint?i:null}isFresh(e){return e!==null&&Date.parse(e.expiresAt)-Date.now()>6e4}async ensure(e=!1){if(!e&&this.isFresh(this.cache))return this.cache;let t={...await gn(this.sandboxBaseUrl,this.sessionToken,this.deviceId),sessionFingerprint:this.fingerprint};return this.cache=t,hn(this.profile,t),t}};function vn(e){if(!e)return!1;let t=e.trim().toLowerCase();return t===`1`||t===`true`||t===`yes`}function yn(e,t){if(e.baseUrl)return e.baseUrl;let n=Nt(process.env.TANGLE_HUB_URL);if(n)return n;let r=mn(t);return r?r.platformUrl:`https://id.tangle.tools`}function bn(e,t){let n=S(),r=C(e.apiKey,n),i=jt(e.apiKey,n),a=process.env.TANGLE_HUB_CAPABILITY_TOKEN?.trim()||void 0;if(a&&i===`env`)throw Error(`Set exactly one of TANGLE_API_KEY/SANDBOX_API_KEY or TANGLE_HUB_CAPABILITY_TOKEN, not both`);if(i===`flag`&&r)return{kind:`static`,apiKey:r,baseUrl:yn(e,n)};if(i===`env`&&r)return{kind:`static`,apiKey:a??r,baseUrl:yn(e,n)};if(a)return{kind:`static`,apiKey:a,baseUrl:yn(e,n)};if(!r)throw Error(`No API key found for profile '${n}'. Set TANGLE_API_KEY or run: tangle auth login${n===`default`?``:` --profile ${n}`}`);return t?{kind:`static`,apiKey:r,baseUrl:yn(e,n)}:{kind:`managed`,profile:n,sandboxBaseUrl:Mt(void 0,n),sessionToken:r,deviceId:Tt(),baseUrlOverride:e.baseUrl}}async function z(e={}){let t=bn(e,e.hubKey===!1||vn(process.env.TANGLE_SKIP_HUB_KEY));if(t.kind===`static`)return new _e({baseUrl:t.baseUrl,apiKey:t.apiKey});let n=new _n(t.profile,t.sandboxBaseUrl,t.sessionToken,t.deviceId),r=await n.ensure();return new _e({baseUrl:t.baseUrlOverride??r.platformUrl,authHeaders:async()=>({Authorization:`Bearer ${(await n.ensure()).key}`}),fetch:async(e,t)=>{let r=await fetch(e,t);if(r.status!==401)return r;let i=await n.ensure(!0),a=new Headers(t?.headers);return a.set(`Authorization`,`Bearer ${i.key}`),fetch(e,{...t,headers:a})}})}function xn(e){return e instanceof Date?e.toISOString():String(e)}function Sn(e,t){return`Sandbox name is ambiguous: ${e}. Use a sandbox id instead.\n${t.map(e=>`- ${e.id} (status: ${e.status}, created: ${xn(e.createdAt)})`).join(`
|
|
4
4
|
`)}`}function Cn(e){return e.activeTeamId?`team:${e.activeTeamId}`:void 0}async function B(e,t,n){let r=await e.get(n);if(r||n.startsWith(`sandbox-`)){if(!r)throw Error(`Sandbox not found: ${n}`);return r}let i=(await e.list({scope:Cn(t)})).filter(e=>e.name?.toLowerCase()===n.toLowerCase());if(i.length===0)throw Error(`Sandbox not found: ${n}`);if(i.length>1)throw Error(Sn(n,i));return i[0]}async function V(e,t){if(e.status!==`stopped`)return e;let n=t?.resumeTimeoutMs??12e4;M(`Sandbox ${e.id} is stopped. Resuming...`);try{await e.resume({timeoutMs:n}),await e.waitFor(`running`,{timeoutMs:n})}catch(t){let r=t instanceof Error?t.message:String(t);throw Error(`Failed to resume sandbox ${e.id}: ${r}. Run \`tangle sandbox resume ${e.id} --timeout ${n}\` and retry.`)}return e}const wn=[`anthropic`,`openai`,`vercel-ai`,`mastra`,`mcp-local`,`claude-desktop`,`cursor`,`zed`];function Tn(e,t,n){switch(e){case`anthropic`:return`// pnpm add @anthropic-ai/sdk @tangle-network/sandbox
|
|
5
5
|
import Anthropic from "@anthropic-ai/sdk";
|
|
6
6
|
import { Sandbox } from "@tangle-network/sandbox";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/sandbox-cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1-develop.20260725201418.71254fb",
|
|
4
4
|
"description": "CLI for Tangle Sandbox operations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"ora": "^9.4.0",
|
|
26
26
|
"ws": "^8.20.0",
|
|
27
27
|
"@tangle-network/hub-sdk": "0.3.0",
|
|
28
|
-
"@tangle-network/sandbox": "0.12.
|
|
28
|
+
"@tangle-network/sandbox": "0.12.1-develop.20260725201418.71254fb"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "25.6.0",
|