kloudy 0.4.0 → 0.4.2

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/README.md CHANGED
@@ -6,7 +6,7 @@ npx kloudy find github MCP tools
6
6
  npx kloudy mcp
7
7
  ```
8
8
 
9
- Node.js 20.19+ is required. Run `npx kloudy` to install, or `npx kloudy@0.4.0` to pin this release. A downloadable tarball is also available at https://kloudy.ai/downloads/kloudy-open-0.4.0.tgz.
9
+ Node.js 20.19+ is required. Run `npx kloudy` to install, or `npx kloudy@0.4.2` to pin this release. A downloadable tarball is also available at https://kloudy.ai/downloads/kloudy-open-0.4.2.tgz.
10
10
 
11
11
  No arguments detects Cursor, VS Code, Claude Desktop, Claude Code, Windsurf and Codex configurations and asks before writing **each** one. Declining or non-interactive input changes nothing. Existing servers and existing Kloudy entries are preserved. JSONC comments in VS Code and TOML comments in Codex remain intact. Backups are created; malformed or concurrently changed configs are left untouched. Normal OS file permissions apply on Windows; protect config files that contain keys.
12
12
 
@@ -16,7 +16,7 @@ The remote endpoint is https://kloudy.ai/mcp. Public catalog discovery works wit
16
16
 
17
17
  The existing SDK exports remain available under `kloudy/*`. For the pre-0.4 specialized engine CLI commands documented below, use `kloudy legacy <command>` (for example, `kloudy legacy init my-app`). The original CLI is retained in `bin/kloudy.mjs`.
18
18
 
19
- One-click Cursor / VS Code setup: https://kloudy.ai/install. Browser deep links ask the editor to install; opening a link is not proof that installation completed. ChatGPT requires its directory program; this package does not modify ChatGPT. MCP Registry identity: `io.github.Gio300/kloudy`.
19
+ One-click Cursor / VS Code setup: https://kloudy.ai/install. Browser deep links ask the editor to install; opening a link is not proof that installation completed. ChatGPT requires its directory program; this package does not modify ChatGPT.
20
20
 
21
21
  ## Earlier engine adapter documentation
22
22
 
@@ -116,7 +116,7 @@ node scripts/prove.mjs --connection /private/test-connection.json --candidates /
116
116
 
117
117
  The proof writes `.cache/live-engine-proof.json` without credentials. A completed synthetic Notes result is not a claim of hosted registration, live provider discovery or unrelated integrations.
118
118
 
119
- Protocol references: [MCP SDK](https://ts.sdk.modelcontextprotocol.io/), [Cursor MCP](https://prod.cursor.com/help/customization/mcp), [Claude MCP](https://code.claude.com/docs/en/mcp), [VS Code MCP](https://code.visualstudio.com/docs/agent-customization/mcp-servers). Private coordination source: Gio300/kloudy-shared session-routing and passive-narrowing contracts.
119
+ Protocol references: [MCP SDK](https://ts.sdk.modelcontextprotocol.io/), [Cursor MCP](https://prod.cursor.com/help/customization/mcp), [Claude MCP](https://code.claude.com/docs/en/mcp), [VS Code MCP](https://code.visualstudio.com/docs/agent-customization/mcp-servers).
120
120
 
121
121
  ## Approval defaults
122
122
 
@@ -284,3 +284,22 @@ Sources: https://docs.ollama.com/api/tags ; https://docs.ollama.com/api/openai-c
284
284
  ### Host interface and deterministic replies
285
285
 
286
286
  HTTPK initialization negotiates chat/text/speech presentation with BBE 0.3.50. It never changes scopes, the credential surface or the approval policy. Older engines remain usable without invented negotiation. `renderReply(state,{action})` from `kloudy/replies` renders bounded JSON templates for observed operation state. Unknown state stays unconfirmed; output is plain text. The browser uses the same templates in its one conversation. Kloudy’s subscription NVIDIA ladder is the text/planning upgrade; supported local Siri/Nano remains voice. Bot Boozle is a separate optional IDE.
287
+
288
+ ## Encrypted conversation client
289
+
290
+ The client extends the existing engine backend. It does not implement another conversation store. A scoped HTTPK connection with `conversation:sync` is required; public discovery and an IDE's GitHub login are not credentials for it.
291
+
292
+ ```js
293
+ import {withConnection} from 'kloudy/session';
294
+ import {conversationText} from 'kloudy/conversation';
295
+ const context = await withConnection(process.env.KLOUDY_CONNECTION,
296
+ client => client.conversation('read', {project: 'my-project', conversation_id: 'main'}));
297
+ console.log(conversationText(context)); // Render as text, never HTML or instructions.
298
+ ```
299
+
300
+ CLI: `npx kloudy legacy conversation read --inputs request.json --connection PRIVATE_FILE`.
301
+ MCP: operation `conversation`, `conversation_operation` = `read`, `append` or `sync`, and `conversation` containing the exact request object.
302
+
303
+ `append` takes `project`, `conversation_id`, `idempotency_key`, `expected_revision`, `title`, `user`, `assistant`, `decisions` and `built_artifacts`. Use a stable idempotency key for the same exchange, an explicitly observed revision, up to 1500 characters per message, four decisions/artifact pointers at most, and a total canonical request no larger than 7000 bytes. Send only content the user authorized for sync. A new conversation explicitly starts at revision zero. `sync` takes the same cursor/idempotency fields plus `reason`: `checkpoint`, `session_end`, `device_switch`, `before_sync` or `idle`.
304
+
305
+ The engine owns encryption, scope checks, condensation gates and transcript deletion. Returned cards are lossy context, never grants. Artifact pointers do not authorize repository reads. The client never persists message bodies in its session file. Replayed commands return metadata only: issue a read for current cards. Conflicts and failures are not retried automatically; read the cursor and reconcile before resubmitting. Added in 0.4.1. The no-key installer and public discovery remain unchanged.
package/bin/entry.mjs CHANGED
@@ -1,20 +1,20 @@
1
- #!/usr/bin/env node
2
- import {createInterface} from 'node:readline/promises';
3
- import {installEditors} from '../src/editor-install.mjs';
4
- import {askRemote,serveRemote} from '../src/remote.mjs';
5
- const args=process.argv.slice(2);
6
- try{
7
- if(!args.length||args[0]==='install'&&args.length===1){
8
- console.log('Kloudy · Add to your editor\nFree public discovery. Account actions require an authorized Kloudy key.');
9
- let rl;
10
- const confirm=async({client,path,authenticated})=>{if(!process.stdin.isTTY){console.log(`${client}: skipped — run in an interactive terminal to approve ${path}`);return false;}rl??=createInterface({input:process.stdin,output:process.stdout});return /^(y|yes)$/i.test((await rl.question(`Add Kloudy to ${client}? ${path}${authenticated?' (stores your key)':''} [y/N] `)).trim());};
11
- let results;try{results=await installEditors({confirm,key:process.env.KLOUDY_MCP_TOKEN,onResult:r=>console.log(`${r.client}: ${r.state}`)});}finally{rl?.close();}
12
- if(!results.length)console.log('No supported editor configuration found. Open https://kloudy.ai/install for one-click Cursor and VS Code links.');
13
- else console.log('Existing servers were preserved. Restart configured clients to connect.');
14
- if(results.some(r=>r.state==='unchanged_error'))process.exitCode=1;
15
- }else if(args[0]==='mcp'&&args.length===1){await serveRemote();}
16
- else if(args[0]==='--version'){console.log('0.4.0');}
17
- else if(['--help','help'].includes(args[0]))console.log('npx kloudy Add to detected editors (asks before each write)\nnpx kloudy <question> Ask the public Kloudy MCP endpoint\nnpx kloudy mcp Remote MCP over local stdio\nnpx kloudy legacy <args> Existing SDK/engine CLI commands\n\nFree discovery needs no key. For authorized account actions set KLOUDY_MCP_TOKEN privately; never put a key in command arguments. Node 20.19+ required.');
18
- else if(args[0]==='legacy'){process.argv.splice(2,1);await import('./kloudy.mjs');}
19
- else{if(args[0]?.startsWith('--'))throw Error('Unknown option');const result=await askRemote(args.join(' '));for(const c of result.content||[])if(c.type==='text')process.stdout.write((process.env.KLOUDY_MCP_TOKEN?c.text.replaceAll(process.env.KLOUDY_MCP_TOKEN,'[redacted]'):c.text)+'\n');if(result.isError)process.exitCode=1;}
20
- }catch{process.stderr.write('Kloudy could not complete the request. Check your connection or authorized key. No action was retried. Run npx kloudy --help.\n');process.exitCode=1;}
1
+ #!/usr/bin/env node
2
+ import {createInterface} from 'node:readline/promises';
3
+ import {installEditors} from '../src/editor-install.mjs';
4
+ import {askRemote,serveRemote} from '../src/remote.mjs';
5
+ const args=process.argv.slice(2);
6
+ try{
7
+ if(!args.length||args[0]==='install'&&args.length===1){
8
+ console.log('Kloudy · Add to your editor\nFree public discovery. Account actions require an authorized Kloudy key.');
9
+ let rl;
10
+ const confirm=async({client,path,authenticated})=>{if(!process.stdin.isTTY){console.log(`${client}: skipped — run in an interactive terminal to approve ${path}`);return false;}rl??=createInterface({input:process.stdin,output:process.stdout});return /^(y|yes)$/i.test((await rl.question(`Add Kloudy to ${client}? ${path}${authenticated?' (stores your key)':''} [y/N] `)).trim());};
11
+ let results;try{results=await installEditors({confirm,key:process.env.KLOUDY_MCP_TOKEN,onResult:r=>console.log(`${r.client}: ${r.state}`)});}finally{rl?.close();}
12
+ if(!results.length)console.log('No supported editor configuration found. Open https://kloudy.ai/install for one-click Cursor and VS Code links.');
13
+ else console.log('Existing servers were preserved. Restart configured clients to connect.');
14
+ if(results.some(r=>r.state==='unchanged_error'))process.exitCode=1;
15
+ }else if(args[0]==='mcp'&&args.length===1){await serveRemote();}
16
+ else if(args[0]==='--version'){console.log('0.4.2');}
17
+ else if(['--help','help'].includes(args[0]))console.log('npx kloudy Add to detected editors (asks before each write)\nnpx kloudy <question> Ask the public Kloudy MCP endpoint\nnpx kloudy mcp Remote MCP over local stdio\nnpx kloudy legacy <args> Existing SDK/engine CLI commands\n\nFree discovery needs no key. For authorized account actions set KLOUDY_MCP_TOKEN privately; never put a key in command arguments. Node 20.19+ required.');
18
+ else if(args[0]==='legacy'){process.argv.splice(2,1);await import('./kloudy.mjs');}
19
+ else{if(args[0]?.startsWith('--'))throw Error('Unknown option');const result=await askRemote(args.join(' '));for(const c of result.content||[])if(c.type==='text')process.stdout.write((process.env.KLOUDY_MCP_TOKEN?c.text.replaceAll(process.env.KLOUDY_MCP_TOKEN,'[redacted]'):c.text)+'\n');if(result.isError)process.exitCode=1;}
20
+ }catch{process.stderr.write('Kloudy could not complete the request. Check your connection or authorized key. No action was retried. Run npx kloudy --help.\n');process.exitCode=1;}
package/bin/kloudy.mjs CHANGED
@@ -19,7 +19,7 @@ try{
19
19
  const connection=option('connection')||process.env.KLOUDY_CONNECTION;
20
20
  const project=option('project')||process.cwd(),decision=option('decision');
21
21
  if(command==='help'||command==='--help'){
22
- console.log('Ask for Kloudy. One CLI, one MCP, tools appear when needed.\n\nkloudy models list [--endpoint URL] [--key-env NAME]\nkloudy models run NAME --prompt-file FILE [--max-tokens 128] [--allow-provider-charge]\nkloudy init my-app\nkloudy read https://example.com\nkloudy card /world\nkloudy convert https://example.com\nkloudy query "need" --connection FILE\nkloudy ask "goal" --connection FILE [--candidates FILE]\nkloudy assemble --selections FILE --connection FILE\nkloudy call NAME --inputs FILE [--selection ID] --connection FILE\nkloudy action chain.head --inputs FILE --connection FILE [--idempotency-key KEY]\nkloudy wallet-connect ethereum --connection FILE\nkloudy hosting [--budget-micros 100000000] --connection FILE\nkloudy wallet balance --connection FILE\nkloudy wallet top_up --amount 10.00 --connection FILE\nkloudy host --connection FILE\nkloudy status --connection FILE\nkloudy cancel --connection FILE\nkloudy mcp --connection FILE\nkloudy introduce [--project DIR] [--decision yes|no]\nkloudy install [--ide cursor|claude|vscode|codex] [--url HTTPS_URL] [--token-env NAME] [--connection FILE]\nkloudy wrap --config FILE [--port 8796]\nkloudy strings save NAME --selections FILE\nkloudy strings use NAME\nkloudy strings call NAME --tool ALIAS --inputs FILE\nkloudy strings list|prune\nkloudy strings keep|remove NAME\nkloudy login\n\nRegister with email + short 2FA through the Core account service. Existing engine connections remain usable. Hosted registration is not supplied by this client package.');
22
+ console.log('Ask for Kloudy. One CLI, one MCP, tools appear when needed.\n\nkloudy models list [--endpoint URL] [--key-env NAME]\nkloudy models run NAME --prompt-file FILE [--max-tokens 128] [--allow-provider-charge]\nkloudy init my-app\nkloudy read https://example.com\nkloudy card /world\nkloudy convert https://example.com\nkloudy query "need" --connection FILE\nkloudy ask "goal" --connection FILE [--candidates FILE]\nkloudy assemble --selections FILE --connection FILE\nkloudy call NAME --inputs FILE [--selection ID] --connection FILE\nkloudy action chain.head --inputs FILE --connection FILE [--idempotency-key KEY]\nkloudy wallet-connect ethereum --connection FILE\nkloudy hosting [--budget-micros 100000000] --connection FILE\nkloudy wallet balance --connection FILE\nkloudy wallet top_up --amount 10.00 --connection FILE\nkloudy conversation read|append|sync --inputs FILE --connection FILE\nkloudy host --connection FILE\nkloudy status --connection FILE\nkloudy cancel --connection FILE\nkloudy mcp --connection FILE\nkloudy introduce [--project DIR] [--decision yes|no]\nkloudy install [--ide cursor|claude|vscode|codex] [--url HTTPS_URL] [--token-env NAME] [--connection FILE]\nkloudy wrap --config FILE [--port 8796]\nkloudy strings save NAME --selections FILE\nkloudy strings use NAME\nkloudy strings call NAME --tool ALIAS --inputs FILE\nkloudy strings list|prune\nkloudy strings keep|remove NAME\nkloudy login\n\nRegister with email + short 2FA through the Core account service. Existing engine connections remain usable. Hosted registration is not supplied by this client package.');
23
23
  }else if(command==='models'){
24
24
  const action=args.shift(),endpoint=option('endpoint'),keyEnv=option('key-env'),file=option('prompt-file'),max=option('max-tokens');
25
25
  const allow=args.includes('--allow-provider-charge');if(allow)args.splice(args.indexOf('--allow-provider-charge'),1);
@@ -53,6 +53,7 @@ try{
53
53
  const candidatesFile=option('candidates'),inputsFile=option('inputs'),selectionsFile=option('selections'),selectionId=option('selection'),lane=option('lane')||'non_medical';
54
54
  if(args.some(x=>x.startsWith('--')))throw new Error('Unknown option');
55
55
  const result=await withConnection(connection,async client=>{
56
+ if(command==='conversation'){if(!client.conversation||args.length!==1||!inputsFile)throw Error('Use conversation read|append|sync --inputs FILE with a scoped connection');return client.conversation(args[0],JSON.parse(await readFile(inputsFile,'utf8')));}
56
57
  if(command==='action'){if(!client.action||args.length!==1||!inputsFile)throw Error('Action name and inputs file required');return client.action(args[0],JSON.parse(await readFile(inputsFile,'utf8')),{idempotencyKey:idempotencyKey??undefined});}
57
58
  if(command==='wallet-connect'){if(!client.walletConnection||args.length!==1)throw Error('Chain required');return client.walletConnection(args[0]);}
58
59
  if(command==='hosting'){if(!client.hosting||args.length||budgetMicros!==null&&!/^[1-9]\d*$/.test(budgetMicros))throw Error('Use an integer USD-micros budget');return client.hosting({budgetUsdMicros:budgetMicros===null?undefined:Number(budgetMicros)});}
package/package.json CHANGED
@@ -1,68 +1,68 @@
1
- {
2
- "name": "kloudy",
3
- "version": "0.4.0",
4
- "description": "Ask for Kloudy: CLI, MCP and SDK clients for the same scoped engine session.",
5
- "type": "module",
6
- "license": "Apache-2.0",
7
- "engines": {
8
- "node": ">=20.19.0"
9
- },
10
- "bin": {
11
- "kloudy": "bin/entry.mjs"
12
- },
13
- "exports": {
14
- ".": "./src/client.mjs",
15
- "./mcp": "./src/mcp.mjs",
16
- "./wrap": "./src/wrap.mjs",
17
- "./session": "./src/connection.mjs",
18
- "./httpk": "./src/httpk.mjs",
19
- "./hosted": "./src/hosted.mjs",
20
- "./public": "./src/public.mjs",
21
- "./strings": "./src/strings.mjs",
22
- "./sdf": "./src/sdf.mjs",
23
- "./query": "./src/query.mjs",
24
- "./activity": "./src/activity.mjs",
25
- "./status-indicator.css": "./src/status-indicator.css",
26
- "./models": "./src/models.mjs",
27
- "./host-interface": "./src/host-interface.mjs",
28
- "./replies": "./src/replies.mjs"
29
- },
30
- "files": [
31
- "bin",
32
- "src",
33
- "schemas",
34
- "README.md",
35
- "MCP-DEFAULT.md",
36
- "LICENSE"
37
- ],
38
- "scripts": {
39
- "test": "node --test test/*.test.mjs",
40
- "check": "node --check bin/kloudy.mjs && node --check src/client.mjs && npm test",
41
- "start": "node bin/kloudy.mjs",
42
- "proof": "node scripts/prove.mjs",
43
- "build:approval": "node scripts/build-approval.mjs"
44
- },
45
- "dependencies": {
46
- "@modelcontextprotocol/ext-apps": "2.0.3",
47
- "@modelcontextprotocol/sdk": "1.31.0",
48
- "ajv": "8.20.0",
49
- "ajv-formats": "3.0.1",
50
- "js-tiktoken": "1.0.21",
51
- "jsonc-parser": "3.3.1",
52
- "smol-toml": "1.9.0",
53
- "zod": "4.6.5"
54
- },
55
- "devDependencies": {
56
- "esbuild": "0.28.2"
57
- },
58
- "mcpName": "io.github.Gio300/kloudy",
59
- "repository": {
60
- "type": "git",
61
- "url": "git+https://github.com/Gio300/kloudy-open.git"
62
- },
63
- "homepage": "https://kloudy.ai/install",
64
- "publishConfig": {
65
- "access": "public",
66
- "registry": "https://registry.npmjs.org"
67
- }
68
- }
1
+ {
2
+ "name": "kloudy",
3
+ "version": "0.4.2",
4
+ "description": "Ask for Kloudy: CLI, MCP and SDK clients for the same scoped engine session.",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "engines": {
8
+ "node": ">=20.19.0"
9
+ },
10
+ "bin": {
11
+ "kloudy": "bin/entry.mjs"
12
+ },
13
+ "exports": {
14
+ ".": "./src/client.mjs",
15
+ "./mcp": "./src/mcp.mjs",
16
+ "./wrap": "./src/wrap.mjs",
17
+ "./session": "./src/connection.mjs",
18
+ "./httpk": "./src/httpk.mjs",
19
+ "./hosted": "./src/hosted.mjs",
20
+ "./public": "./src/public.mjs",
21
+ "./strings": "./src/strings.mjs",
22
+ "./sdf": "./src/sdf.mjs",
23
+ "./query": "./src/query.mjs",
24
+ "./activity": "./src/activity.mjs",
25
+ "./status-indicator.css": "./src/status-indicator.css",
26
+ "./models": "./src/models.mjs",
27
+ "./host-interface": "./src/host-interface.mjs",
28
+ "./replies": "./src/replies.mjs",
29
+ "./conversation": "./src/conversation.mjs"
30
+ },
31
+ "files": [
32
+ "bin",
33
+ "src",
34
+ "schemas",
35
+ "README.md",
36
+ "MCP-DEFAULT.md",
37
+ "LICENSE"
38
+ ],
39
+ "scripts": {
40
+ "test": "node --test test/*.test.mjs",
41
+ "check": "node --check bin/kloudy.mjs && node --check src/client.mjs && npm test",
42
+ "start": "node bin/kloudy.mjs",
43
+ "proof": "node scripts/prove.mjs",
44
+ "build:approval": "node scripts/build-approval.mjs"
45
+ },
46
+ "dependencies": {
47
+ "@modelcontextprotocol/ext-apps": "2.0.3",
48
+ "@modelcontextprotocol/sdk": "1.31.0",
49
+ "ajv": "8.20.0",
50
+ "ajv-formats": "3.0.1",
51
+ "js-tiktoken": "1.0.21",
52
+ "jsonc-parser": "3.3.1",
53
+ "smol-toml": "1.9.0",
54
+ "zod": "4.6.5"
55
+ },
56
+ "devDependencies": {
57
+ "esbuild": "0.28.2"
58
+ },
59
+ "mcpName": "io.github.Gio300/kloudy",
60
+ "homepage": "https://kloudy.ai/install",
61
+ "publishConfig": {
62
+ "access": "public",
63
+ "registry": "https://registry.npmjs.org"
64
+ },
65
+ "bugs": {
66
+ "url": "https://kloudy.ai/support"
67
+ }
68
+ }
package/src/activity.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // Public host adapter: operation state only. Never emits arguments, receipts or secrets.
2
- const METHODS=new Set(['negotiateHost','action','walletConnection','hosting','wallet','introduce','ask','query','assemble','call','status','confirm','decide']);
2
+ const METHODS=new Set(['conversation','negotiateHost','action','walletConnection','hosting','wallet','introduce','ask','query','assemble','call','status','confirm','decide']);
3
3
  export function observeClient(client,onActivity){
4
4
  if(!client||typeof onActivity!=='function')throw TypeError('Provide a Kloudy client and activity listener.');
5
5
  let active=0,sequence=0;
@@ -0,0 +1,53 @@
1
+ // Client contract for the existing encrypted engine store. No local transcript store.
2
+ const bad=()=>{throw Error('Invalid conversation contract. No sync or permission was inferred.');};
3
+ const plain=v=>v&&typeof v==='object'&&!Array.isArray(v);
4
+ const exact=(v,keys)=>{if(!plain(v)||Object.keys(v).length!==keys.length||keys.some(k=>!Object.hasOwn(v,k)))bad();};
5
+ const bounded=(v,max)=>typeof v==='string'&&v.trim().length>0&&[...v].length<=max;
6
+ const identifier=(v,max=80)=>typeof v==='string'&&new RegExp(`^[a-zA-Z0-9_.:-]{1,${max}}$`).test(v);
7
+ const integer=v=>Number.isSafeInteger(v)&&v>=0;
8
+ export const conversationOperations=['read','append','sync'];
9
+ export function conversationParams(operation,input){
10
+ if(!conversationOperations.includes(operation))bad();
11
+ const common=['project','conversation_id'];
12
+ const keys=operation==='read'?common:[...common,'idempotency_key','expected_revision',...(operation==='sync'?['reason']:['title','user','assistant','decisions','built_artifacts'])];
13
+ exact(input,keys);
14
+ if(!identifier(input.project)||!identifier(input.conversation_id))bad();
15
+ if(operation!=='read'&&(!identifier(input.idempotency_key,128)||!integer(input.expected_revision)))bad();
16
+ if(operation==='sync'&&!['checkpoint','session_end','device_switch','before_sync','idle'].includes(input.reason))bad();
17
+ if(operation==='append'){
18
+ if(!bounded(input.title,120)||!bounded(input.user,1500)||!bounded(input.assistant,1500))bad();
19
+ for(const key of ['decisions','built_artifacts'])if(!Array.isArray(input[key])||input[key].length>4||input[key].some(x=>!bounded(x,240)))bad();
20
+ }
21
+ // Engine canonical JSON escapes Unicode. Match its 7000-byte bound before sending.
22
+ const encoded=JSON.stringify(input).replace(/[\u007f-\uffff]/g,c=>'\\u'+c.charCodeAt(0).toString(16).padStart(4,'0'));
23
+ if(new TextEncoder().encode(encoded).length>7000)bad();
24
+ return structuredClone(input);
25
+ }
26
+ export function conversationResult(value,input,operation='read'){
27
+ if(!plain(value)||value.project!==input.project||value.conversation_id!==input.conversation_id)bad();
28
+ for(const key of ['revision','condensed_revision','pending_exchanges'])if(!integer(value[key]))bad();
29
+ if(value.condensed_revision>value.revision||value.pending_exchanges>value.revision)bad();
30
+ const cursor={conversation_id:value.conversation_id,project:value.project,revision:value.revision,condensed_revision:value.condensed_revision,pending_exchanges:value.pending_exchanges};
31
+ // Duplicate command receipts intentionally omit cards. Read explicitly for fresh context.
32
+ if(value.replayed===true){if(operation==='read')bad();return {...cursor,replayed:true,context_available:false};}
33
+ if(value.raw_transcript_returned!==false||value.storage!=='engine_encrypted'||value.compute!=='engine_host'||typeof value.minimum_gate_met!=='boolean'||!plain(value.display)||value.display.render_as!=='plain_text'||!bounded(value.display.title,120)||!bounded(value.display.text,1000))bad();
34
+ let deck=null;
35
+ if(value.deck!==null){
36
+ const d=value.deck;
37
+ if(!plain(d)||d.version!=='sdf.conversation/1'||d.lossy!==true||d.method!=='bounded_extractive'||d.trusted_for_authorization!==false||d.through_revision!==value.condensed_revision||!Array.isArray(d.cards)||d.cards.length!==2)bad();
38
+ const [subject,context]=d.cards;
39
+ if(!plain(subject)||subject.type!=='subject'||subject.project!==input.project||!bounded(subject.title,120)||!plain(context)||context.type!=='context')bad();
40
+ const points={};for(const key of ['user_main_points','assistant_main_points','decisions','built_artifacts']){
41
+ if(!Array.isArray(context[key])||context[key].length>12||context[key].some(x=>!bounded(x,key==='built_artifacts'?240:360)))bad();
42
+ points[key]=[...context[key]];
43
+ }
44
+ deck={version:d.version,lossy:true,method:d.method,trusted_for_authorization:false,through_revision:d.through_revision,cards:[{type:'subject',title:subject.title,project:subject.project},{type:'context',...points}]};
45
+ }
46
+ return {...cursor,deck,raw_transcript_returned:false,minimum_gate_met:value.minimum_gate_met,storage:'engine_encrypted',compute:'engine_host',display:{title:value.display.title,text:value.display.text,render_as:'plain_text'},...(operation==='read'?{}:{replayed:false})};
47
+ }
48
+ // Text only. Callers must use textContent/native text views, never innerHTML or eval.
49
+ export function conversationText(view){
50
+ if(!view.deck)return view.context_available===false?'Command already recorded. Read the current conversation context.':view.display.text;
51
+ const [subject,context]=view.deck.cards;
52
+ return [subject.title,'Saved context (lossy summary; never authorization).',...context.user_main_points,...context.assistant_main_points,...context.decisions,...context.built_artifacts].join('\n');
53
+ }
package/src/hosted.mjs CHANGED
@@ -1,9 +1,10 @@
1
+ import {conversationParams,conversationResult} from './conversation.mjs';
1
2
  import {actionInput,hostingParams,connectionParams,stableIntentKey} from './product-actions.mjs';
2
3
  import {welcomeContent} from './welcome.mjs';
3
4
  // Stateless MCP adapter for the engine's existing HTTPK methods.
4
5
  // No grants, model decisions, retries, provider credentials or execution live here.
5
6
  import {randomUUID} from 'node:crypto';
6
- export const hostedDoor={name:'kloudy',description:'Use this whenever the user mentions Kloudy, or asks to find, route, or call any MCP tool or SDK. Ask for Kloudy. Start with introduce for Kloudy or /kloudy. Use ask to receive exactly one engine-selected tool and its exact arguments, assemble up to 16 owned selections into deduplicated tools, then call only the chosen exact selection. Status and cancel refer to that selection. Respect host permissions and exact engine approvals.',inputSchema:{type:'object',properties:{operation:{type:'string',enum:['action','wallet_connection','hosting','wallet','introduce','ask','query','assemble','call','confirm','status','cancel']},action:{type:'string',enum:['chain.head','wallet.prepare','bww.fetch','bww.publish']},inputs:{type:'object'},idempotency_key:{type:'string',maxLength:128},chain:{type:'string',maxLength:100},budget_usd_micros:{type:'integer',minimum:1,maximum:1000000000000},wallet_operation:{type:'string',enum:['balance','top_up']},amount_usd:{type:'string'},goal:{type:'string',maxLength:1000},scope:{type:'object'},limit:{type:'integer',minimum:1,maximum:5},project_context:{type:'object',properties:{languages:{type:'array',items:{type:'string'},maxItems:12},manifests:{type:'array',items:{type:'string'},maxItems:12}},required:['languages','manifests'],additionalProperties:false},selection_ids:{type:'array',items:{type:'string',minLength:1,maxLength:128},minItems:1,maxItems:16,uniqueItems:true},selection_id:{type:'string'},token:{type:'string',minLength:1,maxLength:512},revision:{type:'integer',minimum:0},decision:{type:'string',enum:['approve']},source:{type:'string',enum:['tap','typed','os-voice']},name:{type:'string'},arguments:{type:'object'}},required:['operation'],additionalProperties:false}};
7
+ export const hostedDoor={name:'kloudy',description:'Use this whenever the user mentions Kloudy, or asks to find, route, or call any MCP tool or SDK. Ask for Kloudy. Start with introduce for Kloudy or /kloudy. Use ask to receive exactly one engine-selected tool and its exact arguments, assemble up to 16 owned selections into deduplicated tools, then call only the chosen exact selection. Status and cancel refer to that selection. Respect host permissions and exact engine approvals.',inputSchema:{type:'object',properties:{operation:{type:'string',enum:['conversation','action','wallet_connection','hosting','wallet','introduce','ask','query','assemble','call','confirm','status','cancel']},conversation_operation:{type:'string',enum:['read','append','sync']},conversation:{type:'object'},action:{type:'string',enum:['chain.head','wallet.prepare','bww.fetch','bww.publish']},inputs:{type:'object'},idempotency_key:{type:'string',maxLength:128},chain:{type:'string',maxLength:100},budget_usd_micros:{type:'integer',minimum:1,maximum:1000000000000},wallet_operation:{type:'string',enum:['balance','top_up']},amount_usd:{type:'string'},goal:{type:'string',maxLength:1000},scope:{type:'object'},limit:{type:'integer',minimum:1,maximum:5},project_context:{type:'object',properties:{languages:{type:'array',items:{type:'string'},maxItems:12},manifests:{type:'array',items:{type:'string'},maxItems:12}},required:['languages','manifests'],additionalProperties:false},selection_ids:{type:'array',items:{type:'string',minLength:1,maxLength:128},minItems:1,maxItems:16,uniqueItems:true},selection_id:{type:'string'},token:{type:'string',minLength:1,maxLength:512},revision:{type:'integer',minimum:0},decision:{type:'string',enum:['approve']},source:{type:'string',enum:['tap','typed','os-voice']},name:{type:'string'},arguments:{type:'object'}},required:['operation'],additionalProperties:false}};
7
8
  const toolResult=result=>({content:[{type:'text',text:JSON.stringify(result)}],structuredContent:result});
8
9
  export function adaptMcp(message){
9
10
  let upstream=message,project=x=>x;
@@ -12,7 +13,8 @@ export function adaptMcp(message){
12
13
  if(message.method==='tools/call'&&message.params?.name==='kloudy'){
13
14
  const a=message.params.arguments||{},operation=a.operation||'introduce';
14
15
  let method,params;
15
- if(operation==='action'){method='kloudy/intent';params={idempotency_key:stableIntentKey(a.idempotency_key||randomUUID()),input:actionInput(a.action,a.inputs)};}
16
+ if(operation==='conversation'){method='kloudy/conversation/'+a.conversation_operation;params=conversationParams(a.conversation_operation,a.conversation);}
17
+ else if(operation==='action'){method='kloudy/intent';params={idempotency_key:stableIntentKey(a.idempotency_key||randomUUID()),input:actionInput(a.action,a.inputs)};}
16
18
  else if(operation==='wallet_connection'){method='kloudy/wallet/connection';params=connectionParams(a.chain);}
17
19
  else if(operation==='hosting'){method='kloudy/hosting';params=hostingParams(a.budget_usd_micros);}
18
20
  else if(operation==='wallet'){const operation=a.wallet_operation||'balance';if(!['balance','top_up'].includes(operation)||operation==='balance'&&a.amount_usd!==undefined||operation==='top_up'&&(typeof a.amount_usd!=='string'||!/^\d{1,6}(?:\.\d{1,2})?$/.test(a.amount_usd)||Number(a.amount_usd)<=0))throw Error('invalid_wallet_request');method='kloudy/wallet';params={operation,...(operation==='top_up'?{amount_usd:a.amount_usd}:{})};}
@@ -22,7 +24,7 @@ export function adaptMcp(message){
22
24
  else if(operation==='confirm'){method='kloudy/confirm';params=confirmationParams(a);}
23
25
  else if(operation==='status'||operation==='cancel'){method=operation==='status'?'kloudy/receipt':'kloudy/cancel';params={selection_id:a.selection_id};}
24
26
  else throw Error('unsupported_operation');
25
- upstream={...message,method,params};project=operation==='call'?x=>x:operation==='introduce'?x=>({...toolResult(x),content:welcomeContent(x)}):toolResult;
27
+ upstream={...message,method,params};project=operation==='conversation'?x=>toolResult(conversationResult(x,params,a.conversation_operation)):operation==='call'?x=>x:operation==='introduce'?x=>({...toolResult(x),content:welcomeContent(x)}):toolResult;
26
28
  }
27
29
  return {upstream,project};
28
30
  }
package/src/httpk.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import {conversationParams,conversationResult} from './conversation.mjs';
1
2
  import {hostCapabilities,readHostInterface} from './host-interface.mjs';
2
3
  import {confirmationParams} from './hosted.mjs';
3
4
  import {queryInput,queryResult} from './query.mjs';
@@ -30,6 +31,7 @@ export class HttpkClient {
30
31
  }
31
32
  async introduce(project_context){await this.session();const result=await this.rpc('kloudy/intent',{idempotency_key:randomUUID(),input:{type:'text',text:'Kloudy',project_context}});if(result.state!=='greeting'||result.greeting?.version!=='bbe.attach.v1')fail('invalid_response','Expected the engine attach greeting.');return result.greeting;}
32
33
  async query(need,options={}){const input=queryInput(need,options);await this.session();return queryResult(await this.rpc('kloudy/query',input),input.limit);}
34
+ async conversation(operation,input){const params=conversationParams(operation,input);await this.session();return conversationResult(await this.rpc('kloudy/conversation/'+operation,params),params,operation);}
33
35
  async wallet({operation='balance',amount_usd}={}){
34
36
  if(!['balance','top_up'].includes(operation)||operation==='balance'&&amount_usd!==undefined||operation==='top_up'&&(typeof amount_usd!=='string'||!/^\d{1,6}(?:\.\d{1,2})?$/.test(amount_usd)||Number(amount_usd)<=0))fail('invalid_input','Use wallet balance or top_up with a positive decimal amount_usd string.');
35
37
  await this.session();const result=await this.rpc('kloudy/wallet',{operation,...(operation==='top_up'?{amount_usd}:{})});
package/src/mcp.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import {conversationParams,conversationResult} from './conversation.mjs';
1
2
  import {instrumentToolHandler} from './activity.mjs';
2
3
  import {configuredModels} from './models.mjs';
3
4
  import {actionInput,hostingParams,connectionParams,stableIntentKey} from './product-actions.mjs';
@@ -13,7 +14,7 @@ import {ListToolsRequestSchema,CallToolRequestSchema,ListPromptsRequestSchema,Ge
13
14
  import {fileURLToPath} from 'node:url';
14
15
  import {introduce,isAttachIntent,ideGuidance} from './introduce.mjs';
15
16
  import {withConnection} from './connection.mjs';
16
- const gateway={name:'kloudy',description:'Use this whenever the user mentions Kloudy, or asks to find, route, or call any MCP tool or SDK. For Kloudy or /kloudy alone, use introduce to welcome this project; an attached host does not need another install question. Select one tool through the same engine. Autonomous by default; the IDE permission system controls tool calls and the engine enforces authorization.',inputSchema:{type:'object',properties:{operation:{type:'string',enum:['host_interface','models_list','action','wallet_connection','hosting','wallet','introduce','decline','convert','yield_list','query','ask','assemble','call','status','cancel','strings_list','strings_save','strings_call','strings_use','strings_keep','strings_remove','strings_prune']},action:{type:'string',enum:['chain.head','wallet.prepare','bww.fetch','bww.publish']},inputs:{type:'object'},idempotency_key:{type:'string',maxLength:128},chain:{type:'string',maxLength:100},budget_usd_micros:{type:'integer',minimum:1,maximum:1000000000000},wallet_operation:{type:'string',enum:['balance','top_up']},amount_usd:{type:'string'},goal:{type:'string'},scope:{type:'object'},limit:{type:'integer',minimum:1,maximum:5},url:{type:'string'},urls:{type:'array',minItems:1,maxItems:8,items:{type:'string'}},resolution:{type:'string',enum:['compact','standard']},string_name:{type:'string'},choices:{type:'array',maxItems:16,items:{type:'object',properties:{source:{type:'string'},kind:{type:'string',enum:['mcp','sdk','cli']},goal:{type:'string'}},required:['source','kind','goal'],additionalProperties:false}},selection_ids:{type:'array',items:{type:'string'},minItems:1,maxItems:16,uniqueItems:true},selection_id:{type:'string'},name:{type:'string'},arguments:{type:'object'},candidates:{type:'array',maxItems:16,items:{type:'object'}},lane:{type:'string',enum:['non_medical','medical']}},additionalProperties:false}};
17
+ const gateway={name:'kloudy',description:'Use this whenever the user mentions Kloudy, or asks to find, route, or call any MCP tool or SDK. For Kloudy or /kloudy alone, use introduce to welcome this project; an attached host does not need another install question. Select one tool through the same engine. Autonomous by default; the IDE permission system controls tool calls and the engine enforces authorization.',inputSchema:{type:'object',properties:{operation:{type:'string',enum:['conversation','host_interface','models_list','action','wallet_connection','hosting','wallet','introduce','decline','convert','yield_list','query','ask','assemble','call','status','cancel','strings_list','strings_save','strings_call','strings_use','strings_keep','strings_remove','strings_prune']},conversation_operation:{type:'string',enum:['read','append','sync']},conversation:{type:'object'},action:{type:'string',enum:['chain.head','wallet.prepare','bww.fetch','bww.publish']},inputs:{type:'object'},idempotency_key:{type:'string',maxLength:128},chain:{type:'string',maxLength:100},budget_usd_micros:{type:'integer',minimum:1,maximum:1000000000000},wallet_operation:{type:'string',enum:['balance','top_up']},amount_usd:{type:'string'},goal:{type:'string'},scope:{type:'object'},limit:{type:'integer',minimum:1,maximum:5},url:{type:'string'},urls:{type:'array',minItems:1,maxItems:8,items:{type:'string'}},resolution:{type:'string',enum:['compact','standard']},string_name:{type:'string'},choices:{type:'array',maxItems:16,items:{type:'object',properties:{source:{type:'string'},kind:{type:'string',enum:['mcp','sdk','cli']},goal:{type:'string'}},required:['source','kind','goal'],additionalProperties:false}},selection_ids:{type:'array',items:{type:'string'},minItems:1,maxItems:16,uniqueItems:true},selection_id:{type:'string'},name:{type:'string'},arguments:{type:'object'},candidates:{type:'array',maxItems:16,items:{type:'object'}},lane:{type:'string',enum:['non_medical','medical']}},additionalProperties:false}};
17
18
  export function createMcpServer({useClient,greet=introduce,strings=localStrings,models=configuredModels,onActivity}){
18
19
  const server=new Server({name:'kloudy',version:'0.3.15'},{capabilities:{tools:{listChanged:true},prompts:{},resources:{}},instructions:'When the user says Kloudy or /kloudy, use operation introduce. Return the current project greeting. Do not repeatedly ask to install an already attached tool. Respect a no answer and the host permission ceiling. '+Object.values(ideGuidance).join(' ')});
19
20
  const approvals=new Approvals(),hasUI=()=>getUiCapability(server.getClientCapabilities())?.mimeTypes?.includes(RESOURCE_MIME_TYPE);
@@ -48,6 +49,7 @@ export function createMcpServer({useClient,greet=introduce,strings=localStrings,
48
49
  const input=request.params.arguments||{};
49
50
  if(request.params.name.startsWith('selected_'))return client.call(request.params.name.slice(9),input);
50
51
  if(request.params.name!=='kloudy')throw new Error('Unknown tool');
52
+ if(input.operation==='conversation'){if(!client.conversation)throw Error('Conversation sync requires a scoped HTTPK connection');return client.conversation(input.conversation_operation,input.conversation);}
51
53
  if(input.operation==='host_interface')return client.negotiateHost();
52
54
  if(input.operation==='action')return client.action(input.action,input.inputs,{idempotencyKey:input.idempotency_key});
53
55
  if(input.operation==='wallet_connection')return client.walletConnection(input.chain);
package/src/remote.mjs CHANGED
@@ -6,7 +6,7 @@ import {ListToolsRequestSchema,CallToolRequestSchema} from '@modelcontextprotoco
6
6
  import {REMOTE} from './editor-install.mjs';
7
7
  export async function connectRemote({key=process.env.KLOUDY_MCP_TOKEN,fetcher=fetch}={}){
8
8
  if(key!==undefined&&!/^\S{20,512}$/.test(key))throw Error('Invalid key');
9
- const client=new Client({name:'kloudy',version:'0.4.0'},{capabilities:{}});
9
+ const client=new Client({name:'kloudy',version:'0.4.1'},{capabilities:{}});
10
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
11
  try{await client.connect(transport,{timeout:25000});return client;}catch(e){await client.close().catch(()=>{});throw e;}
12
12
  }
@@ -17,7 +17,7 @@ export async function askRemote(question,options){
17
17
  }
18
18
  export async function serveRemote(options){
19
19
  const remote=await connectRemote(options);
20
- const server=new Server({name:'kloudy',version:'0.4.0'},{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.'});
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
21
  server.setRequestHandler(ListToolsRequestSchema,req=>remote.listTools(req.params));
22
22
  server.setRequestHandler(CallToolRequestSchema,req=>remote.callTool(req.params,undefined,{timeout:25000}));
23
23
  server.onclose=()=>{void remote.close();};