kloudy 0.4.3 → 0.4.4

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/src/remote.mjs CHANGED
@@ -1,25 +1,27 @@
1
- import {Client} from '@modelcontextprotocol/sdk/client/index.js';
2
- import {StreamableHTTPClientTransport} from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
- import {Server} from '@modelcontextprotocol/sdk/server/index.js';
4
- import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
5
- import {ListToolsRequestSchema,CallToolRequestSchema} from '@modelcontextprotocol/sdk/types.js';
6
- import {REMOTE} from './editor-install.mjs';
7
- export async function connectRemote({key=process.env.KLOUDY_MCP_TOKEN,fetcher=fetch}={}){
8
- if(key!==undefined&&!/^\S{20,512}$/.test(key))throw Error('Invalid key');
9
- const client=new Client({name:'kloudy',version:'0.4.1'},{capabilities:{}});
10
- const transport=new StreamableHTTPClientTransport(new URL(REMOTE),{requestInit:{headers:key?{Authorization:'Bearer '+key}:{},redirect:'error'},fetch:(url,options)=>fetcher(url,{...options,redirect:'error'}),reconnectionOptions:{maxRetries:0}});
11
- try{await client.connect(transport,{timeout:25000});return client;}catch(e){await client.close().catch(()=>{});throw e;}
12
- }
13
- export async function askRemote(question,options){
14
- if(typeof question!=='string'||!question.trim()||question.length>1000)throw Error('Use a question of 1–1000 characters');
15
- const client=await connectRemote(options);
16
- try{return await client.callTool({name:'kloudy',arguments:{operation:'ask',goal:question}},undefined,{timeout:25000});}finally{await client.close();}
17
- }
18
- export async function serveRemote(options){
19
- const remote=await connectRemote(options);
20
- const server=new Server({name:'kloudy',version:'0.4.1'},{capabilities:{tools:{}},instructions:'Use Kloudy when mentioned, or to discover MCP tools and SDKs. Public discovery is free; account actions need an existing authorized key.'});
21
- server.setRequestHandler(ListToolsRequestSchema,req=>remote.listTools(req.params));
22
- server.setRequestHandler(CallToolRequestSchema,req=>remote.callTool(req.params,undefined,{timeout:25000}));
23
- server.onclose=()=>{void remote.close();};
24
- await server.connect(new StdioServerTransport());return server;
25
- }
1
+ import {readFileSync} from 'node:fs';
2
+ const packageVersion=JSON.parse(readFileSync(new URL('../package.json',import.meta.url),'utf8')).version;
3
+ import {Client} from '@modelcontextprotocol/sdk/client/index.js';
4
+ import {StreamableHTTPClientTransport} from '@modelcontextprotocol/sdk/client/streamableHttp.js';
5
+ import {Server} from '@modelcontextprotocol/sdk/server/index.js';
6
+ import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import {ListToolsRequestSchema,CallToolRequestSchema} from '@modelcontextprotocol/sdk/types.js';
8
+ import {REMOTE} from './editor-install.mjs';
9
+ export async function connectRemote({key=process.env.KLOUDY_MCP_TOKEN,fetcher=fetch}={}){
10
+ if(key!==undefined&&!/^\S{20,512}$/.test(key))throw Error('Invalid key');
11
+ const client=new Client({name:'kloudy',version:packageVersion},{capabilities:{}});
12
+ const transport=new StreamableHTTPClientTransport(new URL(REMOTE),{requestInit:{headers:key?{Authorization:'Bearer '+key}:{},redirect:'error'},fetch:(url,options)=>fetcher(url,{...options,redirect:'error'}),reconnectionOptions:{maxRetries:0}});
13
+ try{await client.connect(transport,{timeout:25000});return client;}catch(e){await client.close().catch(()=>{});throw e;}
14
+ }
15
+ export async function askRemote(question,options){
16
+ if(typeof question!=='string'||!question.trim()||question.length>1000)throw Error('Use a question of 1–1000 characters');
17
+ const client=await connectRemote(options);
18
+ try{return await client.callTool({name:'kloudy',arguments:{operation:'ask',goal:question}},undefined,{timeout:25000});}finally{await client.close();}
19
+ }
20
+ export async function serveRemote(options){
21
+ const remote=await connectRemote(options);
22
+ const server=new Server({name:'kloudy',version:packageVersion},{capabilities:{tools:{}},instructions:'Use Kloudy when mentioned, or to discover MCP tools and SDKs. Public discovery is free; account actions need an existing authorized key.'});
23
+ server.setRequestHandler(ListToolsRequestSchema,req=>remote.listTools(req.params));
24
+ server.setRequestHandler(CallToolRequestSchema,req=>remote.callTool(req.params,undefined,{timeout:25000}));
25
+ server.onclose=()=>{void remote.close();};
26
+ await server.connect(new StdioServerTransport());return server;
27
+ }
@@ -2,9 +2,9 @@
2
2
  export const SAMPLE_VERSION='kloudy.sample/1';
3
3
  export const SAMPLE_TITLE='Kloudy Sample card';
4
4
  export const preferenceChoices={
5
- tools:[['open_source','Open source'],['paid','Paid · convenience'],['hybrid','Mix of both'],['free_then_paid','Free tier, then ask']],
6
- target:[['local','Local only'],['www','World Wide Web'],['bww','Bot Wide Web'],['mixed','A mix']],
7
- delivery:[['silent','Silent · recommended'],['paid','Cards for paid tools'],['all','Cards for every tool']]
5
+ tools:[['open_source','Open source'],['paid','Paid tools'],['hybrid','Mix of both'],['free_then_paid','Free tier, then ask']],
6
+ target:[['local','This device'],['www','World Wide Web'],['bww','Bot Wide Web'],['mixed','A mix']],
7
+ delivery:[['silent','Quietly'],['paid','Paid tools only'],['all','Every tool']]
8
8
  };
9
9
  export const preferenceDefaults=()=>({tools:null,target:null,delivery:'silent'});
10
10
  export function validPreferences(value){return !!value&&typeof value==='object'&&!Array.isArray(value)&&Object.keys(value).length===3&&Object.entries(preferenceChoices).every(([key,options])=>(key!=='delivery'&&value[key]===null)||options.some(([id])=>id===value[key]));}
@@ -12,8 +12,8 @@ export function preferenceQuestion(step,surface='site'){
12
12
  const external=['ide','chatbot','terminal'].includes(surface);
13
13
  return [
14
14
  {key:'tools',question:'Which tools do you prefer?',intro:external?'Kloudy gives your assistant tools to build your projects.':'Kloudy uses tools to build your projects.'},
15
- {key:'target',question:'Where will your projects run?',intro:'Choose the places you want to serve.'},
16
- {key:'delivery',question:'How much should Kloudy show?',intro:'Work quietly, or show the tools as they are offered.'}
15
+ {key:'target',question:'Where will your projects run?',intro:'Choose where people will use your projects.'},
16
+ {key:'delivery',question:'How much should Kloudy show?',intro:'Quietly is the default. You can ask for updates instead.'}
17
17
  ][step]||null;
18
18
  }
19
19
  export class SamplePreferences{
@@ -42,6 +42,6 @@ export class SampleQueue{
42
42
  const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
43
43
  export function samplePreferencesMarkup(flow,{surface='site',notice=''}={}){
44
44
  const q=preferenceQuestion(flow.step,surface),complete=!q;
45
- return `<section class="sample-card" aria-label="${SAMPLE_TITLE}"><header><span class="sample-orb" aria-hidden="true"></span><span>${SAMPLE_TITLE}</span><small>${complete?'Ready':(flow.step+1)+' / 3'}</small></header><h1 tabindex="-1">${complete?'Your way to build.':esc(q.question)}</h1>${complete?'':`<p>${esc(q.intro)}</p>`}${complete?`<dl>${Object.entries(preferenceChoices).map(([key,options])=>`<dt>${esc({tools:'Tools',target:'Where',delivery:'Updates'}[key])}</dt><dd>${esc(options.find(([id])=>id===flow.value[key])?.[1]||'Ask when needed')}</dd>`).join('')}</dl><button class="sample-primary" data-sample-save>Save preferences</button>`:`<div class="sample-options">${preferenceChoices[q.key].map(([id,label])=>`<button data-sample-choice="${id}" aria-pressed="${flow.value[q.key]===id}">${esc(label)}</button>`).join('')}</div>`}<p class="sample-note">${q?.key==='tools'?'Open source describes the tools; hosting or compute may still cost money.':q?.key==='target'?'BWW sites need hosting: your compute or a paid host.':'New costs always need your approval. Ask which tools are connected anytime.'}</p><details><summary>How this works</summary><p>Silent means fewer updates, not permission to spend. Free tiers ending always prompt a choice. These preferences do not install tools or authorize a connection.</p></details><footer>${flow.step?'<button data-sample-back>Back</button>':''}<button data-sample-later>Not now</button></footer><p role="status" class="sample-status">${esc(notice)}</p></section>`;
45
+ return `<section class="sample-card" aria-label="${SAMPLE_TITLE}"><header><span class="sample-orb" aria-hidden="true"></span><span>${SAMPLE_TITLE}</span><small>${complete?'Ready':(flow.step+1)+' / 3'}</small></header><h1 tabindex="-1">${complete?'Your way to build.':esc(q.question)}</h1>${complete?'':`<p>${esc(q.intro)}</p>`}${complete?`<dl>${Object.entries(preferenceChoices).map(([key,options])=>`<dt>${esc({tools:'Tools',target:'Where',delivery:'Updates'}[key])}</dt><dd>${esc(options.find(([id])=>id===flow.value[key])?.[1]||'Ask when needed')}</dd>`).join('')}</dl><button class="sample-primary" data-sample-save>Save preferences</button>`:`<div class="sample-options">${preferenceChoices[q.key].map(([id,label])=>`<button data-sample-choice="${id}" aria-pressed="${flow.value[q.key]===id}">${esc(label)}</button>`).join('')}</div>`}<p class="sample-note">${q?.key==='tools'?'Open source describes the tools; hosting or compute may still cost money.':q?.key==='target'?'Bot Wide Web sites need hosting: your computer, your cloud, or a paid host.':'New costs always need your approval. Ask which tools are connected anytime.'}</p><details><summary>How this works</summary><p>Quietly means fewer updates, not permission to spend. Free tiers ending always prompt a choice. These preferences do not install tools or authorize a connection.</p></details><footer>${q?.key==='delivery'?'<button data-sample-choice="'+esc(flow.value.delivery)+'">Continue</button>':''}${flow.step?'<button data-sample-back>Back</button>':''}<button data-sample-later>Not now</button></footer><p role="status" class="sample-status">${esc(notice)}</p></section>`;
46
46
  }
47
47
  export function conversionCardMarkup(){return `<section class="sample-card"><header>${SAMPLE_TITLE}</header><h1>Let bots reach your site.</h1><p>Publish a BWW card so compatible assistants can fetch the part a user needs.</p><a class="sample-primary" href="https://kloudy.ai/hosting">Choose hosting</a><details><summary>Benefits and costs</summary><p>A converted site can offer structured fragments instead of a full browser visit. Actual compute, water and cost savings depend on measured usage; no fixed savings are promised.</p><p>Bring your PC or cloud, or use a paid host. If your compute goes offline, your site becomes unavailable. Concierge and ad bots are optional paid services.</p></details></section>`;}