meshwire 0.1.0 → 0.1.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/package.json +9 -3
- package/src/auth.js +75 -0
- package/src/cli.js +16 -0
- package/src/commands/init.js +63 -8
- package/src/commands/login.js +179 -0
- package/src/commands/mesh.js +18 -2
- package/src/harness/claude.js +88 -0
- package/src/harness/copilot.js +266 -0
- package/src/harness/hermes.js +101 -0
- package/src/mesh-schema.js +41 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meshwire",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "CLI and MCP tools for the MeshWire multi-agent messaging service",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -17,8 +17,14 @@
|
|
|
17
17
|
"dev": "node bin/meshwire.js"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
|
-
"meshwire",
|
|
21
|
-
"agent
|
|
20
|
+
"meshwire",
|
|
21
|
+
"multi-agent",
|
|
22
|
+
"agent-mesh",
|
|
23
|
+
"mcp",
|
|
24
|
+
"cli",
|
|
25
|
+
"agent-communication",
|
|
26
|
+
"long-polling",
|
|
27
|
+
"ai-agents"
|
|
22
28
|
],
|
|
23
29
|
"author": "htekdev",
|
|
24
30
|
"license": "MIT",
|
package/src/auth.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// MeshWire credential resolution — reads from multiple sources in priority order
|
|
2
|
+
//
|
|
3
|
+
// Priority: credentials.json > MESHWIRE_TOKEN env > .mesh.json > config.json > null
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { readConfig } from './config.js';
|
|
8
|
+
import { readMeshJson } from './mesh-schema.js';
|
|
9
|
+
|
|
10
|
+
const CREDS_DIR = join(homedir(), '.meshwire');
|
|
11
|
+
const CREDS_FILE = join(CREDS_DIR, 'credentials.json');
|
|
12
|
+
|
|
13
|
+
export function readCredentials() {
|
|
14
|
+
if (!existsSync(CREDS_FILE)) return null;
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(CREDS_FILE, 'utf8'));
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function writeCredentials(data) {
|
|
23
|
+
mkdirSync(CREDS_DIR, { recursive: true });
|
|
24
|
+
writeFileSync(CREDS_FILE, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Resolve token from all sources in priority order
|
|
28
|
+
export function resolveToken() {
|
|
29
|
+
// 1. credentials.json
|
|
30
|
+
const creds = readCredentials();
|
|
31
|
+
if (creds?.token) return creds.token;
|
|
32
|
+
|
|
33
|
+
// 2. Environment variable
|
|
34
|
+
if (process.env.MESHWIRE_TOKEN) return process.env.MESHWIRE_TOKEN;
|
|
35
|
+
|
|
36
|
+
// 3. Legacy config.json
|
|
37
|
+
const config = readConfig();
|
|
38
|
+
if (config.token) return config.token;
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Resolve mesh ID — workspace .mesh.json takes precedence (it's repo-specific)
|
|
44
|
+
export function resolveMeshId() {
|
|
45
|
+
// 1. Workspace .mesh.json
|
|
46
|
+
const meshJson = readMeshJson();
|
|
47
|
+
if (meshJson?.mesh_id) return meshJson.mesh_id;
|
|
48
|
+
|
|
49
|
+
// 2. credentials.json default mesh
|
|
50
|
+
const creds = readCredentials();
|
|
51
|
+
if (creds?.defaultMeshId) return creds.defaultMeshId;
|
|
52
|
+
|
|
53
|
+
// 3. Environment variable
|
|
54
|
+
if (process.env.MESHWIRE_MESH_ID) return process.env.MESHWIRE_MESH_ID;
|
|
55
|
+
|
|
56
|
+
// 4. Legacy config.json
|
|
57
|
+
const config = readConfig();
|
|
58
|
+
if (config.meshId) return config.meshId;
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function resolveAgentName() {
|
|
64
|
+
const meshJson = readMeshJson();
|
|
65
|
+
if (meshJson?.agent_name) return meshJson.agent_name;
|
|
66
|
+
|
|
67
|
+
const config = readConfig();
|
|
68
|
+
return config.agentName || 'meshwire-agent';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function resolveUrl() {
|
|
72
|
+
if (process.env.MESHWIRE_URL) return process.env.MESHWIRE_URL;
|
|
73
|
+
const config = readConfig();
|
|
74
|
+
return config.url || 'https://meshwire.io';
|
|
75
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import { cmdListen } from './commands/listen.js';
|
|
|
9
9
|
import { cmdAgents } from './commands/agents.js';
|
|
10
10
|
import { cmdMesh } from './commands/mesh.js';
|
|
11
11
|
import { cmdIntegrate } from './commands/integrate.js';
|
|
12
|
+
import { cmdLogin } from './commands/login.js';
|
|
12
13
|
import { cmdMcp } from './mcp/server.js';
|
|
13
14
|
|
|
14
15
|
export async function run(version) {
|
|
@@ -23,6 +24,15 @@ export async function run(version) {
|
|
|
23
24
|
)
|
|
24
25
|
.version(version, '-v, --version');
|
|
25
26
|
|
|
27
|
+
// ─── meshwire login ──────────────────────────────────────────────
|
|
28
|
+
program
|
|
29
|
+
.command('login')
|
|
30
|
+
.description('Sign in with GitHub — saves credentials to ~/.meshwire/credentials.json')
|
|
31
|
+
.option('--url <url>', 'MeshWire URL', 'https://meshwire.io')
|
|
32
|
+
.option('--force', 'Re-authenticate even if already signed in')
|
|
33
|
+
.option('--skip-mesh', 'Skip mesh setup prompt after login')
|
|
34
|
+
.action(cmdLogin);
|
|
35
|
+
|
|
26
36
|
// ─── meshwire init ───────────────────────────────────────────────
|
|
27
37
|
program
|
|
28
38
|
.command('init')
|
|
@@ -30,6 +40,12 @@ export async function run(version) {
|
|
|
30
40
|
.option('--token <token>', 'API token (mw_...)')
|
|
31
41
|
.option('--url <url>', 'MeshWire API URL', 'https://meshwire.io')
|
|
32
42
|
.option('--mesh <meshId>', 'Mesh ID to connect to')
|
|
43
|
+
.option(
|
|
44
|
+
'--harness <name>',
|
|
45
|
+
'Set up for a specific harness: copilot | claude | hermes | cursor',
|
|
46
|
+
)
|
|
47
|
+
.option('--agent <name>', 'Agent name for harness setup')
|
|
48
|
+
.option('--workspace <name>', 'Workspace name for .mesh.json')
|
|
33
49
|
.action(cmdInit);
|
|
34
50
|
|
|
35
51
|
// ─── meshwire status ─────────────────────────────────────────────
|
package/src/commands/init.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
// meshwire init — configure token, URL, and mesh
|
|
1
|
+
// meshwire init — configure token, URL, and mesh. Supports --harness for harness-specific setup.
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { createInterface } from 'readline/promises';
|
|
4
4
|
import { writeConfig, readConfig } from '../config.js';
|
|
5
5
|
import { MeshWireClient } from '../api.js';
|
|
6
|
+
import { resolveToken, resolveMeshId, readCredentials, writeCredentials } from '../auth.js';
|
|
6
7
|
|
|
7
8
|
const MESHWIRE_URL = 'https://meshwire.io';
|
|
8
9
|
|
|
@@ -12,8 +13,54 @@ function prompt(rl, question, defaultVal) {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export async function cmdInit(opts) {
|
|
16
|
+
// ── Harness-specific setup ────────────────────────────────────────────────
|
|
17
|
+
if (opts.harness) {
|
|
18
|
+
const harness = opts.harness.toLowerCase();
|
|
19
|
+
const config = readConfig();
|
|
20
|
+
const meshId = opts.mesh || resolveMeshId();
|
|
21
|
+
const token = opts.token || resolveToken();
|
|
22
|
+
const url = opts.url || config.url || MESHWIRE_URL;
|
|
23
|
+
|
|
24
|
+
switch (harness) {
|
|
25
|
+
case 'copilot': {
|
|
26
|
+
const { setupCopilot } = await import('../harness/copilot.js');
|
|
27
|
+
await setupCopilot({ meshId, agentName: opts.agent, meshwireUrl: url, workspaceName: opts.workspace });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
case 'claude':
|
|
31
|
+
case 'cursor': {
|
|
32
|
+
const { setupClaude } = await import('../harness/claude.js');
|
|
33
|
+
await setupClaude({ meshId, agentName: opts.agent, meshwireUrl: url, workspaceName: opts.workspace });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
case 'hermes': {
|
|
37
|
+
const { setupHermes } = await import('../harness/hermes.js');
|
|
38
|
+
await setupHermes({ meshId, agentName: opts.agent, meshwireUrl: url, workspaceName: opts.workspace });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
default:
|
|
42
|
+
console.error(chalk.red(` Unknown harness: ${harness}`));
|
|
43
|
+
console.error(chalk.dim(' Supported: copilot, claude, cursor, hermes\n'));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
15
48
|
console.log('\n' + chalk.bold('🕸 MeshWire Setup') + '\n');
|
|
16
49
|
|
|
50
|
+
// Auto-detect existing token from credentials or env
|
|
51
|
+
const detectedToken = resolveToken();
|
|
52
|
+
const detectedMesh = resolveMeshId();
|
|
53
|
+
|
|
54
|
+
if (detectedToken && !opts.token) {
|
|
55
|
+
const creds = readCredentials();
|
|
56
|
+
console.log(chalk.dim(` Found credentials for ${creds?.login || 'your account'}`));
|
|
57
|
+
console.log(chalk.dim(` Token: mw_••••••••${detectedToken.slice(-6)}`));
|
|
58
|
+
if (detectedMesh) {
|
|
59
|
+
console.log(chalk.dim(` Mesh: ${detectedMesh}`));
|
|
60
|
+
}
|
|
61
|
+
console.log('');
|
|
62
|
+
}
|
|
63
|
+
|
|
17
64
|
// If token + mesh passed via flags, use them directly
|
|
18
65
|
if (opts.token && opts.mesh) {
|
|
19
66
|
const config = writeConfig({
|
|
@@ -21,6 +68,7 @@ export async function cmdInit(opts) {
|
|
|
21
68
|
url: opts.url || MESHWIRE_URL,
|
|
22
69
|
meshId: opts.mesh,
|
|
23
70
|
});
|
|
71
|
+
writeCredentials({ token: opts.token, defaultMeshId: opts.mesh, savedAt: new Date().toISOString() });
|
|
24
72
|
printSuccess(config);
|
|
25
73
|
return;
|
|
26
74
|
}
|
|
@@ -29,12 +77,16 @@ export async function cmdInit(opts) {
|
|
|
29
77
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
30
78
|
|
|
31
79
|
try {
|
|
32
|
-
|
|
80
|
+
// Suggest login if no credentials found
|
|
81
|
+
if (!detectedToken) {
|
|
82
|
+
console.log(chalk.dim(` No credentials found. Run ${chalk.cyan('meshwire login')} for browser auth,`));
|
|
83
|
+
console.log(chalk.dim(` or get your token at: ${MESHWIRE_URL}/dashboard\n`));
|
|
84
|
+
}
|
|
33
85
|
|
|
34
|
-
const token = opts.token || await prompt(rl, 'API token (mw_...)', existing.token);
|
|
86
|
+
const token = opts.token || detectedToken || await prompt(rl, 'API token (mw_...)', existing.token);
|
|
35
87
|
if (!token || !token.startsWith('mw_')) {
|
|
36
88
|
console.error(chalk.red('\n Token must start with mw_'));
|
|
37
|
-
console.error(chalk.dim(`
|
|
89
|
+
console.error(chalk.dim(` Run 'meshwire login' or sign in at ${MESHWIRE_URL}.\n`));
|
|
38
90
|
process.exit(1);
|
|
39
91
|
}
|
|
40
92
|
|
|
@@ -88,8 +140,11 @@ function printSuccess(config) {
|
|
|
88
140
|
console.log(` ${chalk.bold('Agent:')} ${config.agentId || '(not registered)'}`);
|
|
89
141
|
console.log(` ${chalk.bold('URL:')} ${config.url}`);
|
|
90
142
|
console.log('\n' + chalk.dim(' Next steps:'));
|
|
91
|
-
console.log(chalk.cyan(' meshwire status') + chalk.dim('
|
|
92
|
-
console.log(chalk.cyan(' meshwire listen') + chalk.dim('
|
|
93
|
-
console.log(chalk.cyan(' meshwire send "hello"') + chalk.dim('
|
|
94
|
-
console.log(chalk.cyan(' meshwire
|
|
143
|
+
console.log(chalk.cyan(' meshwire status') + chalk.dim(' — check connection'));
|
|
144
|
+
console.log(chalk.cyan(' meshwire listen') + chalk.dim(' — watch for messages'));
|
|
145
|
+
console.log(chalk.cyan(' meshwire send "hello"') + chalk.dim(' — send your first message'));
|
|
146
|
+
console.log(chalk.cyan(' meshwire init --harness copilot') + chalk.dim(' — set up Copilot CLI extension'));
|
|
147
|
+
console.log(chalk.cyan(' meshwire init --harness claude') + chalk.dim(' — set up Claude Desktop MCP'));
|
|
148
|
+
console.log(chalk.cyan(' meshwire init --harness hermes') + chalk.dim(' — set up Hermes integration'));
|
|
149
|
+
console.log(chalk.cyan(' meshwire integrate') + chalk.dim(' — get full API guide\n'));
|
|
95
150
|
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// meshwire login — browser-based OAuth, saves credentials to ~/.meshwire/credentials.json
|
|
2
|
+
//
|
|
3
|
+
// Flow:
|
|
4
|
+
// 1. Find a free local port
|
|
5
|
+
// 2. Start a tiny HTTP server to receive the OAuth callback
|
|
6
|
+
// 3. Open browser to meshwire.io/auth/cli?port=PORT
|
|
7
|
+
// 4. User signs in with GitHub (standard OAuth flow)
|
|
8
|
+
// 5. Server redirects to http://localhost:PORT?token=mw_xxx&login=username
|
|
9
|
+
// 6. CLI captures the token, saves to ~/.meshwire/credentials.json
|
|
10
|
+
// 7. Optionally register as an agent in the last-used mesh
|
|
11
|
+
|
|
12
|
+
import { createServer } from 'http';
|
|
13
|
+
import { URL } from 'url';
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
import { writeCredentials, readCredentials } from '../auth.js';
|
|
16
|
+
import { readConfig, writeConfig } from '../config.js';
|
|
17
|
+
import { MeshWireClient } from '../api.js';
|
|
18
|
+
|
|
19
|
+
const DEFAULT_URL = 'https://meshwire.io';
|
|
20
|
+
const CLI_PORT_RANGE = [57700, 57799]; // range of ports to try
|
|
21
|
+
|
|
22
|
+
function findFreePort() {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const server = createServer();
|
|
25
|
+
server.listen(0, '127.0.0.1', () => {
|
|
26
|
+
const { port } = server.address();
|
|
27
|
+
server.close(() => resolve(port));
|
|
28
|
+
});
|
|
29
|
+
server.on('error', reject);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function waitForCallback(port, timeoutMs = 120_000) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const timer = setTimeout(() => {
|
|
36
|
+
server.close();
|
|
37
|
+
reject(new Error('Login timed out after 2 minutes. Try again.'));
|
|
38
|
+
}, timeoutMs);
|
|
39
|
+
|
|
40
|
+
const server = createServer((req, res) => {
|
|
41
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
42
|
+
const token = url.searchParams.get('token');
|
|
43
|
+
const login = url.searchParams.get('login');
|
|
44
|
+
const error = url.searchParams.get('error');
|
|
45
|
+
|
|
46
|
+
// Send a nice success/error page back to the browser
|
|
47
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
48
|
+
if (token) {
|
|
49
|
+
res.end(`<!DOCTYPE html><html><head><title>MeshWire</title>
|
|
50
|
+
<style>body{font-family:system-ui;background:#080808;color:#f5f5f5;display:grid;place-items:center;height:100vh;margin:0}
|
|
51
|
+
.box{text-align:center;padding:40px;border:1px solid rgba(124,58,237,.4);border-radius:20px;background:rgba(124,58,237,.08)}
|
|
52
|
+
h2{color:#a78bfa;margin-bottom:12px}p{color:rgba(255,255,255,.6)}</style></head>
|
|
53
|
+
<body><div class="box"><h2>✅ Signed in as ${login || 'you'}!</h2>
|
|
54
|
+
<p>You can close this tab and return to your terminal.</p></div></body></html>`);
|
|
55
|
+
} else {
|
|
56
|
+
res.end(`<!DOCTYPE html><html><head><title>MeshWire</title>
|
|
57
|
+
<style>body{font-family:system-ui;background:#080808;color:#f5f5f5;display:grid;place-items:center;height:100vh;margin:0}
|
|
58
|
+
.box{text-align:center;padding:40px;border:1px solid rgba(251,113,133,.4);border-radius:20px}
|
|
59
|
+
h2{color:#fb7185;margin-bottom:12px}p{color:rgba(255,255,255,.6)}</style></head>
|
|
60
|
+
<body><div class="box"><h2>❌ Login failed</h2>
|
|
61
|
+
<p>${error || 'Unknown error'}. Please close this tab and try again.</p></div></body></html>`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
server.close();
|
|
66
|
+
|
|
67
|
+
if (token) {
|
|
68
|
+
resolve({ token, login });
|
|
69
|
+
} else {
|
|
70
|
+
reject(new Error(`Login failed: ${error || 'unknown error'}`));
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
server.listen(port, '127.0.0.1', () => {
|
|
75
|
+
// Server is ready
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
server.on('error', (err) => {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
reject(err);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function openBrowser(url) {
|
|
86
|
+
const { platform } = process;
|
|
87
|
+
const { execSync } = await import('child_process');
|
|
88
|
+
try {
|
|
89
|
+
if (platform === 'darwin') execSync(`open "${url}"`, { stdio: 'ignore' });
|
|
90
|
+
else if (platform === 'win32') execSync(`start "" "${url}"`, { shell: true, stdio: 'ignore' });
|
|
91
|
+
else execSync(`xdg-open "${url}"`, { stdio: 'ignore' });
|
|
92
|
+
return true;
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function cmdLogin(opts) {
|
|
99
|
+
const meshwireUrl = opts.url || readConfig().url || DEFAULT_URL;
|
|
100
|
+
|
|
101
|
+
console.log('\n' + chalk.bold('🕸 MeshWire Login') + '\n');
|
|
102
|
+
|
|
103
|
+
// Check if already logged in
|
|
104
|
+
const existing = readCredentials();
|
|
105
|
+
if (existing?.token && !opts.force) {
|
|
106
|
+
console.log(chalk.dim(' Already signed in as ') + chalk.bold(existing.login || 'unknown'));
|
|
107
|
+
console.log(chalk.dim(' Use --force to re-authenticate.\n'));
|
|
108
|
+
|
|
109
|
+
const { createInterface } = await import('readline/promises');
|
|
110
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
111
|
+
const answer = await rl.question(chalk.dim(' Re-authenticate? (y/N) '));
|
|
112
|
+
rl.close();
|
|
113
|
+
if (!answer.toLowerCase().startsWith('y')) {
|
|
114
|
+
console.log(chalk.dim('\n Keeping existing credentials.\n'));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Find a free port
|
|
120
|
+
const port = await findFreePort();
|
|
121
|
+
const authUrl = `${meshwireUrl}/auth/cli?port=${port}`;
|
|
122
|
+
|
|
123
|
+
console.log(chalk.dim(' Opening browser for GitHub OAuth...\n'));
|
|
124
|
+
console.log(chalk.cyan(` ${authUrl}\n`));
|
|
125
|
+
console.log(chalk.dim(' If the browser did not open, paste the URL above.\n'));
|
|
126
|
+
|
|
127
|
+
const opened = await openBrowser(authUrl);
|
|
128
|
+
if (!opened) {
|
|
129
|
+
console.log(chalk.yellow(' Could not open browser automatically.'));
|
|
130
|
+
console.log(chalk.dim(' Please open the URL above in your browser.\n'));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Wait for callback
|
|
134
|
+
let result;
|
|
135
|
+
try {
|
|
136
|
+
result = await waitForCallback(port);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error('\n' + chalk.red(` ✗ ${err.message}\n`));
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const { token, login } = result;
|
|
143
|
+
|
|
144
|
+
// Save to credentials.json
|
|
145
|
+
writeCredentials({ token, login, savedAt: new Date().toISOString() });
|
|
146
|
+
|
|
147
|
+
// Also update legacy config.json for backward compat
|
|
148
|
+
writeConfig({ token });
|
|
149
|
+
|
|
150
|
+
console.log(chalk.bold.green(`\n ✅ Signed in as ${login}!\n`));
|
|
151
|
+
console.log(chalk.dim(' Credentials saved to ~/.meshwire/credentials.json\n'));
|
|
152
|
+
|
|
153
|
+
// Optionally create a mesh if none exists
|
|
154
|
+
const config = readConfig();
|
|
155
|
+
if (!config.meshId && !opts.skipMesh) {
|
|
156
|
+
console.log(chalk.dim(' No mesh configured yet.'));
|
|
157
|
+
console.log(chalk.dim(` Run ${chalk.cyan('meshwire mesh create')} to create one, or`));
|
|
158
|
+
console.log(chalk.dim(` run ${chalk.cyan('meshwire init')} for full interactive setup.\n`));
|
|
159
|
+
} else if (config.meshId) {
|
|
160
|
+
// Update credentials with default mesh
|
|
161
|
+
writeCredentials({ token, login, defaultMeshId: config.meshId, savedAt: new Date().toISOString() });
|
|
162
|
+
console.log(chalk.dim(` Default mesh: ${config.meshId}`));
|
|
163
|
+
console.log('');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Verify connection
|
|
167
|
+
try {
|
|
168
|
+
const client = new MeshWireClient({ url: meshwireUrl, token });
|
|
169
|
+
await client.health();
|
|
170
|
+
console.log(chalk.green(' ✓ Connected to MeshWire\n'));
|
|
171
|
+
} catch {
|
|
172
|
+
console.log(chalk.yellow(' ⚠ Could not verify connection — check your URL\n'));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log(chalk.dim(' Next steps:'));
|
|
176
|
+
console.log(chalk.cyan(' meshwire init') + chalk.dim(' — full workspace setup'));
|
|
177
|
+
console.log(chalk.cyan(' meshwire init --harness copilot') + chalk.dim(' — set up Copilot CLI extension'));
|
|
178
|
+
console.log(chalk.cyan(' meshwire status') + chalk.dim(' — verify everything is wired\n'));
|
|
179
|
+
}
|
package/src/commands/mesh.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// meshwire mesh — create, list, switch meshes
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { requireConfig, writeConfig, readConfig } from '../config.js';
|
|
4
|
+
import { writeMeshJson } from '../mesh-schema.js';
|
|
4
5
|
import { MeshWireClient } from '../api.js';
|
|
5
6
|
|
|
6
7
|
export async function cmdMesh(subcommand, opts) {
|
|
@@ -22,8 +23,23 @@ export async function cmdMesh(subcommand, opts) {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
case 'use': {
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const { meshId } = opts;
|
|
27
|
+
// Write to global config
|
|
28
|
+
writeConfig({ meshId });
|
|
29
|
+
|
|
30
|
+
// Also write .mesh.json in the current workspace directory
|
|
31
|
+
// so this folder is associated with the mesh
|
|
32
|
+
const config = readConfig();
|
|
33
|
+
writeMeshJson({
|
|
34
|
+
mesh_id: meshId,
|
|
35
|
+
workspace_name: process.cwd().split(/[/\\]/).pop() || 'workspace',
|
|
36
|
+
agent_name: config.agentName || 'agent',
|
|
37
|
+
harness: config.harness || 'copilot',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
console.log(chalk.green(`✓ Active mesh: ${meshId}`));
|
|
41
|
+
console.log(chalk.dim(` ~/.meshwire/config.json updated`));
|
|
42
|
+
console.log(chalk.dim(` .mesh.json written to current directory`));
|
|
27
43
|
break;
|
|
28
44
|
}
|
|
29
45
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Claude Desktop / Cursor harness setup
|
|
2
|
+
// Generates MCP server config at ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
3
|
+
// (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows)
|
|
4
|
+
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
6
|
+
import { homedir, platform } from 'os';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { writeMeshJson, readMeshJson } from '../mesh-schema.js';
|
|
10
|
+
import { readCredentials } from '../auth.js';
|
|
11
|
+
import { MeshWireClient } from '../api.js';
|
|
12
|
+
|
|
13
|
+
function getClaudeConfigPath() {
|
|
14
|
+
if (platform() === 'win32') {
|
|
15
|
+
return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
|
|
16
|
+
}
|
|
17
|
+
if (platform() === 'darwin') {
|
|
18
|
+
return join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
19
|
+
}
|
|
20
|
+
return join(homedir(), '.config', 'claude', 'claude_desktop_config.json');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function setupClaude({ meshId, agentName, meshwireUrl, workspaceName }) {
|
|
24
|
+
console.log('\n' + chalk.bold('⚙️ Setting up Claude Desktop / Cursor harness') + '\n');
|
|
25
|
+
|
|
26
|
+
const creds = readCredentials();
|
|
27
|
+
if (!creds?.token) {
|
|
28
|
+
console.error(chalk.red(' ✗ Not authenticated. Run `meshwire login` first.\n'));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 1. Write .mesh.json
|
|
33
|
+
const existing = readMeshJson() || {};
|
|
34
|
+
const meshJsonData = {
|
|
35
|
+
mesh_id: meshId || existing.mesh_id,
|
|
36
|
+
workspace_name: workspaceName || existing.workspace_name || process.cwd().split(/[/\\]/).pop(),
|
|
37
|
+
agent_name: agentName || existing.agent_name || 'claude-agent',
|
|
38
|
+
harness: 'claude',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (!meshJsonData.mesh_id) {
|
|
42
|
+
console.error(chalk.red(' ✗ No mesh ID. Run `meshwire mesh create` first.\n'));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
writeMeshJson(meshJsonData);
|
|
47
|
+
console.log(chalk.green(' ✓ .mesh.json written'));
|
|
48
|
+
|
|
49
|
+
// 2. Update Claude Desktop config
|
|
50
|
+
const configPath = getClaudeConfigPath();
|
|
51
|
+
let config = {};
|
|
52
|
+
if (existsSync(configPath)) {
|
|
53
|
+
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch { config = {}; }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
config.mcpServers = config.mcpServers || {};
|
|
57
|
+
config.mcpServers.meshwire = {
|
|
58
|
+
command: 'npx',
|
|
59
|
+
args: ['meshwire', 'mcp', '--mesh', meshJsonData.mesh_id, '--agent', meshJsonData.agent_name],
|
|
60
|
+
env: {
|
|
61
|
+
MESHWIRE_TOKEN: creds.token,
|
|
62
|
+
MESHWIRE_URL: meshwireUrl || 'https://meshwire.io',
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
mkdirSync(join(configPath, '..'), { recursive: true });
|
|
67
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
68
|
+
console.log(chalk.green(`\n ✓ Claude Desktop config updated`));
|
|
69
|
+
console.log(chalk.dim(` ${configPath}`));
|
|
70
|
+
|
|
71
|
+
// 3. Register agent
|
|
72
|
+
try {
|
|
73
|
+
const client = new MeshWireClient({ url: meshwireUrl, token: creds.token, meshId: meshJsonData.mesh_id });
|
|
74
|
+
const agent = await client.registerAgent(meshJsonData.mesh_id, {
|
|
75
|
+
name: meshJsonData.agent_name,
|
|
76
|
+
description: 'Claude Desktop / Cursor agent',
|
|
77
|
+
workspace: meshJsonData.workspace_name,
|
|
78
|
+
metadata: { platform: 'claude', harness: 'meshwire' },
|
|
79
|
+
});
|
|
80
|
+
console.log(chalk.green(` ✓ Registered as ${agent.name} (${agent.agent_id})`));
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.log(chalk.yellow(` ⚠ ${err.message}`));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log('\n' + chalk.bold.green(' ✅ Claude harness ready!\n'));
|
|
86
|
+
console.log(chalk.dim(' Restart Claude Desktop to load the MeshWire MCP server.'));
|
|
87
|
+
console.log(chalk.dim(' MCP tools: meshwire_send_message, meshwire_get_messages, meshwire_list_agents\n'));
|
|
88
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// Copilot CLI harness setup
|
|
2
|
+
// Generates a user-level Copilot extension at ~/.copilot/extensions/meshwire.mjs
|
|
3
|
+
// The extension reads credentials from ~/.meshwire/credentials.json and
|
|
4
|
+
// workspace config from .mesh.json in the current directory.
|
|
5
|
+
|
|
6
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { writeMeshJson, readMeshJson } from '../mesh-schema.js';
|
|
11
|
+
import { readCredentials } from '../auth.js';
|
|
12
|
+
import { MeshWireClient } from '../api.js';
|
|
13
|
+
|
|
14
|
+
const EXTENSION_DIR = join(homedir(), '.copilot', 'extensions');
|
|
15
|
+
const EXTENSION_FILE = join(EXTENSION_DIR, 'meshwire.mjs');
|
|
16
|
+
|
|
17
|
+
// The generated extension template
|
|
18
|
+
function buildExtensionSource({ meshwireUrl }) {
|
|
19
|
+
return `// MeshWire — Copilot CLI Extension
|
|
20
|
+
// Auto-generated by: meshwire init --harness copilot
|
|
21
|
+
// Reads credentials from ~/.meshwire/credentials.json
|
|
22
|
+
// Reads workspace mesh from .mesh.json in the current directory
|
|
23
|
+
|
|
24
|
+
import { readFileSync, existsSync } from 'fs';
|
|
25
|
+
import { homedir } from 'os';
|
|
26
|
+
import { join } from 'path';
|
|
27
|
+
|
|
28
|
+
const CREDS_FILE = join(homedir(), '.meshwire', 'credentials.json');
|
|
29
|
+
const DEFAULT_URL = '${meshwireUrl}';
|
|
30
|
+
|
|
31
|
+
function loadCreds() {
|
|
32
|
+
if (!existsSync(CREDS_FILE)) return null;
|
|
33
|
+
try { return JSON.parse(readFileSync(CREDS_FILE, 'utf8')); } catch { return null; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function loadMeshJson() {
|
|
37
|
+
const file = join(process.cwd(), '.mesh.json');
|
|
38
|
+
if (!existsSync(file)) return null;
|
|
39
|
+
try { return JSON.parse(readFileSync(file, 'utf8')); } catch { return null; }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function api(path, method = 'GET', body = null) {
|
|
43
|
+
const creds = loadCreds();
|
|
44
|
+
if (!creds?.token) throw new Error('Not authenticated. Run: meshwire login');
|
|
45
|
+
const meshJson = loadMeshJson();
|
|
46
|
+
const url = DEFAULT_URL + path.replace('{meshId}', meshJson?.mesh_id || '');
|
|
47
|
+
const opts = {
|
|
48
|
+
method,
|
|
49
|
+
headers: {
|
|
50
|
+
'Authorization': 'Bearer ' + creds.token,
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
if (body) opts.body = JSON.stringify(body);
|
|
55
|
+
const res = await fetch(url, opts);
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const txt = await res.text().catch(() => '');
|
|
58
|
+
let msg; try { msg = JSON.parse(txt).error; } catch { msg = txt || res.statusText; }
|
|
59
|
+
throw new Error(msg);
|
|
60
|
+
}
|
|
61
|
+
return res.json();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Tool Definitions ────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
export const tools = [
|
|
67
|
+
{
|
|
68
|
+
name: 'mesh_send_message',
|
|
69
|
+
description: 'Send a message to agents in the mesh. Use recipient_id "*" to broadcast.',
|
|
70
|
+
parameters: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
content: { type: 'string', description: 'Message content' },
|
|
74
|
+
recipient_id: { type: 'string', default: '*', description: 'Target agent ID or "*" for all' },
|
|
75
|
+
priority: { type: 'string', enum: ['urgent', 'high', 'normal', 'low'], default: 'normal' },
|
|
76
|
+
},
|
|
77
|
+
required: ['content'],
|
|
78
|
+
},
|
|
79
|
+
async execute({ content, recipient_id = '*', priority = 'normal' }) {
|
|
80
|
+
const creds = loadCreds();
|
|
81
|
+
const meshJson = loadMeshJson();
|
|
82
|
+
if (!meshJson?.mesh_id) throw new Error('No mesh configured. Add .mesh.json to this workspace.');
|
|
83
|
+
const agent_id = creds?.agentId || 'copilot-cli';
|
|
84
|
+
const msg = await api(
|
|
85
|
+
'/mesh/{meshId}/messages', 'POST',
|
|
86
|
+
{ sender_id: agent_id, recipient_id, content, priority }
|
|
87
|
+
);
|
|
88
|
+
return { sent: true, message_id: msg.message_id, to: recipient_id };
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
{
|
|
93
|
+
name: 'mesh_get_messages',
|
|
94
|
+
description: 'Fetch new messages from the mesh. Uses long-polling (up to 10s).',
|
|
95
|
+
parameters: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
offset: { type: 'integer', default: 0, description: 'Return messages with ID > offset' },
|
|
99
|
+
timeout: { type: 'integer', default: 10, description: 'Poll timeout in seconds (max 30)' },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
async execute({ offset = 0, timeout = 10 }) {
|
|
103
|
+
const creds = loadCreds();
|
|
104
|
+
const meshJson = loadMeshJson();
|
|
105
|
+
if (!meshJson?.mesh_id) throw new Error('No mesh configured. Add .mesh.json to this workspace.');
|
|
106
|
+
const agentId = creds?.agentId || undefined;
|
|
107
|
+
const params = new URLSearchParams({ offset: String(offset), timeout: String(Math.min(timeout, 30)) });
|
|
108
|
+
if (agentId) params.set('recipient', agentId);
|
|
109
|
+
const result = await api('/mesh/{meshId}/messages?' + params.toString());
|
|
110
|
+
if (!result.messages?.length) return { messages: [], count: 0 };
|
|
111
|
+
return {
|
|
112
|
+
count: result.count,
|
|
113
|
+
messages: result.messages.map(m => ({
|
|
114
|
+
id: m.message_id,
|
|
115
|
+
from: m.sender_id,
|
|
116
|
+
to: m.recipient_id,
|
|
117
|
+
content: m.content,
|
|
118
|
+
priority: m.priority,
|
|
119
|
+
time: m.created_at,
|
|
120
|
+
})),
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
{
|
|
126
|
+
name: 'mesh_list_agents',
|
|
127
|
+
description: 'List all agents currently registered in this workspace mesh.',
|
|
128
|
+
parameters: { type: 'object', properties: {} },
|
|
129
|
+
async execute() {
|
|
130
|
+
const meshJson = loadMeshJson();
|
|
131
|
+
if (!meshJson?.mesh_id) throw new Error('No mesh configured. Add .mesh.json to this workspace.');
|
|
132
|
+
const result = await api('/mesh/{meshId}/agents');
|
|
133
|
+
return {
|
|
134
|
+
count: result.count,
|
|
135
|
+
agents: result.agents.map(a => ({
|
|
136
|
+
id: a.agent_id,
|
|
137
|
+
name: a.name,
|
|
138
|
+
status: a.status,
|
|
139
|
+
workspace: a.workspace,
|
|
140
|
+
last_seen: a.last_seen,
|
|
141
|
+
})),
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
{
|
|
147
|
+
name: 'mesh_status',
|
|
148
|
+
description: 'Check MeshWire connection status and current mesh/agent info.',
|
|
149
|
+
parameters: { type: 'object', properties: {} },
|
|
150
|
+
async execute() {
|
|
151
|
+
const creds = loadCreds();
|
|
152
|
+
const meshJson = loadMeshJson();
|
|
153
|
+
const health = await fetch(DEFAULT_URL + '/health').then(r => r.json()).catch(() => null);
|
|
154
|
+
return {
|
|
155
|
+
authenticated: Boolean(creds?.token),
|
|
156
|
+
login: creds?.login || null,
|
|
157
|
+
mesh_id: meshJson?.mesh_id || null,
|
|
158
|
+
workspace: meshJson?.workspace_name || null,
|
|
159
|
+
agent_name: meshJson?.agent_name || null,
|
|
160
|
+
service: health?.status || 'unreachable',
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
// ─── Hooks ───────────────────────────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
export const hooks = {
|
|
169
|
+
// Called on session start — register this agent in the mesh
|
|
170
|
+
async onSessionStart({ agentName } = {}) {
|
|
171
|
+
const creds = loadCreds();
|
|
172
|
+
const meshJson = loadMeshJson();
|
|
173
|
+
if (!creds?.token || !meshJson?.mesh_id) return;
|
|
174
|
+
|
|
175
|
+
const name = agentName || meshJson?.agent_name || 'copilot-cli';
|
|
176
|
+
try {
|
|
177
|
+
const agent = await api('/mesh/{meshId}/agents', 'POST', {
|
|
178
|
+
name,
|
|
179
|
+
description: 'GitHub Copilot CLI agent',
|
|
180
|
+
workspace: process.cwd().split('/').pop() || 'workspace',
|
|
181
|
+
metadata: { platform: 'copilot-cli', harness: 'meshwire' },
|
|
182
|
+
});
|
|
183
|
+
// Store agent ID in a runtime variable for this session
|
|
184
|
+
if (typeof globalThis !== 'undefined') {
|
|
185
|
+
globalThis.__meshwire_agent_id__ = agent.agent_id;
|
|
186
|
+
}
|
|
187
|
+
console.log('[meshwire] Registered as ' + name + ' (' + agent.agent_id + ')');
|
|
188
|
+
} catch (err) {
|
|
189
|
+
console.warn('[meshwire] Could not register agent:', err.message);
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// Called periodically — keep heartbeat alive
|
|
194
|
+
async onHeartbeat() {
|
|
195
|
+
const creds = loadCreds();
|
|
196
|
+
const meshJson = loadMeshJson();
|
|
197
|
+
const agentId = globalThis.__meshwire_agent_id__;
|
|
198
|
+
if (!creds?.token || !meshJson?.mesh_id || !agentId) return;
|
|
199
|
+
await api('/mesh/{meshId}/agents/' + agentId + '/heartbeat', 'POST').catch(() => {});
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function setupCopilot({ meshId, agentName, meshwireUrl, workspaceName }) {
|
|
206
|
+
console.log('\n' + chalk.bold('⚙️ Setting up Copilot CLI harness') + '\n');
|
|
207
|
+
|
|
208
|
+
const creds = readCredentials();
|
|
209
|
+
if (!creds?.token) {
|
|
210
|
+
console.error(chalk.red(' ✗ Not authenticated. Run `meshwire login` first.\n'));
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 1. Create/update .mesh.json in the current workspace
|
|
215
|
+
const existing = readMeshJson() || {};
|
|
216
|
+
const meshJsonData = {
|
|
217
|
+
mesh_id: meshId || existing.mesh_id,
|
|
218
|
+
workspace_name: workspaceName || existing.workspace_name || process.cwd().split(/[/\\]/).pop(),
|
|
219
|
+
agent_name: agentName || existing.agent_name || 'copilot-agent',
|
|
220
|
+
harness: 'copilot',
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
if (!meshJsonData.mesh_id) {
|
|
224
|
+
console.error(chalk.red(' ✗ No mesh ID. Create a mesh first: meshwire mesh create\n'));
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
writeMeshJson(meshJsonData);
|
|
229
|
+
console.log(chalk.green(' ✓ .mesh.json written'));
|
|
230
|
+
console.log(chalk.dim(` mesh_id: ${meshJsonData.mesh_id}`));
|
|
231
|
+
console.log(chalk.dim(` agent_name: ${meshJsonData.agent_name}`));
|
|
232
|
+
console.log(chalk.dim(` harness: copilot`));
|
|
233
|
+
|
|
234
|
+
// 2. Generate and write the Copilot extension
|
|
235
|
+
mkdirSync(EXTENSION_DIR, { recursive: true });
|
|
236
|
+
const source = buildExtensionSource({ meshwireUrl: meshwireUrl || 'https://meshwire.io' });
|
|
237
|
+
writeFileSync(EXTENSION_FILE, source, 'utf8');
|
|
238
|
+
console.log(chalk.green(`\n ✓ Extension installed at ${EXTENSION_FILE}`));
|
|
239
|
+
|
|
240
|
+
// 3. Register the agent in the mesh now
|
|
241
|
+
console.log(chalk.dim('\n Registering agent in mesh...'));
|
|
242
|
+
try {
|
|
243
|
+
const client = new MeshWireClient({ url: meshwireUrl, token: creds.token, meshId: meshJsonData.mesh_id });
|
|
244
|
+
const agent = await client.registerAgent(meshJsonData.mesh_id, {
|
|
245
|
+
name: meshJsonData.agent_name,
|
|
246
|
+
description: 'GitHub Copilot CLI agent',
|
|
247
|
+
workspace: meshJsonData.workspace_name,
|
|
248
|
+
metadata: { platform: 'copilot-cli', harness: 'meshwire' },
|
|
249
|
+
});
|
|
250
|
+
console.log(chalk.green(` ✓ Registered as ${agent.name} (${agent.agent_id})`));
|
|
251
|
+
} catch (err) {
|
|
252
|
+
console.log(chalk.yellow(` ⚠ Could not register now: ${err.message}`));
|
|
253
|
+
console.log(chalk.dim(' The extension will auto-register on next Copilot CLI session.'));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 4. Print summary
|
|
257
|
+
console.log('\n' + chalk.bold.green(' ✅ Copilot harness ready!\n'));
|
|
258
|
+
console.log(chalk.dim(' The extension provides these tools in every Copilot session:'));
|
|
259
|
+
console.log(chalk.cyan(' mesh_send_message') + chalk.dim(' — broadcast or target a specific agent'));
|
|
260
|
+
console.log(chalk.cyan(' mesh_get_messages') + chalk.dim(' — poll for new messages'));
|
|
261
|
+
console.log(chalk.cyan(' mesh_list_agents') + chalk.dim(' — discover agents in the mesh'));
|
|
262
|
+
console.log(chalk.cyan(' mesh_status') + chalk.dim(' — check connection health'));
|
|
263
|
+
console.log('');
|
|
264
|
+
console.log(chalk.dim(' The .mesh.json file in this repo defines which mesh Copilot connects to.'));
|
|
265
|
+
console.log(chalk.dim(' Commit .mesh.json so your teammates auto-join the same mesh.\n'));
|
|
266
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Hermes / generic harness setup
|
|
2
|
+
// Writes a skill document + env file for Hermes (Pi) and other harnesses
|
|
3
|
+
|
|
4
|
+
import { writeFileSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { writeMeshJson, readMeshJson } from '../mesh-schema.js';
|
|
8
|
+
import { readCredentials } from '../auth.js';
|
|
9
|
+
import { MeshWireClient } from '../api.js';
|
|
10
|
+
|
|
11
|
+
export async function setupHermes({ meshId, agentName, meshwireUrl, workspaceName }) {
|
|
12
|
+
console.log('\n' + chalk.bold('⚙️ Setting up Hermes / generic harness') + '\n');
|
|
13
|
+
|
|
14
|
+
const creds = readCredentials();
|
|
15
|
+
if (!creds?.token) {
|
|
16
|
+
console.error(chalk.red(' ✗ Not authenticated. Run `meshwire login` first.\n'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const existing = readMeshJson() || {};
|
|
21
|
+
const meshJsonData = {
|
|
22
|
+
mesh_id: meshId || existing.mesh_id,
|
|
23
|
+
workspace_name: workspaceName || existing.workspace_name || 'hermes',
|
|
24
|
+
agent_name: agentName || existing.agent_name || 'hermes-agent',
|
|
25
|
+
harness: 'hermes',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (!meshJsonData.mesh_id) {
|
|
29
|
+
console.error(chalk.red(' ✗ No mesh ID. Run `meshwire mesh create` first.\n'));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
writeMeshJson(meshJsonData);
|
|
34
|
+
console.log(chalk.green(' ✓ .mesh.json written'));
|
|
35
|
+
|
|
36
|
+
// Write a .env.meshwire file for Hermes to source
|
|
37
|
+
const envContent = [
|
|
38
|
+
`MESHWIRE_TOKEN=${creds.token}`,
|
|
39
|
+
`MESHWIRE_URL=${meshwireUrl || 'https://meshwire.io'}`,
|
|
40
|
+
`MESHWIRE_MESH_ID=${meshJsonData.mesh_id}`,
|
|
41
|
+
`MESHWIRE_AGENT_NAME=${meshJsonData.agent_name}`,
|
|
42
|
+
'',
|
|
43
|
+
].join('\n');
|
|
44
|
+
|
|
45
|
+
writeFileSync(join(process.cwd(), '.env.meshwire'), envContent, 'utf8');
|
|
46
|
+
console.log(chalk.green(' ✓ .env.meshwire written'));
|
|
47
|
+
|
|
48
|
+
// Write skill document
|
|
49
|
+
const skillContent = `# MeshWire Skill — ${meshJsonData.workspace_name}
|
|
50
|
+
|
|
51
|
+
## Mesh
|
|
52
|
+
\`\`\`
|
|
53
|
+
MESH_ID=${meshJsonData.mesh_id}
|
|
54
|
+
AGENT_NAME=${meshJsonData.agent_name}
|
|
55
|
+
BASE_URL=${meshwireUrl || 'https://meshwire.io'}
|
|
56
|
+
\`\`\`
|
|
57
|
+
|
|
58
|
+
## Quick commands
|
|
59
|
+
|
|
60
|
+
### Send message
|
|
61
|
+
\`\`\`bash
|
|
62
|
+
curl -sX POST $MESHWIRE_URL/mesh/${meshJsonData.mesh_id}/messages \\
|
|
63
|
+
-H "Authorization: Bearer $MESHWIRE_TOKEN" \\
|
|
64
|
+
-H "Content-Type: application/json" \\
|
|
65
|
+
-d '{"sender_id":"YOUR_AGENT_ID","content":"hello","recipient_id":"*"}'
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
### Poll messages
|
|
69
|
+
\`\`\`bash
|
|
70
|
+
curl -s "$MESHWIRE_URL/mesh/${meshJsonData.mesh_id}/messages?timeout=30&offset=0" \\
|
|
71
|
+
-H "Authorization: Bearer $MESHWIRE_TOKEN"
|
|
72
|
+
\`\`\`
|
|
73
|
+
|
|
74
|
+
### List agents
|
|
75
|
+
\`\`\`bash
|
|
76
|
+
curl -s "$MESHWIRE_URL/mesh/${meshJsonData.mesh_id}/agents" \\
|
|
77
|
+
-H "Authorization: Bearer $MESHWIRE_TOKEN"
|
|
78
|
+
\`\`\`
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
writeFileSync(join(process.cwd(), 'MESHWIRE_SKILL.md'), skillContent, 'utf8');
|
|
82
|
+
console.log(chalk.green(' ✓ MESHWIRE_SKILL.md written'));
|
|
83
|
+
|
|
84
|
+
// Register agent
|
|
85
|
+
try {
|
|
86
|
+
const client = new MeshWireClient({ url: meshwireUrl, token: creds.token, meshId: meshJsonData.mesh_id });
|
|
87
|
+
const agent = await client.registerAgent(meshJsonData.mesh_id, {
|
|
88
|
+
name: meshJsonData.agent_name,
|
|
89
|
+
description: 'Hermes agent',
|
|
90
|
+
workspace: meshJsonData.workspace_name,
|
|
91
|
+
metadata: { platform: 'hermes', harness: 'meshwire' },
|
|
92
|
+
});
|
|
93
|
+
console.log(chalk.green(` ✓ Registered as ${agent.name} (${agent.agent_id})`));
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.log(chalk.yellow(` ⚠ ${err.message}`));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log('\n' + chalk.bold.green(' ✅ Hermes harness ready!\n'));
|
|
99
|
+
console.log(chalk.dim(' Source .env.meshwire in your Hermes startup script.'));
|
|
100
|
+
console.log(chalk.dim(' Drop MESHWIRE_SKILL.md into your agent context.\n'));
|
|
101
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// .mesh.json — workspace-level mesh config
|
|
2
|
+
// Lives in the repo root. Defines which mesh this workspace participates in.
|
|
3
|
+
//
|
|
4
|
+
// Schema:
|
|
5
|
+
// {
|
|
6
|
+
// "mesh_id": "kR9xQpLmW3aZ", — which mesh this workspace joins
|
|
7
|
+
// "workspace_name": "rocha-family", — human label for this workspace
|
|
8
|
+
// "agent_name": "copilot-assistant", — how this agent appears in the mesh
|
|
9
|
+
// "harness": "copilot" — which harness drives this workspace
|
|
10
|
+
// }
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
|
|
15
|
+
const MESH_JSON_FILE = '.mesh.json';
|
|
16
|
+
|
|
17
|
+
export function readMeshJson(cwd = process.cwd()) {
|
|
18
|
+
const file = join(cwd, MESH_JSON_FILE);
|
|
19
|
+
if (!existsSync(file)) return null;
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(readFileSync(file, 'utf8'));
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function writeMeshJson(data, cwd = process.cwd()) {
|
|
28
|
+
const file = join(cwd, MESH_JSON_FILE);
|
|
29
|
+
writeFileSync(file, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function meshJsonExists(cwd = process.cwd()) {
|
|
33
|
+
return existsSync(join(cwd, MESH_JSON_FILE));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const MESH_JSON_SCHEMA = {
|
|
37
|
+
mesh_id: 'string — which mesh this workspace connects to',
|
|
38
|
+
workspace_name: 'string — human-readable label for this workspace',
|
|
39
|
+
agent_name: 'string — how this agent appears in the mesh',
|
|
40
|
+
harness: 'string — copilot | claude | hermes | cursor | raw',
|
|
41
|
+
};
|