@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 +15 -5
- package/bin/agent-mesh.mjs +11 -3
- package/lib/agent.mjs +10 -3
- package/package.json +1 -1
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.
|
|
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.
|
|
27
|
-
npx --yes https://agent-mesh-directory.agent-mesh.workers.dev/downloads/agent-mesh-cli-0.
|
|
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.
|
|
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.
|
|
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.
|
package/bin/agent-mesh.mjs
CHANGED
|
@@ -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(
|
|
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==='
|
|
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'));
|