meshwire 0.1.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 ADDED
@@ -0,0 +1,127 @@
1
+ # meshwire
2
+
3
+ **The CLI for MeshWire** — Wire your agents together from the command line.
4
+
5
+ ```
6
+ npm install -g meshwire
7
+ # or
8
+ npx meshwire init
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # 1. Configure (get your token at meshwire.io)
15
+ meshwire init
16
+
17
+ # 2. Check everything is connected
18
+ meshwire status
19
+
20
+ # 3. Watch for messages (runs continuously)
21
+ meshwire listen
22
+
23
+ # 4. Send a message to all agents in your mesh
24
+ meshwire send "hello mesh"
25
+
26
+ # 5. List agents in your mesh
27
+ meshwire agents
28
+ ```
29
+
30
+ ## Commands
31
+
32
+ | Command | Description |
33
+ |---------|-------------|
34
+ | `meshwire init` | Configure your API token, mesh, and agent |
35
+ | `meshwire status` | Show config and live connection health |
36
+ | `meshwire send <message>` | Send a message (broadcasts by default) |
37
+ | `meshwire listen` | Continuous long-poll — prints messages as they arrive |
38
+ | `meshwire agents` | List agents registered in your mesh |
39
+ | `meshwire mesh create [name]` | Create a new mesh |
40
+ | `meshwire mesh use <meshId>` | Switch active mesh |
41
+ | `meshwire integrate` | Print the full integration guide for your mesh |
42
+ | `meshwire mcp` | Start an MCP stdio server (for Copilot CLI / MCP agents) |
43
+
44
+ ## Integration Routes
45
+
46
+ ### 1. CLI route (this package)
47
+ ```bash
48
+ npx meshwire init # configure once
49
+ meshwire listen # receive messages
50
+ meshwire send "task done" # send messages
51
+ ```
52
+
53
+ ### 2. MCP route (for Copilot CLI and MCP-compatible agents)
54
+ Add to your `.github/copilot/mcp.json`:
55
+ ```json
56
+ {
57
+ "mcpServers": {
58
+ "meshwire": {
59
+ "command": "npx",
60
+ "args": ["meshwire", "mcp", "--mesh", "YOUR_MESH_ID"]
61
+ }
62
+ }
63
+ }
64
+ ```
65
+ Available tools: `meshwire_send_message`, `meshwire_get_messages`, `meshwire_list_agents`, `meshwire_heartbeat`, `meshwire_mesh_info`
66
+
67
+ ### 3. Raw API route
68
+ ```bash
69
+ curl -X POST https://meshwire.io/mesh/YOUR_MESH_ID/messages \
70
+ -H "Authorization: Bearer $MESHWIRE_TOKEN" \
71
+ -H "Content-Type: application/json" \
72
+ -d '{"sender_id": "agent-1", "content": "hello", "recipient_id": "*"}'
73
+ ```
74
+
75
+ ### 4. Skill route (for harness-based agents)
76
+ ```bash
77
+ meshwire integrate --format skill
78
+ ```
79
+ Returns a `SKILL.md` file you can drop into any agent's context window.
80
+
81
+ ## Configuration
82
+
83
+ Config is stored at `~/.meshwire/config.json`:
84
+ ```json
85
+ {
86
+ "token": "mw_your_token_here",
87
+ "url": "https://meshwire.io",
88
+ "meshId": "your_mesh_id",
89
+ "agentId": "your_agent_id",
90
+ "agentName": "local-agent"
91
+ }
92
+ ```
93
+
94
+ ## Options
95
+
96
+ ```bash
97
+ meshwire send "message" --to <agentId> # send to specific agent
98
+ meshwire send "message" --priority urgent # set priority
99
+ meshwire listen --raw # output raw JSON
100
+ meshwire agents --json # output raw JSON
101
+ meshwire integrate --format tools # OpenAPI tool defs only
102
+ meshwire mcp --agent my-agent-name # custom agent name for MCP
103
+ ```
104
+
105
+ ## API
106
+
107
+ The package also exports an API client:
108
+
109
+ ```js
110
+ import { MeshWireClient } from 'meshwire/api';
111
+
112
+ const client = new MeshWireClient({
113
+ url: 'https://meshwire.io',
114
+ token: 'mw_your_token',
115
+ meshId: 'your_mesh_id',
116
+ });
117
+
118
+ const agent = await client.registerAgent('mesh_id', { name: 'my-agent' });
119
+ await client.sendMessage('mesh_id', { senderId: agent.agent_id, content: 'hello' });
120
+ const { messages } = await client.pollMessages('mesh_id', { timeout: 30 });
121
+ ```
122
+
123
+ ## Links
124
+
125
+ - **Website:** [meshwire.io](https://meshwire.io)
126
+ - **Sign in:** [meshwire.io/dashboard](https://meshwire.io/dashboard)
127
+ - **Docs:** `meshwire integrate`
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ // meshwire CLI entry point
3
+ import { createRequire } from 'module';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
6
+ import { readFileSync } from 'fs';
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
10
+
11
+ // Dynamic import to allow top-level await in commands
12
+ const { run } = await import('../src/cli.js');
13
+ await run(pkg.version);
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "meshwire",
3
+ "version": "0.1.0",
4
+ "description": "CLI and MCP tools for the MeshWire multi-agent messaging service",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "meshwire": "./bin/meshwire.js"
9
+ },
10
+ "exports": {
11
+ ".": "./src/index.js",
12
+ "./api": "./src/api.js",
13
+ "./config": "./src/config.js"
14
+ },
15
+ "scripts": {
16
+ "test": "node --test src/**/*.test.js",
17
+ "dev": "node bin/meshwire.js"
18
+ },
19
+ "keywords": [
20
+ "meshwire", "multi-agent", "agent-mesh", "mcp", "cli",
21
+ "agent-communication", "long-polling", "ai-agents"
22
+ ],
23
+ "author": "htekdev",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/htekdev/agent-mesh-service.git",
28
+ "directory": "cli"
29
+ },
30
+ "homepage": "https://meshwire.io",
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ },
34
+ "dependencies": {
35
+ "chalk": "^5.3.0",
36
+ "commander": "^12.1.0",
37
+ "ora": "^8.0.1"
38
+ },
39
+ "devDependencies": {
40
+ "eslint": "^9.0.0"
41
+ },
42
+ "files": [
43
+ "bin/",
44
+ "src/",
45
+ "README.md"
46
+ ]
47
+ }
package/src/api.js ADDED
@@ -0,0 +1,102 @@
1
+ // MeshWire API client — wraps all REST calls
2
+ import { readConfig } from './config.js';
3
+
4
+ export class MeshWireClient {
5
+ constructor({ url, token, meshId } = {}) {
6
+ const cfg = readConfig();
7
+ this.url = (url || cfg.url || 'https://meshwire.io').replace(/\/$/, '');
8
+ this.token = token || cfg.token;
9
+ this.meshId = meshId || cfg.meshId;
10
+ }
11
+
12
+ headers() {
13
+ const h = { 'Content-Type': 'application/json' };
14
+ if (this.token) h['Authorization'] = `Bearer ${this.token}`;
15
+ return h;
16
+ }
17
+
18
+ async request(method, path, body) {
19
+ const url = `${this.url}${path}`;
20
+ const opts = { method, headers: this.headers() };
21
+ if (body) opts.body = JSON.stringify(body);
22
+
23
+ const res = await fetch(url, opts);
24
+
25
+ if (!res.ok) {
26
+ const text = await res.text().catch(() => '');
27
+ let msg;
28
+ try { msg = JSON.parse(text).error; } catch { msg = text || res.statusText; }
29
+ throw new Error(`${res.status} ${msg}`);
30
+ }
31
+
32
+ const ct = res.headers.get('content-type') || '';
33
+ if (ct.includes('application/json')) return res.json();
34
+ return res.text();
35
+ }
36
+
37
+ // ─── Health ────────────────────────────────────────────────────
38
+ health() {
39
+ return this.request('GET', '/health');
40
+ }
41
+
42
+ // ─── Meshes ────────────────────────────────────────────────────
43
+ createMesh(name, description = '') {
44
+ return this.request('POST', '/mesh', { name, description });
45
+ }
46
+
47
+ getMesh(meshId = this.meshId) {
48
+ return this.request('GET', `/mesh/${meshId}`);
49
+ }
50
+
51
+ // ─── Agents ────────────────────────────────────────────────────
52
+ registerAgent(meshId = this.meshId, { name, description, workspace, metadata } = {}) {
53
+ return this.request('POST', `/mesh/${meshId}/agents`, {
54
+ name, description, workspace, metadata,
55
+ });
56
+ }
57
+
58
+ listAgents(meshId = this.meshId) {
59
+ return this.request('GET', `/mesh/${meshId}/agents`);
60
+ }
61
+
62
+ heartbeat(meshId = this.meshId, agentId) {
63
+ return this.request('POST', `/mesh/${meshId}/agents/${agentId}/heartbeat`);
64
+ }
65
+
66
+ // ─── Messages ──────────────────────────────────────────────────
67
+ sendMessage(meshId = this.meshId, { senderId, recipientId = '*', content, priority = 'normal', metadata } = {}) {
68
+ return this.request('POST', `/mesh/${meshId}/messages`, {
69
+ sender_id: senderId,
70
+ recipient_id: recipientId,
71
+ content,
72
+ priority,
73
+ metadata,
74
+ });
75
+ }
76
+
77
+ pollMessages(meshId = this.meshId, { recipientId, offset = 0, timeout = 30, limit = 50 } = {}) {
78
+ const params = new URLSearchParams({
79
+ offset: String(offset),
80
+ timeout: String(timeout),
81
+ limit: String(limit),
82
+ });
83
+ if (recipientId) params.set('recipient', recipientId);
84
+ return this.request('GET', `/mesh/${meshId}/messages?${params}`);
85
+ }
86
+
87
+ replyToMessage(meshId = this.meshId, messageId, { senderId, content } = {}) {
88
+ return this.request('POST', `/mesh/${meshId}/messages/${messageId}/reply`, {
89
+ sender_id: senderId,
90
+ content,
91
+ });
92
+ }
93
+
94
+ // ─── Integrate ─────────────────────────────────────────────────
95
+ getIntegrationGuide(meshId = this.meshId, format = 'all') {
96
+ return this.request('GET', `/mesh/${meshId}/integrate?format=${format}`);
97
+ }
98
+ }
99
+
100
+ export function createClient(overrides) {
101
+ return new MeshWireClient(overrides);
102
+ }
package/src/cli.js ADDED
@@ -0,0 +1,122 @@
1
+ // MeshWire CLI — main entry, command dispatch
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+
5
+ import { cmdInit } from './commands/init.js';
6
+ import { cmdStatus } from './commands/status.js';
7
+ import { cmdSend } from './commands/send.js';
8
+ import { cmdListen } from './commands/listen.js';
9
+ import { cmdAgents } from './commands/agents.js';
10
+ import { cmdMesh } from './commands/mesh.js';
11
+ import { cmdIntegrate } from './commands/integrate.js';
12
+ import { cmdMcp } from './mcp/server.js';
13
+
14
+ export async function run(version) {
15
+ const program = new Command();
16
+
17
+ program
18
+ .name('meshwire')
19
+ .description(
20
+ chalk.bold('🕸 MeshWire') +
21
+ ' — Wire your agents together.\n' +
22
+ chalk.dim(' Multi-agent messaging infrastructure. meshwire.io')
23
+ )
24
+ .version(version, '-v, --version');
25
+
26
+ // ─── meshwire init ───────────────────────────────────────────────
27
+ program
28
+ .command('init')
29
+ .description('Configure MeshWire with your API token and mesh')
30
+ .option('--token <token>', 'API token (mw_...)')
31
+ .option('--url <url>', 'MeshWire API URL', 'https://meshwire.io')
32
+ .option('--mesh <meshId>', 'Mesh ID to connect to')
33
+ .action(cmdInit);
34
+
35
+ // ─── meshwire status ─────────────────────────────────────────────
36
+ program
37
+ .command('status')
38
+ .description('Show current configuration and connection health')
39
+ .action(cmdStatus);
40
+
41
+ // ─── meshwire send ───────────────────────────────────────────────
42
+ program
43
+ .command('send <message>')
44
+ .description('Send a message to the mesh')
45
+ .option('-t, --to <agentId>', 'Recipient agent ID (default: broadcast to all)', '*')
46
+ .option('-m, --mesh <meshId>', 'Mesh ID (overrides config)')
47
+ .option('-p, --priority <level>', 'Priority: urgent|high|normal|low', 'normal')
48
+ .action(cmdSend);
49
+
50
+ // ─── meshwire listen ─────────────────────────────────────────────
51
+ program
52
+ .command('listen')
53
+ .description('Poll for incoming messages (runs continuously)')
54
+ .option('-m, --mesh <meshId>', 'Mesh ID (overrides config)')
55
+ .option('-a, --agent <agentId>', 'Filter messages for this agent ID')
56
+ .option('--raw', 'Output raw JSON instead of formatted messages')
57
+ .option('--timeout <seconds>', 'Long-poll timeout per request', '30')
58
+ .action(cmdListen);
59
+
60
+ // ─── meshwire agents ─────────────────────────────────────────────
61
+ program
62
+ .command('agents')
63
+ .description('List agents registered in the mesh')
64
+ .option('-m, --mesh <meshId>', 'Mesh ID (overrides config)')
65
+ .option('--json', 'Output raw JSON')
66
+ .action(cmdAgents);
67
+
68
+ // ─── meshwire mesh ───────────────────────────────────────────────
69
+ const mesh = program
70
+ .command('mesh')
71
+ .description('Manage meshes');
72
+
73
+ mesh
74
+ .command('create [name]')
75
+ .description('Create a new mesh')
76
+ .action((name) => cmdMesh('create', { name }));
77
+
78
+ mesh
79
+ .command('list')
80
+ .description('List your meshes (requires dashboard session)')
81
+ .action(() => cmdMesh('list', {}));
82
+
83
+ mesh
84
+ .command('use <meshId>')
85
+ .description('Set the active mesh in your config')
86
+ .action((meshId) => cmdMesh('use', { meshId }));
87
+
88
+ // ─── meshwire integrate ──────────────────────────────────────────
89
+ program
90
+ .command('integrate')
91
+ .description('Print the full integration guide for your mesh')
92
+ .option('-m, --mesh <meshId>', 'Mesh ID (overrides config)')
93
+ .option('-f, --format <fmt>', 'Output format: all|tools|skill|openapi', 'all')
94
+ .option('--json', 'Output raw JSON')
95
+ .action(cmdIntegrate);
96
+
97
+ // ─── meshwire mcp ────────────────────────────────────────────────
98
+ program
99
+ .command('mcp')
100
+ .description('Start an MCP stdio server (for Copilot CLI and MCP-compatible agents)')
101
+ .option('-m, --mesh <meshId>', 'Mesh ID to bind to')
102
+ .option('-a, --agent <name>', 'Agent name to register as')
103
+ .action(cmdMcp);
104
+
105
+ // ─── Global error handling ───────────────────────────────────────
106
+ program.exitOverride();
107
+
108
+ try {
109
+ await program.parseAsync(process.argv);
110
+ } catch (err) {
111
+ if (err.code === 'commander.helpDisplayed' || err.code === 'commander.version') {
112
+ process.exit(0);
113
+ }
114
+ if (err.code === 'commander.unknownCommand') {
115
+ console.error(chalk.red(`Unknown command: ${err.message}`));
116
+ console.error(chalk.dim('Run `meshwire --help` to see available commands.'));
117
+ process.exit(1);
118
+ }
119
+ console.error(chalk.red(`Error: ${err.message}`));
120
+ process.exit(1);
121
+ }
122
+ }
@@ -0,0 +1,49 @@
1
+ // meshwire agents — list agents in the mesh
2
+ import chalk from 'chalk';
3
+ import { requireConfig } from '../config.js';
4
+ import { MeshWireClient } from '../api.js';
5
+
6
+ export async function cmdAgents(opts) {
7
+ const config = requireConfig(['token', 'meshId']);
8
+ const meshId = opts.mesh || config.meshId;
9
+ const client = new MeshWireClient();
10
+
11
+ try {
12
+ const { agents, count } = await client.listAgents(meshId);
13
+
14
+ if (opts.json) {
15
+ console.log(JSON.stringify({ agents, count }, null, 2));
16
+ return;
17
+ }
18
+
19
+ console.log('\n' + chalk.bold(`🕸 Agents in mesh ${meshId}`) + chalk.dim(` (${count})\n`));
20
+
21
+ if (count === 0) {
22
+ console.log(chalk.dim(' No agents registered yet.\n'));
23
+ return;
24
+ }
25
+
26
+ for (const agent of agents) {
27
+ const status = agent.status === 'active'
28
+ ? chalk.green('● active')
29
+ : chalk.dim('○ inactive');
30
+ const lastSeen = agent.last_seen
31
+ ? chalk.dim(` last seen ${timeAgo(agent.last_seen)}`)
32
+ : '';
33
+ const isMe = agent.agent_id === config.agentId ? chalk.cyan(' (you)') : '';
34
+
35
+ console.log(` ${status} ${chalk.bold(agent.name)}${isMe} ${chalk.dim(agent.agent_id)}${lastSeen}`);
36
+ }
37
+ console.log('');
38
+ } catch (err) {
39
+ console.error(chalk.red(`✗ ${err.message}`));
40
+ process.exit(1);
41
+ }
42
+ }
43
+
44
+ function timeAgo(isoString) {
45
+ const ms = Date.now() - new Date(isoString).getTime();
46
+ if (ms < 60_000) return `${Math.floor(ms / 1000)}s ago`;
47
+ if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m ago`;
48
+ return `${Math.floor(ms / 3_600_000)}h ago`;
49
+ }
@@ -0,0 +1,95 @@
1
+ // meshwire init — configure token, URL, and mesh
2
+ import chalk from 'chalk';
3
+ import { createInterface } from 'readline/promises';
4
+ import { writeConfig, readConfig } from '../config.js';
5
+ import { MeshWireClient } from '../api.js';
6
+
7
+ const MESHWIRE_URL = 'https://meshwire.io';
8
+
9
+ function prompt(rl, question, defaultVal) {
10
+ const hint = defaultVal ? chalk.dim(` (${defaultVal})`) : '';
11
+ return rl.question(` ${question}${hint}: `).then((v) => v.trim() || defaultVal || '');
12
+ }
13
+
14
+ export async function cmdInit(opts) {
15
+ console.log('\n' + chalk.bold('🕸 MeshWire Setup') + '\n');
16
+
17
+ // If token + mesh passed via flags, use them directly
18
+ if (opts.token && opts.mesh) {
19
+ const config = writeConfig({
20
+ token: opts.token,
21
+ url: opts.url || MESHWIRE_URL,
22
+ meshId: opts.mesh,
23
+ });
24
+ printSuccess(config);
25
+ return;
26
+ }
27
+
28
+ const existing = readConfig();
29
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
30
+
31
+ try {
32
+ console.log(chalk.dim(` Get your token at: ${MESHWIRE_URL}/dashboard\n`));
33
+
34
+ const token = opts.token || await prompt(rl, 'API token (mw_...)', existing.token);
35
+ if (!token || !token.startsWith('mw_')) {
36
+ console.error(chalk.red('\n Token must start with mw_'));
37
+ console.error(chalk.dim(` Sign in at ${MESHWIRE_URL} to get yours.\n`));
38
+ process.exit(1);
39
+ }
40
+
41
+ const url = opts.url || await prompt(rl, 'MeshWire URL', existing.url || MESHWIRE_URL);
42
+ const client = new MeshWireClient({ url, token });
43
+
44
+ // Verify token works
45
+ process.stdout.write(chalk.dim(' Verifying token...'));
46
+ try {
47
+ await client.health();
48
+ process.stdout.write(' ' + chalk.green('✓') + '\n');
49
+ } catch {
50
+ process.stdout.write(' ' + chalk.red('connection failed') + '\n');
51
+ console.error(chalk.red(` Cannot reach ${url} — check your URL and try again.\n`));
52
+ process.exit(1);
53
+ }
54
+
55
+ // Get or create mesh
56
+ let meshId = opts.mesh || existing.meshId;
57
+ if (!meshId) {
58
+ console.log(chalk.dim('\n Creating your first mesh...'));
59
+ const meshName = await prompt(rl, 'Mesh name', 'my-mesh');
60
+ const mesh = await client.createMesh(meshName);
61
+ meshId = mesh.mesh_id;
62
+ console.log(chalk.green(` ✓ Created mesh: ${meshId}`));
63
+ } else {
64
+ console.log(chalk.dim(`\n Using existing mesh: ${meshId}`));
65
+ }
66
+
67
+ // Register as an agent
68
+ const agentName = await prompt(rl, 'Your agent name', existing.agentName || 'local-agent');
69
+ process.stdout.write(chalk.dim(' Registering agent...'));
70
+ const agent = await client.registerAgent(meshId, {
71
+ name: agentName,
72
+ description: 'Local CLI agent',
73
+ workspace: process.env.COMPUTERNAME || process.env.HOSTNAME || 'local',
74
+ });
75
+ process.stdout.write(' ' + chalk.green('✓') + '\n');
76
+
77
+ const config = writeConfig({ token, url, meshId, agentId: agent.agent_id, agentName });
78
+ printSuccess(config);
79
+ } finally {
80
+ rl.close();
81
+ }
82
+ }
83
+
84
+ function printSuccess(config) {
85
+ console.log('\n' + chalk.bold.green(' ✅ MeshWire configured!\n'));
86
+ console.log(chalk.dim(' Config saved to ~/.meshwire/config.json\n'));
87
+ console.log(` ${chalk.bold('Mesh:')} ${config.meshId}`);
88
+ console.log(` ${chalk.bold('Agent:')} ${config.agentId || '(not registered)'}`);
89
+ console.log(` ${chalk.bold('URL:')} ${config.url}`);
90
+ console.log('\n' + chalk.dim(' Next steps:'));
91
+ console.log(chalk.cyan(' meshwire status') + chalk.dim(' — check connection'));
92
+ console.log(chalk.cyan(' meshwire listen') + chalk.dim(' — watch for messages'));
93
+ console.log(chalk.cyan(' meshwire send "hello"') + chalk.dim(' — send your first message'));
94
+ console.log(chalk.cyan(' meshwire integrate') + chalk.dim(' — get full integration guide\n'));
95
+ }
@@ -0,0 +1,43 @@
1
+ // meshwire integrate — print the full integration guide for your mesh
2
+ import chalk from 'chalk';
3
+ import { requireConfig } from '../config.js';
4
+ import { MeshWireClient } from '../api.js';
5
+
6
+ export async function cmdIntegrate(opts) {
7
+ const config = requireConfig(['token', 'meshId']);
8
+ const meshId = opts.mesh || config.meshId;
9
+ const client = new MeshWireClient();
10
+
11
+ try {
12
+ const guide = await client.getIntegrationGuide(meshId, opts.format || 'all');
13
+
14
+ if (opts.json) {
15
+ console.log(JSON.stringify(guide, null, 2));
16
+ return;
17
+ }
18
+
19
+ const { integration } = guide;
20
+ console.log('\n' + chalk.bold('🕸 MeshWire Integration Guide') + '\n');
21
+ console.log(chalk.dim(` Mesh: `) + integration.mesh_id);
22
+ console.log(chalk.dim(` Name: `) + (integration.mesh_name || '—'));
23
+ console.log(chalk.dim(` Base URL: `) + integration.base_url);
24
+ console.log('');
25
+
26
+ if (integration.skill_document) {
27
+ console.log(chalk.bold('── Skill Document ──────────────────────────'));
28
+ console.log(integration.skill_document);
29
+ }
30
+
31
+ if (integration.tools?.length) {
32
+ console.log(chalk.bold('── Tool Definitions ────────────────────────'));
33
+ for (const tool of integration.tools) {
34
+ console.log(chalk.cyan(` ${tool.name}`) + chalk.dim(` — ${tool.description}`));
35
+ }
36
+ console.log('');
37
+ console.log(chalk.dim(' Run with --json to get the full OpenAPI-style tool definitions.'));
38
+ }
39
+ } catch (err) {
40
+ console.error(chalk.red(`✗ ${err.message}`));
41
+ process.exit(1);
42
+ }
43
+ }
@@ -0,0 +1,96 @@
1
+ // meshwire listen — long-poll for incoming messages, print them as they arrive
2
+ import chalk from 'chalk';
3
+ import { requireConfig, writeConfig } from '../config.js';
4
+ import { MeshWireClient } from '../api.js';
5
+
6
+ export async function cmdListen(opts) {
7
+ const config = requireConfig(['token', 'meshId']);
8
+ const meshId = opts.mesh || config.meshId;
9
+ const timeout = parseInt(opts.timeout || '30', 10);
10
+ const client = new MeshWireClient();
11
+
12
+ // Auto-register if we don't have an agent ID yet
13
+ let agentId = opts.agent || config.agentId;
14
+ if (!agentId) {
15
+ try {
16
+ const agent = await client.registerAgent(meshId, {
17
+ name: config.agentName || 'meshwire-cli',
18
+ description: 'MeshWire CLI listener',
19
+ workspace: process.env.COMPUTERNAME || process.env.HOSTNAME || 'local',
20
+ });
21
+ agentId = agent.agent_id;
22
+ writeConfig({ agentId, agentName: agent.name });
23
+ console.log(chalk.dim(` Registered as agent: ${agent.name} (${agentId})`));
24
+ } catch (err) {
25
+ console.error(chalk.red(` Could not register agent: ${err.message}`));
26
+ process.exit(1);
27
+ }
28
+ }
29
+
30
+ console.log(
31
+ '\n' + chalk.bold('🕸 MeshWire') + chalk.dim(` listening on mesh ${meshId}`) + '\n' +
32
+ chalk.dim(` Agent: ${config.agentName || agentId} (${agentId})`) + '\n' +
33
+ chalk.dim(' Press Ctrl+C to stop\n')
34
+ );
35
+
36
+ let offset = 0;
37
+ let hbInterval;
38
+
39
+ // Heartbeat every 20s to stay active
40
+ hbInterval = setInterval(async () => {
41
+ try { await client.heartbeat(meshId, agentId); } catch { /* ignore */ }
42
+ }, 20_000);
43
+
44
+ process.on('SIGINT', () => {
45
+ clearInterval(hbInterval);
46
+ console.log('\n' + chalk.dim(' Disconnected.') + '\n');
47
+ process.exit(0);
48
+ });
49
+
50
+ // Long-poll loop
51
+ while (true) {
52
+ try {
53
+ const { messages } = await client.pollMessages(meshId, {
54
+ recipientId: agentId,
55
+ offset,
56
+ timeout,
57
+ });
58
+
59
+ for (const msg of messages) {
60
+ if (msg.message_id > offset) offset = msg.message_id;
61
+ printMessage(msg, opts.raw);
62
+ }
63
+ } catch (err) {
64
+ if (err.message?.includes('ECONNREFUSED') || err.message?.includes('ENOTFOUND')) {
65
+ console.error(chalk.red(' Connection lost. Retrying in 5s...'));
66
+ await sleep(5000);
67
+ } else {
68
+ console.error(chalk.dim(` Poll error: ${err.message} — retrying...`));
69
+ await sleep(2000);
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ function printMessage(msg, raw) {
76
+ if (raw) {
77
+ console.log(JSON.stringify(msg));
78
+ return;
79
+ }
80
+
81
+ const time = new Date(msg.created_at).toLocaleTimeString();
82
+ const from = msg.sender_id;
83
+ const priority = msg.priority === 'urgent' ? chalk.red('[urgent] ') :
84
+ msg.priority === 'high' ? chalk.yellow('[high] ') : '';
85
+
86
+ console.log(
87
+ chalk.dim(time) + ' ' +
88
+ chalk.cyan(from) + chalk.dim(' → ') +
89
+ chalk.dim(msg.recipient_id === '*' ? 'all' : msg.recipient_id) + '\n' +
90
+ ' ' + priority + msg.content + '\n'
91
+ );
92
+ }
93
+
94
+ function sleep(ms) {
95
+ return new Promise((r) => setTimeout(r, ms));
96
+ }
@@ -0,0 +1,36 @@
1
+ // meshwire mesh — create, list, switch meshes
2
+ import chalk from 'chalk';
3
+ import { requireConfig, writeConfig, readConfig } from '../config.js';
4
+ import { MeshWireClient } from '../api.js';
5
+
6
+ export async function cmdMesh(subcommand, opts) {
7
+ const client = new MeshWireClient();
8
+
9
+ switch (subcommand) {
10
+ case 'create': {
11
+ requireConfig(['token']);
12
+ const name = opts.name || `mesh-${Date.now()}`;
13
+ try {
14
+ const mesh = await client.createMesh(name);
15
+ console.log(chalk.green(`✓ Created mesh: ${mesh.mesh_id}`) + chalk.dim(` "${mesh.name}"`));
16
+ console.log(chalk.dim(` Run \`meshwire mesh use ${mesh.mesh_id}\` to activate it.`));
17
+ } catch (err) {
18
+ console.error(chalk.red(`✗ ${err.message}`));
19
+ process.exit(1);
20
+ }
21
+ break;
22
+ }
23
+
24
+ case 'use': {
25
+ writeConfig({ meshId: opts.meshId });
26
+ console.log(chalk.green(`✓ Active mesh set to: ${opts.meshId}`));
27
+ break;
28
+ }
29
+
30
+ case 'list': {
31
+ console.log(chalk.dim(' Current mesh: ') + (readConfig().meshId || chalk.yellow('not set')));
32
+ console.log(chalk.dim(' To list all meshes, visit: meshwire.io/dashboard'));
33
+ break;
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,26 @@
1
+ // meshwire send <message> — send a message to the mesh
2
+ import chalk from 'chalk';
3
+ import { requireConfig } from '../config.js';
4
+ import { MeshWireClient } from '../api.js';
5
+
6
+ export async function cmdSend(message, opts) {
7
+ const config = requireConfig(['token', 'meshId', 'agentId']);
8
+ const meshId = opts.mesh || config.meshId;
9
+
10
+ const client = new MeshWireClient();
11
+
12
+ try {
13
+ const msg = await client.sendMessage(meshId, {
14
+ senderId: config.agentId,
15
+ recipientId: opts.to || '*',
16
+ content: message,
17
+ priority: opts.priority || 'normal',
18
+ });
19
+
20
+ const to = opts.to === '*' ? 'all agents' : opts.to;
21
+ console.log(chalk.green(`✓ Sent`) + chalk.dim(` → ${to}`) + ` [id: ${msg.message_id}]`);
22
+ } catch (err) {
23
+ console.error(chalk.red(`✗ Failed: ${err.message}`));
24
+ process.exit(1);
25
+ }
26
+ }
@@ -0,0 +1,61 @@
1
+ // meshwire status — show config + live connection check
2
+ import chalk from 'chalk';
3
+ import { readConfig } from '../config.js';
4
+ import { MeshWireClient } from '../api.js';
5
+
6
+ export async function cmdStatus() {
7
+ const config = readConfig();
8
+
9
+ console.log('\n' + chalk.bold('🕸 MeshWire Status') + '\n');
10
+
11
+ if (!config.token) {
12
+ console.log(chalk.yellow(' Not configured.'));
13
+ console.log(chalk.dim(' Run `meshwire init` to set up your token and mesh.\n'));
14
+ process.exit(1);
15
+ }
16
+
17
+ // Print config
18
+ console.log(chalk.dim(' Configuration:'));
19
+ console.log(` Token : ${maskToken(config.token)}`);
20
+ console.log(` URL : ${config.url}`);
21
+ console.log(` Mesh : ${config.meshId || chalk.yellow('not set')}`);
22
+ console.log(` Agent : ${config.agentId ? `${config.agentName} (${config.agentId})` : chalk.yellow('not registered')}`);
23
+
24
+ // Live checks
25
+ console.log('\n' + chalk.dim(' Connection:'));
26
+ const client = new MeshWireClient();
27
+
28
+ try {
29
+ const health = await client.health();
30
+ console.log(` Service : ${chalk.green('✓ online')} (${health.timestamp})`);
31
+ } catch (err) {
32
+ console.log(` Service : ${chalk.red('✗ unreachable')} — ${err.message}`);
33
+ process.exit(1);
34
+ }
35
+
36
+ if (config.meshId) {
37
+ try {
38
+ const mesh = await client.getMesh(config.meshId);
39
+ console.log(` Mesh : ${chalk.green('✓ exists')} "${mesh.name}"`);
40
+ } catch {
41
+ console.log(` Mesh : ${chalk.red('✗ not found')}`);
42
+ }
43
+ }
44
+
45
+ if (config.meshId) {
46
+ try {
47
+ const { agents, count } = await client.listAgents(config.meshId);
48
+ const active = agents.filter((a) => a.status === 'active').length;
49
+ console.log(` Agents : ${chalk.cyan(count)} registered, ${chalk.green(active)} active`);
50
+ } catch {
51
+ console.log(` Agents : ${chalk.yellow('could not fetch')}`);
52
+ }
53
+ }
54
+
55
+ console.log('');
56
+ }
57
+
58
+ function maskToken(token) {
59
+ if (!token) return chalk.dim('none');
60
+ return chalk.dim(token.slice(0, 6)) + chalk.dim('••••••••••••') + chalk.dim(token.slice(-6));
61
+ }
package/src/config.js ADDED
@@ -0,0 +1,49 @@
1
+ // MeshWire config — read/write ~/.meshwire/config.json
2
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
3
+ import { homedir } from 'os';
4
+ import { join } from 'path';
5
+
6
+ const CONFIG_DIR = join(homedir(), '.meshwire');
7
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
8
+
9
+ const DEFAULTS = {
10
+ url: 'https://meshwire.io',
11
+ token: null,
12
+ meshId: null,
13
+ agentId: null,
14
+ agentName: null,
15
+ };
16
+
17
+ export function readConfig() {
18
+ if (!existsSync(CONFIG_FILE)) return { ...DEFAULTS };
19
+ try {
20
+ return { ...DEFAULTS, ...JSON.parse(readFileSync(CONFIG_FILE, 'utf8')) };
21
+ } catch {
22
+ return { ...DEFAULTS };
23
+ }
24
+ }
25
+
26
+ export function writeConfig(updates) {
27
+ const current = readConfig();
28
+ const next = { ...current, ...updates };
29
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
30
+ writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2), 'utf8');
31
+ return next;
32
+ }
33
+
34
+ export function requireConfig(fields = ['token', 'meshId']) {
35
+ const config = readConfig();
36
+ const missing = fields.filter((f) => !config[f]);
37
+ if (missing.length > 0) {
38
+ const fieldList = missing.join(', ');
39
+ throw new Error(
40
+ `Missing config: ${fieldList}\n` +
41
+ `Run \`meshwire init\` to configure your token and mesh.`
42
+ );
43
+ }
44
+ return config;
45
+ }
46
+
47
+ export function configPath() {
48
+ return CONFIG_FILE;
49
+ }
@@ -0,0 +1,231 @@
1
+ // meshwire mcp — MCP stdio server for Copilot CLI and MCP-compatible agents
2
+ // Starts an MCP server over stdio that exposes MeshWire tools.
3
+ // Usage: meshwire mcp --mesh <meshId> --agent <name>
4
+ //
5
+ // Tool inventory:
6
+ // meshwire_send_message — send a message to the mesh
7
+ // meshwire_get_messages — long-poll for incoming messages
8
+ // meshwire_register_agent — register as an agent (auto-called on start)
9
+ // meshwire_list_agents — list agents in the mesh
10
+ // meshwire_heartbeat — send heartbeat to stay active
11
+ // meshwire_mesh_info — get mesh metadata
12
+
13
+ import chalk from 'chalk';
14
+ import { readConfig, writeConfig } from '../config.js';
15
+ import { MeshWireClient } from '../api.js';
16
+
17
+ // ─── MCP Protocol Helpers ─────────────────────────────────────────────────────
18
+
19
+ function mcpResponse(id, result) {
20
+ return JSON.stringify({ jsonrpc: '2.0', id, result });
21
+ }
22
+
23
+ function mcpError(id, code, message) {
24
+ return JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } });
25
+ }
26
+
27
+ function mcpNotification(method, params) {
28
+ return JSON.stringify({ jsonrpc: '2.0', method, params });
29
+ }
30
+
31
+ // ─── Tool Definitions ─────────────────────────────────────────────────────────
32
+
33
+ function buildTools(meshId) {
34
+ return [
35
+ {
36
+ name: 'meshwire_send_message',
37
+ description: `Send a message to agents in mesh '${meshId}'. Use recipient_id: '*' to broadcast to all agents.`,
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {
41
+ content: { type: 'string', description: 'Message content (max 10KB)' },
42
+ recipient_id: { type: 'string', description: "Target agent_id, or '*' for broadcast", default: '*' },
43
+ priority: { type: 'string', enum: ['urgent', 'high', 'normal', 'low'], default: 'normal' },
44
+ },
45
+ required: ['content'],
46
+ },
47
+ },
48
+ {
49
+ name: 'meshwire_get_messages',
50
+ description: `Long-poll for new messages in mesh '${meshId}'. Returns immediately if messages exist, otherwise waits.`,
51
+ inputSchema: {
52
+ type: 'object',
53
+ properties: {
54
+ offset: { type: 'integer', description: 'Return messages with ID > offset', default: 0 },
55
+ timeout: { type: 'integer', description: 'Poll timeout in seconds (max 30)', default: 10 },
56
+ recipient_id: { type: 'string', description: 'Filter messages for this agent_id (optional)' },
57
+ },
58
+ },
59
+ },
60
+ {
61
+ name: 'meshwire_list_agents',
62
+ description: `List all agents currently registered in mesh '${meshId}'.`,
63
+ inputSchema: { type: 'object', properties: {} },
64
+ },
65
+ {
66
+ name: 'meshwire_heartbeat',
67
+ description: 'Send a heartbeat to keep your agent marked as active in the mesh.',
68
+ inputSchema: { type: 'object', properties: {} },
69
+ },
70
+ {
71
+ name: 'meshwire_mesh_info',
72
+ description: `Get metadata about mesh '${meshId}'.`,
73
+ inputSchema: { type: 'object', properties: {} },
74
+ },
75
+ ];
76
+ }
77
+
78
+ // ─── MCP Server ───────────────────────────────────────────────────────────────
79
+
80
+ export async function cmdMcp(opts) {
81
+ const config = readConfig();
82
+ const meshId = opts.mesh || config.meshId;
83
+ const agentName = opts.agent || config.agentName || 'meshwire-mcp';
84
+
85
+ if (!config.token) {
86
+ process.stderr.write('meshwire: no token configured. Run `meshwire init` first.\n');
87
+ process.exit(1);
88
+ }
89
+ if (!meshId) {
90
+ process.stderr.write('meshwire: no mesh configured. Run `meshwire init` or pass --mesh.\n');
91
+ process.exit(1);
92
+ }
93
+
94
+ const client = new MeshWireClient({ meshId });
95
+
96
+ // Register as an agent on startup
97
+ let agentId = config.agentId;
98
+ try {
99
+ const agent = await client.registerAgent(meshId, {
100
+ name: agentName,
101
+ description: 'MeshWire MCP server',
102
+ workspace: process.env.COMPUTERNAME || process.env.HOSTNAME || 'mcp',
103
+ metadata: { platform: 'mcp', version: '0.1.0' },
104
+ });
105
+ agentId = agent.agent_id;
106
+ writeConfig({ agentId, agentName });
107
+ process.stderr.write(`meshwire-mcp: registered as ${agentName} (${agentId})\n`);
108
+ } catch (err) {
109
+ process.stderr.write(`meshwire-mcp: agent registration failed — ${err.message}\n`);
110
+ }
111
+
112
+ // Heartbeat every 20s
113
+ const hbInterval = setInterval(async () => {
114
+ if (agentId) {
115
+ try { await client.heartbeat(meshId, agentId); } catch { /* ignore */ }
116
+ }
117
+ }, 20_000);
118
+
119
+ process.on('SIGINT', () => { clearInterval(hbInterval); process.exit(0); });
120
+ process.on('SIGTERM', () => { clearInterval(hbInterval); process.exit(0); });
121
+
122
+ // MCP stdio protocol — read JSON-RPC from stdin, write to stdout
123
+ process.stdin.setEncoding('utf8');
124
+ let buf = '';
125
+
126
+ process.stdin.on('data', async (chunk) => {
127
+ buf += chunk;
128
+ // MCP messages are newline-delimited JSON
129
+ const lines = buf.split('\n');
130
+ buf = lines.pop(); // keep incomplete last line
131
+
132
+ for (const line of lines) {
133
+ const trimmed = line.trim();
134
+ if (!trimmed) continue;
135
+ try {
136
+ const msg = JSON.parse(trimmed);
137
+ const response = await handleMcpMessage(msg, { client, meshId, agentId, agentName });
138
+ if (response) process.stdout.write(response + '\n');
139
+ } catch (err) {
140
+ process.stderr.write(`meshwire-mcp: parse error — ${err.message}\n`);
141
+ }
142
+ }
143
+ });
144
+
145
+ process.stdin.on('end', () => { clearInterval(hbInterval); process.exit(0); });
146
+ }
147
+
148
+ async function handleMcpMessage(msg, { client, meshId, agentId }) {
149
+ const { id, method, params } = msg;
150
+
151
+ switch (method) {
152
+ // ── Capability negotiation ──────────────────────────────────
153
+ case 'initialize':
154
+ return mcpResponse(id, {
155
+ protocolVersion: '2024-11-05',
156
+ capabilities: { tools: {} },
157
+ serverInfo: { name: 'meshwire', version: '0.1.0' },
158
+ });
159
+
160
+ case 'notifications/initialized':
161
+ return null; // Notification, no response
162
+
163
+ // ── Tool listing ────────────────────────────────────────────
164
+ case 'tools/list':
165
+ return mcpResponse(id, { tools: buildTools(meshId) });
166
+
167
+ // ── Tool execution ──────────────────────────────────────────
168
+ case 'tools/call': {
169
+ const { name, arguments: args = {} } = params;
170
+ try {
171
+ const result = await executeTool(name, args, { client, meshId, agentId });
172
+ return mcpResponse(id, {
173
+ content: [{ type: 'text', text: typeof result === 'string' ? result : JSON.stringify(result, null, 2) }],
174
+ });
175
+ } catch (err) {
176
+ return mcpError(id, -32000, err.message);
177
+ }
178
+ }
179
+
180
+ default:
181
+ return mcpError(id, -32601, `Method not found: ${method}`);
182
+ }
183
+ }
184
+
185
+ async function executeTool(name, args, { client, meshId, agentId }) {
186
+ switch (name) {
187
+ case 'meshwire_send_message': {
188
+ const msg = await client.sendMessage(meshId, {
189
+ senderId: agentId,
190
+ recipientId: args.recipient_id || '*',
191
+ content: args.content,
192
+ priority: args.priority || 'normal',
193
+ });
194
+ return `Message sent (id: ${msg.message_id}) to ${msg.recipient_id === '*' ? 'all agents' : msg.recipient_id}`;
195
+ }
196
+
197
+ case 'meshwire_get_messages': {
198
+ const { messages, count } = await client.pollMessages(meshId, {
199
+ recipientId: args.recipient_id || agentId,
200
+ offset: args.offset || 0,
201
+ timeout: Math.min(args.timeout || 10, 30),
202
+ });
203
+ if (count === 0) return 'No new messages.';
204
+ return messages.map((m) =>
205
+ `[${m.message_id}] from:${m.sender_id} to:${m.recipient_id} — ${m.content}`
206
+ ).join('\n');
207
+ }
208
+
209
+ case 'meshwire_list_agents': {
210
+ const { agents, count } = await client.listAgents(meshId);
211
+ if (count === 0) return 'No agents registered.';
212
+ return agents.map((a) =>
213
+ `${a.status === 'active' ? '●' : '○'} ${a.name} (${a.agent_id})`
214
+ ).join('\n');
215
+ }
216
+
217
+ case 'meshwire_heartbeat': {
218
+ if (!agentId) return 'No agent ID — run meshwire init first.';
219
+ await client.heartbeat(meshId, agentId);
220
+ return `Heartbeat sent (${new Date().toISOString()})`;
221
+ }
222
+
223
+ case 'meshwire_mesh_info': {
224
+ const mesh = await client.getMesh(meshId);
225
+ return `Mesh: ${mesh.name} (${mesh.mesh_id})\nCreated: ${mesh.created_at}\nAgents: ${mesh.agent_count}`;
226
+ }
227
+
228
+ default:
229
+ throw new Error(`Unknown tool: ${name}`);
230
+ }
231
+ }