@soba-so/react 0.0.5 → 0.0.6

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 CHANGED
@@ -1,8 +1,8 @@
1
1
  "use client"
2
- var ee=class extends Error{status;constructor(t,n){super(t),this.name="ConnectError",this.status=n}},Q=class{session;baseUrl;doFetch;constructor(t){this.session=t.session,this.baseUrl=(t.baseUrl??"").replace(/\/+$/,""),this.doFetch=t.fetchImpl??fetch.bind(globalThis)}context(){return this.request("GET","/v1/connect")}async machines(){return(await this.request("GET","/v1/machines")).machines}lookup(t){return this.request("GET",`/v1/device/lookup?code=${encodeURIComponent(t)}`)}approve(t){return this.request("POST","/v1/device/approve",{user_code:t})}deny(t){return this.request("POST","/v1/device/deny",{user_code:t})}plans(){return this.request("GET","/v1/plans")}selectPlan(t){return this.request("POST","/v1/plans/select",{plan:t})}checkoutPlan(t,n){return this.request("POST","/v1/plans/checkout",{plan:t,...n?.successUrl?{success_url:n.successUrl}:{},...n?.cancelUrl?{cancel_url:n.cancelUrl}:{}})}leavePlan(){return this.request("DELETE","/v1/plans/select")}approveRuntime(t,n){return this.request("POST","/v1/runtimes/approve",{machine_id:t,runtime_id:n})}async request(t,n,i){let s=await this.doFetch(`${this.baseUrl}${n}`,{method:t,headers:{authorization:`Bearer ${this.session}`,...i?{"content-type":"application/json"}:{}},...i?{body:JSON.stringify(i)}:{}}),r={};try{r=await s.json()}catch{}if(!s.ok){let u=typeof r.error=="string"?r.error:`Request failed (${s.status})`;throw new ee(u,s.status)}return r}};function ae(e){return e.authenticated===!0?{tone:"ok",text:"signed in"}:e.authenticated===!1?{tone:"warn",text:e.auth_hint??"signed out on that machine"}:{tone:"unknown",text:"could not tell"}}function ie(e){return e.state==="busy"?{tone:"busy",text:`serving ${e.active} of ${e.capacity}`}:e.state==="online"?{tone:"ok",text:"connected"}:{tone:"off",text:"asleep or offline"}}import{jsx as P,jsxs as G}from"react/jsx-runtime";function H({machines:e,onApproveRuntime:t}){return e.length?P("div",{children:e.map(n=>{let i=ie(n);return G("div",{className:"sc-machine",children:[G("div",{className:"sc-row-between",children:[G("div",{className:"sc-row",children:[P("span",{className:`sc-dot sc-dot-${i.tone}`,"aria-hidden":"true"}),P("strong",{children:n.label??"This machine"})]}),P("span",{className:"sc-note",children:i.text})]}),P("p",{className:"sc-note",children:[n.platform,n.arch,n.version&&`worker ${n.version}`,n.always_on&&"always on"].filter(Boolean).join(" \xB7 ")}),G("ul",{className:"sc-runtimes",children:[n.runtimes.map(s=>{let r=ae(s);return s.quarantined?G("li",{className:"sc-row-between",children:[P("span",{children:s.display_name}),t?P("button",{type:"button",className:"sc-btn",onClick:()=>t(n.id,s.id),children:"New here. Let it serve"}):P("span",{className:"sc-tone-warn",children:"new, not serving yet"})]},s.id):G("li",{className:"sc-row-between",children:[P("span",{children:s.display_name}),P("span",{className:`sc-tone-${r.tone}`,children:r.text})]},s.id)}),!n.runtimes.length&&P("li",{className:"sc-note",children:"No runtime advertised. Install Claude Code, Codex or Ollama on that machine."})]})]},n.id)})}):P("p",{className:"sc-note",children:"Nothing connected yet."})}import{useCallback as te,useEffect as le,useMemo as qe,useRef as je,useState as O}from"react";var ce=[{id:"claude",title:"The Claude plan you already pay for",cost:"Nothing beyond the plan",prerequisite:"Claude Code installed, and signed in",hint:"Install it from claude.ai/code, then run `claude` once and sign in.",expects:["claude-code"]},{id:"chatgpt",title:"The ChatGPT plan you already pay for",cost:"Nothing beyond the plan",prerequisite:"Codex installed, and signed in",hint:"Install Codex, then run `codex` once and sign in.",expects:["codex"]},{id:"local",title:"A model on your own machine",cost:"Free. You bought the hardware",prerequisite:"Ollama running, with a model pulled",hint:"Install Ollama, then `ollama pull` a model before pairing.",expects:["ollama","openai-api"]}];import{Fragment as Pe,jsx as a,jsxs as f}from"react/jsx-runtime";var Fe=2e3,De=1e4;function ue({client:e,initialCode:t,onConnected:n,showStatus:i=!0}){let[s,r]=O("loading"),[u,y]=O(null),[g,h]=O([]),[b,o]=O(null),[d,$]=O(t??""),[E,j]=O(null),[M,k]=O(null),[U,_]=O(!1),F=je(null);le(()=>{let l=!0;return Promise.all([e.context(),e.machines()]).then(([C,w])=>{l&&(y(C),h(w),F.current=new Set(w.map(R=>R.id)),t?r("code"):i&&w.length>0?r("manage"):r("choose"))}).catch(C=>{l&&(k(C.message),r("choose"))}),()=>{l=!1}},[e,t,i]),le(()=>{if(s!=="manage")return;let l=!0,C=setInterval(async()=>{try{let w=await e.machines();l&&h(w)}catch{}},De);return()=>{l=!1,clearInterval(C)}},[s,e]),le(()=>{if(s!=="waiting")return;let l=!0,C=setInterval(async()=>{try{let w=await e.machines();if(!l)return;h(w);let R=w.filter(B=>!F.current?.has(B.id));(R.find(B=>B.state!=="offline")??R[0])&&(r("connected"),n?.(w))}catch{}},Fe);return()=>{l=!1,clearInterval(C)}},[s,e,n]);let D=te(async()=>{k(null),_(!0);try{let l=await e.lookup(d);j(l),r("confirm")}catch(l){k(l.message)}finally{_(!1)}},[e,d]),re=te(async()=>{k(null),_(!0);try{await e.approve(d),r("waiting")}catch(l){k(l.message)}finally{_(!1)}},[e,d]),Z=te(async()=>{_(!0);try{await e.deny(d),r("denied")}catch(l){k(l.message)}finally{_(!1)}},[e,d]),W=qe(()=>u?.connect_url??null,[u]),Y=te(async(l,C)=>{try{await e.approveRuntime(l,C),h(await e.machines())}catch(w){k(w.message)}},[e]);return s==="loading"?a("div",{className:"soba-connect",children:a("div",{className:"sc-card",children:a("p",{className:"sc-note",children:"Loading\u2026"})})}):f("div",{className:"soba-connect",children:[s==="manage"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Your compute"}),f("p",{className:"sc-sub",children:[u?.app.name??"This app"," runs on these machines, while they are awake."]}),a(H,{machines:g,onApproveRuntime:Y}),M&&a("p",{className:"sc-error",children:M}),a("button",{type:"button",className:"sc-btn",style:{marginTop:16},onClick:()=>r("choose"),children:"Connect another machine"})]}),s==="choose"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Use the AI you already pay for"}),f("p",{className:"sc-sub",children:[u?.app.name??"This app"," can run on your machine instead of selling you credits. Pick what you already have."]}),a("div",{className:"sc-choices",children:ce.map(l=>f("button",{type:"button",className:"sc-choice","aria-pressed":b?.id===l.id,onClick:()=>o(l),children:[a("span",{className:"sc-choice-title",children:l.title}),a("br",{}),f("span",{className:"sc-choice-meta",children:[l.cost," \xB7 ",l.prerequisite]})]},l.id))}),b&&f("div",{className:"sc-stack",style:{marginTop:16},children:[a("p",{className:"sc-note",children:b.hint}),W?f(Pe,{children:[a("p",{className:"sc-label",children:"Install Soba Connect on the machine you want to use"}),a("a",{className:"sc-btn sc-btn-primary",href:"https://soba.so/soba-connect",target:"_blank",rel:"noreferrer noopener",children:"Get Soba Connect"}),a("p",{className:"sc-label",children:"Then paste this link into it"}),a("code",{className:"sc-code",children:W}),a("p",{className:"sc-note",children:"It will show you a short code. Nothing is pasted back: the credential is written straight to that machine."}),a("button",{type:"button",className:"sc-btn",onClick:()=>r("code"),children:"I have a code"})]}):a("p",{className:"sc-error",children:"This app has no publishable key yet, so there is nothing to pair against."})]})]}),s==="code"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Enter the code from that machine"}),a("p",{className:"sc-sub",children:"Eight characters, shown by Soba Connect once you paste the link."}),f("form",{className:"sc-stack",onSubmit:l=>{l.preventDefault(),D()},children:[a("input",{className:"sc-input",value:d,onChange:l=>$(l.target.value),placeholder:"ACDE-F234",autoComplete:"off",autoFocus:!0,"aria-label":"Pairing code"}),M&&a("p",{className:"sc-error",children:M}),a("button",{type:"submit",className:"sc-btn sc-btn-primary",disabled:U||d.length<8,children:U?"Checking\u2026":"Continue"})]})]}),s==="confirm"&&E&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Attach this machine to your account?"}),f("p",{className:"sc-sub",children:["Once you approve, ",u?.app.name??"this app"," may send your runs to it. It can never see the account or the keys on it."]}),f("ul",{className:"sc-facts",children:[a(X,{k:"Code",v:E.user_code}),a(X,{k:"Machine",v:E.hostname??"not stated"}),a(X,{k:"System",v:[E.platform,E.arch].filter(Boolean).join(" / ")||"not stated"}),a(X,{k:"Asked by",v:E.client??"not stated"}),E.labels.length>0&&a(X,{k:"Labels",v:E.labels.join(", ")})]}),a("p",{className:"sc-note",style:{marginTop:12},children:"Every line above is what that machine said about itself. If none of it looks like a computer of yours, say no."}),M&&a("p",{className:"sc-error",children:M}),f("div",{className:"sc-row",style:{marginTop:16},children:[a("button",{type:"button",className:"sc-btn sc-btn-primary",onClick:()=>void re(),disabled:U,children:"Approve"}),a("button",{type:"button",className:"sc-btn",onClick:()=>void Z(),disabled:U,children:"No, deny it"})]})]}),s==="waiting"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Approved. Waiting for it to connect\u2026"}),a("p",{className:"sc-sub",children:"The worker is starting on that machine. This page will change on its own the moment it arrives."}),a("p",{className:"sc-note",children:"If nothing happens, look at Soba Connect on that machine: it says what it is doing."})]}),s==="connected"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Connected"}),f("p",{className:"sc-sub",children:[u?.app.name??"This app"," will use this machine from now on, while it is awake."]}),i&&f(Pe,{children:[a(H,{machines:g,onApproveRuntime:Y}),a("button",{type:"button",className:"sc-btn",style:{marginTop:16},onClick:()=>r("manage"),children:"Done"})]})]}),s==="denied"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Declined"}),a("p",{className:"sc-sub",children:"That machine was not attached to your account, and the terminal has been told so."})]})]})}function X({k:e,v:t}){return f("li",{className:"sc-fact",children:[a("span",{className:"sc-fact-key",children:e}),a("span",{children:t})]})}import{createContext as Be,useContext as Ge}from"react";var pe=Be(null);function T(){let e=Ge(pe);if(!e)throw new Error("useSoba() must be used inside a <SobaProvider>.");return e}import{jsx as V}from"react/jsx-runtime";function He({className:e}){let{compute:t}=T(),n=e?`soba-connect ${e}`:"soba-connect";return t.loading&&!t.machines.length?V("div",{className:n,children:V("p",{className:"sc-note",children:"Loading\u2026"})}):t.error&&!t.machines.length?V("div",{className:n,children:V("p",{className:"sc-error",children:t.error.message})}):V("div",{className:n,children:V(H,{machines:t.machines})})}import{createContext as Ve,useContext as Je}from"react";var de=Ve(null);function xe(){return Je(de)}import{jsx as L}from"react/jsx-runtime";function We({initialCode:e,onConnected:t,className:n,showStatus:i}){let{error:s}=T(),r=xe();return s?L("div",{className:Ee(n),children:L("div",{className:"sc-card",children:L("p",{className:"sc-error",children:s.message})})}):r?L("div",{className:n,children:L(ue,{client:r,...e?{initialCode:e}:{},...t?{onConnected:t}:{},...i===void 0?{}:{showStatus:i}})}):L("div",{className:Ee(n),children:L("div",{className:"sc-card",children:L("p",{className:"sc-note",children:"Loading\u2026"})})})}function Ee(e){return e?`soba-connect ${e}`:"soba-connect"}var Ye=new Set(["delta","thinking","status","done","error"]);function ne(e){let t;try{t=JSON.parse(e)}catch{return null}if(!t||typeof t!="object")return null;let n=t,i=n.type==="event"?n.event:n;if(!i||typeof i!="object")return null;let s=i;if(typeof s.type!="string"||!Ye.has(s.type))return null;let r={type:s.type};return typeof s.text=="string"&&(r.text=s.text),typeof s.message=="string"&&(r.message=s.message),typeof s.tool=="string"&&(r.tool=s.tool),typeof s.tier=="string"&&(r.tier=s.tier),s.usage&&typeof s.usage=="object"&&(r.usage=s.usage),r}async function*me(e){let t=e.body;if(!t)throw new Error("That run response had no body to stream.");let n=(e.headers.get("content-type")??"").includes("ndjson"),i=t.getReader(),s=new TextDecoder,r="",u=[];try{for(;;){let g=await i.read();if(g.done)break;for(r+=s.decode(g.value,{stream:!0});;){let h=r.indexOf(`
3
- `);if(h===-1)break;let b=_e(r.slice(0,h));if(r=r.slice(h+1),n){let o=b.trim()?ne(b):null;o&&(yield o);continue}if(b===""){let o=u.join(`
4
- `);if(u=[],o==="[DONE]")return;let d=o?ne(o):null;d&&(yield d);continue}ke(b,u)}}!n&&r&&ke(_e(r),u);let y=n?r.trim():u.join(`
5
- `);if(y&&y!=="[DONE]"){let g=ne(y);g&&(yield g)}}finally{i.cancel().catch(()=>{})}}function ke(e,t){if(!e||e.startsWith(":"))return;let n=e.indexOf(":");if(n===-1||e.slice(0,n)!=="data")return;let i=e.slice(n+1);t.push(i.startsWith(" ")?i.slice(1):i)}function _e(e){return e.endsWith("\r")?e.slice(0,-1):e}async function ze(e){let t="";for await(let n of e)if(n.type==="delta"&&n.text&&(t+=n.text),n.type==="error")throw new Error(n.message??"The run ended with an error.");return t}import{useState as Re}from"react";import{jsx as Te,jsxs as Qe}from"react/jsx-runtime";function Ke({plan:e,successUrl:t,cancelUrl:n,label:i,className:s}){let{plan:r}=T(),[u,y]=Re(!1),[g,h]=Re(null);async function b(){y(!0),h(null);try{let o=await r.checkout(e.id,{...t?{successUrl:t}:{},...n?{cancelUrl:n}:{}});window.location.assign(o)}catch(o){h(o.message),y(!1)}}return Qe("div",{className:s,children:[Te("button",{type:"button",className:"sc-btn sc-btn-primary",disabled:u,onClick:()=>void b(),children:u?"Opening\u2026":i??`Subscribe \u2014 $${e.price.toLocaleString()}/${e.interval}`}),g&&Te("p",{className:"sc-error",children:g})]})}import{useState as Me}from"react";import{Fragment as et,jsx as m,jsxs as N}from"react/jsx-runtime";var Xe={"user-hardware":"your own machine","user-subscription":"your own AI subscription","open-weights":"open models we host",frontier:"frontier models"};function Ze({onSelect:e,successUrl:t,cancelUrl:n,showCompute:i=!0,className:s}){let{plan:r}=T(),[u,y]=Me(null),[g,h]=Me(null);if(r.error)return m("div",{className:s,children:m("div",{className:"sc-card",children:m("p",{className:"sc-error",children:r.error.message})})});if(r.loading)return m("div",{className:s,children:m("div",{className:"sc-card",children:m("p",{className:"sc-note",children:"Loading plans\u2026"})})});if(r.plans.length===0)return m("div",{className:s,children:m("div",{className:"sc-card",children:m("p",{className:"sc-note",children:"This app has no plans yet."})})});async function b(o){y(o.id),h(null);try{if(o.selectable)await r.select(o.id);else if(e)e(o);else{let d=await r.checkout(o.id,{...t?{successUrl:t}:{},...n?{cancelUrl:n}:{}});window.location.assign(d);return}}catch(d){h(d.message)}finally{y(null)}}return N("div",{className:s,children:[m("div",{className:"sc-plans",children:r.plans.map(o=>{let d=r.current?.plan_id===o.id;return N("div",{className:d?"sc-plan sc-plan-current":"sc-plan",children:[N("div",{className:"sc-row-between",children:[m("span",{className:"sc-title",children:o.name}),d&&m("span",{className:"sc-plan-badge",children:"Current"})]}),o.description&&m("p",{className:"sc-sub",children:o.description}),m("p",{className:"sc-plan-price",children:o.price===0?m("span",{className:"sc-plan-amount",children:"Free"}):N(et,{children:[N("span",{className:"sc-plan-amount",children:["$",o.price.toLocaleString()]}),N("span",{className:"sc-plan-interval",children:["/",o.interval]})]})}),N("div",{className:"sc-facts",children:[N("div",{className:"sc-fact",children:[m("span",{className:"sc-fact-key",children:"Included"}),m("span",{children:o.included_units===null?"Unlimited":`${o.included_units.toLocaleString()} ${o.unit}${o.included_units===1?"":"s"}`})]}),o.overage_per_unit>0&&N("div",{className:"sc-fact",children:[m("span",{className:"sc-fact-key",children:"Then"}),N("span",{children:["$",o.overage_per_unit," / ",o.unit]})]}),o.trial_days>0&&N("div",{className:"sc-fact",children:[m("span",{className:"sc-fact-key",children:"Trial"}),N("span",{children:[o.trial_days," days"]})]}),i&&N("div",{className:"sc-fact",children:[m("span",{className:"sc-fact-key",children:"Runs on"}),m("span",{children:o.route_allow===null?"any compute":o.route_allow.length===0?"no compute":o.route_allow.map($=>Xe[$]??$).join(", ")})]})]}),m("button",{type:"button",className:d?"sc-btn":"sc-btn sc-btn-primary",disabled:d||u!==null,onClick:()=>void b(o),children:d?"Your plan":u===o.id?o.selectable?"Switching\u2026":"Opening\u2026":o.selectable?"Choose":`Subscribe \u2014 $${o.price.toLocaleString()}/${o.interval}`})]},o.id)})}),g&&m("p",{className:"sc-error",children:g})]})}import{useCallback as q,useEffect as J,useMemo as fe,useRef as nt,useState as x}from"react";var se=class{#t;#s=null;#n=null;#e;#r=new Set;constructor(t){this.#e=t,this.#t=t.token??null}get token(){return this.#t}get expiresAt(){return this.#s}get canRefresh(){return!!this.#e.endpoint}get refreshAt(){return!this.canRefresh||this.#s===null?null:Math.max(Date.now()+1e4,this.#s-6e4)}subscribe(t){return this.#r.add(t),()=>this.#r.delete(t)}async ensure(){return this.#t?this.#t:this.refresh()}async refresh(){if(this.#n)return this.#n;let t=this.#e.endpoint;if(!t)throw new Error("This Soba session cannot be refreshed. Pass `sessionEndpoint` so the provider can mint a new one.");let n=this.#e.fetchImpl??fetch.bind(globalThis);this.#n=(async()=>{let i=await n(t,{method:"POST",headers:{"content-type":"application/json"},credentials:"same-origin",body:JSON.stringify(this.#e.user?{user:this.#e.user}:{})});if(!i.ok)throw new Error(`Could not start a Soba session (${i.status} from ${t}).`);let s=await i.json(),r=s.session??s.token;if(!r)throw new Error(`${t} answered without a \`session\`. See @soba-so/react's README.`);let u=s.expires_in??s.expiresIn;this.#t=r,this.#s=typeof u=="number"?Date.now()+u*1e3:null;for(let y of this.#r)y();return r})();try{return await this.#n}finally{this.#n=null}}};function Ae(e,t=fetch.bind(globalThis)){return async(n,i)=>{let s=await t(n,i);if(s.status!==401||!e.canRefresh)return s;let r;try{r=await e.refresh()}catch{return s}return t(n,tt(i,r))}}function tt(e,t){let n=new Headers(e?.headers);return n.set("authorization",`Bearer ${t}`),{...e,headers:n}}var Ie="data-soba-react",Oe=!1;function he(){if(Oe||typeof document>"u"||(Oe=!0,document.querySelector(`style[${Ie}]`)))return;let e=document.createElement("style");e.setAttribute(Ie,""),e.textContent=`.soba-connect {
2
+ var ee=class extends Error{status;constructor(t,n){super(t),this.name="ConnectError",this.status=n}},Q=class{session;baseUrl;doFetch;constructor(t){this.session=t.session,this.baseUrl=(t.baseUrl??"").replace(/\/+$/,""),this.doFetch=t.fetchImpl??fetch.bind(globalThis)}context(){return this.request("GET","/v1/connect")}async machines(){return(await this.request("GET","/v1/machines")).machines}lookup(t){return this.request("GET",`/v1/device/lookup?code=${encodeURIComponent(t)}`)}approve(t){return this.request("POST","/v1/device/approve",{user_code:t})}deny(t){return this.request("POST","/v1/device/deny",{user_code:t})}plans(){return this.request("GET","/v1/plans")}selectPlan(t){return this.request("POST","/v1/plans/select",{plan:t})}checkoutPlan(t,n){return this.request("POST","/v1/plans/checkout",{plan:t,...n?.successUrl?{success_url:n.successUrl}:{},...n?.cancelUrl?{cancel_url:n.cancelUrl}:{}})}leavePlan(){return this.request("DELETE","/v1/plans/select")}approveRuntime(t,n){return this.request("POST","/v1/runtimes/approve",{machine_id:t,runtime_id:n})}async request(t,n,c){let s=await this.doFetch(`${this.baseUrl}${n}`,{method:t,headers:{authorization:`Bearer ${this.session}`,...c?{"content-type":"application/json"}:{}},...c?{body:JSON.stringify(c)}:{}}),r={};try{r=await s.json()}catch{}if(!s.ok){let u=typeof r.error=="string"?r.error:`Request failed (${s.status})`;throw new ee(u,s.status)}return r}};function ce(e){return e.authenticated===!0?{tone:"ok",text:"signed in"}:e.authenticated===!1?{tone:"warn",text:e.auth_hint??"signed out on that machine"}:{tone:"unknown",text:"could not tell"}}function le(e){return e.state==="busy"?{tone:"busy",text:`serving ${e.active} of ${e.capacity}`}:e.state==="online"?{tone:"ok",text:"connected"}:{tone:"off",text:"asleep or offline"}}import{jsx as x,jsxs as V}from"react/jsx-runtime";function W({machines:e,onApproveRuntime:t}){return e.length?x("div",{children:e.map(n=>{let c=le(n);return V("div",{className:"sc-machine",children:[V("div",{className:"sc-row-between",children:[V("div",{className:"sc-row",children:[x("span",{className:`sc-dot sc-dot-${c.tone}`,"aria-hidden":"true"}),x("strong",{children:n.label??"This machine"})]}),x("span",{className:"sc-note",children:c.text})]}),x("p",{className:"sc-note",children:[n.platform,n.arch,n.version&&`worker ${n.version}`,n.always_on&&"always on"].filter(Boolean).join(" \xB7 ")}),V("ul",{className:"sc-runtimes",children:[n.runtimes.map(s=>{let r=ce(s);return s.quarantined?V("li",{className:"sc-row-between",children:[x("span",{children:s.display_name}),t?x("button",{type:"button",className:"sc-btn",onClick:()=>t(n.id,s.id),children:"New here. Let it serve"}):x("span",{className:"sc-tone-warn",children:"new, not serving yet"})]},s.id):V("li",{className:"sc-row-between",children:[x("span",{children:s.display_name}),x("span",{className:`sc-tone-${r.tone}`,children:r.text})]},s.id)}),!n.runtimes.length&&x("li",{className:"sc-note",children:"No runtime advertised. Install Claude Code, Codex or Ollama on that machine."})]})]},n.id)})}):x("p",{className:"sc-note",children:"Nothing connected yet."})}import{useCallback as te,useEffect as pe,useMemo as Fe,useRef as je,useState as $}from"react";var ue=[{id:"claude",title:"The Claude plan you already pay for",cost:"Nothing beyond the plan",prerequisite:"Claude Code installed, and signed in",hint:"Install it from claude.ai/code, then run `claude` once and sign in.",expects:["claude-code"]},{id:"chatgpt",title:"The ChatGPT plan you already pay for",cost:"Nothing beyond the plan",prerequisite:"Codex installed, and signed in",hint:"Install Codex, then run `codex` once and sign in.",expects:["codex"]},{id:"local",title:"A model on your own machine",cost:"Free. You bought the hardware",prerequisite:"Ollama running, with a model pulled",hint:"Install Ollama, then `ollama pull` a model before pairing.",expects:["ollama","openai-api"]}];import{Fragment as Ee,jsx as a,jsxs as f}from"react/jsx-runtime";var De=2e3,Be=1e4;function de({client:e,initialCode:t,onConnected:n,showStatus:c=!0}){let[s,r]=$("loading"),[u,y]=$(null),[g,h]=$([]),[b,o]=$(null),[m,v]=$(t??""),[k,D]=$(null),[A,_]=$(null),[I,R]=$(!1),q=je(null);pe(()=>{let l=!0;return Promise.all([e.context(),e.machines()]).then(([w,N])=>{l&&(y(w),h(N),q.current=new Set(N.map(T=>T.id)),t?r("code"):c&&N.length>0?r("manage"):r("choose"))}).catch(w=>{l&&(_(w.message),r("choose"))}),()=>{l=!1}},[e,t,c]),pe(()=>{if(s!=="manage")return;let l=!0,w=setInterval(async()=>{try{let N=await e.machines();l&&h(N)}catch{}},Be);return()=>{l=!1,clearInterval(w)}},[s,e]),pe(()=>{if(s!=="waiting")return;let l=!0,w=setInterval(async()=>{try{let N=await e.machines();if(!l)return;h(N);let T=N.filter(H=>!q.current?.has(H.id));(T.find(H=>H.state!=="offline")??T[0])&&(r("connected"),n?.(N))}catch{}},De);return()=>{l=!1,clearInterval(w)}},[s,e,n]);let B=te(async()=>{_(null),R(!0);try{let l=await e.lookup(m);D(l),r("confirm")}catch(l){_(l.message)}finally{R(!1)}},[e,m]),oe=te(async()=>{_(null),R(!0);try{await e.approve(m),r("waiting")}catch(l){_(l.message)}finally{R(!1)}},[e,m]),Z=te(async()=>{R(!0);try{await e.deny(m),r("denied")}catch(l){_(l.message)}finally{R(!1)}},[e,m]),G=Fe(()=>u?.connect_url??null,[u]),Y=te(async(l,w)=>{try{await e.approveRuntime(l,w),h(await e.machines())}catch(N){_(N.message)}},[e]);return s==="loading"?a("div",{className:"soba-connect",children:a("div",{className:"sc-card",children:a("p",{className:"sc-note",children:"Loading\u2026"})})}):f("div",{className:"soba-connect",children:[s==="manage"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Your compute"}),f("p",{className:"sc-sub",children:[u?.app.name??"This app"," runs on these machines, while they are awake."]}),a(W,{machines:g,onApproveRuntime:Y}),A&&a("p",{className:"sc-error",children:A}),a("button",{type:"button",className:"sc-btn",style:{marginTop:16},onClick:()=>r("choose"),children:"Connect another machine"})]}),s==="choose"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Use the AI you already pay for"}),f("p",{className:"sc-sub",children:[u?.app.name??"This app"," can run on your machine instead of selling you credits. Pick what you already have."]}),a("div",{className:"sc-choices",children:ue.map(l=>f("button",{type:"button",className:"sc-choice","aria-pressed":b?.id===l.id,onClick:()=>o(l),children:[a("span",{className:"sc-choice-title",children:l.title}),a("br",{}),f("span",{className:"sc-choice-meta",children:[l.cost," \xB7 ",l.prerequisite]})]},l.id))}),b&&f("div",{className:"sc-stack",style:{marginTop:16},children:[a("p",{className:"sc-note",children:b.hint}),G?f(Ee,{children:[a("p",{className:"sc-label",children:"Install Soba Connect on the machine you want to use"}),a("a",{className:"sc-btn sc-btn-primary",href:"https://soba.so/soba-connect",target:"_blank",rel:"noreferrer noopener",children:"Get Soba Connect"}),a("p",{className:"sc-label",children:"Then paste this link into it"}),a("code",{className:"sc-code",children:G}),a("p",{className:"sc-note",children:"It will show you a short code. Nothing is pasted back: the credential is written straight to that machine."}),a("button",{type:"button",className:"sc-btn",onClick:()=>r("code"),children:"I have a code"})]}):a("p",{className:"sc-error",children:"This app has no publishable key yet, so there is nothing to pair against."})]})]}),s==="code"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Enter the code from that machine"}),a("p",{className:"sc-sub",children:"Eight characters, shown by Soba Connect once you paste the link."}),f("form",{className:"sc-stack",onSubmit:l=>{l.preventDefault(),B()},children:[a("input",{className:"sc-input",value:m,onChange:l=>v(l.target.value),placeholder:"ACDE-F234",autoComplete:"off",autoFocus:!0,"aria-label":"Pairing code"}),A&&a("p",{className:"sc-error",children:A}),a("button",{type:"submit",className:"sc-btn sc-btn-primary",disabled:I||m.length<8,children:I?"Checking\u2026":"Continue"})]})]}),s==="confirm"&&k&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Attach this machine to your account?"}),f("p",{className:"sc-sub",children:["Once you approve, ",u?.app.name??"this app"," may send your runs to it. It can never see the account or the keys on it."]}),f("ul",{className:"sc-facts",children:[a(X,{k:"Code",v:k.user_code}),a(X,{k:"Machine",v:k.hostname??"not stated"}),a(X,{k:"System",v:[k.platform,k.arch].filter(Boolean).join(" / ")||"not stated"}),a(X,{k:"Asked by",v:k.client??"not stated"}),k.labels.length>0&&a(X,{k:"Labels",v:k.labels.join(", ")})]}),a("p",{className:"sc-note",style:{marginTop:12},children:"Every line above is what that machine said about itself. If none of it looks like a computer of yours, say no."}),A&&a("p",{className:"sc-error",children:A}),f("div",{className:"sc-row",style:{marginTop:16},children:[a("button",{type:"button",className:"sc-btn sc-btn-primary",onClick:()=>void oe(),disabled:I,children:"Approve"}),a("button",{type:"button",className:"sc-btn",onClick:()=>void Z(),disabled:I,children:"No, deny it"})]})]}),s==="waiting"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Approved. Waiting for it to connect\u2026"}),a("p",{className:"sc-sub",children:"The worker is starting on that machine. This page will change on its own the moment it arrives."}),a("p",{className:"sc-note",children:"If nothing happens, look at Soba Connect on that machine: it says what it is doing."})]}),s==="connected"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Connected"}),f("p",{className:"sc-sub",children:[u?.app.name??"This app"," will use this machine from now on, while it is awake."]}),c&&f(Ee,{children:[a(W,{machines:g,onApproveRuntime:Y}),a("button",{type:"button",className:"sc-btn",style:{marginTop:16},onClick:()=>r("manage"),children:"Done"})]})]}),s==="denied"&&f("div",{className:"sc-card",children:[a("h2",{className:"sc-title",children:"Declined"}),a("p",{className:"sc-sub",children:"That machine was not attached to your account, and the terminal has been told so."})]})]})}function X({k:e,v:t}){return f("li",{className:"sc-fact",children:[a("span",{className:"sc-fact-key",children:e}),a("span",{children:t})]})}import{createContext as Ge,useContext as He}from"react";var me=Ge(null);function M(){let e=He(me);if(!e)throw new Error("useSoba() must be used inside a <SobaProvider>.");return e}import{jsx as J}from"react/jsx-runtime";function Ve({className:e}){let{compute:t}=M(),n=e?`soba-connect ${e}`:"soba-connect";return t.loading&&!t.machines.length?J("div",{className:n,children:J("p",{className:"sc-note",children:"Loading\u2026"})}):t.error&&!t.machines.length?J("div",{className:n,children:J("p",{className:"sc-error",children:t.error.message})}):J("div",{className:n,children:J(W,{machines:t.machines})})}import{createContext as We,useContext as Je}from"react";var he=We(null);function ke(){return Je(he)}import{jsx as U}from"react/jsx-runtime";function Ye({initialCode:e,onConnected:t,className:n,showStatus:c}){let{error:s}=M(),r=ke();return s?U("div",{className:_e(n),children:U("div",{className:"sc-card",children:U("p",{className:"sc-error",children:s.message})})}):r?U("div",{className:n,children:U(de,{client:r,...e?{initialCode:e}:{},...t?{onConnected:t}:{},...c===void 0?{}:{showStatus:c}})}):U("div",{className:_e(n),children:U("div",{className:"sc-card",children:U("p",{className:"sc-note",children:"Loading\u2026"})})})}function _e(e){return e?`soba-connect ${e}`:"soba-connect"}var ze=new Set(["delta","thinking","status","done","error"]);function ne(e){let t;try{t=JSON.parse(e)}catch{return null}if(!t||typeof t!="object")return null;let n=t,c=n.type==="event"?n.event:n;if(!c||typeof c!="object")return null;let s=c;if(typeof s.type!="string"||!ze.has(s.type))return null;let r={type:s.type};return typeof s.text=="string"&&(r.text=s.text),typeof s.message=="string"&&(r.message=s.message),typeof s.tool=="string"&&(r.tool=s.tool),typeof s.tier=="string"&&(r.tier=s.tier),s.usage&&typeof s.usage=="object"&&(r.usage=s.usage),r}async function*fe(e){let t=e.body;if(!t)throw new Error("That run response had no body to stream.");let n=(e.headers.get("content-type")??"").includes("ndjson"),c=t.getReader(),s=new TextDecoder,r="",u=[];try{for(;;){let g=await c.read();if(g.done)break;for(r+=s.decode(g.value,{stream:!0});;){let h=r.indexOf(`
3
+ `);if(h===-1)break;let b=Te(r.slice(0,h));if(r=r.slice(h+1),n){let o=b.trim()?ne(b):null;o&&(yield o);continue}if(b===""){let o=u.join(`
4
+ `);if(u=[],o==="[DONE]")return;let m=o?ne(o):null;m&&(yield m);continue}Re(b,u)}}!n&&r&&Re(Te(r),u);let y=n?r.trim():u.join(`
5
+ `);if(y&&y!=="[DONE]"){let g=ne(y);g&&(yield g)}}finally{c.cancel().catch(()=>{})}}function Re(e,t){if(!e||e.startsWith(":"))return;let n=e.indexOf(":");if(n===-1||e.slice(0,n)!=="data")return;let c=e.slice(n+1);t.push(c.startsWith(" ")?c.slice(1):c)}function Te(e){return e.endsWith("\r")?e.slice(0,-1):e}async function Ke(e){let t="";for await(let n of e)if(n.type==="delta"&&n.text&&(t+=n.text),n.type==="error")throw new Error(n.message??"The run ended with an error.");return t}import{useState as Me}from"react";import{jsx as Ae,jsxs as Xe}from"react/jsx-runtime";function Qe({plan:e,successUrl:t,cancelUrl:n,label:c,className:s}){let{plan:r}=M(),[u,y]=Me(!1),[g,h]=Me(null);async function b(){y(!0),h(null);try{let o=await r.checkout(e.id,{...t?{successUrl:t}:{},...n?{cancelUrl:n}:{}});window.location.assign(o)}catch(o){h(o.message),y(!1)}}return Xe("div",{className:s?`soba-connect ${s}`:"soba-connect",children:[Ae("button",{type:"button",className:"sc-btn sc-btn-primary",disabled:u,onClick:()=>void b(),children:u?"Opening\u2026":c??`Subscribe \u2014 $${e.price.toLocaleString()}/${e.interval}`}),g&&Ae("p",{className:"sc-error",children:g})]})}import{useState as Ie}from"react";import{Fragment as nt,jsx as d,jsxs as S}from"react/jsx-runtime";var Ze={"user-hardware":"your own machine","user-subscription":"your own AI subscription","open-weights":"open models we host",frontier:"frontier models"},et=new Set(["user-hardware","user-subscription"]);function se(e){return e?`soba-connect ${e}`:"soba-connect"}function tt({onSelect:e,successUrl:t,cancelUrl:n,showCompute:c=!0,className:s}){let{plan:r}=M(),[u,y]=Ie(null),[g,h]=Ie(null);if(r.error)return d("div",{className:se(s),children:d("div",{className:"sc-card",children:d("p",{className:"sc-error",children:r.error.message})})});if(r.loading)return d("div",{className:se(s),children:d("div",{className:"sc-card",children:d("p",{className:"sc-note",children:"Loading plans\u2026"})})});if(r.plans.length===0)return d("div",{className:se(s),children:d("div",{className:"sc-card",children:d("p",{className:"sc-note",children:"This app has no plans yet."})})});async function b(o){y(o.id),h(null);try{if(o.selectable)await r.select(o.id);else if(e)e(o);else{let m=await r.checkout(o.id,{...t?{successUrl:t}:{},...n?{cancelUrl:n}:{}});window.location.assign(m);return}}catch(m){h(m.message)}finally{y(null)}}return S("div",{className:se(s),children:[d("div",{className:"sc-plans",children:r.plans.map(o=>{let m=r.current?.plan_id===o.id;return S("div",{className:m?"sc-plan sc-plan-current":"sc-plan",children:[S("div",{className:"sc-row-between",children:[d("span",{className:"sc-title",children:o.name}),m&&d("span",{className:"sc-plan-badge",children:"Current"})]}),o.description&&d("p",{className:"sc-sub",children:o.description}),d("p",{className:"sc-plan-price",children:o.price===0?d("span",{className:"sc-plan-amount",children:"Free"}):S(nt,{children:[S("span",{className:"sc-plan-amount",children:["$",o.price.toLocaleString()]}),S("span",{className:"sc-plan-interval",children:["/",o.interval]})]})}),S("div",{className:"sc-facts",children:[S("div",{className:"sc-fact",children:[d("span",{className:"sc-fact-key",children:"Included"}),d("span",{children:o.included_units===null?"Unlimited":`${o.included_units.toLocaleString()} ${o.unit}${o.included_units===1?"":"s"}`})]}),o.overage_per_unit>0&&S("div",{className:"sc-fact",children:[d("span",{className:"sc-fact-key",children:"Then"}),S("span",{children:["$",o.overage_per_unit," / ",o.unit]})]}),o.trial_days>0&&S("div",{className:"sc-fact",children:[d("span",{className:"sc-fact-key",children:"Trial"}),S("span",{children:[o.trial_days," days"]})]}),c&&S("div",{className:"sc-plan-compute",children:[d("span",{className:"sc-fact-key",children:"Runs on"}),o.route_allow===null||o.route_allow.length===0?d("span",{children:o.route_allow===null?"any compute":"no compute"}):d("ul",{className:"sc-compute",children:o.route_allow.map(v=>d("li",{className:et.has(v)?"sc-compute-own":void 0,children:Ze[v]??v},v))})]})]}),d("button",{type:"button",className:m?"sc-btn":"sc-btn sc-btn-primary",disabled:m||u!==null,onClick:()=>void b(o),children:m?"Your plan":u===o.id?o.selectable?"Switching\u2026":"Opening\u2026":o.selectable?"Choose":`Subscribe \u2014 $${o.price.toLocaleString()}/${o.interval}`})]},o.id)})}),g&&d("p",{className:"sc-error",children:g})]})}import{useCallback as F,useEffect as j,useMemo as ye,useRef as rt,useState as E}from"react";var re=class{#t;#s=null;#n=null;#e;#r=new Set;constructor(t){this.#e=t,this.#t=t.token??null}get token(){return this.#t}get expiresAt(){return this.#s}get canRefresh(){return!!this.#e.endpoint}get refreshAt(){return!this.canRefresh||this.#s===null?null:Math.max(Date.now()+1e4,this.#s-6e4)}subscribe(t){return this.#r.add(t),()=>this.#r.delete(t)}async ensure(){return this.#t?this.#t:this.refresh()}async refresh(){if(this.#n)return this.#n;let t=this.#e.endpoint;if(!t)throw new Error("This Soba session cannot be refreshed. Pass `sessionEndpoint` so the provider can mint a new one.");let n=this.#e.fetchImpl??fetch.bind(globalThis);this.#n=(async()=>{let c=await n(t,{method:"POST",headers:{"content-type":"application/json"},credentials:"same-origin",body:JSON.stringify(this.#e.user?{user:this.#e.user}:{})});if(!c.ok)throw new Error(`Could not start a Soba session (${c.status} from ${t}).`);let s=await c.json(),r=s.session??s.token;if(!r)throw new Error(`${t} answered without a \`session\`. See @soba-so/react's README.`);let u=s.expires_in??s.expiresIn;this.#t=r,this.#s=typeof u=="number"?Date.now()+u*1e3:null;for(let y of this.#r)y();return r})();try{return await this.#n}finally{this.#n=null}}};function Oe(e,t=fetch.bind(globalThis)){return async(n,c)=>{let s=await t(n,c);if(s.status!==401||!e.canRefresh)return s;let r;try{r=await e.refresh()}catch{return s}return t(n,st(c,r))}}function st(e,t){let n=new Headers(e?.headers);return n.set("authorization",`Bearer ${t}`),{...e,headers:n}}var Le="data-soba-react",$e=!1;function ge(){if($e||typeof document>"u"||($e=!0,document.querySelector(`style[${Le}]`)))return;let e=document.createElement("style");e.setAttribute(Le,""),e.textContent=`.soba-connect {
6
6
  --sc-ground: #ffffff;
7
7
  --sc-sheet: #ffffff;
8
8
  --sc-raised: #f6f6f7;
@@ -20,7 +20,7 @@ var ee=class extends Error{status;constructor(t,n){super(t),this.name="ConnectEr
20
20
  box-sizing: border-box;
21
21
  max-width: 560px;
22
22
  margin: 0 auto;
23
- color: var(--sc-text);
23
+ color: var(--sc-text, #1a1a1c);
24
24
  font-family: "Inter", ui-sans-serif, -apple-system, "Helvetica Neue", Arial, sans-serif;
25
25
  font-size: 15px;
26
26
  line-height: 1.5;
@@ -33,9 +33,9 @@ var ee=class extends Error{status;constructor(t,n){super(t),this.name="ConnectEr
33
33
  }
34
34
 
35
35
  .sc-card {
36
- background: var(--sc-sheet);
37
- border: 1px solid var(--sc-line);
38
- border-radius: var(--sc-r);
36
+ background: var(--sc-sheet, #ffffff);
37
+ border: 1px solid var(--sc-line, #e9e9eb);
38
+ border-radius: var(--sc-r, 10px);
39
39
  padding: 20px;
40
40
  }
41
41
  .sc-card + .sc-card {
@@ -49,12 +49,12 @@ var ee=class extends Error{status;constructor(t,n){super(t),this.name="ConnectEr
49
49
  margin: 0 0 4px;
50
50
  }
51
51
  .sc-sub {
52
- color: var(--sc-muted);
52
+ color: var(--sc-muted, #6b6b73);
53
53
  margin: 0 0 16px;
54
54
  }
55
55
  .sc-label {
56
56
  font-size: 13px;
57
- color: var(--sc-muted);
57
+ color: var(--sc-muted, #6b6b73);
58
58
  margin: 0 0 6px;
59
59
  letter-spacing: -0.005em;
60
60
  }
@@ -67,33 +67,33 @@ var ee=class extends Error{status;constructor(t,n){super(t),this.name="ConnectEr
67
67
  display: block;
68
68
  width: 100%;
69
69
  text-align: left;
70
- background: var(--sc-sheet);
71
- border: 1px solid var(--sc-line-2);
72
- border-radius: var(--sc-r);
70
+ background: var(--sc-sheet, #ffffff);
71
+ border: 1px solid var(--sc-line-2, #dcdce0);
72
+ border-radius: var(--sc-r, 10px);
73
73
  padding: 12px 14px;
74
74
  cursor: pointer;
75
75
  font: inherit;
76
76
  color: inherit;
77
77
  }
78
78
  .sc-choice:hover {
79
- background: var(--sc-raised);
79
+ background: var(--sc-raised, #f6f6f7);
80
80
  }
81
81
  .sc-choice[aria-pressed="true"] {
82
- border-color: var(--sc-accent);
83
- box-shadow: inset 0 0 0 1px var(--sc-accent);
82
+ border-color: var(--sc-accent, #18181b);
83
+ box-shadow: inset 0 0 0 1px var(--sc-accent, #18181b);
84
84
  }
85
85
  .sc-choice-title {
86
86
  font-weight: 550;
87
87
  }
88
88
  .sc-choice-meta {
89
- color: var(--sc-muted);
89
+ color: var(--sc-muted, #6b6b73);
90
90
  font-size: 13px;
91
91
  }
92
92
 
93
93
  .sc-code {
94
94
  display: block;
95
- background: var(--sc-raised);
96
- border: 1px solid var(--sc-line);
95
+ background: var(--sc-raised, #f6f6f7);
96
+ border: 1px solid var(--sc-line, #e9e9eb);
97
97
  border-radius: 8px;
98
98
  padding: 12px 14px;
99
99
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
@@ -123,13 +123,13 @@ var ee=class extends Error{status;constructor(t,n){super(t),this.name="ConnectEr
123
123
  font-weight: 550;
124
124
  border-radius: 8px;
125
125
  padding: 9px 14px;
126
- border: 1px solid var(--sc-line-2);
127
- background: var(--sc-sheet);
128
- color: var(--sc-text);
126
+ border: 1px solid var(--sc-line-2, #dcdce0);
127
+ background: var(--sc-sheet, #ffffff);
128
+ color: var(--sc-text, #1a1a1c);
129
129
  cursor: pointer;
130
130
  }
131
131
  .sc-btn:hover {
132
- background: var(--sc-raised);
132
+ background: var(--sc-raised, #f6f6f7);
133
133
  }
134
134
 
135
135
  a.sc-btn {
@@ -138,13 +138,13 @@ a.sc-btn {
138
138
  text-decoration: none;
139
139
  }
140
140
  .sc-btn-primary {
141
- background: var(--sc-accent);
142
- color: var(--sc-accent-fg);
143
- border-color: var(--sc-accent);
141
+ background: var(--sc-accent, #18181b);
142
+ color: var(--sc-accent-fg, #ffffff);
143
+ border-color: var(--sc-accent, #18181b);
144
144
  }
145
145
  .sc-btn-primary:hover {
146
146
  opacity: 0.9;
147
- background: var(--sc-accent);
147
+ background: var(--sc-accent, #18181b);
148
148
  }
149
149
  .sc-btn:disabled {
150
150
  opacity: 0.5;
@@ -157,9 +157,9 @@ a.sc-btn {
157
157
  letter-spacing: 0.08em;
158
158
  text-transform: uppercase;
159
159
  padding: 10px 12px;
160
- border: 1px solid var(--sc-line-2);
160
+ border: 1px solid var(--sc-line-2, #dcdce0);
161
161
  border-radius: 8px;
162
- background: var(--sc-sheet);
162
+ background: var(--sc-sheet, #ffffff);
163
163
  color: inherit;
164
164
  width: 100%;
165
165
  }
@@ -169,34 +169,34 @@ a.sc-btn {
169
169
  height: 8px;
170
170
  border-radius: 50%;
171
171
  flex: none;
172
- background: var(--sc-muted);
172
+ background: var(--sc-muted, #6b6b73);
173
173
  }
174
174
  .sc-dot-ok {
175
- background: var(--sc-ok);
175
+ background: var(--sc-ok, #22a06b);
176
176
  }
177
177
  .sc-dot-busy {
178
- background: var(--sc-warn);
178
+ background: var(--sc-warn, #b06f14);
179
179
  }
180
180
  .sc-dot-off {
181
- background: var(--sc-line-2);
181
+ background: var(--sc-line-2, #dcdce0);
182
182
  }
183
183
 
184
184
  .sc-tone-ok {
185
- color: var(--sc-ok);
185
+ color: var(--sc-ok, #22a06b);
186
186
  }
187
187
  .sc-tone-warn {
188
- color: var(--sc-warn);
188
+ color: var(--sc-warn, #b06f14);
189
189
  }
190
190
  .sc-tone-unknown {
191
- color: var(--sc-muted);
191
+ color: var(--sc-muted, #6b6b73);
192
192
  }
193
193
 
194
194
  .sc-error {
195
- color: var(--sc-warn);
195
+ color: var(--sc-warn, #b06f14);
196
196
  font-size: 14px;
197
197
  }
198
198
  .sc-note {
199
- color: var(--sc-muted);
199
+ color: var(--sc-muted, #6b6b73);
200
200
  font-size: 13px;
201
201
  }
202
202
 
@@ -213,12 +213,12 @@ a.sc-btn {
213
213
  font-size: 14px;
214
214
  }
215
215
  .sc-fact-key {
216
- color: var(--sc-muted);
216
+ color: var(--sc-muted, #6b6b73);
217
217
  min-width: 92px;
218
218
  }
219
219
 
220
220
  .sc-machine {
221
- border-top: 1px solid var(--sc-line);
221
+ border-top: 1px solid var(--sc-line, #e9e9eb);
222
222
  padding-top: 12px;
223
223
  margin-top: 12px;
224
224
  }
@@ -254,8 +254,9 @@ a.sc-btn {
254
254
  .sc-plans {
255
255
  display: grid;
256
256
  gap: 12px;
257
+ align-items: stretch;
257
258
 
258
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
259
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
259
260
  }
260
261
 
261
262
  .sc-plan {
@@ -264,8 +265,8 @@ a.sc-btn {
264
265
  gap: 10px;
265
266
  padding: 16px;
266
267
  border: 1px solid var(--sc-line, #e5e5e7);
267
- border-radius: var(--sc-radius, 10px);
268
- background: var(--sc-card, #fff);
268
+ border-radius: var(--sc-r, 10px);
269
+ background: var(--sc-sheet, #fff);
269
270
  }
270
271
 
271
272
  .sc-plan-current {
@@ -277,7 +278,7 @@ a.sc-btn {
277
278
  padding: 2px 6px;
278
279
  border-radius: 999px;
279
280
  background: var(--sc-text, #18181b);
280
- color: var(--sc-card, #fff);
281
+ color: var(--sc-sheet, #fff);
281
282
  }
282
283
 
283
284
  .sc-plan-price {
@@ -302,5 +303,75 @@ a.sc-btn {
302
303
 
303
304
  .sc-plan .sc-btn {
304
305
  margin-top: auto;
306
+ width: 100%;
307
+ }
308
+
309
+ .sc-plan .sc-facts {
310
+ gap: 6px;
311
+ padding-bottom: 4px;
312
+ }
313
+
314
+ .sc-plan .sc-fact {
315
+ gap: 8px;
316
+ justify-content: space-between;
317
+ align-items: baseline;
318
+ }
319
+
320
+ .sc-plan .sc-fact-key {
321
+ min-width: 0;
322
+ flex: none;
323
+ }
324
+
325
+ .sc-plan .sc-fact > :last-child {
326
+ text-align: right;
327
+ }
328
+
329
+ .sc-plan-compute {
330
+ display: grid;
331
+ gap: 4px;
332
+ font-size: 14px;
333
+ }
334
+
335
+ .sc-compute {
336
+ list-style: none;
337
+ margin: 0;
338
+ padding: 0;
339
+ display: grid;
340
+ gap: 3px;
341
+ }
342
+
343
+ .sc-compute li {
344
+ position: relative;
345
+ padding-left: 16px;
346
+ color: var(--sc-muted, #6b6b73);
347
+ line-height: 1.4;
348
+ }
349
+
350
+ .sc-compute li::before {
351
+ content: "\u2013";
352
+ position: absolute;
353
+ left: 2px;
354
+ }
355
+
356
+ .sc-compute li.sc-compute-own {
357
+ color: var(--sc-text-2, #3f3f46);
358
+ }
359
+
360
+ .sc-compute li.sc-compute-own::before {
361
+ content: "\u2713";
362
+ color: var(--sc-ok, #22a06b);
363
+ font-size: 11px;
364
+ top: 1px;
365
+ }
366
+
367
+ .sc-plan .sc-sub {
368
+ min-height: 2.8em;
369
+ margin: 0;
370
+ }
371
+
372
+ .sc-plan .sc-plan-price {
373
+ padding-bottom: 4px;
374
+ border-bottom: 1px solid var(--sc-line, #e9e9eb);
375
+ margin-bottom: 2px;
305
376
  }
306
- `,document.head.appendChild(e)}import{jsx as Le}from"react/jsx-runtime";var st="https://www.soba.so",rt=15e3;function ot(e){let{children:t,session:n,sessionEndpoint:i,user:s,runEndpoint:r,baseUrl:u=st,pollMs:y=rt,injectStyles:g=!0,fetchImpl:h,onError:b}=e,[o,d]=x(n??null),[$,E]=x(null),[j,M]=x(null),[k,U]=x(null),[_,F]=x(!0),[D,re]=x([]),[Z,W]=x(null),[Y,l]=x(!0),[C,w]=x([]),[R,ge]=x(null),[B,ye]=x(null),[be,$e]=x(!0),ve=nt(b);ve.current=b;let z=q(c=>{E(c),ve.current?.(c)},[]);J(()=>{g&&he()},[g]);let S=fe(()=>new se({...n?{token:n}:{},...i?{endpoint:i}:{},...s?{user:s}:{},...h?{fetchImpl:h}:{}}),[n,i,s,h]);J(()=>{let c=!0,v=S.subscribe(()=>{c&&d(S.token)});return!S.token&&!S.canRefresh?z(new Error("<SobaProvider> needs a `session` or a `sessionEndpoint`. A browser cannot mint one: that takes your secret key.")):S.ensure().then(()=>c&&d(S.token),I=>c&&z(I)),()=>{c=!1,v()}},[S,z]),J(()=>{let c=S.refreshAt;if(c===null)return;let v=setTimeout(()=>{S.refresh().catch(I=>z(I))},Math.max(0,c-Date.now()));return()=>clearTimeout(v)},[S,z,o]);let p=fe(()=>o?new Q({session:o,baseUrl:u,fetchImpl:Ae(S,h??fetch.bind(globalThis))}):null,[o,u,S,h]);J(()=>{if(!p)return;let c=!0;return F(!0),p.context().then(v=>{c&&(M(v),U(null),F(!1))},v=>{c&&(U(v),F(!1))}),()=>{c=!1}},[p]);let K=q(async()=>{if(p)try{let c=await p.machines();re(c),W(null)}catch(c){W(c)}finally{l(!1)}},[p]),A=q(async()=>{if(p)try{let c=await p.plans();w(c.plans),ge(c.current),ye(null)}catch(c){ye(c)}finally{$e(!1)}},[p]);J(()=>{p&&A()},[p,A]);let Ce=q(async c=>{if(!p)throw new Error("No session yet.");await p.selectPlan(c),await A()},[p,A]),we=q(async(c,v)=>{if(!p)throw new Error("No session yet.");let I=await p.checkoutPlan(c,v);if(!I.url)throw new Error("Checkout could not be started.");return I.url},[p]),Ne=q(async()=>{if(!p)throw new Error("No session yet.");await p.leavePlan(),await A()},[p,A]);J(()=>{if(!p)return;let c=!0;K();let v=setInterval(()=>{typeof document<"u"&&document.hidden||c&&K()},y);return()=>{c=!1,clearInterval(v)}},[p,K,y]);let Se=q(async c=>{if(!r)throw new Error("Pass `runEndpoint` to <SobaProvider> to start runs from the browser. It is your own route: it holds your secret key, calls soba.run(), and forwards the stream.");let{signal:v,...I}=c,oe=await(h??fetch.bind(globalThis))(r,{method:"POST",headers:{"content-type":"application/json",accept:"text/event-stream"},credentials:"same-origin",body:JSON.stringify(I),...v?{signal:v}:{}});if(!oe.ok)throw new Error(`${r} answered ${oe.status}.`);return me(oe)},[r,h]),Ue=fe(()=>({session:o,client:p,error:$,plan:{plans:C,current:R,plan:R?C.find(c=>c.id===R.plan_id)??null:null,loading:be,error:B,select:Ce,checkout:we,leave:Ne,refresh:A},compute:{machines:D,connected:D.length>0,online:D.some(c=>c.state!=="offline"),loading:Y,error:Z,refresh:K},connect:{app:j?.app??null,command:j?.command??null,publishableKey:j?.publishable_key??null,loading:_,error:k},run:Se}),[o,p,$,D,Y,Z,K,C,R,be,B,Ce,we,Ne,A,j,_,k,Se]);return Le(de.Provider,{value:p,children:Le(pe.Provider,{value:Ue,children:t})})}export{He as ComputeStatus,We as ConnectCompute,Ke as SobaCheckout,Ze as SobaPlans,ot as SobaProvider,ze as collectText,he as injectStyles,ne as parseEvent,me as readEvents,T as useSoba};
377
+ `,document.head.appendChild(e)}import{jsx as Ue}from"react/jsx-runtime";var ot="https://www.soba.so",at=15e3;function it(e){let{children:t,session:n,sessionEndpoint:c,user:s,runEndpoint:r,baseUrl:u=ot,pollMs:y=at,injectStyles:g=!0,fetchImpl:h,onError:b}=e,[o,m]=E(n??null),[v,k]=E(null),[D,A]=E(null),[_,I]=E(null),[R,q]=E(!0),[B,oe]=E([]),[Z,G]=E(null),[Y,l]=E(!0),[w,N]=E([]),[T,be]=E(null),[H,ae]=E(null),[ve,Ce]=E(!0),we=rt(b);we.current=b;let z=F(i=>{k(i),we.current?.(i)},[]);j(()=>{g&&ge()},[g]);let P=ye(()=>new re({...n?{token:n}:{},...c?{endpoint:c}:{},...s?{user:s}:{},...h?{fetchImpl:h}:{}}),[n,c,s,h]);j(()=>{let i=!0,C=P.subscribe(()=>{i&&m(P.token)});return!P.token&&!P.canRefresh?z(new Error("<SobaProvider> needs a `session` or a `sessionEndpoint`. A browser cannot mint one: that takes your secret key.")):P.ensure().then(()=>i&&m(P.token),L=>i&&z(L)),()=>{i=!1,C()}},[P,z]),j(()=>{v&&(q(!1),l(!1),Ce(!1),I(i=>i??v),G(i=>i??v),ae(i=>i??v))},[v]),j(()=>{let i=P.refreshAt;if(i===null)return;let C=setTimeout(()=>{P.refresh().catch(L=>z(L))},Math.max(0,i-Date.now()));return()=>clearTimeout(C)},[P,z,o]);let p=ye(()=>o?new Q({session:o,baseUrl:u,fetchImpl:Oe(P,h??fetch.bind(globalThis))}):null,[o,u,P,h]);j(()=>{if(!p)return;let i=!0;return q(!0),p.context().then(C=>{i&&(A(C),I(null),q(!1))},C=>{i&&(I(C),q(!1))}),()=>{i=!1}},[p]);let K=F(async()=>{if(p)try{let i=await p.machines();oe(i),G(null)}catch(i){G(i)}finally{l(!1)}},[p]),O=F(async()=>{if(p)try{let i=await p.plans();N(i.plans),be(i.current),ae(null)}catch(i){ae(i)}finally{Ce(!1)}},[p]);j(()=>{p&&O()},[p,O]);let Ne=F(async i=>{if(!p)throw new Error("No session yet.");await p.selectPlan(i),await O()},[p,O]),Se=F(async(i,C)=>{if(!p)throw new Error("No session yet.");let L=await p.checkoutPlan(i,C);if(!L.url)throw new Error("Checkout could not be started.");return L.url},[p]),Pe=F(async()=>{if(!p)throw new Error("No session yet.");await p.leavePlan(),await O()},[p,O]);j(()=>{if(!p)return;let i=!0;K();let C=setInterval(()=>{typeof document<"u"&&document.hidden||i&&K()},y);return()=>{i=!1,clearInterval(C)}},[p,K,y]);let xe=F(async i=>{if(!r)throw new Error("Pass `runEndpoint` to <SobaProvider> to start runs from the browser. It is your own route: it holds your secret key, calls soba.run(), and forwards the stream.");let{signal:C,...L}=i,ie=await(h??fetch.bind(globalThis))(r,{method:"POST",headers:{"content-type":"application/json",accept:"text/event-stream"},credentials:"same-origin",body:JSON.stringify(L),...C?{signal:C}:{}});if(!ie.ok)throw new Error(`${r} answered ${ie.status}.`);return fe(ie)},[r,h]),qe=ye(()=>({session:o,client:p,error:v,plan:{plans:w,current:T,plan:T?w.find(i=>i.id===T.plan_id)??null:null,loading:ve,error:H,select:Ne,checkout:Se,leave:Pe,refresh:O},compute:{machines:B,connected:B.length>0,online:B.some(i=>i.state!=="offline"),loading:Y,error:Z,refresh:K},connect:{app:D?.app??null,command:D?.command??null,publishableKey:D?.publishable_key??null,loading:R,error:_},run:xe}),[o,p,v,B,Y,Z,K,w,T,ve,H,Ne,Se,Pe,O,D,R,_,xe]);return Ue(he.Provider,{value:p,children:Ue(me.Provider,{value:qe,children:t})})}export{Ve as ComputeStatus,Ye as ConnectCompute,Qe as SobaCheckout,tt as SobaPlans,it as SobaProvider,Ke as collectText,ge as injectStyles,ne as parseEvent,fe as readEvents,M as useSoba};
package/dist/styles.css CHANGED
@@ -16,7 +16,7 @@
16
16
  box-sizing: border-box;
17
17
  max-width: 560px;
18
18
  margin: 0 auto;
19
- color: var(--sc-text);
19
+ color: var(--sc-text, #1a1a1c);
20
20
  font-family: "Inter", ui-sans-serif, -apple-system, "Helvetica Neue", Arial, sans-serif;
21
21
  font-size: 15px;
22
22
  line-height: 1.5;
@@ -29,9 +29,9 @@
29
29
  }
30
30
 
31
31
  .sc-card {
32
- background: var(--sc-sheet);
33
- border: 1px solid var(--sc-line);
34
- border-radius: var(--sc-r);
32
+ background: var(--sc-sheet, #ffffff);
33
+ border: 1px solid var(--sc-line, #e9e9eb);
34
+ border-radius: var(--sc-r, 10px);
35
35
  padding: 20px;
36
36
  }
37
37
  .sc-card + .sc-card {
@@ -45,12 +45,12 @@
45
45
  margin: 0 0 4px;
46
46
  }
47
47
  .sc-sub {
48
- color: var(--sc-muted);
48
+ color: var(--sc-muted, #6b6b73);
49
49
  margin: 0 0 16px;
50
50
  }
51
51
  .sc-label {
52
52
  font-size: 13px;
53
- color: var(--sc-muted);
53
+ color: var(--sc-muted, #6b6b73);
54
54
  margin: 0 0 6px;
55
55
  letter-spacing: -0.005em;
56
56
  }
@@ -63,33 +63,33 @@
63
63
  display: block;
64
64
  width: 100%;
65
65
  text-align: left;
66
- background: var(--sc-sheet);
67
- border: 1px solid var(--sc-line-2);
68
- border-radius: var(--sc-r);
66
+ background: var(--sc-sheet, #ffffff);
67
+ border: 1px solid var(--sc-line-2, #dcdce0);
68
+ border-radius: var(--sc-r, 10px);
69
69
  padding: 12px 14px;
70
70
  cursor: pointer;
71
71
  font: inherit;
72
72
  color: inherit;
73
73
  }
74
74
  .sc-choice:hover {
75
- background: var(--sc-raised);
75
+ background: var(--sc-raised, #f6f6f7);
76
76
  }
77
77
  .sc-choice[aria-pressed="true"] {
78
- border-color: var(--sc-accent);
79
- box-shadow: inset 0 0 0 1px var(--sc-accent);
78
+ border-color: var(--sc-accent, #18181b);
79
+ box-shadow: inset 0 0 0 1px var(--sc-accent, #18181b);
80
80
  }
81
81
  .sc-choice-title {
82
82
  font-weight: 550;
83
83
  }
84
84
  .sc-choice-meta {
85
- color: var(--sc-muted);
85
+ color: var(--sc-muted, #6b6b73);
86
86
  font-size: 13px;
87
87
  }
88
88
 
89
89
  .sc-code {
90
90
  display: block;
91
- background: var(--sc-raised);
92
- border: 1px solid var(--sc-line);
91
+ background: var(--sc-raised, #f6f6f7);
92
+ border: 1px solid var(--sc-line, #e9e9eb);
93
93
  border-radius: 8px;
94
94
  padding: 12px 14px;
95
95
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
@@ -119,13 +119,13 @@
119
119
  font-weight: 550;
120
120
  border-radius: 8px;
121
121
  padding: 9px 14px;
122
- border: 1px solid var(--sc-line-2);
123
- background: var(--sc-sheet);
124
- color: var(--sc-text);
122
+ border: 1px solid var(--sc-line-2, #dcdce0);
123
+ background: var(--sc-sheet, #ffffff);
124
+ color: var(--sc-text, #1a1a1c);
125
125
  cursor: pointer;
126
126
  }
127
127
  .sc-btn:hover {
128
- background: var(--sc-raised);
128
+ background: var(--sc-raised, #f6f6f7);
129
129
  }
130
130
 
131
131
  a.sc-btn {
@@ -134,13 +134,13 @@ a.sc-btn {
134
134
  text-decoration: none;
135
135
  }
136
136
  .sc-btn-primary {
137
- background: var(--sc-accent);
138
- color: var(--sc-accent-fg);
139
- border-color: var(--sc-accent);
137
+ background: var(--sc-accent, #18181b);
138
+ color: var(--sc-accent-fg, #ffffff);
139
+ border-color: var(--sc-accent, #18181b);
140
140
  }
141
141
  .sc-btn-primary:hover {
142
142
  opacity: 0.9;
143
- background: var(--sc-accent);
143
+ background: var(--sc-accent, #18181b);
144
144
  }
145
145
  .sc-btn:disabled {
146
146
  opacity: 0.5;
@@ -153,9 +153,9 @@ a.sc-btn {
153
153
  letter-spacing: 0.08em;
154
154
  text-transform: uppercase;
155
155
  padding: 10px 12px;
156
- border: 1px solid var(--sc-line-2);
156
+ border: 1px solid var(--sc-line-2, #dcdce0);
157
157
  border-radius: 8px;
158
- background: var(--sc-sheet);
158
+ background: var(--sc-sheet, #ffffff);
159
159
  color: inherit;
160
160
  width: 100%;
161
161
  }
@@ -165,34 +165,34 @@ a.sc-btn {
165
165
  height: 8px;
166
166
  border-radius: 50%;
167
167
  flex: none;
168
- background: var(--sc-muted);
168
+ background: var(--sc-muted, #6b6b73);
169
169
  }
170
170
  .sc-dot-ok {
171
- background: var(--sc-ok);
171
+ background: var(--sc-ok, #22a06b);
172
172
  }
173
173
  .sc-dot-busy {
174
- background: var(--sc-warn);
174
+ background: var(--sc-warn, #b06f14);
175
175
  }
176
176
  .sc-dot-off {
177
- background: var(--sc-line-2);
177
+ background: var(--sc-line-2, #dcdce0);
178
178
  }
179
179
 
180
180
  .sc-tone-ok {
181
- color: var(--sc-ok);
181
+ color: var(--sc-ok, #22a06b);
182
182
  }
183
183
  .sc-tone-warn {
184
- color: var(--sc-warn);
184
+ color: var(--sc-warn, #b06f14);
185
185
  }
186
186
  .sc-tone-unknown {
187
- color: var(--sc-muted);
187
+ color: var(--sc-muted, #6b6b73);
188
188
  }
189
189
 
190
190
  .sc-error {
191
- color: var(--sc-warn);
191
+ color: var(--sc-warn, #b06f14);
192
192
  font-size: 14px;
193
193
  }
194
194
  .sc-note {
195
- color: var(--sc-muted);
195
+ color: var(--sc-muted, #6b6b73);
196
196
  font-size: 13px;
197
197
  }
198
198
 
@@ -209,12 +209,12 @@ a.sc-btn {
209
209
  font-size: 14px;
210
210
  }
211
211
  .sc-fact-key {
212
- color: var(--sc-muted);
212
+ color: var(--sc-muted, #6b6b73);
213
213
  min-width: 92px;
214
214
  }
215
215
 
216
216
  .sc-machine {
217
- border-top: 1px solid var(--sc-line);
217
+ border-top: 1px solid var(--sc-line, #e9e9eb);
218
218
  padding-top: 12px;
219
219
  margin-top: 12px;
220
220
  }
@@ -250,8 +250,9 @@ a.sc-btn {
250
250
  .sc-plans {
251
251
  display: grid;
252
252
  gap: 12px;
253
+ align-items: stretch;
253
254
 
254
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
255
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
255
256
  }
256
257
 
257
258
  .sc-plan {
@@ -260,8 +261,8 @@ a.sc-btn {
260
261
  gap: 10px;
261
262
  padding: 16px;
262
263
  border: 1px solid var(--sc-line, #e5e5e7);
263
- border-radius: var(--sc-radius, 10px);
264
- background: var(--sc-card, #fff);
264
+ border-radius: var(--sc-r, 10px);
265
+ background: var(--sc-sheet, #fff);
265
266
  }
266
267
 
267
268
  .sc-plan-current {
@@ -273,7 +274,7 @@ a.sc-btn {
273
274
  padding: 2px 6px;
274
275
  border-radius: 999px;
275
276
  background: var(--sc-text, #18181b);
276
- color: var(--sc-card, #fff);
277
+ color: var(--sc-sheet, #fff);
277
278
  }
278
279
 
279
280
  .sc-plan-price {
@@ -298,4 +299,74 @@ a.sc-btn {
298
299
 
299
300
  .sc-plan .sc-btn {
300
301
  margin-top: auto;
302
+ width: 100%;
303
+ }
304
+
305
+ .sc-plan .sc-facts {
306
+ gap: 6px;
307
+ padding-bottom: 4px;
308
+ }
309
+
310
+ .sc-plan .sc-fact {
311
+ gap: 8px;
312
+ justify-content: space-between;
313
+ align-items: baseline;
314
+ }
315
+
316
+ .sc-plan .sc-fact-key {
317
+ min-width: 0;
318
+ flex: none;
319
+ }
320
+
321
+ .sc-plan .sc-fact > :last-child {
322
+ text-align: right;
323
+ }
324
+
325
+ .sc-plan-compute {
326
+ display: grid;
327
+ gap: 4px;
328
+ font-size: 14px;
329
+ }
330
+
331
+ .sc-compute {
332
+ list-style: none;
333
+ margin: 0;
334
+ padding: 0;
335
+ display: grid;
336
+ gap: 3px;
337
+ }
338
+
339
+ .sc-compute li {
340
+ position: relative;
341
+ padding-left: 16px;
342
+ color: var(--sc-muted, #6b6b73);
343
+ line-height: 1.4;
344
+ }
345
+
346
+ .sc-compute li::before {
347
+ content: "–";
348
+ position: absolute;
349
+ left: 2px;
350
+ }
351
+
352
+ .sc-compute li.sc-compute-own {
353
+ color: var(--sc-text-2, #3f3f46);
354
+ }
355
+
356
+ .sc-compute li.sc-compute-own::before {
357
+ content: "✓";
358
+ color: var(--sc-ok, #22a06b);
359
+ font-size: 11px;
360
+ top: 1px;
361
+ }
362
+
363
+ .sc-plan .sc-sub {
364
+ min-height: 2.8em;
365
+ margin: 0;
366
+ }
367
+
368
+ .sc-plan .sc-plan-price {
369
+ padding-bottom: 4px;
370
+ border-bottom: 1px solid var(--sc-line, #e9e9eb);
371
+ margin-bottom: 2px;
301
372
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soba-so/react",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Connect the AI your users already pay for, from your own React app.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://soba.so/docs/components",