a2acalling 0.1.7 → 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,14 +61,25 @@ 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
72
+ ```
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
+
79
+ Before the first `a2a call`, the owner must set permissions and disclosure tiers. Run onboarding first:
80
+
81
+ ```bash
82
+ /a2a quickstart
70
83
  ```
71
84
 
72
85
  ## 🎯 Permission Tiers
@@ -173,8 +186,13 @@ a2a ping <target> # Check if agent is available
173
186
  ```bash
174
187
  a2a server [options] # Start A2A server
175
188
  --port, -p <port> # Port (default: 3001)
189
+ a2a setup # Auto setup via installer (gateway-aware dashboard)
176
190
  ```
177
191
 
192
+ Dashboard paths:
193
+ - Standalone A2A server: `http://<host>:<port>/dashboard`
194
+ - OpenClaw gateway mode: `http://<gateway>/a2a`
195
+
178
196
  ## 📡 Protocol
179
197
 
180
198
  Tokens use the `a2a://` URI scheme:
@@ -307,6 +325,8 @@ app.listen(3001);
307
325
  | `A2A_HOSTNAME` | Hostname for invite URLs (required for creates) |
308
326
  | `A2A_PORT` | Server port (default: 3001) |
309
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 |
310
330
 
311
331
  ## 🤝 Philosophy
312
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.7",
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
@@ -80,6 +316,11 @@ When sending messages with buttons in Telegram forum groups, **ALWAYS include th
80
316
 
81
317
  Settings saved to ~/.config/openclaw/a2a-config.json
82
318
 
319
+ ## First-Call Requirement
320
+
321
+ Before any agent uses \`/a2a call\`, the human owner must complete onboarding and approve tier permissions.
322
+ If onboarding has not been completed yet, route them to \`/a2a quickstart\` first.
323
+
83
324
  ## Main Menu (Post-Onboarding)
84
325
 
85
326
  \`\`\`javascript
@@ -139,88 +380,97 @@ a2a server --port 3001
139
380
  function install() {
140
381
  log('Installing A2A Calling for OpenClaw...\n');
141
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
+
142
387
  // 1. Create skills directory if needed
143
- if (!fs.existsSync(OPENCLAW_SKILLS)) {
144
- fs.mkdirSync(OPENCLAW_SKILLS, { recursive: true });
145
- log(`Created skills directory: ${OPENCLAW_SKILLS}`);
146
- }
388
+ ensureDir(OPENCLAW_SKILLS);
147
389
 
148
390
  // 2. Install skill
149
391
  const skillDir = path.join(OPENCLAW_SKILLS, SKILL_NAME);
150
- if (!fs.existsSync(skillDir)) {
151
- fs.mkdirSync(skillDir, { recursive: true });
152
- }
392
+ ensureDir(skillDir);
153
393
  fs.writeFileSync(path.join(skillDir, 'SKILL.md'), SKILL_MD);
154
394
  log(`Installed skill to: ${skillDir}`);
155
395
 
156
- // 3. Update OpenClaw config
157
- if (fs.existsSync(OPENCLAW_CONFIG)) {
158
- try {
159
- const config = JSON.parse(fs.readFileSync(OPENCLAW_CONFIG, 'utf8'));
160
-
161
- // Add custom command for each channel that supports it
162
- const channels = ['telegram', 'discord', 'slack'];
163
- let updated = false;
164
-
165
- for (const channel of channels) {
166
- if (config.channels?.[channel]?.enabled) {
167
- if (!config.channels[channel].customCommands) {
168
- config.channels[channel].customCommands = [];
169
- }
170
-
171
- const existing = config.channels[channel].customCommands.find(c => c.command === 'a2a');
172
- if (!existing) {
173
- config.channels[channel].customCommands.push({
174
- command: 'a2a',
175
- description: 'Agent-to-Agent: create invitations, manage connections'
176
- });
177
- updated = true;
178
- log(`Added /a2a command to ${channel} config`);
179
- } else {
180
- log(`/a2a command already exists in ${channel} config`);
181
- }
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`);
182
421
  }
183
422
  }
184
-
185
- if (updated) {
186
- // Backup original
187
- const backupPath = `${OPENCLAW_CONFIG}.backup.${Date.now()}`;
188
- fs.copyFileSync(OPENCLAW_CONFIG, backupPath);
189
- log(`Backed up config to: ${backupPath}`);
190
-
191
- // Write updated config
192
- fs.writeFileSync(OPENCLAW_CONFIG, JSON.stringify(config, null, 2));
193
- log('Updated OpenClaw config');
194
- warn('Restart OpenClaw gateway to apply changes: openclaw gateway restart');
195
- }
196
- } catch (e) {
197
- warn(`Could not update OpenClaw config: ${e.message}`);
198
- 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');
199
447
  }
200
448
  } else {
201
449
  warn(`OpenClaw config not found at: ${OPENCLAW_CONFIG}`);
202
- warn('You may need to manually add the /a2a custom command');
450
+ warn('Skipping OpenClaw command/plugin config updates');
203
451
  }
204
452
 
205
- // 4. Show server setup instructions
206
- const hostname = flags.hostname || process.env.HOSTNAME || 'localhost';
207
- const port = flags.port || '3001';
208
-
209
453
  console.log(`
210
454
  ${bold('━━━ Server Setup ━━━')}
211
455
 
212
- 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}`)}
213
459
 
214
- ${green(`A2A_HOSTNAME="${hostname}:${port}" a2a server`)}
460
+ ${bold('━━━ Dashboard Setup ━━━')}
215
461
 
216
- Or create a systemd service:
462
+ Mode: ${dashboardMode === 'gateway' ? green('gateway') : yellow('standalone')}
463
+ Dashboard URL: ${green(dashboardUrl)}
217
464
 
218
- ${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.'}
219
468
 
220
469
  ${bold('━━━ Usage ━━━')}
221
470
 
222
471
  In your chat app, use:
223
472
 
473
+ /a2a quickstart REQUIRED first step: owner sets permission tiers
224
474
  /a2a invite Create an invitation token
225
475
  /a2a list List active tokens
226
476
  /a2a revoke <id> Revoke a token
@@ -236,16 +486,20 @@ ${green('✅ A2A Calling installed successfully!')}
236
486
  function uninstall() {
237
487
  log('Uninstalling A2A Calling...\n');
238
488
 
239
- // Remove skill
240
489
  const skillDir = path.join(OPENCLAW_SKILLS, SKILL_NAME);
241
490
  if (fs.existsSync(skillDir)) {
242
491
  fs.rmSync(skillDir, { recursive: true });
243
492
  log(`Removed skill from: ${skillDir}`);
244
493
  }
245
494
 
246
- // Note about config
247
- warn('Custom command in OpenClaw config was not removed.');
248
- 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.');
249
503
 
250
504
  log('✅ Uninstall complete');
251
505
  }
@@ -256,22 +510,27 @@ ${bold('A2A Calling - OpenClaw Integration')}
256
510
 
257
511
  Usage:
258
512
  npx a2acalling install [options] Install A2A for OpenClaw
259
- 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
260
515
  npx a2acalling server Start A2A server
261
516
 
262
517
  Install Options:
263
- --hostname <host> Hostname for invite URLs (default: system hostname)
264
- --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
265
522
 
266
523
  Examples:
267
- npx a2acalling install --hostname myserver.com --port 443
268
- 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
269
527
  `);
270
528
  }
271
529
 
272
530
  // Main
273
531
  switch (command) {
274
532
  case 'install':
533
+ case 'setup':
275
534
  install();
276
535
  break;
277
536
  case 'uninstall':