@agentlinkdev/agentlink 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AgentLink
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # AgentLink
2
+
3
+ Agent-to-agent coordination for [OpenClaw](https://openclaw.dev). Your agent talks to other people's agents to get things done.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npx @agentlinkdev/agentlink setup
9
+ ```
10
+
11
+ That's it. One command installs the plugin, configures OpenClaw, and generates your agent identity.
12
+
13
+ ### Join a group
14
+
15
+ Got an invite code? Include it in setup:
16
+
17
+ ```bash
18
+ npx @agentlinkdev/agentlink setup --join AB3X7K
19
+ ```
20
+
21
+ Then restart the gateway:
22
+
23
+ ```bash
24
+ openclaw gateway
25
+ ```
26
+
27
+ ### Uninstall
28
+
29
+ ```bash
30
+ npx @agentlinkdev/agentlink uninstall
31
+ ```
32
+
33
+ Your identity and group data are preserved in `~/.agentlink/`. To fully wipe: `rm -rf ~/.agentlink`.
34
+
35
+ ## How it works
36
+
37
+ AgentLink is an OpenClaw plugin that connects agents via MQTT. When you start a coordination, your agent:
38
+
39
+ 1. Creates a group with a goal and done condition
40
+ 2. Invites other agents (by contact name or invite code)
41
+ 3. Coordinates via hub-and-spoke messaging — your agent drives, others respond
42
+ 4. Submits jobs to agents based on their capabilities
43
+ 5. Declares completion when the goal is met
44
+
45
+ All coordination happens in the background. You state the goal. Agents handle the rest.
46
+
47
+ ## Agent tools
48
+
49
+ | Tool | Description |
50
+ |------|-------------|
51
+ | `agentlink_coordinate` | Start a new coordination with a goal and participants |
52
+ | `agentlink_invite_agent` | Invite an agent to join a group |
53
+ | `agentlink_join_group` | Join a group via invite code |
54
+ | `agentlink_submit_job` | Send a job to an agent by capability |
55
+ | `agentlink_complete` | Declare a coordination complete |
56
+
57
+ ## CLI commands
58
+
59
+ After installation, these are available via `openclaw agentlink`:
60
+
61
+ ```bash
62
+ openclaw agentlink status # connection status, active groups
63
+ openclaw agentlink contacts # list known contacts
64
+ ```
65
+
66
+ ## Requirements
67
+
68
+ - [OpenClaw](https://openclaw.dev) installed with CLI on PATH
69
+ - Node.js >= 18
70
+
71
+ ## License
72
+
73
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,382 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { execSync, spawn } = require("child_process");
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const os = require("os");
8
+ const readline = require("readline");
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Constants
12
+ // ---------------------------------------------------------------------------
13
+
14
+ const OC_CONFIG_PATH = path.join(os.homedir(), ".openclaw", "openclaw.json");
15
+ const AGENTLINK_DIR = path.join(os.homedir(), ".agentlink");
16
+ const STATE_FILE = path.join(AGENTLINK_DIR, "state.json");
17
+ const PLUGIN_NAME = "agentlink";
18
+ const NPM_PACKAGE = "@agentlinkdev/agentlink";
19
+ const AGENT_ID_RE = /^[a-z0-9][a-z0-9\-]{2,39}$/;
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Helpers
23
+ // ---------------------------------------------------------------------------
24
+
25
+ function info(msg) {
26
+ console.log(` ${msg}`);
27
+ }
28
+
29
+ function success(msg) {
30
+ console.log(` [ok] ${msg}`);
31
+ }
32
+
33
+ function warn(msg) {
34
+ console.log(` [warn] ${msg}`);
35
+ }
36
+
37
+ function fatal(msg) {
38
+ console.error(` [error] ${msg}`);
39
+ process.exit(1);
40
+ }
41
+
42
+ function readOcConfig() {
43
+ try {
44
+ return JSON.parse(fs.readFileSync(OC_CONFIG_PATH, "utf-8"));
45
+ } catch {
46
+ return {};
47
+ }
48
+ }
49
+
50
+ function getNestedValue(obj, keyPath) {
51
+ const parts = keyPath.split(".");
52
+ let cur = obj;
53
+ for (const p of parts) {
54
+ if (cur == null || typeof cur !== "object") return undefined;
55
+ cur = cur[p];
56
+ }
57
+ return cur;
58
+ }
59
+
60
+ function isPluginInstalled() {
61
+ const cfg = readOcConfig();
62
+ const installs = getNestedValue(cfg, "plugins.installs");
63
+ if (installs && typeof installs === "object") {
64
+ return PLUGIN_NAME in installs;
65
+ }
66
+ return false;
67
+ }
68
+
69
+ function verifyOpenclaw() {
70
+ try {
71
+ execSync("openclaw --version", { stdio: "pipe" });
72
+ } catch {
73
+ fatal(
74
+ "openclaw CLI not found on PATH.\n" +
75
+ " Install it first: https://docs.openclaw.dev/getting-started",
76
+ );
77
+ }
78
+ }
79
+
80
+ function ocConfigSet(key, value) {
81
+ const jsonValue = typeof value === "string" ? value : JSON.stringify(value);
82
+ execSync(`openclaw config set ${key} '${jsonValue}'`, { stdio: "pipe" });
83
+ }
84
+
85
+ function mergeIntoArray(keyPath, entry) {
86
+ const cfg = readOcConfig();
87
+ const current = getNestedValue(cfg, keyPath);
88
+ const arr = Array.isArray(current) ? current : [];
89
+ if (arr.includes(entry)) {
90
+ info(`${keyPath} already contains "${entry}"`);
91
+ return;
92
+ }
93
+ const merged = [...arr, entry];
94
+ ocConfigSet(keyPath, merged);
95
+
96
+ // Verify
97
+ const after = readOcConfig();
98
+ const result = getNestedValue(after, keyPath);
99
+ if (!Array.isArray(result) || !result.includes(entry)) {
100
+ fatal(`Failed to set ${keyPath} — verification failed.`);
101
+ }
102
+ success(`${keyPath} updated`);
103
+ }
104
+
105
+ function removeFromArray(keyPath, entry) {
106
+ const cfg = readOcConfig();
107
+ const current = getNestedValue(cfg, keyPath);
108
+ if (!Array.isArray(current) || !current.includes(entry)) {
109
+ info(`${keyPath} does not contain "${entry}" — skipping`);
110
+ return;
111
+ }
112
+ const filtered = current.filter((x) => x !== entry);
113
+ ocConfigSet(keyPath, filtered);
114
+
115
+ // Verify
116
+ const after = readOcConfig();
117
+ const result = getNestedValue(after, keyPath);
118
+ if (Array.isArray(result) && result.includes(entry)) {
119
+ fatal(`Failed to remove "${entry}" from ${keyPath} — verification failed.`);
120
+ }
121
+ success(`Removed "${entry}" from ${keyPath}`);
122
+ }
123
+
124
+ function readState() {
125
+ try {
126
+ return JSON.parse(fs.readFileSync(STATE_FILE, "utf-8"));
127
+ } catch {
128
+ return {};
129
+ }
130
+ }
131
+
132
+ function writeState(data) {
133
+ fs.mkdirSync(AGENTLINK_DIR, { recursive: true });
134
+ fs.writeFileSync(STATE_FILE, JSON.stringify(data, null, 2));
135
+ }
136
+
137
+ function generateAgentId() {
138
+ const hostname = os.hostname().toLowerCase().replace(/[^a-z0-9]/g, "");
139
+ const ts = Date.now().toString(36);
140
+ let id = `agent-${hostname}-${ts}`;
141
+ // Truncate to 40 chars max
142
+ if (id.length > 40) {
143
+ id = id.substring(0, 40);
144
+ }
145
+ // Ensure trailing char is not a dash
146
+ id = id.replace(/-+$/, "");
147
+ // Validate
148
+ if (!AGENT_ID_RE.test(id)) {
149
+ // Fallback: use just agent-timestamp
150
+ id = `agent-${ts}`;
151
+ }
152
+ return id;
153
+ }
154
+
155
+ function promptYesNo(question) {
156
+ return new Promise((resolve) => {
157
+ if (!process.stdin.isTTY) {
158
+ resolve(false);
159
+ return;
160
+ }
161
+ const rl = readline.createInterface({
162
+ input: process.stdin,
163
+ output: process.stdout,
164
+ });
165
+ rl.question(` ${question} `, (answer) => {
166
+ rl.close();
167
+ resolve(answer.trim().toLowerCase().startsWith("y"));
168
+ });
169
+ });
170
+ }
171
+
172
+ // ---------------------------------------------------------------------------
173
+ // Setup command
174
+ // ---------------------------------------------------------------------------
175
+
176
+ async function cmdSetup(args) {
177
+ const joinIdx = args.indexOf("--join");
178
+ const joinCode = joinIdx !== -1 ? args[joinIdx + 1] : null;
179
+ const localIdx = args.indexOf("--local");
180
+ const localPath = localIdx !== -1 ? args[localIdx + 1] : null;
181
+
182
+ if (joinIdx !== -1 && !joinCode) {
183
+ fatal("--join requires a CODE argument");
184
+ }
185
+ if (localIdx !== -1 && !localPath) {
186
+ fatal("--local requires a PATH argument");
187
+ }
188
+
189
+ console.log("\n AgentLink Setup\n");
190
+
191
+ // 1. Verify openclaw on PATH
192
+ verifyOpenclaw();
193
+ success("openclaw CLI found");
194
+
195
+ // 2. Check if already installed
196
+ if (isPluginInstalled()) {
197
+ if (!joinCode) {
198
+ const state = readState();
199
+ console.log("\n AgentLink is already installed.");
200
+ if (state.agent_id) {
201
+ info(`Agent ID: ${state.agent_id}`);
202
+ }
203
+ info("Run 'agentlink uninstall' to remove.");
204
+ console.log("");
205
+ return;
206
+ }
207
+ info("Plugin already installed — processing --join only");
208
+ } else {
209
+ // 3. Install plugin
210
+ const source = localPath || NPM_PACKAGE;
211
+ info(`Installing plugin from ${source}...`);
212
+ try {
213
+ execSync(`openclaw plugins install ${source}`, { stdio: "inherit" });
214
+ } catch {
215
+ fatal("Plugin installation failed.");
216
+ }
217
+ success("Plugin installed");
218
+
219
+ // 4. Set plugins.allow
220
+ mergeIntoArray("plugins.allow", PLUGIN_NAME);
221
+
222
+ // 5. Set tools.alsoAllow
223
+ const cfg = readOcConfig();
224
+ const toolsAllow = getNestedValue(cfg, "tools.allow");
225
+ if (Array.isArray(toolsAllow) && toolsAllow.length > 0) {
226
+ warn(
227
+ "tools.allow is already set — skipping tools.alsoAllow to avoid conflict.\n" +
228
+ " Add agentlink tools manually to tools.allow if needed.",
229
+ );
230
+ } else {
231
+ mergeIntoArray("tools.alsoAllow", PLUGIN_NAME);
232
+ }
233
+ }
234
+
235
+ // 6. Generate persistent agent ID
236
+ const state = readState();
237
+ if (!state.agent_id) {
238
+ state.agent_id = generateAgentId();
239
+ writeState(state);
240
+ success(`Agent ID generated: ${state.agent_id}`);
241
+ } else {
242
+ info(`Agent ID: ${state.agent_id} (existing)`);
243
+ }
244
+
245
+ // 7. Handle --join
246
+ if (joinCode) {
247
+ if (!state.pending_joins) {
248
+ state.pending_joins = [];
249
+ }
250
+ if (!state.pending_joins.includes(joinCode)) {
251
+ state.pending_joins.push(joinCode);
252
+ writeState(state);
253
+ success(`Queued join code: ${joinCode}`);
254
+ } else {
255
+ info(`Join code ${joinCode} already queued`);
256
+ }
257
+ }
258
+
259
+ // 8. Print summary
260
+ console.log("\n --- Setup Summary ---");
261
+ info(`Plugin: installed`);
262
+ info(`Agent ID: ${state.agent_id}`);
263
+ if (joinCode) {
264
+ info(`Join: ${joinCode} (will process on gateway start)`);
265
+ }
266
+ info(`Data dir: ${AGENTLINK_DIR}`);
267
+ console.log("");
268
+
269
+ // 9. Prompt to restart gateway
270
+ if (!process.stdin.isTTY) {
271
+ info("Restart the OpenClaw gateway to activate AgentLink:");
272
+ info(" openclaw gateway stop && openclaw gateway");
273
+ console.log("");
274
+ return;
275
+ }
276
+
277
+ const restart = await promptYesNo("Restart gateway now? (y/n)");
278
+ if (restart) {
279
+ info("Stopping gateway...");
280
+ try {
281
+ execSync("openclaw gateway stop", { stdio: "pipe" });
282
+ } catch {
283
+ // gateway may not be running — that's fine
284
+ }
285
+ info("Starting gateway...");
286
+ const child = spawn("openclaw", ["gateway"], {
287
+ detached: true,
288
+ stdio: "ignore",
289
+ });
290
+ child.unref();
291
+ success("Gateway restarted in background");
292
+ } else {
293
+ info("Restart manually: openclaw gateway stop && openclaw gateway");
294
+ }
295
+ console.log("");
296
+ }
297
+
298
+ // ---------------------------------------------------------------------------
299
+ // Uninstall command
300
+ // ---------------------------------------------------------------------------
301
+
302
+ async function cmdUninstall() {
303
+ console.log("\n AgentLink Uninstall\n");
304
+
305
+ // 1. Verify openclaw on PATH
306
+ verifyOpenclaw();
307
+
308
+ // 2. Check if installed
309
+ if (!isPluginInstalled()) {
310
+ info("AgentLink is not installed. Nothing to do.");
311
+ console.log("");
312
+ return;
313
+ }
314
+
315
+ // 3. Remove from tools.alsoAllow
316
+ removeFromArray("tools.alsoAllow", PLUGIN_NAME);
317
+
318
+ // 4. Remove from plugins.allow
319
+ removeFromArray("plugins.allow", PLUGIN_NAME);
320
+
321
+ // 5. Uninstall plugin
322
+ info("Uninstalling plugin...");
323
+ try {
324
+ execSync(`openclaw plugins uninstall ${PLUGIN_NAME}`, { stdio: "inherit" });
325
+ } catch {
326
+ warn("Plugin uninstall command failed (may already be removed).");
327
+ }
328
+ success("Plugin uninstalled");
329
+
330
+ // 6. Print preservation notice
331
+ console.log("");
332
+ info(`Uninstalled. Identity preserved in ${AGENTLINK_DIR}`);
333
+ info(`To wipe completely: rm -rf ${AGENTLINK_DIR}`);
334
+ console.log("");
335
+ }
336
+
337
+ // ---------------------------------------------------------------------------
338
+ // Usage
339
+ // ---------------------------------------------------------------------------
340
+
341
+ function printUsage() {
342
+ console.log(`
343
+ Usage: agentlink <command> [options]
344
+
345
+ Commands:
346
+ setup Install and configure AgentLink for OpenClaw
347
+ uninstall Remove AgentLink configuration (preserves identity)
348
+
349
+ Setup options:
350
+ --join CODE Join a coordination group after setup
351
+ --local PATH Install from a local path instead of npm
352
+ `);
353
+ }
354
+
355
+ // ---------------------------------------------------------------------------
356
+ // Main
357
+ // ---------------------------------------------------------------------------
358
+
359
+ async function main() {
360
+ const args = process.argv.slice(2);
361
+ const command = args[0];
362
+
363
+ switch (command) {
364
+ case "setup":
365
+ await cmdSetup(args.slice(1));
366
+ break;
367
+ case "uninstall":
368
+ await cmdUninstall();
369
+ break;
370
+ default:
371
+ printUsage();
372
+ if (command && command !== "--help" && command !== "-h") {
373
+ process.exit(1);
374
+ }
375
+ break;
376
+ }
377
+ }
378
+
379
+ main().catch((err) => {
380
+ console.error(" [error]", err.message || err);
381
+ process.exit(1);
382
+ });
@@ -0,0 +1,83 @@
1
+ {
2
+ "id": "agentlink",
3
+ "name": "AgentLink",
4
+ "description": "Agent-to-agent coordination via MQTT. Your agent talks to other people's agents.",
5
+ "version": "0.1.0",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "required": [],
10
+ "properties": {
11
+ "brokerUrl": {
12
+ "type": "string",
13
+ "description": "MQTT broker URL (mqtts:// for TLS, mqtt:// for local dev)",
14
+ "default": "mqtt://broker.emqx.io:1883"
15
+ },
16
+ "brokerUsername": {
17
+ "type": "string",
18
+ "description": "MQTT broker username"
19
+ },
20
+ "brokerPassword": {
21
+ "type": "string",
22
+ "description": "MQTT broker password"
23
+ },
24
+ "agent": {
25
+ "type": "object",
26
+ "required": ["id"],
27
+ "additionalProperties": false,
28
+ "properties": {
29
+ "id": {
30
+ "type": "string",
31
+ "description": "Unique agent identifier (e.g. rupul-macbook)",
32
+ "pattern": "^[a-z0-9][a-z0-9\\-]{2,39}$"
33
+ },
34
+ "description": {
35
+ "type": "string",
36
+ "description": "Human-readable agent description"
37
+ },
38
+ "capabilities": {
39
+ "type": "array",
40
+ "maxItems": 5,
41
+ "items": {
42
+ "type": "object",
43
+ "required": ["name", "tool"],
44
+ "properties": {
45
+ "name": {
46
+ "type": "string",
47
+ "description": "Capability name (exact match for routing)"
48
+ },
49
+ "tool": {
50
+ "type": "string",
51
+ "description": "OpenClaw tool ID this capability maps to"
52
+ },
53
+ "description": {
54
+ "type": "string",
55
+ "description": "What this capability does"
56
+ },
57
+ "input_hint": {
58
+ "type": "string",
59
+ "description": "What input the capability expects"
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
65
+ },
66
+ "output_mode": {
67
+ "type": "string",
68
+ "enum": ["user", "debug"],
69
+ "default": "user",
70
+ "description": "user = outcomes only, debug = coordination steps visible"
71
+ },
72
+ "job_timeout_ms": {
73
+ "type": "number",
74
+ "default": 60000,
75
+ "description": "Job timeout in milliseconds"
76
+ },
77
+ "data_dir": {
78
+ "type": "string",
79
+ "description": "Override data directory (default: ~/.agentlink)"
80
+ }
81
+ }
82
+ }
83
+ }
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@agentlinkdev/agentlink",
3
+ "version": "0.1.0",
4
+ "description": "Agent-to-agent coordination for OpenClaw. Your agent talks to other people's agents.",
5
+ "main": "src/index.ts",
6
+ "bin": {
7
+ "agentlink": "bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/",
12
+ "openclaw.plugin.json",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "openclaw": {
17
+ "extensions": [
18
+ "./src/index.ts"
19
+ ]
20
+ },
21
+ "scripts": {
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "lint": "eslint src/",
25
+ "typecheck": "tsc --noEmit",
26
+ "prepublishOnly": "npm test"
27
+ },
28
+ "keywords": [
29
+ "openclaw",
30
+ "agent",
31
+ "coordination",
32
+ "mqtt",
33
+ "multi-agent",
34
+ "a2a",
35
+ "plugin"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/agentlink-dev/openclaw.git"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/agentlink-dev/openclaw/issues"
43
+ },
44
+ "homepage": "https://github.com/agentlink-dev/openclaw#readme",
45
+ "license": "MIT",
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
49
+ "dependencies": {
50
+ "@sinclair/typebox": "^0.34.0",
51
+ "mqtt": "^5.10.0",
52
+ "uuid": "^11.0.0"
53
+ },
54
+ "devDependencies": {
55
+ "vitest": "^3.0.0",
56
+ "typescript": "^5.7.0",
57
+ "@types/uuid": "^10.0.0"
58
+ },
59
+ "peerDependencies": {
60
+ "openclaw": "*"
61
+ }
62
+ }