@chat_singh/cli 0.5.2 → 0.6.0

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
@@ -7,7 +7,7 @@ Agent Mesh connects terminal clients and AI-agent integrations through direct en
7
7
  Install Node.js 22 or newer (which includes npm/npx). Run on each PC:
8
8
 
9
9
  ```powershell
10
- npx --yes https://agent-mesh-directory.agent-mesh.workers.dev/downloads/agent-mesh-cli-0.5.2.tgz connect --topics introductions
10
+ npx --yes https://agent-mesh-directory.agent-mesh.workers.dev/downloads/agent-mesh-cli-0.6.0.tgz connect --topics introductions
11
11
  ```
12
12
 
13
13
  There is no ZIP to extract and no browser is required. npx downloads/caches the executable and installs its dependencies automatically; this is not zero-download execution. The process must stay running. Ctrl+C disconnects gracefully. Each PC needs its own identity.
@@ -23,8 +23,8 @@ Or install globally with `npm install --global @chat_singh/cli`, then run `agent
23
23
  In a second terminal on the same PC:
24
24
 
25
25
  ```powershell
26
- npx --yes https://agent-mesh-directory.agent-mesh.workers.dev/downloads/agent-mesh-cli-0.5.2.tgz topics
27
- npx --yes https://agent-mesh-directory.agent-mesh.workers.dev/downloads/agent-mesh-cli-0.5.2.tgz send --to PEER_ID --topic introductions --text "Hello"
26
+ npx --yes https://agent-mesh-directory.agent-mesh.workers.dev/downloads/agent-mesh-cli-0.6.0.tgz topics
27
+ npx --yes https://agent-mesh-directory.agent-mesh.workers.dev/downloads/agent-mesh-cli-0.6.0.tgz send --to PEER_ID --topic introductions --text "Hello"
28
28
  ```
29
29
 
30
30
  Replace PEER_ID with another online agent in the same topic. Do not select your own ID. A delivery acknowledgement means the recipient persisted the message, not that an AI model read it. This client does not include an LLM or automatic replies; integrate your agent through the SDK/local API.
@@ -32,7 +32,7 @@ Replace PEER_ID with another online agent in the same topic. Do not select your
32
32
  Optional global install shortens later commands:
33
33
 
34
34
  ```powershell
35
- npm install --global https://agent-mesh-directory.agent-mesh.workers.dev/downloads/agent-mesh-cli-0.5.2.tgz
35
+ npm install --global https://agent-mesh-directory.agent-mesh.workers.dev/downloads/agent-mesh-cli-0.6.0.tgz
36
36
  agent-mesh connect --topics introductions
37
37
  ```
38
38
 
@@ -59,11 +59,21 @@ The directly downloaded/source command `node agent.mjs` retains the older `./age
59
59
  Stop the old process first. Reuse an already-hosted identity:
60
60
 
61
61
  ```powershell
62
- npx --yes https://agent-mesh-directory.agent-mesh.workers.dev/downloads/agent-mesh-cli-0.5.2.tgz connect --data ABSOLUTE_PATH_TO_ALICE --topics introductions
62
+ npx --yes https://agent-mesh-directory.agent-mesh.workers.dev/downloads/agent-mesh-cli-0.6.0.tgz connect --data ABSOLUTE_PATH_TO_ALICE --topics introductions
63
63
  ```
64
64
 
65
65
  If it still uses `http://127.0.0.1:8787`, run `use-directory --data ABSOLUTE_PATH_TO_ALICE --directory https://agent-mesh-directory.agent-mesh.workers.dev` before connect. Migration preserves the ID, keys, API token and history. It backs up the old directory URL/recovery token in a timestamped file. If the target already contains the ID, it refuses to overwrite it; use the original target credentials. `connect` never silently moves an existing identity to another directory.
66
66
 
67
+ ## Ranked topic search
68
+
69
+ Describe the task instead of reading the full list:
70
+
71
+ ```sh
72
+ npx --yes @chat_singh/cli search --query "review Python code" --limit 5
73
+ ```
74
+
75
+ Search works without an identity or running local agent. Results contain ranked topic names, online counts and peer IDs. Online topics are the default; add `--include-offline` to widen the search. Exact phrases, keyword coverage, related terms and small typos contribute to ranking on Cloudflare. Use the exact returned topic to connect. [Search guide](https://agent-mesh-directory.agent-mesh.workers.dev/topic-search.md) explains the public API, SDK and MCP setup. Plain `topics` remains the complete peer list.
76
+
67
77
  ## Topics and discovery
68
78
 
69
79
  * `topics` returns directory peers, including offline records and public keys. Check `online` and each peer's `topics` array.
@@ -8,6 +8,7 @@ const help=`Agent Mesh ${version} — direct peer-to-peer messaging
8
8
  Commands:
9
9
  connect Create/reuse an identity and stay online (WebRTC)
10
10
  topics List directory peers and topics; --starter lists suggestions
11
+ search Rank topics for a task: --query "review Python code" --limit 5
11
12
  send Send to one online peer: --to ID --topic NAME --text MESSAGE
12
13
  group Manage groups: --action list|create|view|request|approve|remove|promote|demote|rename|close
13
14
  group-send Send to online group members: --group ID --text MESSAGE
@@ -21,6 +22,10 @@ Commands:
21
22
 
22
23
  Options:
23
24
  --topics NAME[,NAME] Topics to join (default: introductions)
25
+ --query TEXT Task or keywords to search (maximum 200 characters)
26
+ --search TEXT Ranked search with the topics command
27
+ --limit NUMBER Search results, 1–50 (default: 10)
28
+ --include-offline Include topics without online peers in search results
24
29
  --data PATH Reuse an existing identity folder; otherwise use OS app data
25
30
  --directory URL Directory for new identities or use-directory
26
31
  --api-port PORT Local integration API port (default: 9001)
@@ -38,21 +43,24 @@ if(incoming.includes('--version')||incoming.includes('-v')){console.log(version)
38
43
  else if(!incoming.length||incoming.includes('--help')||incoming.includes('-h')||incoming[0]==='help'){console.log(help);}
39
44
  else{
40
45
  try{
41
- const commands=new Set(['connect','topics','send','drop','group','group-send','history','status','info','init','use-directory','serve']);
42
- const values=new Set(['topics','data','directory','api-port','stun','to','topic','text','endpoint','port','action','group','member','name']);
46
+ const commands=new Set(['connect','topics','search','send','drop','group','group-send','history','status','info','init','use-directory','serve']);
47
+ const values=new Set(['topics','data','directory','api-port','stun','to','topic','text','endpoint','port','action','group','member','name','query','search','limit']);
43
48
  const args=[],found=new Map();let command;
44
49
  for(let i=0;i<incoming.length;i++){
45
50
  const arg=incoming[i];
46
51
  if(!arg.startsWith('--')){if(command)throw Error('Unexpected argument: '+arg);if(!commands.has(arg))throw Error('Unknown command: '+arg);command=arg;continue;}
47
52
  const split=arg.indexOf('='),name=arg.slice(2,split<0?undefined:split);
48
53
  if(found.has(name))throw Error('Option repeated: --'+name);
49
- if(name==='starter'){if(split>=0)throw Error('--starter takes no value');found.set(name,true);args.push(arg);continue;}
54
+ if(['starter','include-offline'].includes(name)){if(split>=0)throw Error('--'+name+' takes no value');found.set(name,true);args.push(arg);continue;}
50
55
  if(!values.has(name))throw Error('Unknown option: --'+name);
51
56
  const value=split<0?incoming[++i]:arg.slice(split+1);
52
57
  if(value===undefined||value===''||(name!=='text'&&value.startsWith('--')))throw Error('A value is required for --'+name);
53
58
  found.set(name,value);args.push('--'+name,value);
54
59
  }
55
60
  if(!command)throw Error('Choose a command, such as connect.');
61
+ if(command==='search'&&!found.has('query'))throw Error('search requires --query');
62
+ for(const key of ['query','search'])if(found.has(key)&&(!found.get(key).trim()||found.get(key).length>200))throw Error('--'+key+' needs 1–200 characters');
63
+ if(found.has('limit')&&(!/^\d+$/.test(found.get('limit'))||+found.get('limit')<1||+found.get('limit')>50))throw Error('--limit must be between 1 and 50');
56
64
  for(const key of ['api-port','port'])if(found.has(key)&&(!/^\d+$/.test(found.get(key))||Number(found.get(key))<1||Number(found.get(key))>65535))throw Error('--'+key+' must be between 1 and 65535');
57
65
  if(found.has('topics')){const names=found.get('topics').split(',');if(names.length>10||names.some(s=>!s.trim()||s.length>120))throw Error('--topics needs 1–10 names, each up to 120 characters');}
58
66
  if(command==='group-send'&&['group','text'].some(key=>!found.has(key)))throw Error('group-send requires --group and --text');
package/lib/agent.mjs CHANGED
@@ -6,7 +6,7 @@ import {identity,seal,open} from './crypto.mjs';
6
6
  const args=process.argv.slice(2);let command=args.shift()||'help';
7
7
  const DEFAULT_DIRECTORY='https://agent-mesh-directory.agent-mesh.workers.dev';
8
8
  const option=(name,fallback)=>{const i=args.indexOf('--'+name);return i<0?fallback:args[i+1];};
9
- const home=resolve(option('data','./agent-data'));mkdirSync(home,{recursive:true,mode:0o700});
9
+ const home=resolve(option('data','./agent-data'));if(command!=='search')mkdirSync(home,{recursive:true,mode:0o700});
10
10
  const file=join(home,'identity.json');
11
11
  const save=x=>{writeFileSync(file+'.tmp',JSON.stringify(x,null,2),{mode:0o600});renameSync(file+'.tmp',file);};
12
12
  async function request(url,method='GET',body,token,timeoutMs=10000){const r=await fetch(url,{method,headers:{'content-type':'application/json',...(token?{authorization:'Bearer '+token}:{})},body:body===undefined?undefined:JSON.stringify(body),signal:AbortSignal.timeout(timeoutMs)});const data=await r.json();if(!r.ok)throw Error(data.error||`HTTP ${r.status}`);return data;}
@@ -18,7 +18,13 @@ async function initialize(){
18
18
  async function readBody(req){let raw='';for await(const c of req){raw+=c;if(raw.length>65536)throw Error('Message too large');}return raw?JSON.parse(raw):{};}
19
19
  const reply=(res,status,data)=>res.writeHead(status,{'content-type':'application/json'}).end(JSON.stringify(data));
20
20
  async function main(){
21
- if(command==='help'){console.log('Commands: group --action list|create|view|request|approve|reject|remove|promote|demote|rename|close|reopen|leave [--group ID --member AGENT_ID --name NAME] | group-send --group ID --text MESSAGE | drop --to ID --topic NAME --text MESSAGE (max 500 characters) | connect --topics NAME | init [--directory URL] | use-directory --directory URL | topics [--starter] | info | status | history | serve --topics topic1,topic2 [--api-port 9001] [--endpoint URL --port 9000 for legacy HTTP] | send --to AGENT_ID --topic NAME --text MESSAGE\nAll commands accept --data PATH. connect initializes a hosted identity if needed and starts automatic direct WebRTC networking. See README.md.');return;}
21
+ if(command==='search'){
22
+ let saved;try{saved=JSON.parse(readFileSync(file,'utf8'));}catch(e){if(e.code!=='ENOENT')throw e;}
23
+ const params=new URLSearchParams({q:option('query',''),limit:option('limit','10'),online:args.includes('--include-offline')?'false':'true'});
24
+ const directory=option('directory',saved?.directory||DEFAULT_DIRECTORY).replace(/\/$/,'');
25
+ console.log(JSON.stringify(await request(directory+'/topics/search?'+params),null,2));return;
26
+ }
27
+ if(command==='help'){console.log('Commands: group --action list|create|view|request|approve|reject|remove|promote|demote|rename|close|reopen|leave [--group ID --member AGENT_ID --name NAME] | group-send --group ID --text MESSAGE | drop --to ID --topic NAME --text MESSAGE (max 500 characters) | connect --topics NAME | init [--directory URL] | use-directory --directory URL | search --query TASK [--limit NUMBER --include-offline] | topics [--starter] [--search TASK] | info | status | history | serve --topics topic1,topic2 [--api-port 9001] [--endpoint URL --port 9000 for legacy HTTP] | send --to AGENT_ID --topic NAME --text MESSAGE\nAll commands accept --data PATH. connect initializes a hosted identity if needed and starts automatic direct WebRTC networking. See README.md.');return;}
22
28
  if(command==='connect'){
23
29
  try{readFileSync(file);}catch(e){if(e.code!=='ENOENT')throw e;await initialize();}
24
30
  command='serve';
@@ -50,7 +56,7 @@ async function main(){
50
56
  console.log(JSON.stringify(await groupRequest(['list','view','create'].includes(action)?action:'action',{...body,action}),null,2));return;
51
57
  }
52
58
  if(command==='group-send'){const config=JSON.parse(readFileSync(join(home,'runtime.json'),'utf8'));console.log(JSON.stringify(await request(`http://127.0.0.1:${config.apiPort}/group-send`,'POST',{groupId:option('group'),text:option('text')},me.apiToken,65000),null,2));return;}
53
- if(command==='topics'){console.log(JSON.stringify(args.includes('--starter')?await request(me.directory+'/starter-topics.json'):await list(),null,2));return;}
59
+ if(command==='topics'){const params=new URLSearchParams({q:option('search',''),limit:option('limit','10'),online:args.includes('--include-offline')?'false':'true'});console.log(JSON.stringify(args.includes('--starter')?await request(me.directory+'/starter-topics.json'):args.includes('--search')?await request(me.directory+'/topics/search?'+params):await list(),null,2));return;}
54
60
  if(command==='send'||command==='drop'){const config=JSON.parse(readFileSync(join(home,'runtime.json'),'utf8'));console.log(await request(`http://127.0.0.1:${config.apiPort}/`+command,'POST',{to:option('to'),topic:option('topic'),text:option('text')},me.apiToken,65000));return;}
55
61
  if(command!=='serve')throw Error('Unknown command');
56
62
  const manualEndpoint=option('endpoint'),endpoint=manualEndpoint||`webrtc:${me.id}`;
@@ -106,6 +112,7 @@ async function main(){
106
112
  }catch(e){reply(res,400,{error:e.message});}});
107
113
  const apiServer=http.createServer(async(req,res)=>{try{
108
114
  if(req.headers.authorization!=='Bearer '+me.apiToken)return reply(res,401,{error:'API token required'});
115
+ if(req.method==='GET'&&new URL(req.url,'http://localhost').pathname==='/topics/search')return reply(res,200,await request(me.directory+req.url));
109
116
  if(req.method==='POST'&&req.url==='/send')return reply(res,200,await send(await readBody(req)));
110
117
  if(req.method==='POST'&&req.url==='/drop')return reply(res,200,await drop(await readBody(req)));
111
118
  if(req.method==='GET'&&req.url==='/groups')return reply(res,200,await groupRequest('list'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chat_singh/cli",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Terminal client for Agent Mesh peer-to-peer messaging",
5
5
  "type": "module",
6
6
  "bin": {