a2acalling 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,6 +18,8 @@ Your AI agent can now call other AI agents — across instances, with scoped per
18
18
  - ⏱️ **Flexible tokens** — expiring or permanent, call limits optional
19
19
  - 🚦 **Rate limiting** — 10/min, 100/hr, 1000/day built-in
20
20
  - 🔄 **Multi-turn conversations** — continue threads across calls
21
+ - 🧭 **Adaptive collaboration mode** — dynamic phase changes based on overlap and depth
22
+ - 🗂️ **Minimal dashboard** — contacts, calls, tier settings, and invite generation
21
23
  - 💾 **Conversation history** — SQLite storage with context retrieval
22
24
 
23
25
  ## 🚀 Quick Start
@@ -59,16 +61,21 @@ npm install a2acalling
59
61
  ### For OpenClaw Users
60
62
 
61
63
  ```bash
62
- # Run the installer
63
- npx a2acalling install
64
+ # Auto setup (detects gateway and configures dashboard location)
65
+ npx a2acalling setup
64
66
 
65
67
  # Or clone and install
66
- git clone https://github.com/onthegonow/A2A_for_OpenClaw.git
67
- cd A2A_for_OpenClaw
68
+ git clone https://github.com/onthegonow/a2a_calling.git
69
+ cd a2a_calling
68
70
  npm install
69
- node scripts/install-openclaw.js
71
+ node scripts/install-openclaw.js setup
70
72
  ```
71
73
 
74
+ Setup behavior:
75
+ - If OpenClaw gateway is detected, dashboard is exposed on gateway at `/a2a` (proxied to A2A backend).
76
+ - If gateway is not detected, dashboard is served directly by A2A server at `/dashboard`.
77
+ - Setup prints the exact dashboard URL at the end.
78
+
72
79
  Before the first `a2a call`, the owner must set permissions and disclosure tiers. Run onboarding first:
73
80
 
74
81
  ```bash
@@ -179,8 +186,13 @@ a2a ping <target> # Check if agent is available
179
186
  ```bash
180
187
  a2a server [options] # Start A2A server
181
188
  --port, -p <port> # Port (default: 3001)
189
+ a2a setup # Auto setup via installer (gateway-aware dashboard)
182
190
  ```
183
191
 
192
+ Dashboard paths:
193
+ - Standalone A2A server: `http://<host>:<port>/dashboard`
194
+ - OpenClaw gateway mode: `http://<gateway>/a2a`
195
+
184
196
  ## 📡 Protocol
185
197
 
186
198
  Tokens use the `a2a://` URI scheme:
@@ -313,6 +325,8 @@ app.listen(3001);
313
325
  | `A2A_HOSTNAME` | Hostname for invite URLs (required for creates) |
314
326
  | `A2A_PORT` | Server port (default: 3001) |
315
327
  | `A2A_CONFIG_DIR` | Config directory (default: `~/.config/openclaw`) |
328
+ | `A2A_COLLAB_MODE` | Conversation style: `adaptive` (default) or `deep_dive` |
329
+ | `A2A_ADMIN_TOKEN` | Protect dashboard/conversation admin routes for non-local access |
316
330
 
317
331
  ## 🤝 Philosophy
318
332
 
package/bin/cli.js CHANGED
@@ -10,6 +10,7 @@
10
10
  * a2a remotes List remote agents
11
11
  * a2a call <url> <msg> Call a remote agent
12
12
  * a2a ping <url> Ping a remote agent
13
+ * a2a setup Auto setup (gateway-aware dashboard install)
13
14
  */
14
15
 
15
16
  const { TokenStore } = require('../src/lib/tokens');
@@ -158,7 +159,7 @@ npm install -g a2acalling
158
159
  a2a add "${inviteUrl}" "${agentName}"
159
160
  a2a call "${agentName}" "Hello!"
160
161
 
161
- 📚 https://github.com/onthegonow/A2A_for_OpenClaw`;
162
+ 📚 https://github.com/onthegonow/a2a_calling`;
162
163
 
163
164
  console.log(invite);
164
165
  console.log(`\n${'─'.repeat(50)}`);
@@ -804,7 +805,7 @@ ${inviteUrl}
804
805
 
805
806
  3. Call: a2a call "${inviteUrl}" "Hello!"
806
807
 
807
- 📚 Docs: https://github.com/onthegonow/A2A_for_OpenClaw
808
+ 📚 Docs: https://github.com/onthegonow/a2a_calling
808
809
  `);
809
810
  console.log('─'.repeat(50));
810
811
  console.log(`\n✅ Done! Share the invite above with other agents.\n`);
@@ -816,6 +817,10 @@ ${inviteUrl}
816
817
  require('../scripts/install-openclaw.js');
817
818
  },
818
819
 
820
+ setup: () => {
821
+ require('../scripts/install-openclaw.js');
822
+ },
823
+
819
824
  help: () => {
820
825
  console.log(`A2A Calling - Agent-to-Agent Communication
821
826
 
@@ -875,6 +880,7 @@ Server:
875
880
  --owner, -o Owner name (human behind the agent)
876
881
 
877
882
  install Install A2A for OpenClaw
883
+ setup Auto setup (gateway-aware dashboard install)
878
884
 
879
885
  Examples:
880
886
  a2a create --name "bappybot" --owner "Benjamin Pollack" --expires 7d
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2acalling",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Agent-to-agent calling for OpenClaw - A2A agent communication",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -23,12 +23,12 @@
23
23
  "license": "MIT",
24
24
  "repository": {
25
25
  "type": "git",
26
- "url": "git+https://github.com/onthegonow/A2A_for_OpenClaw.git"
26
+ "url": "git+https://github.com/onthegonow/a2a_calling.git"
27
27
  },
28
28
  "bugs": {
29
- "url": "https://github.com/onthegonow/A2A_for_OpenClaw/issues"
29
+ "url": "https://github.com/onthegonow/a2a_calling/issues"
30
30
  },
31
- "homepage": "https://github.com/onthegonow/A2A_for_OpenClaw#readme",
31
+ "homepage": "https://github.com/onthegonow/a2a_calling#readme",
32
32
  "engines": {
33
33
  "node": ">=18.0.0"
34
34
  },
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * A2A Calling - OpenClaw Integration Installer
4
- *
5
- * This script:
6
- * 1. Installs the a2a skill to the user's OpenClaw skills directory
7
- * 2. Adds /a2a as a custom command in OpenClaw config
8
- * 3. Sets up the A2A server as a systemd service (optional)
9
- *
4
+ *
5
+ * Supports automatic setup:
6
+ * - If OpenClaw gateway is detected, install a gateway HTTP proxy plugin
7
+ * so dashboard is accessible at /a2a on gateway.
8
+ * - If gateway is not detected, dashboard runs on standalone A2A server.
9
+ *
10
10
  * Usage:
11
11
  * npx a2acalling install
12
- * npx a2acalling install --hostname myserver.com
13
- * npx a2acalling install --port 3001
12
+ * npx a2acalling setup
13
+ * npx a2acalling install --hostname myserver.com --port 3001
14
14
  * npx a2acalling uninstall
15
15
  */
16
16
 
@@ -21,7 +21,9 @@ const { execSync } = require('child_process');
21
21
  // Paths
22
22
  const OPENCLAW_CONFIG = process.env.OPENCLAW_CONFIG || path.join(process.env.HOME, '.openclaw', 'openclaw.json');
23
23
  const OPENCLAW_SKILLS = process.env.OPENCLAW_SKILLS || path.join(process.env.HOME, '.openclaw', 'skills');
24
+ const OPENCLAW_EXTENSIONS = process.env.OPENCLAW_EXTENSIONS || path.join(process.env.HOME, '.openclaw', 'extensions');
24
25
  const SKILL_NAME = 'a2a';
26
+ const DASHBOARD_PLUGIN_ID = 'a2a-dashboard-proxy';
25
27
 
26
28
  // Parse args
27
29
  const args = process.argv.slice(2);
@@ -44,6 +46,240 @@ function log(msg) { console.log(`${green('[a2a]')} ${msg}`); }
44
46
  function warn(msg) { console.log(`${yellow('[a2a]')} ${msg}`); }
45
47
  function error(msg) { console.error(`${red('[a2a]')} ${msg}`); }
46
48
 
49
+ function ensureDir(dirPath) {
50
+ if (!fs.existsSync(dirPath)) {
51
+ fs.mkdirSync(dirPath, { recursive: true });
52
+ }
53
+ }
54
+
55
+ function commandExists(cmd) {
56
+ try {
57
+ execSync(`command -v ${cmd}`, { stdio: 'ignore' });
58
+ return true;
59
+ } catch (err) {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ function loadOpenClawConfig() {
65
+ if (!fs.existsSync(OPENCLAW_CONFIG)) {
66
+ return null;
67
+ }
68
+ try {
69
+ return JSON.parse(fs.readFileSync(OPENCLAW_CONFIG, 'utf8'));
70
+ } catch (err) {
71
+ warn(`OpenClaw config is unreadable: ${err.message}`);
72
+ return null;
73
+ }
74
+ }
75
+
76
+ function writeOpenClawConfig(config) {
77
+ const backupPath = `${OPENCLAW_CONFIG}.backup.${Date.now()}`;
78
+ fs.copyFileSync(OPENCLAW_CONFIG, backupPath);
79
+ fs.writeFileSync(OPENCLAW_CONFIG, JSON.stringify(config, null, 2));
80
+ log(`Backed up config to: ${backupPath}`);
81
+ log('Updated OpenClaw config');
82
+ }
83
+
84
+ function detectGateway(config) {
85
+ const hasBinary = commandExists('openclaw');
86
+ const hasGatewayBlock = Boolean(config?.gateway);
87
+ return hasBinary && hasGatewayBlock;
88
+ }
89
+
90
+ function resolveGatewayBaseUrl() {
91
+ if (flags['gateway-url']) {
92
+ return String(flags['gateway-url']).replace(/\/+$/, '');
93
+ }
94
+
95
+ try {
96
+ const output = execSync('openclaw dashboard --no-open', {
97
+ encoding: 'utf8',
98
+ timeout: 8000,
99
+ stdio: ['ignore', 'pipe', 'pipe']
100
+ });
101
+ const match = output.match(/Dashboard URL:\s*(\S+)/);
102
+ if (match && match[1]) {
103
+ const parsed = new URL(match[1]);
104
+ return `${parsed.protocol}//${parsed.host}`;
105
+ }
106
+ } catch (err) {
107
+ // fall through to default
108
+ }
109
+
110
+ const fallbackPort = process.env.OPENCLAW_GATEWAY_PORT || '18789';
111
+ return `http://127.0.0.1:${fallbackPort}`;
112
+ }
113
+
114
+ const DASHBOARD_PLUGIN_MANIFEST = {
115
+ id: DASHBOARD_PLUGIN_ID,
116
+ name: 'A2A Dashboard Proxy',
117
+ description: 'Proxy A2A dashboard routes through OpenClaw gateway',
118
+ version: '1.0.0',
119
+ configSchema: {
120
+ type: 'object',
121
+ additionalProperties: false,
122
+ properties: {
123
+ backendUrl: {
124
+ type: 'string',
125
+ default: 'http://127.0.0.1:3001'
126
+ }
127
+ }
128
+ }
129
+ };
130
+
131
+ const DASHBOARD_PLUGIN_TS = `import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
132
+ import type { IncomingMessage, ServerResponse } from "node:http";
133
+ import http from "node:http";
134
+ import https from "node:https";
135
+
136
+ const PLUGIN_ID = "a2a-dashboard-proxy";
137
+ const UI_PREFIX = "/a2a";
138
+ const API_PREFIX = "/api/a2a/dashboard";
139
+
140
+ function sendJson(res: ServerResponse, status: number, body: unknown) {
141
+ res.statusCode = status;
142
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
143
+ res.end(JSON.stringify(body));
144
+ }
145
+
146
+ function sendHtml(res: ServerResponse, status: number, html: string) {
147
+ res.statusCode = status;
148
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
149
+ res.end(html);
150
+ }
151
+
152
+ function resolveBackendUrl(api: OpenClawPluginApi): URL {
153
+ const fallback = process.env.A2A_DASHBOARD_BACKEND_URL || "http://127.0.0.1:3001";
154
+ try {
155
+ const cfg = api.runtime.config.loadConfig() as Record<string, unknown>;
156
+ const plugins = (cfg.plugins || {}) as Record<string, unknown>;
157
+ const entries = (plugins.entries || {}) as Record<string, unknown>;
158
+ const pluginEntry = (entries[PLUGIN_ID] || {}) as Record<string, unknown>;
159
+ const candidate = typeof pluginEntry.backendUrl === "string" && pluginEntry.backendUrl
160
+ ? pluginEntry.backendUrl
161
+ : fallback;
162
+ return new URL(candidate);
163
+ } catch {
164
+ return new URL(fallback);
165
+ }
166
+ }
167
+
168
+ function rewriteUiPath(pathname: string): string {
169
+ if (pathname === UI_PREFIX || pathname === UI_PREFIX + "/") {
170
+ return "/dashboard/";
171
+ }
172
+ if (pathname.startsWith(UI_PREFIX + "/")) {
173
+ return "/dashboard/" + pathname.slice((UI_PREFIX + "/").length);
174
+ }
175
+ return pathname;
176
+ }
177
+
178
+ const plugin = {
179
+ id: PLUGIN_ID,
180
+ name: "A2A Dashboard Proxy",
181
+ description: "Proxy A2A dashboard routes through OpenClaw gateway",
182
+ configSchema: {
183
+ type: "object" as const,
184
+ additionalProperties: false,
185
+ properties: {
186
+ backendUrl: {
187
+ type: "string" as const,
188
+ default: "http://127.0.0.1:3001"
189
+ }
190
+ }
191
+ },
192
+ register(api: OpenClawPluginApi) {
193
+ api.registerHttpHandler(async (req: IncomingMessage, res: ServerResponse) => {
194
+ const incoming = new URL(req.url ?? "/", \`http://\${req.headers.host ?? "localhost"}\`);
195
+ const isUi = incoming.pathname === UI_PREFIX || incoming.pathname.startsWith(UI_PREFIX + "/");
196
+ const isApi = incoming.pathname === API_PREFIX || incoming.pathname.startsWith(API_PREFIX + "/");
197
+ if (!isUi && !isApi) {
198
+ return false;
199
+ }
200
+
201
+ if (incoming.pathname === UI_PREFIX) {
202
+ res.statusCode = 302;
203
+ res.setHeader("Location", UI_PREFIX + "/");
204
+ res.end();
205
+ return true;
206
+ }
207
+
208
+ const backendBase = resolveBackendUrl(api);
209
+ const rewrittenPath = isUi ? rewriteUiPath(incoming.pathname) : incoming.pathname;
210
+ const target = new URL(rewrittenPath + (incoming.search || ""), backendBase);
211
+ const client = target.protocol === "https:" ? https : http;
212
+
213
+ const headers: Record<string, string> = {};
214
+ for (const [key, value] of Object.entries(req.headers)) {
215
+ if (!value || key.toLowerCase() === "host") continue;
216
+ headers[key] = Array.isArray(value) ? value.join(", ") : String(value);
217
+ }
218
+ headers["x-forwarded-by"] = "a2a-dashboard-proxy";
219
+
220
+ const proxyReq = client.request({
221
+ protocol: target.protocol,
222
+ hostname: target.hostname,
223
+ port: target.port || (target.protocol === "https:" ? 443 : 80),
224
+ path: target.pathname + target.search,
225
+ method: req.method,
226
+ headers
227
+ }, (proxyRes) => {
228
+ res.statusCode = proxyRes.statusCode || 502;
229
+ Object.entries(proxyRes.headers).forEach(([key, value]) => {
230
+ if (value !== undefined) {
231
+ res.setHeader(key, value as string | string[]);
232
+ }
233
+ });
234
+ proxyRes.pipe(res);
235
+ });
236
+
237
+ proxyReq.on("error", (err) => {
238
+ const backend = backendBase.toString();
239
+ if (isApi) {
240
+ sendJson(res, 502, {
241
+ success: false,
242
+ error: "dashboard_backend_unreachable",
243
+ message: \`Could not reach A2A server at \${backend}: \${err.message}\`
244
+ });
245
+ return;
246
+ }
247
+
248
+ sendHtml(res, 502, \`<!doctype html>
249
+ <html><head><meta charset="utf-8"><title>A2A Dashboard</title></head>
250
+ <body style="font-family: sans-serif; padding: 2rem;">
251
+ <h1>A2A Dashboard Unavailable</h1>
252
+ <p>The gateway proxy is active, but the A2A backend is not reachable.</p>
253
+ <p>Expected backend: <code>\${backend}</code></p>
254
+ <p>Start the backend with: <code>a2a server --port 3001</code></p>
255
+ </body></html>\`);
256
+ });
257
+
258
+ req.pipe(proxyReq);
259
+ return true;
260
+ });
261
+ }
262
+ };
263
+
264
+ export default plugin;
265
+ `;
266
+
267
+ function installDashboardProxyPlugin(backendUrl) {
268
+ ensureDir(OPENCLAW_EXTENSIONS);
269
+ const pluginDir = path.join(OPENCLAW_EXTENSIONS, DASHBOARD_PLUGIN_ID);
270
+ ensureDir(pluginDir);
271
+
272
+ fs.writeFileSync(
273
+ path.join(pluginDir, 'openclaw.plugin.json'),
274
+ JSON.stringify(DASHBOARD_PLUGIN_MANIFEST, null, 2)
275
+ );
276
+ fs.writeFileSync(path.join(pluginDir, 'index.ts'), DASHBOARD_PLUGIN_TS);
277
+
278
+ log(`Installed gateway dashboard plugin: ${pluginDir}`);
279
+ log(`Dashboard proxy backend: ${backendUrl}`);
280
+ return pluginDir;
281
+ }
282
+
47
283
  // Skill content
48
284
  const SKILL_MD = `---
49
285
  name: a2a
@@ -144,83 +380,91 @@ a2a server --port 3001
144
380
  function install() {
145
381
  log('Installing A2A Calling for OpenClaw...\n');
146
382
 
383
+ const hostname = flags.hostname || process.env.HOSTNAME || 'localhost';
384
+ const port = String(flags.port || process.env.A2A_PORT || '3001');
385
+ const backendUrl = flags['dashboard-backend'] || `http://127.0.0.1:${port}`;
386
+
147
387
  // 1. Create skills directory if needed
148
- if (!fs.existsSync(OPENCLAW_SKILLS)) {
149
- fs.mkdirSync(OPENCLAW_SKILLS, { recursive: true });
150
- log(`Created skills directory: ${OPENCLAW_SKILLS}`);
151
- }
388
+ ensureDir(OPENCLAW_SKILLS);
152
389
 
153
390
  // 2. Install skill
154
391
  const skillDir = path.join(OPENCLAW_SKILLS, SKILL_NAME);
155
- if (!fs.existsSync(skillDir)) {
156
- fs.mkdirSync(skillDir, { recursive: true });
157
- }
392
+ ensureDir(skillDir);
158
393
  fs.writeFileSync(path.join(skillDir, 'SKILL.md'), SKILL_MD);
159
394
  log(`Installed skill to: ${skillDir}`);
160
395
 
161
- // 3. Update OpenClaw config
162
- if (fs.existsSync(OPENCLAW_CONFIG)) {
163
- try {
164
- const config = JSON.parse(fs.readFileSync(OPENCLAW_CONFIG, 'utf8'));
165
-
166
- // Add custom command for each channel that supports it
167
- const channels = ['telegram', 'discord', 'slack'];
168
- let updated = false;
169
-
170
- for (const channel of channels) {
171
- if (config.channels?.[channel]?.enabled) {
172
- if (!config.channels[channel].customCommands) {
173
- config.channels[channel].customCommands = [];
174
- }
175
-
176
- const existing = config.channels[channel].customCommands.find(c => c.command === 'a2a');
177
- if (!existing) {
178
- config.channels[channel].customCommands.push({
179
- command: 'a2a',
180
- description: 'Agent-to-Agent: create invitations, manage connections'
181
- });
182
- updated = true;
183
- log(`Added /a2a command to ${channel} config`);
184
- } else {
185
- log(`/a2a command already exists in ${channel} config`);
186
- }
396
+ // 3. Update OpenClaw config + gateway plugin setup (if available)
397
+ let config = loadOpenClawConfig();
398
+ let configUpdated = false;
399
+ let gatewayDetected = false;
400
+ let dashboardMode = 'standalone';
401
+ let dashboardUrl = `http://${hostname}:${port}/dashboard`;
402
+
403
+ if (config) {
404
+ // Add custom command for each enabled channel
405
+ const channels = ['telegram', 'discord', 'slack'];
406
+ for (const channel of channels) {
407
+ if (config.channels?.[channel]?.enabled) {
408
+ if (!config.channels[channel].customCommands) {
409
+ config.channels[channel].customCommands = [];
410
+ }
411
+ const existing = config.channels[channel].customCommands.find(c => c.command === 'a2a');
412
+ if (!existing) {
413
+ config.channels[channel].customCommands.push({
414
+ command: 'a2a',
415
+ description: 'Agent-to-Agent: create invitations, manage connections'
416
+ });
417
+ configUpdated = true;
418
+ log(`Added /a2a command to ${channel} config`);
419
+ } else {
420
+ log(`/a2a command already exists in ${channel} config`);
187
421
  }
188
422
  }
189
-
190
- if (updated) {
191
- // Backup original
192
- const backupPath = `${OPENCLAW_CONFIG}.backup.${Date.now()}`;
193
- fs.copyFileSync(OPENCLAW_CONFIG, backupPath);
194
- log(`Backed up config to: ${backupPath}`);
195
-
196
- // Write updated config
197
- fs.writeFileSync(OPENCLAW_CONFIG, JSON.stringify(config, null, 2));
198
- log('Updated OpenClaw config');
199
- warn('Restart OpenClaw gateway to apply changes: openclaw gateway restart');
200
- }
201
- } catch (e) {
202
- warn(`Could not update OpenClaw config: ${e.message}`);
203
- warn('You may need to manually add the /a2a custom command');
423
+ }
424
+
425
+ gatewayDetected = detectGateway(config);
426
+ if (gatewayDetected) {
427
+ dashboardMode = 'gateway';
428
+ installDashboardProxyPlugin(backendUrl);
429
+ const gatewayBaseUrl = resolveGatewayBaseUrl();
430
+ dashboardUrl = `${gatewayBaseUrl.replace(/\/+$/, '')}/a2a`;
431
+
432
+ config.plugins = config.plugins || {};
433
+ config.plugins.entries = config.plugins.entries || {};
434
+ const existingEntry = config.plugins.entries[DASHBOARD_PLUGIN_ID] || {};
435
+ config.plugins.entries[DASHBOARD_PLUGIN_ID] = {
436
+ ...existingEntry,
437
+ enabled: true,
438
+ backendUrl
439
+ };
440
+ configUpdated = true;
441
+ log(`Configured gateway plugin entry: ${DASHBOARD_PLUGIN_ID}`);
442
+ }
443
+
444
+ if (configUpdated) {
445
+ writeOpenClawConfig(config);
446
+ warn('Restart OpenClaw gateway to apply changes: openclaw gateway restart');
204
447
  }
205
448
  } else {
206
449
  warn(`OpenClaw config not found at: ${OPENCLAW_CONFIG}`);
207
- warn('You may need to manually add the /a2a custom command');
450
+ warn('Skipping OpenClaw command/plugin config updates');
208
451
  }
209
452
 
210
- // 4. Show server setup instructions
211
- const hostname = flags.hostname || process.env.HOSTNAME || 'localhost';
212
- const port = flags.port || '3001';
213
-
214
453
  console.log(`
215
454
  ${bold('━━━ Server Setup ━━━')}
216
455
 
217
- To receive incoming calls, run the A2A server:
456
+ To receive incoming calls and host A2A APIs:
457
+
458
+ ${green(`A2A_HOSTNAME="${hostname}:${port}" a2a server --port ${port}`)}
218
459
 
219
- ${green(`A2A_HOSTNAME="${hostname}:${port}" a2a server`)}
460
+ ${bold('━━━ Dashboard Setup ━━━')}
220
461
 
221
- Or create a systemd service:
462
+ Mode: ${dashboardMode === 'gateway' ? green('gateway') : yellow('standalone')}
463
+ Dashboard URL: ${green(dashboardUrl)}
222
464
 
223
- ${green('sudo a2a service install')}
465
+ ${dashboardMode === 'gateway'
466
+ ? `Gateway path /a2a is now proxied to ${backendUrl}.`
467
+ : 'No gateway detected. Dashboard is served directly from the A2A server.'}
224
468
 
225
469
  ${bold('━━━ Usage ━━━')}
226
470
 
@@ -242,16 +486,20 @@ ${green('✅ A2A Calling installed successfully!')}
242
486
  function uninstall() {
243
487
  log('Uninstalling A2A Calling...\n');
244
488
 
245
- // Remove skill
246
489
  const skillDir = path.join(OPENCLAW_SKILLS, SKILL_NAME);
247
490
  if (fs.existsSync(skillDir)) {
248
491
  fs.rmSync(skillDir, { recursive: true });
249
492
  log(`Removed skill from: ${skillDir}`);
250
493
  }
251
494
 
252
- // Note about config
253
- warn('Custom command in OpenClaw config was not removed.');
254
- warn('You can manually remove it if desired.');
495
+ const pluginDir = path.join(OPENCLAW_EXTENSIONS, DASHBOARD_PLUGIN_ID);
496
+ if (fs.existsSync(pluginDir)) {
497
+ fs.rmSync(pluginDir, { recursive: true });
498
+ log(`Removed dashboard plugin: ${pluginDir}`);
499
+ }
500
+
501
+ warn('Custom command and plugin entries in OpenClaw config were not removed.');
502
+ warn('You can remove them manually if desired.');
255
503
 
256
504
  log('✅ Uninstall complete');
257
505
  }
@@ -262,22 +510,27 @@ ${bold('A2A Calling - OpenClaw Integration')}
262
510
 
263
511
  Usage:
264
512
  npx a2acalling install [options] Install A2A for OpenClaw
265
- npx a2acalling uninstall Remove A2A skill
513
+ npx a2acalling setup [options] Alias for install (auto gateway/standalone)
514
+ npx a2acalling uninstall Remove A2A skill + dashboard plugin
266
515
  npx a2acalling server Start A2A server
267
516
 
268
517
  Install Options:
269
- --hostname <host> Hostname for invite URLs (default: system hostname)
270
- --port <port> Server port (default: 3001)
518
+ --hostname <host> Hostname for invite URLs (default: system hostname)
519
+ --port <port> A2A server port (default: 3001)
520
+ --gateway-url <url> Force gateway base URL for printed dashboard link
521
+ --dashboard-backend <url> Backend URL used by gateway dashboard proxy
271
522
 
272
523
  Examples:
273
- npx a2acalling install --hostname myserver.com --port 443
274
- npx a2acalling server --port 3001
524
+ npx a2acalling install --hostname myserver.com --port 3001
525
+ npx a2acalling setup --dashboard-backend http://127.0.0.1:3001
526
+ npx a2acalling uninstall
275
527
  `);
276
528
  }
277
529
 
278
530
  // Main
279
531
  switch (command) {
280
532
  case 'install':
533
+ case 'setup':
281
534
  install();
282
535
  break;
283
536
  case 'uninstall':