From 183664ab336d4706e8dad26d119c0bf70987fbe7 Mon Sep 17 00:00:00 2001 From: Alma Tuck Date: Thu, 8 Oct 2026 20:57:33 -0600 Subject: [PATCH] nebo.share: a file into the owner's Work, and Nebo's Share dialog on it The page hands Nebo a file (name + text). Nebo puts it into Work beside the chat the app is open on and opens its one Share dialog on it; the owner picks who can open the link. The page never makes or changes a link. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BhtnszTw2DX2kybFGA2EZt --- README.md | 18 +++++++ dist/index.cjs | 6 +-- dist/index.d.ts | 3 ++ dist/index.js | 123 ++++++++++++++++++++++++-------------------- dist/nebo.global.js | 6 +-- dist/sdk.d.ts | 2 + dist/share.d.ts | 17 ++++++ src/index.ts | 3 ++ src/sdk.ts | 2 + src/share.ts | 34 ++++++++++++ 10 files changed, 152 insertions(+), 62 deletions(-) create mode 100644 dist/share.d.ts create mode 100644 src/share.ts diff --git a/README.md b/README.md index e4f7fc3..6bf6b2c 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,24 @@ const { answers } = await nebo.decide({ - Billed like any model call; see pricing at https://neboai.com/pricing. - Types: `DecideQuestion`, `DecideRequest`, `DecideAnswer`, `Decision`. +### `nebo.share({ name, content })` +Hand the owner a file to share. The file goes into his Work folder, beside +the chat the app is open on, and Nebo opens its own Share dialog on it. He +chooses who can open the link (anyone with it, a password, or only him) and +when it ends. The link is `neboai.com/s/`; an HTML file opens there as +a live page. The page never makes or changes a link itself. + +```js +const { artifact } = await nebo.share({ name: 'Launch deck.html', content: html }); +// artifact → '/api/v1/files/Design Studio/Launch deck.html' +``` + +- `content` is the file's text (HTML, Markdown, CSV, SVG …), up to the + bot's upload limit. +- Sharing the same `name` again replaces the file in Work. +- Throws an `Error` whose message is the reason (400: a bad name or empty + content). + ### `nebo.chat` - `mount(element, options?)`: Embed chat UI - `unmount()`: Remove chat UI diff --git a/dist/index.cjs b/dist/index.cjs index fc8030f..fa6f6a9 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -1,3 +1,3 @@ -"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let d=null,h=null;function i(){if(d)return d;if(window.location.protocol==="neboapp:")return d=window.location.hostname,d;const s=window.location.pathname.match(/^\/apps\/([^/]+)\/ui/);if(s)return d=s[1],d;const e=document.querySelector('meta[name="nebo-app-id"]');if(e)return d=e.getAttribute("content")||"",d;throw new Error('[nebo-sdk] Cannot detect app ID. In production, serve from /apps/{id}/ui/. In dev, add to index.html.')}function l(){if(h)return h;if(window.location.protocol==="neboapp:")return h="http://localhost:27895",h;const s=document.querySelector('meta[name="nebo-base-url"]');return s?(h=s.getAttribute("content")||"",h):(h=window.location.origin,h)}function j(s){d=s}function E(s){h=s}async function k(s,e){const t=i(),n=l();if(s.startsWith("http://")||s.startsWith("https://")){const f=`${n}/api/v1/apps/${t}/http/proxy`,r={};e!=null&&e.headers&&new Headers(e.headers).forEach((M,R)=>{r[R]=M});const p={url:s,method:(e==null?void 0:e.method)||"GET",headers:r,body:e!=null&&e.body?String(e.body):void 0},u=await(await fetch(f,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)})).json();return new Response(u.body,{status:u.status,headers:u.headers})}const a=s.startsWith("/")?s:`/${s}`,o=`${n}/api/v1/apps/${t}/api${a}`;return fetch(o,e)}class O{constructor(e){this.ws=null,this.reconnectTimer=null,this.reconnectDelay=1e3,this.maxReconnectDelay=3e4,this._closed=!1,this.onopen=null,this.onmessage=null,this.onerror=null,this.onclose=null;const t=i(),a=l().replace(/^http/,"ws");this.url=`${a}/ws/app/${t}`,this.connect()}connect(){this._closed||(this.ws=new WebSocket(this.url),this.ws.onopen=e=>{var t;this.reconnectDelay=1e3,(t=this.onopen)==null||t.call(this,e)},this.ws.onmessage=e=>{var t;return(t=this.onmessage)==null?void 0:t.call(this,e)},this.ws.onerror=e=>{var t;return(t=this.onerror)==null?void 0:t.call(this,e)},this.ws.onclose=e=>{var t;(t=this.onclose)==null||t.call(this,e),this._closed||this.scheduleReconnect()})}scheduleReconnect(){this.reconnectTimer||this._closed||(this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.reconnectDelay=Math.min(this.reconnectDelay*2,this.maxReconnectDelay),this.connect()},this.reconnectDelay))}get readyState(){var e;return((e=this.ws)==null?void 0:e.readyState)??WebSocket.CLOSED}send(e){var t;(t=this.ws)==null||t.send(e)}close(e,t){var n;this._closed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),(n=this.ws)==null||n.close(e,t)}}const $=new Set;let T=null;function B(){T||(T=new O,T.onmessage=s=>{let e;try{e=JSON.parse(s.data)}catch{return}e.type!=="app_data_changed"||!e.data||e.data.appId&&e.data.appId!==i()||$.forEach(t=>t(e.data))})}function H(s){if(typeof s!="string")return s;let e;try{e=JSON.parse(s)}catch{return s}if(typeof e!="string")return e;try{return JSON.parse(e)}catch{return e}}function b(s){const e=i(),t=l();return s?`${t}/api/v1/apps/${e}/storage/${encodeURIComponent(s)}`:`${t}/api/v1/apps/${e}/storage`}const x={async getItem(s){const e=await fetch(b(s));if(e.status===404)return null;const t=await e.json();return H(t.value)},async setItem(s,e){const t=typeof e=="string"?e:JSON.stringify(e);await fetch(b(s),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:t})})},async removeItem(s){await fetch(b(s),{method:"DELETE"})},async clear(){const s=await this.keys();await Promise.all(s.map(e=>this.removeItem(e)))},async keys(){return((await(await fetch(b())).json()).items||[]).map(t=>t[0])},onChange(s){return $.add(s),B(),()=>{$.delete(s)}}};function I(s){const e=i();return`${l()}/api/v1/apps/${e}/agents/${s}`}const D={async invoke(s,e){return(await fetch(I("invoke"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:s,agent:e==null?void 0:e.agent,data:e==null?void 0:e.data})})).json()},async*stream(s,e){const t=await fetch(I("stream"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:s,agent:e==null?void 0:e.agent,data:e==null?void 0:e.data})});if(!t.body)throw new Error("No response body for streaming");const n=t.body.getReader(),a=new TextDecoder;let o="";for(;;){const{done:f,value:r}=await n.read();if(f)break;o+=a.decode(r,{stream:!0});const p=o.split(` -`);o=p.pop()||"";for(const y of p)if(y.startsWith("data: ")){const u=y.slice(6);if(u==="[DONE]")return;try{yield JSON.parse(u)}catch{yield{text:u,done:!1}}}}}};function v(s){const e=i();return`${l()}/api/v1/apps/${e}/janus/${s}`}const J={async complete(s){const t=await(await fetch(v("complete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})).json();return t.text||t.content||""},async*stream(s){const e=await fetch(v("stream"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!e.body)throw new Error("No response body for streaming");const t=e.body.getReader(),n=new TextDecoder;let a="";for(;;){const{done:o,value:f}=await t.read();if(o)break;a+=n.decode(f,{stream:!0});const r=a.split(` -`);a=r.pop()||"";for(const p of r)if(p.startsWith("data: ")){const y=p.slice(6);if(y==="[DONE]")return;try{yield JSON.parse(y).text}catch{yield y}}}}};async function U(s){const e=await fetch(`${l()}/api/v1/apps/${i()}/janus/decide`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)}),t=await e.json().catch(()=>({}));if(!e.ok)throw new Error(t.error||`decide failed (${e.status})`);return t}class W{constructor(){this.ws=null,this.listeners=new Map,this._connected=!1,this._a2uiHandler=null,this.state={}}get connected(){return this._connected}connect(){this.ws||(this.ws=new O,this.ws.onopen=()=>{this._connected=!0},this.ws.onmessage=e=>{var t,n;try{const a=JSON.parse(e.data);if(a.type==="a2ui_message"&&((t=a.data)!=null&&t.message)){(n=this._a2uiHandler)==null||n.call(this,a.data.message);return}this.handleEvent(a)}catch{}},this.ws.onclose=()=>{this._connected=!1},this.ws.onerror=()=>{this._connected=!1})}disconnect(){this.ws&&(this.ws.close(),this.ws=null),this._connected=!1}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{var n;(n=this.listeners.get(e))==null||n.delete(t)}}off(e,t){var n;(n=this.listeners.get(e))==null||n.delete(t)}send(e,t){!this.ws||this.ws.readyState!==WebSocket.OPEN||this.ws.send(JSON.stringify({type:"action",name:e,...t}))}requestState(){this.send("request_state")}_rawSend(e){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(e)}handleEvent(e){e.type==="state_snapshot"?this.state={...e.snapshot}:e.type==="state_delta"&&this.applyDelta(e.delta);const t=this.listeners.get(e.type);if(t)for(const a of t)try{a(e)}catch{}const n=this.listeners.get("*");if(n)for(const a of n)try{a(e)}catch{}}applyDelta(e){for(const t of e){const n=t.path.split("/").filter(Boolean);if(n.length===0)continue;const a=this.resolveParent(n);if(!a)continue;const o=n[n.length-1];switch(t.op){case"add":case"replace":a[o]=t.value;break;case"remove":delete a[o];break}}}resolveParent(e){let t=this.state;for(let n=0;n{m=m.filter(e=>e!==s)}},setContext(s){var e;(e=c==null?void 0:c.contentWindow)==null||e.postMessage({type:"nebo:set-context",context:s},"*")},newThread(){var s;(s=c==null?void 0:c.contentWindow)==null||s.postMessage({type:"nebo:new-thread"},"*")}};class F{constructor(){this.fetch=k,this.WebSocket=O,this.storage=x,this.agents=D,this.janus=J,this.decide=U,this.surfaces=_,this.a2ui=N,this.identity=P,this.chat=A,_._a2uiHandler=e=>N._handleMessage(e),N._setSendFn(e=>_._rawSend(e))}configure(e){e.appId&&j(e.appId),e.baseUrl&&E(e.baseUrl)}}const q=new F;exports.NeboA2UI=C;exports.NeboSDK=F;exports.NeboSurfaces=W;exports.NeboWebSocket=O;exports.a2ui=N;exports.agents=D;exports.chat=A;exports.decide=U;exports.getAppId=i;exports.getBaseUrl=l;exports.identity=P;exports.janus=J;exports.nebo=q;exports.neboFetch=k;exports.setAppId=j;exports.setBaseUrl=E;exports.storage=x;exports.surfaces=_; +"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let h=null,l=null;function i(){if(h)return h;if(window.location.protocol==="neboapp:")return h=window.location.hostname,h;const s=window.location.pathname.match(/^\/apps\/([^/]+)\/ui/);if(s)return h=s[1],h;const e=document.querySelector('meta[name="nebo-app-id"]');if(e)return h=e.getAttribute("content")||"",h;throw new Error('[nebo-sdk] Cannot detect app ID. In production, serve from /apps/{id}/ui/. In dev, add to index.html.')}function d(){if(l)return l;if(window.location.protocol==="neboapp:")return l="http://localhost:27895",l;const s=document.querySelector('meta[name="nebo-base-url"]');return s?(l=s.getAttribute("content")||"",l):(l=window.location.origin,l)}function j(s){h=s}function E(s){l=s}async function k(s,e){const t=i(),n=d();if(s.startsWith("http://")||s.startsWith("https://")){const f=`${n}/api/v1/apps/${t}/http/proxy`,r={};e!=null&&e.headers&&new Headers(e.headers).forEach((R,B)=>{r[B]=R});const p={url:s,method:(e==null?void 0:e.method)||"GET",headers:r,body:e!=null&&e.body?String(e.body):void 0},u=await(await fetch(f,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)})).json();return new Response(u.body,{status:u.status,headers:u.headers})}const a=s.startsWith("/")?s:`/${s}`,o=`${n}/api/v1/apps/${t}/api${a}`;return fetch(o,e)}class _{constructor(e){this.ws=null,this.reconnectTimer=null,this.reconnectDelay=1e3,this.maxReconnectDelay=3e4,this._closed=!1,this.onopen=null,this.onmessage=null,this.onerror=null,this.onclose=null;const t=i(),a=d().replace(/^http/,"ws");this.url=`${a}/ws/app/${t}`,this.connect()}connect(){this._closed||(this.ws=new WebSocket(this.url),this.ws.onopen=e=>{var t;this.reconnectDelay=1e3,(t=this.onopen)==null||t.call(this,e)},this.ws.onmessage=e=>{var t;return(t=this.onmessage)==null?void 0:t.call(this,e)},this.ws.onerror=e=>{var t;return(t=this.onerror)==null?void 0:t.call(this,e)},this.ws.onclose=e=>{var t;(t=this.onclose)==null||t.call(this,e),this._closed||this.scheduleReconnect()})}scheduleReconnect(){this.reconnectTimer||this._closed||(this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.reconnectDelay=Math.min(this.reconnectDelay*2,this.maxReconnectDelay),this.connect()},this.reconnectDelay))}get readyState(){var e;return((e=this.ws)==null?void 0:e.readyState)??WebSocket.CLOSED}send(e){var t;(t=this.ws)==null||t.send(e)}close(e,t){var n;this._closed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),(n=this.ws)==null||n.close(e,t)}}const T=new Set;let $=null;function L(){$||($=new _,$.onmessage=s=>{let e;try{e=JSON.parse(s.data)}catch{return}e.type!=="app_data_changed"||!e.data||e.data.appId&&e.data.appId!==i()||T.forEach(t=>t(e.data))})}function H(s){if(typeof s!="string")return s;let e;try{e=JSON.parse(s)}catch{return s}if(typeof e!="string")return e;try{return JSON.parse(e)}catch{return e}}function b(s){const e=i(),t=d();return s?`${t}/api/v1/apps/${e}/storage/${encodeURIComponent(s)}`:`${t}/api/v1/apps/${e}/storage`}const x={async getItem(s){const e=await fetch(b(s));if(e.status===404)return null;const t=await e.json();return H(t.value)},async setItem(s,e){const t=typeof e=="string"?e:JSON.stringify(e);await fetch(b(s),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:t})})},async removeItem(s){await fetch(b(s),{method:"DELETE"})},async clear(){const s=await this.keys();await Promise.all(s.map(e=>this.removeItem(e)))},async keys(){return((await(await fetch(b())).json()).items||[]).map(t=>t[0])},onChange(s){return T.add(s),L(),()=>{T.delete(s)}}};function v(s){const e=i();return`${d()}/api/v1/apps/${e}/agents/${s}`}const D={async invoke(s,e){return(await fetch(v("invoke"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:s,agent:e==null?void 0:e.agent,data:e==null?void 0:e.data})})).json()},async*stream(s,e){const t=await fetch(v("stream"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:s,agent:e==null?void 0:e.agent,data:e==null?void 0:e.data})});if(!t.body)throw new Error("No response body for streaming");const n=t.body.getReader(),a=new TextDecoder;let o="";for(;;){const{done:f,value:r}=await n.read();if(f)break;o+=a.decode(r,{stream:!0});const p=o.split(` +`);o=p.pop()||"";for(const y of p)if(y.startsWith("data: ")){const u=y.slice(6);if(u==="[DONE]")return;try{yield JSON.parse(u)}catch{yield{text:u,done:!1}}}}}};function I(s){const e=i();return`${d()}/api/v1/apps/${e}/janus/${s}`}const J={async complete(s){const t=await(await fetch(I("complete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})).json();return t.text||t.content||""},async*stream(s){const e=await fetch(I("stream"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!e.body)throw new Error("No response body for streaming");const t=e.body.getReader(),n=new TextDecoder;let a="";for(;;){const{done:o,value:f}=await t.read();if(o)break;a+=n.decode(f,{stream:!0});const r=a.split(` +`);a=r.pop()||"";for(const p of r)if(p.startsWith("data: ")){const y=p.slice(6);if(y==="[DONE]")return;try{yield JSON.parse(y).text}catch{yield y}}}}};async function U(s){const e=await fetch(`${d()}/api/v1/apps/${i()}/janus/decide`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)}),t=await e.json().catch(()=>({}));if(!e.ok)throw new Error(t.error||`decide failed (${e.status})`);return t}async function P(s){const e=new URLSearchParams(window.location.search).get("thread")||"",t=await fetch(`${d()}/api/v1/apps/${i()}/share`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:s.name,content:s.content,thread:e})}),n=await t.json().catch(()=>({}));if(!t.ok)throw new Error(n.error||`share failed (${t.status})`);return n}class C{constructor(){this.ws=null,this.listeners=new Map,this._connected=!1,this._a2uiHandler=null,this.state={}}get connected(){return this._connected}connect(){this.ws||(this.ws=new _,this.ws.onopen=()=>{this._connected=!0},this.ws.onmessage=e=>{var t,n;try{const a=JSON.parse(e.data);if(a.type==="a2ui_message"&&((t=a.data)!=null&&t.message)){(n=this._a2uiHandler)==null||n.call(this,a.data.message);return}this.handleEvent(a)}catch{}},this.ws.onclose=()=>{this._connected=!1},this.ws.onerror=()=>{this._connected=!1})}disconnect(){this.ws&&(this.ws.close(),this.ws=null),this._connected=!1}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{var n;(n=this.listeners.get(e))==null||n.delete(t)}}off(e,t){var n;(n=this.listeners.get(e))==null||n.delete(t)}send(e,t){!this.ws||this.ws.readyState!==WebSocket.OPEN||this.ws.send(JSON.stringify({type:"action",name:e,...t}))}requestState(){this.send("request_state")}_rawSend(e){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(e)}handleEvent(e){e.type==="state_snapshot"?this.state={...e.snapshot}:e.type==="state_delta"&&this.applyDelta(e.delta);const t=this.listeners.get(e.type);if(t)for(const a of t)try{a(e)}catch{}const n=this.listeners.get("*");if(n)for(const a of n)try{a(e)}catch{}}applyDelta(e){for(const t of e){const n=t.path.split("/").filter(Boolean);if(n.length===0)continue;const a=this.resolveParent(n);if(!a)continue;const o=n[n.length-1];switch(t.op){case"add":case"replace":a[o]=t.value;break;case"remove":delete a[o];break}}}resolveParent(e){let t=this.state;for(let n=0;n{m=m.filter(e=>e!==s)}},setContext(s){var e;(e=c==null?void 0:c.contentWindow)==null||e.postMessage({type:"nebo:set-context",context:s},"*")},newThread(){var s;(s=c==null?void 0:c.contentWindow)==null||s.postMessage({type:"nebo:new-thread"},"*")}};class M{constructor(){this.fetch=k,this.WebSocket=_,this.storage=x,this.agents=D,this.janus=J,this.decide=U,this.share=P,this.surfaces=N,this.a2ui=O,this.identity=A,this.chat=F,N._a2uiHandler=e=>O._handleMessage(e),O._setSendFn(e=>N._rawSend(e))}configure(e){e.appId&&j(e.appId),e.baseUrl&&E(e.baseUrl)}}const z=new M;exports.NeboA2UI=W;exports.NeboSDK=M;exports.NeboSurfaces=C;exports.NeboWebSocket=_;exports.a2ui=O;exports.agents=D;exports.chat=F;exports.decide=U;exports.getAppId=i;exports.getBaseUrl=d;exports.identity=A;exports.janus=J;exports.nebo=z;exports.neboFetch=k;exports.setAppId=j;exports.setBaseUrl=E;exports.share=P;exports.storage=x;exports.surfaces=N; diff --git a/dist/index.d.ts b/dist/index.d.ts index 1eae173..7c5a954 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -10,6 +10,7 @@ * - nebo.decide → typed decisions (choice / score / noul) with confidence * - nebo.surfaces → real-time agent events (AG-UI) * - nebo.a2ui → agent-driven UI via @a2ui/web_core (A2UI v0.9) + * - nebo.share → a file into the owner's Work, and Nebo's Share dialog on it * - nebo.identity → agent context (name, persona, skills, inputs) * - nebo.chat → embedded full-featured chat UI via iframe */ @@ -26,10 +27,12 @@ export { janus } from './janus'; export { decide } from './decide'; export { neboFetch } from './fetch'; export { identity } from './identity'; +export { share } from './share'; export { chat } from './chat'; export { setAppId, setBaseUrl, getAppId, getBaseUrl } from './config'; export type { AgentResponse, InvokeOptions, JanusMessage, JanusOptions, StreamChunk, DecideQuestion, DecideRequest, DecideAnswer, Decision, } from './types'; export type { AgentIdentity } from './identity'; +export type { ShareFile, Shared } from './share'; export type { ChatOptions, ChatContext } from './chat'; export type { A2UIMessageProcessor } from './a2ui'; export type { SurfaceEvent, NeboSurfaceEvent, SurfaceEventMap, RunStartedEvent, RunFinishedEvent, RunErrorEvent, TextStartEvent, TextContentEvent, TextEndEvent, ToolCallStartEvent, ToolCallEndEvent, StateSnapshotEvent, StateDeltaEvent, SurfaceCreateEvent, SurfaceUpdateEvent, SurfaceDeleteEvent, DataUpdateEvent, CustomEvent, } from './surfaces'; diff --git a/dist/index.js b/dist/index.js index 0d9ecb2..ca939c3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,33 +1,33 @@ -let i = null, d = null; -function h() { - if (i) return i; +let d = null, h = null; +function i() { + if (d) return d; if (window.location.protocol === "neboapp:") - return i = window.location.hostname, i; + return d = window.location.hostname, d; const s = window.location.pathname.match(/^\/apps\/([^/]+)\/ui/); if (s) - return i = s[1], i; + return d = s[1], d; const e = document.querySelector('meta[name="nebo-app-id"]'); if (e) - return i = e.getAttribute("content") || "", i; + return d = e.getAttribute("content") || "", d; throw new Error( '[nebo-sdk] Cannot detect app ID. In production, serve from /apps/{id}/ui/. In dev, add to index.html.' ); } -function u() { - if (d) return d; +function l() { + if (h) return h; if (window.location.protocol === "neboapp:") - return d = "http://localhost:27895", d; + return h = "http://localhost:27895", h; const s = document.querySelector('meta[name="nebo-base-url"]'); - return s ? (d = s.getAttribute("content") || "", d) : (d = window.location.origin, d); + return s ? (h = s.getAttribute("content") || "", h) : (h = window.location.origin, h); } function k(s) { - i = s; + d = s; } function x(s) { - d = s; + h = s; } async function D(s, e) { - const t = h(), n = u(); + const t = i(), n = l(); if (s.startsWith("http://") || s.startsWith("https://")) { const f = `${n}/api/v1/apps/${t}/http/proxy`, r = {}; e != null && e.headers && new Headers(e.headers).forEach((E, j) => { @@ -38,14 +38,14 @@ async function D(s, e) { method: (e == null ? void 0 : e.method) || "GET", headers: r, body: e != null && e.body ? String(e.body) : void 0 - }, l = await (await fetch(f, { + }, u = await (await fetch(f, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(p) })).json(); - return new Response(l.body, { - status: l.status, - headers: l.headers + return new Response(u.body, { + status: u.status, + headers: u.headers }); } const a = s.startsWith("/") ? s : `/${s}`, o = `${n}/api/v1/apps/${t}/api${a}`; @@ -54,7 +54,7 @@ async function D(s, e) { class N { constructor(e) { this.ws = null, this.reconnectTimer = null, this.reconnectDelay = 1e3, this.maxReconnectDelay = 3e4, this._closed = !1, this.onopen = null, this.onmessage = null, this.onerror = null, this.onclose = null; - const t = h(), a = u().replace(/^http/, "ws"); + const t = i(), a = l().replace(/^http/, "ws"); this.url = `${a}/ws/app/${t}`, this.connect(); } connect() { @@ -100,7 +100,7 @@ function J() { } catch { return; } - e.type !== "app_data_changed" || !e.data || e.data.appId && e.data.appId !== h() || T.forEach((t) => t(e.data)); + e.type !== "app_data_changed" || !e.data || e.data.appId && e.data.appId !== i() || T.forEach((t) => t(e.data)); }); } function C(s) { @@ -119,10 +119,10 @@ function C(s) { } } function b(s) { - const e = h(), t = u(); + const e = i(), t = l(); return s ? `${t}/api/v1/apps/${e}/storage/${encodeURIComponent(s)}` : `${t}/api/v1/apps/${e}/storage`; } -const W = { +const P = { async getItem(s) { const e = await fetch(b(s)); if (e.status === 404) return null; @@ -158,10 +158,10 @@ const W = { } }; function v(s) { - const e = h(); - return `${u()}/api/v1/apps/${e}/agents/${s}`; + const e = i(); + return `${l()}/api/v1/apps/${e}/agents/${s}`; } -const P = { +const U = { async invoke(s, e) { return (await fetch(v("invoke"), { method: "POST", @@ -196,22 +196,22 @@ const P = { o = p.pop() || ""; for (const y of p) if (y.startsWith("data: ")) { - const l = y.slice(6); - if (l === "[DONE]") return; + const u = y.slice(6); + if (u === "[DONE]") return; try { - yield JSON.parse(l); + yield JSON.parse(u); } catch { - yield { text: l, done: !1 }; + yield { text: u, done: !1 }; } } } } }; function I(s) { - const e = h(); - return `${u()}/api/v1/apps/${e}/janus/${s}`; + const e = i(); + return `${l()}/api/v1/apps/${e}/janus/${s}`; } -const U = { +const W = { async complete(s) { const t = await (await fetch(I("complete"), { method: "POST", @@ -250,8 +250,8 @@ const U = { } } }; -async function F(s) { - const e = await fetch(`${u()}/api/v1/apps/${h()}/janus/decide`, { +async function R(s) { + const e = await fetch(`${l()}/api/v1/apps/${i()}/janus/decide`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(s) @@ -260,6 +260,16 @@ async function F(s) { throw new Error(t.error || `decide failed (${e.status})`); return t; } +async function F(s) { + const e = new URLSearchParams(window.location.search).get("thread") || "", t = await fetch(`${l()}/api/v1/apps/${i()}/share`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: s.name, content: s.content, thread: e }) + }), n = await t.json().catch(() => ({})); + if (!t.ok) + throw new Error(n.error || `share failed (${t.status})`); + return n; +} class M { constructor() { this.ws = null, this.listeners = /* @__PURE__ */ new Map(), this._connected = !1, this._a2uiHandler = null, this.state = {}; @@ -362,8 +372,8 @@ class M { return t; } } -const O = new M(); -class R { +const $ = new M(); +class A { constructor() { this.processor = null, this.sendFn = null; } @@ -430,12 +440,12 @@ class R { })); } } -const $ = new R(); +const O = new A(); let w = null; -const A = { +const L = { async get() { if (w) return w; - const s = h(), e = u(), t = await fetch(`${e}/api/v1/apps/${s}/identity`); + const s = i(), e = l(), t = await fetch(`${e}/api/v1/apps/${s}/identity`); if (!t.ok) throw new Error(`[nebo-sdk] identity fetch failed: ${t.status}`); return w = await t.json(), w; @@ -452,10 +462,10 @@ function H(s) { e(s.data); } } -const L = { +const q = { mount(s, e) { c && this.unmount(); - const t = h(), n = u(), a = new URLSearchParams(); + const t = i(), n = l(), a = new URLSearchParams(); e != null && e.placeholder && a.set("placeholder", e.placeholder), e != null && e.theme && a.set("theme", e.theme), e != null && e.borderless && a.set("borderless", "1"), e != null && e.contextId && a.set("ctx", e.contextId), e != null && e.scope && a.set("scope", e.scope); const o = a.toString(), f = `${n}/chat-embed/${t}${o ? "?" + o : ""}`, r = document.createElement("iframe"); r.src = f, r.style.width = "100%", r.style.height = (e == null ? void 0 : e.height) || "400px", r.style.border = e != null && e.borderless ? "none" : "", r.style.borderRadius = e != null && e.borderless ? "0" : "0.5rem", r.style.colorScheme = "normal", r.setAttribute("allow", "microphone"), s.appendChild(r), c = r, S = s, m = H, window.addEventListener("message", m); @@ -497,9 +507,9 @@ const L = { ); } }; -class q { +class B { constructor() { - this.fetch = D, this.WebSocket = N, this.storage = W, this.agents = P, this.janus = U, this.decide = F, this.surfaces = O, this.a2ui = $, this.identity = A, this.chat = L, O._a2uiHandler = (e) => $._handleMessage(e), $._setSendFn((e) => O._rawSend(e)); + this.fetch = D, this.WebSocket = N, this.storage = P, this.agents = U, this.janus = W, this.decide = R, this.share = F, this.surfaces = $, this.a2ui = O, this.identity = L, this.chat = q, $._a2uiHandler = (e) => O._handleMessage(e), O._setSendFn((e) => $._rawSend(e)); } /** * Manually configure the SDK (optional — auto-detection works in most cases). @@ -508,24 +518,25 @@ class q { e.appId && k(e.appId), e.baseUrl && x(e.baseUrl); } } -const z = new q(); +const G = new B(); export { - R as NeboA2UI, - q as NeboSDK, + A as NeboA2UI, + B as NeboSDK, M as NeboSurfaces, N as NeboWebSocket, - $ as a2ui, - P as agents, - L as chat, - F as decide, - h as getAppId, - u as getBaseUrl, - A as identity, - U as janus, - z as nebo, + O as a2ui, + U as agents, + q as chat, + R as decide, + i as getAppId, + l as getBaseUrl, + L as identity, + W as janus, + G as nebo, D as neboFetch, k as setAppId, x as setBaseUrl, - W as storage, - O as surfaces + F as share, + P as storage, + $ as surfaces }; diff --git a/dist/nebo.global.js b/dist/nebo.global.js index 2a813aa..ec130f7 100644 --- a/dist/nebo.global.js +++ b/dist/nebo.global.js @@ -1,3 +1,3 @@ -var NeboAppSDK=(function(r){"use strict";let h=null,l=null;function d(){if(h)return h;if(window.location.protocol==="neboapp:")return h=window.location.hostname,h;const s=window.location.pathname.match(/^\/apps\/([^/]+)\/ui/);if(s)return h=s[1],h;const e=document.querySelector('meta[name="nebo-app-id"]');if(e)return h=e.getAttribute("content")||"",h;throw new Error('[nebo-sdk] Cannot detect app ID. In production, serve from /apps/{id}/ui/. In dev, add to index.html.')}function u(){if(l)return l;if(window.location.protocol==="neboapp:")return l="http://localhost:27895",l;const s=document.querySelector('meta[name="nebo-base-url"]');return s?(l=s.getAttribute("content")||"",l):(l=window.location.origin,l)}function I(s){h=s}function j(s){l=s}async function E(s,e){const t=d(),n=u();if(s.startsWith("http://")||s.startsWith("https://")){const p=`${n}/api/v1/apps/${t}/http/proxy`,c={};e!=null&&e.headers&&new Headers(e.headers).forEach((z,K)=>{c[K]=z});const y={url:s,method:(e==null?void 0:e.method)||"GET",headers:c,body:e!=null&&e.body?String(e.body):void 0},f=await(await fetch(p,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(y)})).json();return new Response(f.body,{status:f.status,headers:f.headers})}const a=s.startsWith("/")?s:`/${s}`,i=`${n}/api/v1/apps/${t}/api${a}`;return fetch(i,e)}class S{constructor(e){this.ws=null,this.reconnectTimer=null,this.reconnectDelay=1e3,this.maxReconnectDelay=3e4,this._closed=!1,this.onopen=null,this.onmessage=null,this.onerror=null,this.onclose=null;const t=d(),a=u().replace(/^http/,"ws");this.url=`${a}/ws/app/${t}`,this.connect()}connect(){this._closed||(this.ws=new WebSocket(this.url),this.ws.onopen=e=>{var t;this.reconnectDelay=1e3,(t=this.onopen)==null||t.call(this,e)},this.ws.onmessage=e=>{var t;return(t=this.onmessage)==null?void 0:t.call(this,e)},this.ws.onerror=e=>{var t;return(t=this.onerror)==null?void 0:t.call(this,e)},this.ws.onclose=e=>{var t;(t=this.onclose)==null||t.call(this,e),this._closed||this.scheduleReconnect()})}scheduleReconnect(){this.reconnectTimer||this._closed||(this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.reconnectDelay=Math.min(this.reconnectDelay*2,this.maxReconnectDelay),this.connect()},this.reconnectDelay))}get readyState(){var e;return((e=this.ws)==null?void 0:e.readyState)??WebSocket.CLOSED}send(e){var t;(t=this.ws)==null||t.send(e)}close(e,t){var n;this._closed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),(n=this.ws)==null||n.close(e,t)}}const $=new Set;let v=null;function B(){v||(v=new S,v.onmessage=s=>{let e;try{e=JSON.parse(s.data)}catch{return}e.type!=="app_data_changed"||!e.data||e.data.appId&&e.data.appId!==d()||$.forEach(t=>t(e.data))})}function H(s){if(typeof s!="string")return s;let e;try{e=JSON.parse(s)}catch{return s}if(typeof e!="string")return e;try{return JSON.parse(e)}catch{return e}}function N(s){const e=d(),t=u();return s?`${t}/api/v1/apps/${e}/storage/${encodeURIComponent(s)}`:`${t}/api/v1/apps/${e}/storage`}const k={async getItem(s){const e=await fetch(N(s));if(e.status===404)return null;const t=await e.json();return H(t.value)},async setItem(s,e){const t=typeof e=="string"?e:JSON.stringify(e);await fetch(N(s),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:t})})},async removeItem(s){await fetch(N(s),{method:"DELETE"})},async clear(){const s=await this.keys();await Promise.all(s.map(e=>this.removeItem(e)))},async keys(){return((await(await fetch(N())).json()).items||[]).map(t=>t[0])},onChange(s){return $.add(s),B(),()=>{$.delete(s)}}};function D(s){const e=d();return`${u()}/api/v1/apps/${e}/agents/${s}`}const J={async invoke(s,e){return(await fetch(D("invoke"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:s,agent:e==null?void 0:e.agent,data:e==null?void 0:e.data})})).json()},async*stream(s,e){const t=await fetch(D("stream"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:s,agent:e==null?void 0:e.agent,data:e==null?void 0:e.data})});if(!t.body)throw new Error("No response body for streaming");const n=t.body.getReader(),a=new TextDecoder;let i="";for(;;){const{done:p,value:c}=await n.read();if(p)break;i+=a.decode(c,{stream:!0});const y=i.split(` -`);i=y.pop()||"";for(const w of y)if(w.startsWith("data: ")){const f=w.slice(6);if(f==="[DONE]")return;try{yield JSON.parse(f)}catch{yield{text:f,done:!1}}}}}};function U(s){const e=d();return`${u()}/api/v1/apps/${e}/janus/${s}`}const W={async complete(s){const t=await(await fetch(U("complete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})).json();return t.text||t.content||""},async*stream(s){const e=await fetch(U("stream"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!e.body)throw new Error("No response body for streaming");const t=e.body.getReader(),n=new TextDecoder;let a="";for(;;){const{done:i,value:p}=await t.read();if(i)break;a+=n.decode(p,{stream:!0});const c=a.split(` -`);a=c.pop()||"";for(const y of c)if(y.startsWith("data: ")){const w=y.slice(6);if(w==="[DONE]")return;try{yield JSON.parse(w).text}catch{yield w}}}}};async function C(s){const e=await fetch(`${u()}/api/v1/apps/${d()}/janus/decide`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)}),t=await e.json().catch(()=>({}));if(!e.ok)throw new Error(t.error||`decide failed (${e.status})`);return t}class P{constructor(){this.ws=null,this.listeners=new Map,this._connected=!1,this._a2uiHandler=null,this.state={}}get connected(){return this._connected}connect(){this.ws||(this.ws=new S,this.ws.onopen=()=>{this._connected=!0},this.ws.onmessage=e=>{var t,n;try{const a=JSON.parse(e.data);if(a.type==="a2ui_message"&&((t=a.data)!=null&&t.message)){(n=this._a2uiHandler)==null||n.call(this,a.data.message);return}this.handleEvent(a)}catch{}},this.ws.onclose=()=>{this._connected=!1},this.ws.onerror=()=>{this._connected=!1})}disconnect(){this.ws&&(this.ws.close(),this.ws=null),this._connected=!1}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{var n;(n=this.listeners.get(e))==null||n.delete(t)}}off(e,t){var n;(n=this.listeners.get(e))==null||n.delete(t)}send(e,t){!this.ws||this.ws.readyState!==WebSocket.OPEN||this.ws.send(JSON.stringify({type:"action",name:e,...t}))}requestState(){this.send("request_state")}_rawSend(e){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(e)}handleEvent(e){e.type==="state_snapshot"?this.state={...e.snapshot}:e.type==="state_delta"&&this.applyDelta(e.delta);const t=this.listeners.get(e.type);if(t)for(const a of t)try{a(e)}catch{}const n=this.listeners.get("*");if(n)for(const a of n)try{a(e)}catch{}}applyDelta(e){for(const t of e){const n=t.path.split("/").filter(Boolean);if(n.length===0)continue;const a=this.resolveParent(n);if(!a)continue;const i=n[n.length-1];switch(t.op){case"add":case"replace":a[i]=t.value;break;case"remove":delete a[i];break}}}resolveParent(e){let t=this.state;for(let n=0;n{m=m.filter(e=>e!==s)}},setContext(s){var e;(e=o==null?void 0:o.contentWindow)==null||e.postMessage({type:"nebo:set-context",context:s},"*")},newThread(){var s;(s=o==null?void 0:o.contentWindow)==null||s.postMessage({type:"nebo:new-thread"},"*")}};class R{constructor(){this.fetch=E,this.WebSocket=S,this.storage=k,this.agents=J,this.janus=W,this.decide=C,this.surfaces=_,this.a2ui=O,this.identity=F,this.chat=M,_._a2uiHandler=e=>O._handleMessage(e),O._setSendFn(e=>_._rawSend(e))}configure(e){e.appId&&I(e.appId),e.baseUrl&&j(e.baseUrl)}}const q=new R;return r.NeboA2UI=A,r.NeboSDK=R,r.NeboSurfaces=P,r.NeboWebSocket=S,r.a2ui=O,r.agents=J,r.chat=M,r.decide=C,r.getAppId=d,r.getBaseUrl=u,r.identity=F,r.janus=W,r.nebo=q,r.neboFetch=E,r.setAppId=I,r.setBaseUrl=j,r.storage=k,r.surfaces=_,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}),r})({}); +var NeboAppSDK=(function(r){"use strict";let l=null,u=null;function d(){if(l)return l;if(window.location.protocol==="neboapp:")return l=window.location.hostname,l;const s=window.location.pathname.match(/^\/apps\/([^/]+)\/ui/);if(s)return l=s[1],l;const e=document.querySelector('meta[name="nebo-app-id"]');if(e)return l=e.getAttribute("content")||"",l;throw new Error('[nebo-sdk] Cannot detect app ID. In production, serve from /apps/{id}/ui/. In dev, add to index.html.')}function h(){if(u)return u;if(window.location.protocol==="neboapp:")return u="http://localhost:27895",u;const s=document.querySelector('meta[name="nebo-base-url"]');return s?(u=s.getAttribute("content")||"",u):(u=window.location.origin,u)}function I(s){l=s}function j(s){u=s}async function E(s,e){const t=d(),n=h();if(s.startsWith("http://")||s.startsWith("https://")){const p=`${n}/api/v1/apps/${t}/http/proxy`,c={};e!=null&&e.headers&&new Headers(e.headers).forEach((K,G)=>{c[G]=K});const y={url:s,method:(e==null?void 0:e.method)||"GET",headers:c,body:e!=null&&e.body?String(e.body):void 0},f=await(await fetch(p,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(y)})).json();return new Response(f.body,{status:f.status,headers:f.headers})}const a=s.startsWith("/")?s:`/${s}`,i=`${n}/api/v1/apps/${t}/api${a}`;return fetch(i,e)}class S{constructor(e){this.ws=null,this.reconnectTimer=null,this.reconnectDelay=1e3,this.maxReconnectDelay=3e4,this._closed=!1,this.onopen=null,this.onmessage=null,this.onerror=null,this.onclose=null;const t=d(),a=h().replace(/^http/,"ws");this.url=`${a}/ws/app/${t}`,this.connect()}connect(){this._closed||(this.ws=new WebSocket(this.url),this.ws.onopen=e=>{var t;this.reconnectDelay=1e3,(t=this.onopen)==null||t.call(this,e)},this.ws.onmessage=e=>{var t;return(t=this.onmessage)==null?void 0:t.call(this,e)},this.ws.onerror=e=>{var t;return(t=this.onerror)==null?void 0:t.call(this,e)},this.ws.onclose=e=>{var t;(t=this.onclose)==null||t.call(this,e),this._closed||this.scheduleReconnect()})}scheduleReconnect(){this.reconnectTimer||this._closed||(this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.reconnectDelay=Math.min(this.reconnectDelay*2,this.maxReconnectDelay),this.connect()},this.reconnectDelay))}get readyState(){var e;return((e=this.ws)==null?void 0:e.readyState)??WebSocket.CLOSED}send(e){var t;(t=this.ws)==null||t.send(e)}close(e,t){var n;this._closed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),(n=this.ws)==null||n.close(e,t)}}const T=new Set;let v=null;function L(){v||(v=new S,v.onmessage=s=>{let e;try{e=JSON.parse(s.data)}catch{return}e.type!=="app_data_changed"||!e.data||e.data.appId&&e.data.appId!==d()||T.forEach(t=>t(e.data))})}function H(s){if(typeof s!="string")return s;let e;try{e=JSON.parse(s)}catch{return s}if(typeof e!="string")return e;try{return JSON.parse(e)}catch{return e}}function N(s){const e=d(),t=h();return s?`${t}/api/v1/apps/${e}/storage/${encodeURIComponent(s)}`:`${t}/api/v1/apps/${e}/storage`}const k={async getItem(s){const e=await fetch(N(s));if(e.status===404)return null;const t=await e.json();return H(t.value)},async setItem(s,e){const t=typeof e=="string"?e:JSON.stringify(e);await fetch(N(s),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:t})})},async removeItem(s){await fetch(N(s),{method:"DELETE"})},async clear(){const s=await this.keys();await Promise.all(s.map(e=>this.removeItem(e)))},async keys(){return((await(await fetch(N())).json()).items||[]).map(t=>t[0])},onChange(s){return T.add(s),L(),()=>{T.delete(s)}}};function D(s){const e=d();return`${h()}/api/v1/apps/${e}/agents/${s}`}const J={async invoke(s,e){return(await fetch(D("invoke"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:s,agent:e==null?void 0:e.agent,data:e==null?void 0:e.data})})).json()},async*stream(s,e){const t=await fetch(D("stream"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:s,agent:e==null?void 0:e.agent,data:e==null?void 0:e.data})});if(!t.body)throw new Error("No response body for streaming");const n=t.body.getReader(),a=new TextDecoder;let i="";for(;;){const{done:p,value:c}=await n.read();if(p)break;i+=a.decode(c,{stream:!0});const y=i.split(` +`);i=y.pop()||"";for(const w of y)if(w.startsWith("data: ")){const f=w.slice(6);if(f==="[DONE]")return;try{yield JSON.parse(f)}catch{yield{text:f,done:!1}}}}}};function U(s){const e=d();return`${h()}/api/v1/apps/${e}/janus/${s}`}const P={async complete(s){const t=await(await fetch(U("complete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})).json();return t.text||t.content||""},async*stream(s){const e=await fetch(U("stream"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!e.body)throw new Error("No response body for streaming");const t=e.body.getReader(),n=new TextDecoder;let a="";for(;;){const{done:i,value:p}=await t.read();if(i)break;a+=n.decode(p,{stream:!0});const c=a.split(` +`);a=c.pop()||"";for(const y of c)if(y.startsWith("data: ")){const w=y.slice(6);if(w==="[DONE]")return;try{yield JSON.parse(w).text}catch{yield w}}}}};async function C(s){const e=await fetch(`${h()}/api/v1/apps/${d()}/janus/decide`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)}),t=await e.json().catch(()=>({}));if(!e.ok)throw new Error(t.error||`decide failed (${e.status})`);return t}async function W(s){const e=new URLSearchParams(window.location.search).get("thread")||"",t=await fetch(`${h()}/api/v1/apps/${d()}/share`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:s.name,content:s.content,thread:e})}),n=await t.json().catch(()=>({}));if(!t.ok)throw new Error(n.error||`share failed (${t.status})`);return n}class A{constructor(){this.ws=null,this.listeners=new Map,this._connected=!1,this._a2uiHandler=null,this.state={}}get connected(){return this._connected}connect(){this.ws||(this.ws=new S,this.ws.onopen=()=>{this._connected=!0},this.ws.onmessage=e=>{var t,n;try{const a=JSON.parse(e.data);if(a.type==="a2ui_message"&&((t=a.data)!=null&&t.message)){(n=this._a2uiHandler)==null||n.call(this,a.data.message);return}this.handleEvent(a)}catch{}},this.ws.onclose=()=>{this._connected=!1},this.ws.onerror=()=>{this._connected=!1})}disconnect(){this.ws&&(this.ws.close(),this.ws=null),this._connected=!1}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{var n;(n=this.listeners.get(e))==null||n.delete(t)}}off(e,t){var n;(n=this.listeners.get(e))==null||n.delete(t)}send(e,t){!this.ws||this.ws.readyState!==WebSocket.OPEN||this.ws.send(JSON.stringify({type:"action",name:e,...t}))}requestState(){this.send("request_state")}_rawSend(e){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(e)}handleEvent(e){e.type==="state_snapshot"?this.state={...e.snapshot}:e.type==="state_delta"&&this.applyDelta(e.delta);const t=this.listeners.get(e.type);if(t)for(const a of t)try{a(e)}catch{}const n=this.listeners.get("*");if(n)for(const a of n)try{a(e)}catch{}}applyDelta(e){for(const t of e){const n=t.path.split("/").filter(Boolean);if(n.length===0)continue;const a=this.resolveParent(n);if(!a)continue;const i=n[n.length-1];switch(t.op){case"add":case"replace":a[i]=t.value;break;case"remove":delete a[i];break}}}resolveParent(e){let t=this.state;for(let n=0;n{m=m.filter(e=>e!==s)}},setContext(s){var e;(e=o==null?void 0:o.contentWindow)==null||e.postMessage({type:"nebo:set-context",context:s},"*")},newThread(){var s;(s=o==null?void 0:o.contentWindow)==null||s.postMessage({type:"nebo:new-thread"},"*")}};class B{constructor(){this.fetch=E,this.WebSocket=S,this.storage=k,this.agents=J,this.janus=P,this.decide=C,this.share=W,this.surfaces=O,this.a2ui=_,this.identity=M,this.chat=R,O._a2uiHandler=e=>_._handleMessage(e),_._setSendFn(e=>O._rawSend(e))}configure(e){e.appId&&I(e.appId),e.baseUrl&&j(e.baseUrl)}}const z=new B;return r.NeboA2UI=F,r.NeboSDK=B,r.NeboSurfaces=A,r.NeboWebSocket=S,r.a2ui=_,r.agents=J,r.chat=R,r.decide=C,r.getAppId=d,r.getBaseUrl=h,r.identity=M,r.janus=P,r.nebo=z,r.neboFetch=E,r.setAppId=I,r.setBaseUrl=j,r.share=W,r.storage=k,r.surfaces=O,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}),r})({}); diff --git a/dist/sdk.d.ts b/dist/sdk.d.ts index a587e7c..437a8e0 100644 --- a/dist/sdk.d.ts +++ b/dist/sdk.d.ts @@ -4,6 +4,7 @@ import { neboFetch } from './fetch'; import { NeboWebSocket } from './websocket'; import { decide } from './decide'; +import { share } from './share'; export declare class NeboSDK { fetch: typeof neboFetch; WebSocket: typeof NeboWebSocket; @@ -24,6 +25,7 @@ export declare class NeboSDK { stream(options: import("./types").JanusOptions): AsyncGenerator; }; decide: typeof decide; + share: typeof share; surfaces: import("./surfaces").NeboSurfaces; a2ui: import("./a2ui").NeboA2UI; identity: { diff --git a/dist/share.d.ts b/dist/share.d.ts new file mode 100644 index 0000000..cf66815 --- /dev/null +++ b/dist/share.d.ts @@ -0,0 +1,17 @@ +/** + * nebo.share — hand the owner a file to share. The file goes into his Work + * folder (beside the chat the app is open on), and Nebo opens its own Share + * dialog on it: he chooses who can open the link (anyone, a password, only + * him) and when it ends. The page never makes or changes a link itself. + */ +export interface ShareFile { + /** The file's name with its extension: `Launch deck.html`. */ + name: string; + /** The file's text (HTML, Markdown, CSV, SVG …). */ + content: string; +} +export interface Shared { + /** The file's place in Work (`/api/v1/files/…`). */ + artifact: string; +} +export declare function share(file: ShareFile): Promise; diff --git a/src/index.ts b/src/index.ts index f22afa0..9bd413f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ * - nebo.decide → typed decisions (choice / score / noul) with confidence * - nebo.surfaces → real-time agent events (AG-UI) * - nebo.a2ui → agent-driven UI via @a2ui/web_core (A2UI v0.9) + * - nebo.share → a file into the owner's Work, and Nebo's Share dialog on it * - nebo.identity → agent context (name, persona, skills, inputs) * - nebo.chat → embedded full-featured chat UI via iframe */ @@ -29,6 +30,7 @@ export { janus } from './janus'; export { decide } from './decide'; export { neboFetch } from './fetch'; export { identity } from './identity'; +export { share } from './share'; export { chat } from './chat'; export { setAppId, setBaseUrl, getAppId, getBaseUrl } from './config'; export type { @@ -36,6 +38,7 @@ export type { DecideQuestion, DecideRequest, DecideAnswer, Decision, } from './types'; export type { AgentIdentity } from './identity'; +export type { ShareFile, Shared } from './share'; export type { ChatOptions, ChatContext } from './chat'; export type { A2UIMessageProcessor } from './a2ui'; export type { diff --git a/src/sdk.ts b/src/sdk.ts index e336026..ef70140 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -9,6 +9,7 @@ import { storage } from './storage'; import { agents } from './agents'; import { janus } from './janus'; import { decide } from './decide'; +import { share } from './share'; import { surfaces } from './surfaces'; import { a2ui } from './a2ui'; import { identity } from './identity'; @@ -21,6 +22,7 @@ export class NeboSDK { agents = agents; janus = janus; decide = decide; + share = share; surfaces = surfaces; a2ui = a2ui; identity = identity; diff --git a/src/share.ts b/src/share.ts new file mode 100644 index 0000000..2dbe8a8 --- /dev/null +++ b/src/share.ts @@ -0,0 +1,34 @@ +/** + * nebo.share — hand the owner a file to share. The file goes into his Work + * folder (beside the chat the app is open on), and Nebo opens its own Share + * dialog on it: he chooses who can open the link (anyone, a password, only + * him) and when it ends. The page never makes or changes a link itself. + */ + +import { getAppId, getBaseUrl } from './config'; + +export interface ShareFile { + /** The file's name with its extension: `Launch deck.html`. */ + name: string; + /** The file's text (HTML, Markdown, CSV, SVG …). */ + content: string; +} + +export interface Shared { + /** The file's place in Work (`/api/v1/files/…`). */ + artifact: string; +} + +export async function share(file: ShareFile): Promise { + const thread = new URLSearchParams(window.location.search).get('thread') || ''; + const resp = await fetch(`${getBaseUrl()}/api/v1/apps/${getAppId()}/share`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name: file.name, content: file.content, thread }) + }); + const data = await resp.json().catch(() => ({})); + if (!resp.ok) { + throw new Error(data.error || `share failed (${resp.status})`); + } + return data as Shared; +}