a2acalling 0.6.0 → 0.6.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/README.md +33 -9
- package/SKILL.md +67 -5
- package/bin/cli.js +468 -151
- package/docs/protocol.md +24 -14
- package/package.json +1 -1
- package/scripts/install-openclaw.js +64 -68
- package/src/dashboard/public/app.js +765 -28
- package/src/dashboard/public/index.html +57 -13
- package/src/dashboard/public/style.css +16 -0
- package/src/lib/callbook.js +358 -0
- package/src/lib/client.js +1 -2
- package/src/lib/config.js +67 -15
- package/src/lib/external-ip.js +18 -7
- package/src/lib/invite-host.js +26 -41
- package/src/lib/logger.js +26 -14
- package/src/lib/tokens.js +314 -113
- package/src/routes/a2a.js +11 -2
- package/src/routes/callbook.js +142 -0
- package/src/routes/dashboard.js +557 -25
- package/src/server.js +6 -0
package/bin/cli.js
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* a2a create [options] Create an A2A token
|
|
7
7
|
* a2a list List active tokens
|
|
8
8
|
* a2a revoke <id> Revoke a token
|
|
9
|
-
* a2a add <url> [name] Add a
|
|
10
|
-
* a2a remotes List
|
|
11
|
-
* a2a call <url> <msg> Call a
|
|
12
|
-
* a2a ping <url> Ping
|
|
9
|
+
* a2a add <url> [name] Add a contact (alias of "contacts add")
|
|
10
|
+
* a2a remotes List contacts (alias of "contacts")
|
|
11
|
+
* a2a call <url> <msg> Call a contact (or invite URL)
|
|
12
|
+
* a2a ping <url> Ping an invite URL
|
|
13
13
|
* a2a gui Open the local dashboard GUI in a browser
|
|
14
14
|
* a2a setup Auto setup (gateway-aware dashboard install)
|
|
15
15
|
*/
|
|
@@ -47,8 +47,8 @@ function checkOnboarding(commandName) {
|
|
|
47
47
|
const config = new A2AConfig();
|
|
48
48
|
if (!config.isOnboarded()) {
|
|
49
49
|
console.warn('\n\u26a0\ufe0f A2A onboarding not complete.');
|
|
50
|
-
console.warn(' Run "a2a quickstart"
|
|
51
|
-
console.warn(' Without onboarding, invites
|
|
50
|
+
console.warn(' Run "a2a quickstart" to complete deterministic onboarding.');
|
|
51
|
+
console.warn(' Without onboarding, invites may use default topics/goals and remote dashboard access may not be configured.\n');
|
|
52
52
|
return false;
|
|
53
53
|
}
|
|
54
54
|
return true;
|
|
@@ -286,7 +286,7 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
286
286
|
for (const t of tokens) {
|
|
287
287
|
const expired = t.expires_at && new Date(t.expires_at) < new Date();
|
|
288
288
|
const status = expired ? '⚠️ EXPIRED' : '✅ Active';
|
|
289
|
-
const tier = t.tier ||
|
|
289
|
+
const tier = t.tier || 'public';
|
|
290
290
|
const topics = t.allowed_topics || ['chat'];
|
|
291
291
|
console.log(`${status} ${t.id}`);
|
|
292
292
|
console.log(` Name: ${t.name}`);
|
|
@@ -323,12 +323,12 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
try {
|
|
326
|
-
const result = store.
|
|
326
|
+
const result = store.addContact(url, { name });
|
|
327
327
|
if (!result.success) {
|
|
328
|
-
console.log(`
|
|
328
|
+
console.log(`Contact already registered: ${result.existing.name}`);
|
|
329
329
|
return;
|
|
330
330
|
}
|
|
331
|
-
console.log(`✅
|
|
331
|
+
console.log(`✅ Contact added: ${result.contact.name} (${result.contact.host})`);
|
|
332
332
|
} catch (err) {
|
|
333
333
|
console.error(err.message);
|
|
334
334
|
process.exit(1);
|
|
@@ -336,7 +336,7 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
336
336
|
},
|
|
337
337
|
|
|
338
338
|
remotes: () => {
|
|
339
|
-
//
|
|
339
|
+
// Alias for contacts
|
|
340
340
|
commands.contacts({ _: ['contacts'], flags: {} });
|
|
341
341
|
},
|
|
342
342
|
|
|
@@ -352,22 +352,22 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
352
352
|
if (subcommand === 'rm' || subcommand === 'remove') return commands['contacts:rm'](args);
|
|
353
353
|
|
|
354
354
|
// Default: list contacts
|
|
355
|
-
const
|
|
356
|
-
if (
|
|
355
|
+
const contacts = store.listContacts();
|
|
356
|
+
if (contacts.length === 0) {
|
|
357
357
|
console.log('📇 No contacts yet.\n');
|
|
358
358
|
console.log('Add one with: a2a contacts add <invite_url>');
|
|
359
359
|
return;
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
console.log(`📇 Agent Contacts (${
|
|
363
|
-
for (const r of
|
|
362
|
+
console.log(`📇 Agent Contacts (${contacts.length})\n`);
|
|
363
|
+
for (const r of contacts) {
|
|
364
364
|
const statusIcon = r.status === 'online' ? '🟢' : r.status === 'offline' ? '🔴' : '⚪';
|
|
365
365
|
const ownerText = r.owner ? ` — ${r.owner}` : '';
|
|
366
366
|
|
|
367
367
|
// Permission badge from linked token (what YOU gave THEM)
|
|
368
368
|
let permBadge = '';
|
|
369
369
|
if (r.linked_token) {
|
|
370
|
-
const tier = r.linked_token.tier ||
|
|
370
|
+
const tier = r.linked_token.tier || 'public';
|
|
371
371
|
permBadge = tier === 'family' ? ' ⚡' : tier === 'friends' ? ' 🔧' : ' 🌐';
|
|
372
372
|
}
|
|
373
373
|
|
|
@@ -392,6 +392,7 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
392
392
|
console.error('Options:');
|
|
393
393
|
console.error(' --name, -n Agent name');
|
|
394
394
|
console.error(' --owner, -o Owner name');
|
|
395
|
+
console.error(' --server-name Server label (optional)');
|
|
395
396
|
console.error(' --notes Notes about this contact');
|
|
396
397
|
console.error(' --tags Comma-separated tags');
|
|
397
398
|
console.error(' --link Link to token ID you gave them');
|
|
@@ -401,24 +402,26 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
401
402
|
const options = {
|
|
402
403
|
name: args.flags.name || args.flags.n,
|
|
403
404
|
owner: args.flags.owner || args.flags.o,
|
|
405
|
+
server_name: args.flags['server-name'] || args.flags.server_name || args.flags.serverName || null,
|
|
404
406
|
notes: args.flags.notes,
|
|
405
407
|
tags: args.flags.tags ? args.flags.tags.split(',').map(t => t.trim()) : [],
|
|
406
408
|
linkedTokenId: args.flags.link || null
|
|
407
409
|
};
|
|
408
410
|
|
|
409
411
|
try {
|
|
410
|
-
const result = store.
|
|
412
|
+
const result = store.addContact(url, options);
|
|
411
413
|
if (!result.success) {
|
|
412
414
|
console.log(`Contact already exists: ${result.existing.name}`);
|
|
413
415
|
return;
|
|
414
416
|
}
|
|
415
|
-
console.log(`✅ Contact added: ${result.
|
|
416
|
-
if (result.
|
|
417
|
-
console.log(`
|
|
417
|
+
console.log(`✅ Contact added: ${result.contact.name}`);
|
|
418
|
+
if (result.contact.owner) console.log(` Owner: ${result.contact.owner}`);
|
|
419
|
+
if (result.contact.server_name) console.log(` Server: ${result.contact.server_name}`);
|
|
420
|
+
console.log(` Host: ${result.contact.host}`);
|
|
418
421
|
if (options.linkedTokenId) {
|
|
419
422
|
console.log(` Linked to token: ${options.linkedTokenId}`);
|
|
420
423
|
} else {
|
|
421
|
-
console.log(`\n💡 Link a token: a2a contacts link ${result.
|
|
424
|
+
console.log(`\n💡 Link a token: a2a contacts link ${result.contact.name} <token_id>`);
|
|
422
425
|
}
|
|
423
426
|
} catch (err) {
|
|
424
427
|
console.error(err.message);
|
|
@@ -434,8 +437,8 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
434
437
|
}
|
|
435
438
|
|
|
436
439
|
// Get contact with linked token info
|
|
437
|
-
const
|
|
438
|
-
const remote =
|
|
440
|
+
const contacts = store.listContacts();
|
|
441
|
+
const remote = contacts.find(r => r.name === name || r.id === name);
|
|
439
442
|
if (!remote) {
|
|
440
443
|
console.error(`Contact not found: ${name}`);
|
|
441
444
|
process.exit(1);
|
|
@@ -453,7 +456,7 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
453
456
|
// Show linked token (permissions you gave them)
|
|
454
457
|
if (remote.linked_token) {
|
|
455
458
|
const t = remote.linked_token;
|
|
456
|
-
const tier = t.tier ||
|
|
459
|
+
const tier = t.tier || 'public';
|
|
457
460
|
const topics = t.allowed_topics || ['chat'];
|
|
458
461
|
const tierIcon = tier === 'family' ? '⚡' : tier === 'friends' ? '🔧' : '🌐';
|
|
459
462
|
console.log(`🔐 Your token to them: ${t.id}`);
|
|
@@ -497,6 +500,7 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
497
500
|
console.error('Options:');
|
|
498
501
|
console.error(' --name New name');
|
|
499
502
|
console.error(' --owner Owner name');
|
|
503
|
+
console.error(' --server-name Server label');
|
|
500
504
|
console.error(' --notes Notes');
|
|
501
505
|
console.error(' --tags Comma-separated tags');
|
|
502
506
|
process.exit(1);
|
|
@@ -505,6 +509,7 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
505
509
|
const updates = {};
|
|
506
510
|
if (args.flags.name) updates.name = args.flags.name;
|
|
507
511
|
if (args.flags.owner) updates.owner = args.flags.owner;
|
|
512
|
+
if (args.flags['server-name'] || args.flags.server_name || args.flags.serverName) updates.server_name = args.flags['server-name'] || args.flags.server_name || args.flags.serverName;
|
|
508
513
|
if (args.flags.notes) updates.notes = args.flags.notes;
|
|
509
514
|
if (args.flags.tags) updates.tags = args.flags.tags.split(',').map(t => t.trim());
|
|
510
515
|
|
|
@@ -513,13 +518,13 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
513
518
|
process.exit(1);
|
|
514
519
|
}
|
|
515
520
|
|
|
516
|
-
const result = store.
|
|
521
|
+
const result = store.updateContact(name, updates);
|
|
517
522
|
if (!result.success) {
|
|
518
523
|
console.error(`Contact not found: ${name}`);
|
|
519
524
|
process.exit(1);
|
|
520
525
|
}
|
|
521
526
|
|
|
522
|
-
console.log(`✅ Contact updated: ${result.remote.name}`);
|
|
527
|
+
console.log(`✅ Contact updated: ${(result.contact || result.remote).name}`);
|
|
523
528
|
},
|
|
524
529
|
|
|
525
530
|
'contacts:link': (args) => {
|
|
@@ -548,7 +553,7 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
548
553
|
result.token.tier === 'friends' ? '🔧 friends' : '🌐 public';
|
|
549
554
|
|
|
550
555
|
console.log(`✅ Linked token to contact`);
|
|
551
|
-
console.log(` Contact: ${result.remote.name}`);
|
|
556
|
+
console.log(` Contact: ${result.contact?.name || result.remote.name}`);
|
|
552
557
|
console.log(` Token: ${result.token.id} (${result.token.name})`);
|
|
553
558
|
console.log(` Permissions: ${permLabel}`);
|
|
554
559
|
},
|
|
@@ -560,7 +565,7 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
560
565
|
process.exit(1);
|
|
561
566
|
}
|
|
562
567
|
|
|
563
|
-
const remote = store.
|
|
568
|
+
const remote = store.getContact(name);
|
|
564
569
|
if (!remote) {
|
|
565
570
|
console.error(`Contact not found: ${name}`);
|
|
566
571
|
process.exit(1);
|
|
@@ -573,12 +578,12 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
573
578
|
|
|
574
579
|
try {
|
|
575
580
|
const result = await client.ping(url);
|
|
576
|
-
store.
|
|
581
|
+
store.updateContactStatus(name, 'online');
|
|
577
582
|
console.log(`🟢 ${remote.name} is online`);
|
|
578
583
|
console.log(` Agent: ${result.name}`);
|
|
579
584
|
console.log(` Version: ${result.version}`);
|
|
580
585
|
} catch (err) {
|
|
581
|
-
store.
|
|
586
|
+
store.updateContactStatus(name, 'offline', err.message);
|
|
582
587
|
console.log(`🔴 ${remote.name} is offline`);
|
|
583
588
|
console.log(` Error: ${err.message}`);
|
|
584
589
|
}
|
|
@@ -591,13 +596,13 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
591
596
|
process.exit(1);
|
|
592
597
|
}
|
|
593
598
|
|
|
594
|
-
const result = store.
|
|
599
|
+
const result = store.removeContact(name);
|
|
595
600
|
if (!result.success) {
|
|
596
601
|
console.error(`Contact not found: ${name}`);
|
|
597
602
|
process.exit(1);
|
|
598
603
|
}
|
|
599
604
|
|
|
600
|
-
console.log(`✅ Contact removed: ${result.remote.name}`);
|
|
605
|
+
console.log(`✅ Contact removed: ${(result.contact || result.remote).name}`);
|
|
601
606
|
},
|
|
602
607
|
|
|
603
608
|
// ========== CONVERSATIONS ==========
|
|
@@ -761,7 +766,7 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
761
766
|
let url = target;
|
|
762
767
|
let contactName = null;
|
|
763
768
|
if (!target.startsWith('a2a://')) {
|
|
764
|
-
const remote = store.
|
|
769
|
+
const remote = store.getContact(target);
|
|
765
770
|
if (remote) {
|
|
766
771
|
url = `a2a://${remote.host}/${remote.token}`;
|
|
767
772
|
contactName = remote.name;
|
|
@@ -778,7 +783,7 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
778
783
|
|
|
779
784
|
// Update contact status on success
|
|
780
785
|
if (contactName) {
|
|
781
|
-
store.
|
|
786
|
+
store.updateContactStatus(contactName, 'online');
|
|
782
787
|
}
|
|
783
788
|
|
|
784
789
|
console.log(`\n✅ Response:\n`);
|
|
@@ -789,7 +794,7 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
789
794
|
} catch (err) {
|
|
790
795
|
// Update contact status on failure
|
|
791
796
|
if (contactName) {
|
|
792
|
-
store.
|
|
797
|
+
store.updateContactStatus(contactName, 'offline', err.message);
|
|
793
798
|
}
|
|
794
799
|
console.error(`❌ Call failed: ${err.message}`);
|
|
795
800
|
process.exit(1);
|
|
@@ -895,119 +900,426 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
895
900
|
require('../src/server.js');
|
|
896
901
|
},
|
|
897
902
|
|
|
898
|
-
quickstart: (args) => {
|
|
899
|
-
|
|
903
|
+
quickstart: async (args) => {
|
|
904
|
+
const http = require('http');
|
|
905
|
+
const https = require('https');
|
|
906
|
+
const { A2AConfig } = require('../src/lib/config');
|
|
907
|
+
const disc = require('../src/lib/disclosure');
|
|
908
|
+
const {
|
|
909
|
+
normalizeHostInput,
|
|
910
|
+
splitHostPort,
|
|
911
|
+
isLocalOrUnroutableHost
|
|
912
|
+
} = require('../src/lib/invite-host');
|
|
913
|
+
const { getExternalIp } = require('../src/lib/external-ip');
|
|
914
|
+
const { CallbookStore } = require('../src/lib/callbook');
|
|
915
|
+
const { isPortListening, tryBindPort } = require('../src/lib/port-scanner');
|
|
916
|
+
|
|
917
|
+
const workspaceDir = process.env.A2A_WORKSPACE || process.cwd();
|
|
918
|
+
const config = new A2AConfig();
|
|
919
|
+
|
|
920
|
+
if (args.flags.force) {
|
|
921
|
+
config.resetOnboarding();
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const backendPort = (() => {
|
|
925
|
+
const raw = args.flags.port || process.env.A2A_PORT || process.env.PORT || 3001;
|
|
926
|
+
const n = Number.parseInt(String(raw), 10);
|
|
927
|
+
return (Number.isFinite(n) && n > 0 && n <= 65535) ? n : 3001;
|
|
928
|
+
})();
|
|
929
|
+
|
|
930
|
+
function looksLikePong(body) {
|
|
931
|
+
try {
|
|
932
|
+
const parsed = JSON.parse(String(body || ''));
|
|
933
|
+
if (parsed && typeof parsed === 'object' && parsed.pong === true) return true;
|
|
934
|
+
} catch (err) {
|
|
935
|
+
// ignore
|
|
936
|
+
}
|
|
937
|
+
return String(body || '').includes('"pong":true') || String(body || '').includes('"pong": true');
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function fetchUrlText(url, timeoutMs = 5000) {
|
|
941
|
+
return new Promise((resolve, reject) => {
|
|
942
|
+
let parsed;
|
|
943
|
+
try {
|
|
944
|
+
parsed = new URL(url);
|
|
945
|
+
} catch (err) {
|
|
946
|
+
reject(new Error('invalid_url'));
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const client = parsed.protocol === 'https:' ? https : http;
|
|
950
|
+
const req = client.request({
|
|
951
|
+
protocol: parsed.protocol,
|
|
952
|
+
hostname: parsed.hostname,
|
|
953
|
+
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
954
|
+
method: 'GET',
|
|
955
|
+
path: parsed.pathname + parsed.search,
|
|
956
|
+
headers: {
|
|
957
|
+
'User-Agent': `a2acalling/${process.env.npm_package_version || 'dev'} (quickstart)`
|
|
958
|
+
},
|
|
959
|
+
timeout: timeoutMs
|
|
960
|
+
}, (res) => {
|
|
961
|
+
let data = '';
|
|
962
|
+
res.setEncoding('utf8');
|
|
963
|
+
res.on('data', (chunk) => {
|
|
964
|
+
data += chunk;
|
|
965
|
+
if (data.length > 1024 * 256) {
|
|
966
|
+
req.destroy(new Error('response_too_large'));
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
res.on('end', () => resolve({ statusCode: res.statusCode || 0, body: data }));
|
|
970
|
+
});
|
|
971
|
+
req.on('error', reject);
|
|
972
|
+
req.on('timeout', () => req.destroy(new Error('timeout')));
|
|
973
|
+
req.end();
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
async function probeLocalPing(port, timeoutMs = 1000) {
|
|
978
|
+
try {
|
|
979
|
+
const res = await fetchUrlText(`http://127.0.0.1:${port}/api/a2a/ping`, timeoutMs);
|
|
980
|
+
return { ok: looksLikePong(res.body), statusCode: res.statusCode, body: res.body };
|
|
981
|
+
} catch (err) {
|
|
982
|
+
return { ok: false, error: err && err.message ? err.message : 'request_failed' };
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
async function externalPingCheck(targetUrl) {
|
|
987
|
+
const providers = [
|
|
988
|
+
{
|
|
989
|
+
name: 'allorigins',
|
|
990
|
+
buildUrl: () => {
|
|
991
|
+
const u = new URL('https://api.allorigins.win/raw');
|
|
992
|
+
u.searchParams.set('url', targetUrl);
|
|
993
|
+
return u.toString();
|
|
994
|
+
}
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
name: 'jina',
|
|
998
|
+
buildUrl: () => `https://r.jina.ai/${targetUrl}`
|
|
999
|
+
}
|
|
1000
|
+
];
|
|
1001
|
+
|
|
1002
|
+
for (const provider of providers) {
|
|
1003
|
+
try {
|
|
1004
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1005
|
+
const res = await fetchUrlText(provider.buildUrl(), 8000);
|
|
1006
|
+
if (looksLikePong(res.body)) {
|
|
1007
|
+
return { ok: true, provider: provider.name, statusCode: res.statusCode };
|
|
1008
|
+
}
|
|
1009
|
+
} catch (err) {
|
|
1010
|
+
// try next
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return { ok: false };
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function slugify(value) {
|
|
1017
|
+
return String(value || '')
|
|
1018
|
+
.toLowerCase()
|
|
1019
|
+
.replace(/['"]/g, '')
|
|
1020
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
1021
|
+
.replace(/^-+|-+$/g, '')
|
|
1022
|
+
.slice(0, 60);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function uniqueNonEmpty(items, limit = 24) {
|
|
1026
|
+
const out = [];
|
|
1027
|
+
const seen = new Set();
|
|
1028
|
+
for (const raw of items) {
|
|
1029
|
+
const s = String(raw || '').trim();
|
|
1030
|
+
if (!s) continue;
|
|
1031
|
+
if (seen.has(s)) continue;
|
|
1032
|
+
seen.add(s);
|
|
1033
|
+
out.push(s);
|
|
1034
|
+
if (out.length >= limit) break;
|
|
1035
|
+
}
|
|
1036
|
+
return out;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
function extractSectionBullets(markdown, headingRegex) {
|
|
1040
|
+
const text = String(markdown || '');
|
|
1041
|
+
const match = text.match(new RegExp(`##\\s*(?:${headingRegex})[^\\n]*\\n([\\s\\S]*?)(?=\\n##|$)`, 'i'));
|
|
1042
|
+
if (!match) return [];
|
|
1043
|
+
return match[1]
|
|
1044
|
+
.split('\n')
|
|
1045
|
+
.map(l => l.trim())
|
|
1046
|
+
.filter(l => l.startsWith('-') || l.startsWith('*'))
|
|
1047
|
+
.map(l => l.replace(/^[\\s\\-\\*]+/, '').trim())
|
|
1048
|
+
.filter(Boolean);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function tierFromManifest(manifest, tier, fallback = []) {
|
|
1052
|
+
const t = (manifest && manifest.topics && manifest.topics[tier]) ? manifest.topics[tier] : null;
|
|
1053
|
+
if (!t) return fallback;
|
|
1054
|
+
const items = []
|
|
1055
|
+
.concat(Array.isArray(t.lead_with) ? t.lead_with : [])
|
|
1056
|
+
.concat(Array.isArray(t.discuss_freely) ? t.discuss_freely : [])
|
|
1057
|
+
.concat(Array.isArray(t.deflect) ? t.deflect : []);
|
|
1058
|
+
const topics = items.map(x => (x && x.topic) ? x.topic : '').filter(Boolean);
|
|
1059
|
+
return topics.length ? topics : fallback;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function buildTierRecommendations(contextFiles, manifest) {
|
|
1063
|
+
const publicFallback = ['chat', 'openclaw', 'a2a'];
|
|
1064
|
+
const friendsFallback = ['chat', 'search', 'openclaw', 'a2a'];
|
|
1065
|
+
const familyFallback = ['chat', 'search', 'openclaw', 'a2a', 'tools', 'memory'];
|
|
1066
|
+
|
|
1067
|
+
const rawPublic = tierFromManifest(manifest, 'public', publicFallback);
|
|
1068
|
+
const rawFriends = tierFromManifest(manifest, 'friends', friendsFallback);
|
|
1069
|
+
const rawFamily = tierFromManifest(manifest, 'family', familyFallback);
|
|
1070
|
+
|
|
1071
|
+
const goalsFromUser = extractSectionBullets(contextFiles.user, 'Goals|Current|Seeking|Working On');
|
|
1072
|
+
const baseGoals = goalsFromUser.length
|
|
1073
|
+
? goalsFromUser
|
|
1074
|
+
: ['grow network', 'find collaborators', 'build in public'];
|
|
1075
|
+
|
|
1076
|
+
const publicTopics = uniqueNonEmpty(rawPublic.map(slugify).filter(Boolean), 16);
|
|
1077
|
+
const friendsTopics = uniqueNonEmpty(rawFriends.map(slugify).filter(Boolean), 20);
|
|
1078
|
+
const familyTopics = uniqueNonEmpty(rawFamily.map(slugify).filter(Boolean), 24);
|
|
1079
|
+
|
|
1080
|
+
const goals = uniqueNonEmpty(baseGoals.map(slugify).filter(Boolean), 12);
|
|
1081
|
+
|
|
1082
|
+
return {
|
|
1083
|
+
public: { topics: publicTopics, goals: goals.slice(0, 6) },
|
|
1084
|
+
friends: { topics: uniqueNonEmpty([...publicTopics, ...friendsTopics], 24), goals: goals.slice(0, 8) },
|
|
1085
|
+
family: { topics: uniqueNonEmpty([...publicTopics, ...friendsTopics, ...familyTopics], 30), goals }
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function printTierSummary(tiers) {
|
|
1090
|
+
const format = (t) => {
|
|
1091
|
+
const topics = (t.topics || []).join(' · ') || '(none)';
|
|
1092
|
+
const goals = (t.goals || []).join(' · ') || '(none)';
|
|
1093
|
+
return `Topics: ${topics}\nGoals: ${goals}`;
|
|
1094
|
+
};
|
|
1095
|
+
console.log('\nProposed permission tiers:\n');
|
|
1096
|
+
console.log('PUBLIC');
|
|
1097
|
+
console.log(format(tiers.public));
|
|
1098
|
+
console.log('\nFRIENDS');
|
|
1099
|
+
console.log(format(tiers.friends));
|
|
1100
|
+
console.log('\nFAMILY');
|
|
1101
|
+
console.log(format(tiers.family));
|
|
1102
|
+
console.log('');
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// ── Step 1: Background bootstrap (config + manifest) ─────────
|
|
1106
|
+
let contextFiles = {};
|
|
1107
|
+
let manifest = {};
|
|
900
1108
|
try {
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1109
|
+
contextFiles = disc.readContextFiles(workspaceDir);
|
|
1110
|
+
manifest = disc.loadManifest();
|
|
1111
|
+
if (!manifest || Object.keys(manifest).length === 0) {
|
|
1112
|
+
const generated = disc.generateDefaultManifest(contextFiles);
|
|
1113
|
+
disc.saveManifest(generated);
|
|
1114
|
+
manifest = generated;
|
|
1115
|
+
}
|
|
1116
|
+
} catch (err) {
|
|
1117
|
+
// Non-fatal: onboarding can proceed even if manifest fails.
|
|
1118
|
+
contextFiles = {};
|
|
1119
|
+
manifest = {};
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
console.log('\nA2A deterministic onboarding');
|
|
1123
|
+
console.log('──────────────────────────');
|
|
1124
|
+
|
|
1125
|
+
// ── Step 2: Owner dashboard access (local + optional remote) ─
|
|
1126
|
+
config.setOnboarding({ step: 'access' });
|
|
1127
|
+
|
|
1128
|
+
const hostnameFlagRaw = args.flags.hostname !== undefined ? String(args.flags.hostname) : '';
|
|
1129
|
+
const normalizedHostname = normalizeHostInput(hostnameFlagRaw);
|
|
1130
|
+
|
|
1131
|
+
// Invite host controls the a2a:// hostname we hand out (and remote dashboard pairing URL).
|
|
1132
|
+
let inviteHost = '';
|
|
1133
|
+
if (normalizedHostname) {
|
|
1134
|
+
const parsed = splitHostPort(normalizedHostname);
|
|
1135
|
+
const publicPortRaw = args.flags['public-port'] || args.flags.publicPort || process.env.A2A_PUBLIC_PORT || 443;
|
|
1136
|
+
const publicPort = Number.parseInt(String(publicPortRaw), 10);
|
|
1137
|
+
inviteHost = parsed.port
|
|
1138
|
+
? normalizedHostname
|
|
1139
|
+
: `${parsed.hostname}:${(Number.isFinite(publicPort) && publicPort > 0 && publicPort <= 65535) ? publicPort : 443}`;
|
|
1140
|
+
config.setAgent({ hostname: inviteHost });
|
|
1141
|
+
} else {
|
|
1142
|
+
const existing = normalizeHostInput((config.getAgent() || {}).hostname || '');
|
|
1143
|
+
inviteHost = existing || `localhost:${backendPort}`;
|
|
1144
|
+
if (!existing) {
|
|
1145
|
+
config.setAgent({ hostname: inviteHost });
|
|
923
1146
|
}
|
|
924
|
-
} catch (e) {
|
|
925
|
-
// Non-fatal, continue with quickstart
|
|
926
1147
|
}
|
|
927
1148
|
|
|
928
|
-
const
|
|
929
|
-
const
|
|
930
|
-
const
|
|
931
|
-
const
|
|
932
|
-
const
|
|
1149
|
+
const inviteParsed = splitHostPort(inviteHost);
|
|
1150
|
+
const invitePort = inviteParsed.port;
|
|
1151
|
+
const schemeOverride = String(process.env.A2A_PUBLIC_SCHEME || '').trim();
|
|
1152
|
+
const inviteScheme = schemeOverride || ((!invitePort || invitePort === 443) ? 'https' : 'http');
|
|
1153
|
+
const expectedPingUrl = `${inviteScheme}://${inviteHost}/api/a2a/ping`;
|
|
1154
|
+
const inviteLooksLocal = isLocalOrUnroutableHost(inviteParsed.hostname);
|
|
933
1155
|
|
|
934
|
-
console.log(
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
console.log('1️⃣ Checking server status...');
|
|
938
|
-
const checkServer = (port) => new Promise((resolve) => {
|
|
939
|
-
const req = http.request({
|
|
940
|
-
hostname: '127.0.0.1',
|
|
941
|
-
port,
|
|
942
|
-
path: '/api/a2a/ping',
|
|
943
|
-
timeout: 2000
|
|
944
|
-
}, (res) => {
|
|
945
|
-
resolve(res.statusCode === 200);
|
|
946
|
-
});
|
|
947
|
-
req.on('error', () => resolve(false));
|
|
948
|
-
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
949
|
-
req.end();
|
|
950
|
-
});
|
|
1156
|
+
console.log('\n2️⃣ Owner dashboard access');
|
|
1157
|
+
console.log(`Local dashboard: http://127.0.0.1:${backendPort}/dashboard/`);
|
|
1158
|
+
console.log(`Invite host: ${inviteHost}`);
|
|
951
1159
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
console.log(
|
|
960
|
-
console.log(` Run: A2A_HOSTNAME="${hostname}" a2a server --port ${serverPort}\n`);
|
|
1160
|
+
if (inviteLooksLocal) {
|
|
1161
|
+
console.log('Remote dashboard: not configured (invite host looks local/unroutable)');
|
|
1162
|
+
console.log(' To enable remote access, rerun with: --hostname YOUR_DOMAIN:443');
|
|
1163
|
+
} else {
|
|
1164
|
+
const callbookStore = new CallbookStore();
|
|
1165
|
+
if (!callbookStore.isAvailable()) {
|
|
1166
|
+
console.log('Remote dashboard: Callbook Remote not available (storage unavailable)');
|
|
1167
|
+
console.log(` Hint: ${callbookStore.getDbError ? callbookStore.getDbError() : 'storage_unavailable'}`);
|
|
961
1168
|
} else {
|
|
962
|
-
|
|
1169
|
+
const label = String(args.flags['device-label'] || args.flags.deviceLabel || 'Callbook Remote').trim().slice(0, 120);
|
|
1170
|
+
const ttlHoursRaw = args.flags['callbook-ttl-hours'] || args.flags.callbookTtlHours || 24;
|
|
1171
|
+
const ttlHours = Math.max(1, Math.min(168, Number.parseInt(String(ttlHoursRaw), 10) || 24));
|
|
1172
|
+
const created = callbookStore.createProvisionCode({ label, ttlMs: ttlHours * 60 * 60 * 1000 });
|
|
1173
|
+
if (created && created.success) {
|
|
1174
|
+
const installUrl = `${inviteScheme}://${inviteHost}/callbook/install#code=${created.code}`;
|
|
1175
|
+
console.log(`Remote dashboard: ${installUrl} (one-time, ${ttlHours}h)`);
|
|
1176
|
+
} else {
|
|
1177
|
+
console.log('Remote dashboard: failed to create install link');
|
|
1178
|
+
console.log(` Hint: ${created && created.message ? created.message : (created && created.error ? created.error : 'unknown_error')}`);
|
|
1179
|
+
}
|
|
963
1180
|
}
|
|
1181
|
+
}
|
|
964
1182
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1183
|
+
// ── Step 3: Permission tiers (topics + goals) ───────────────
|
|
1184
|
+
const onboardingAfterAccess = config.getOnboarding();
|
|
1185
|
+
if (!onboardingAfterAccess.tiers_confirmed) {
|
|
1186
|
+
const recommendations = buildTierRecommendations(contextFiles, manifest);
|
|
1187
|
+
|
|
1188
|
+
config.setTier('public', recommendations.public);
|
|
1189
|
+
config.setTier('friends', recommendations.friends);
|
|
1190
|
+
config.setTier('family', recommendations.family);
|
|
1191
|
+
|
|
1192
|
+
printTierSummary(recommendations);
|
|
974
1193
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1194
|
+
if (!args.flags['confirm-tiers']) {
|
|
1195
|
+
console.log('3️⃣ Confirm tiers');
|
|
1196
|
+
console.log('Review the topics/goals above. To confirm and continue, run:');
|
|
1197
|
+
console.log(` a2a quickstart --port ${backendPort} --confirm-tiers`);
|
|
1198
|
+
console.log('Optional (remote access):');
|
|
1199
|
+
console.log(` a2a quickstart --hostname YOUR_DOMAIN:443 --port ${backendPort} --confirm-tiers`);
|
|
1200
|
+
console.log('');
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
config.setOnboarding({
|
|
1205
|
+
step: 'tiers',
|
|
1206
|
+
tiers_confirmed: true
|
|
978
1207
|
});
|
|
1208
|
+
}
|
|
979
1209
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1210
|
+
// ── Step 4: Port scan + reverse proxy guidance (if needed) ──
|
|
1211
|
+
console.log('\n4️⃣ Port scan + reverse proxy');
|
|
1212
|
+
console.log(`Invite host: ${inviteHost}`);
|
|
1213
|
+
console.log(`Expected ping URL: ${expectedPingUrl}\n`);
|
|
1214
|
+
|
|
1215
|
+
const expectsReverseProxy = Boolean(
|
|
1216
|
+
(invitePort === 80 && backendPort !== 80) ||
|
|
1217
|
+
((!invitePort || invitePort === 443) && backendPort !== 443)
|
|
1218
|
+
);
|
|
1219
|
+
|
|
1220
|
+
if (expectsReverseProxy) {
|
|
1221
|
+
const port80Listening = await isPortListening(80, '127.0.0.1', { timeoutMs: 500 });
|
|
1222
|
+
const port80Bind = await tryBindPort(80, '0.0.0.0');
|
|
1223
|
+
const port80Ping = port80Listening.listening ? await probeLocalPing(80) : { ok: false };
|
|
1224
|
+
|
|
1225
|
+
console.log('Port 80:');
|
|
1226
|
+
if (port80Ping.ok) {
|
|
1227
|
+
console.log(' ✅ serves /api/a2a/ping (A2A detected on :80)');
|
|
1228
|
+
} else if (port80Listening.listening) {
|
|
1229
|
+
console.log(` ⚠️ has a listener (${port80Listening.code || 'in_use'})`);
|
|
1230
|
+
} else if (!port80Bind.ok && port80Bind.code === 'EACCES') {
|
|
1231
|
+
console.log(' ⚠️ appears free but is not bindable by this user (EACCES)');
|
|
1232
|
+
} else if (port80Bind.ok) {
|
|
1233
|
+
console.log(' ✅ free and bindable by this user');
|
|
1234
|
+
} else {
|
|
1235
|
+
console.log(` ⚠️ not bindable (${port80Bind.code || 'unknown'})`);
|
|
985
1236
|
}
|
|
986
1237
|
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
console.log('
|
|
992
|
-
console.log('
|
|
993
|
-
console.log(`
|
|
994
|
-
|
|
1238
|
+
console.log('\nReverse proxy required (example routes):');
|
|
1239
|
+
console.log(` /api/a2a/* -> http://127.0.0.1:${backendPort}`);
|
|
1240
|
+
console.log(` /dashboard/* -> http://127.0.0.1:${backendPort}`);
|
|
1241
|
+
console.log(` /callbook/* -> http://127.0.0.1:${backendPort}`);
|
|
1242
|
+
console.log('');
|
|
1243
|
+
console.log('If you have configured your reverse proxy and want to continue, run:');
|
|
1244
|
+
console.log(` a2a quickstart --hostname ${inviteHost} --port ${backendPort} --confirm-tiers --confirm-ingress`);
|
|
1245
|
+
console.log('');
|
|
1246
|
+
if (!args.flags['confirm-ingress']) {
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
} else {
|
|
1250
|
+
console.log('✅ No reverse proxy required based on invite host/port.');
|
|
1251
|
+
}
|
|
995
1252
|
|
|
996
|
-
|
|
1253
|
+
if (!config.getOnboarding().ingress_confirmed) {
|
|
1254
|
+
config.setOnboarding({
|
|
1255
|
+
step: 'ingress',
|
|
1256
|
+
ingress_confirmed: true
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
997
1259
|
|
|
998
|
-
|
|
1260
|
+
// ── Step 5: External IP + reachability check ───────────────
|
|
1261
|
+
console.log('\n5️⃣ External IP + reachability check');
|
|
999
1262
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1263
|
+
if (inviteLooksLocal) {
|
|
1264
|
+
console.log('Skipping external IP probe: invite host looks local/unroutable.');
|
|
1265
|
+
} else {
|
|
1266
|
+
const external = await getExternalIp({ forceRefresh: true });
|
|
1267
|
+
if (external && external.ip) {
|
|
1268
|
+
console.log(`External IP (${external.source || 'resolver'}): ${external.ip}`);
|
|
1269
|
+
} else {
|
|
1270
|
+
console.log(`External IP lookup failed: ${external && external.error ? external.error : 'unknown_error'}`);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1002
1273
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1274
|
+
const localListener = await isPortListening(backendPort, '127.0.0.1', { timeoutMs: 500 });
|
|
1275
|
+
if (!localListener.listening) {
|
|
1276
|
+
console.log('\n⚠️ A2A server is not reachable locally yet.');
|
|
1277
|
+
console.log('Start it, then rerun quickstart:');
|
|
1278
|
+
console.log(` A2A_HOSTNAME="${inviteHost}" a2a server --port ${backendPort}`);
|
|
1279
|
+
console.log('');
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
const localPing = await probeLocalPing(backendPort, inviteLooksLocal ? 250 : 1000);
|
|
1283
|
+
if (!localPing.ok) {
|
|
1284
|
+
if (inviteLooksLocal) {
|
|
1285
|
+
console.log(`\n⚠️ Port ${backendPort} is listening but /api/a2a/ping did not respond within a short timeout.`);
|
|
1286
|
+
console.log('Continuing onboarding anyway (invite host is local/unroutable).');
|
|
1287
|
+
} else {
|
|
1288
|
+
console.log('\n⚠️ A2A server is not responding locally yet.');
|
|
1289
|
+
console.log('Start it, then rerun quickstart:');
|
|
1290
|
+
console.log(` A2A_HOSTNAME="${inviteHost}" a2a server --port ${backendPort}`);
|
|
1291
|
+
console.log('');
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
if (inviteLooksLocal) {
|
|
1297
|
+
console.log('Skipping external reachability check: invite host looks local/unroutable.');
|
|
1298
|
+
} else {
|
|
1299
|
+
const extPing = await externalPingCheck(expectedPingUrl);
|
|
1300
|
+
if (extPing.ok) {
|
|
1301
|
+
console.log(`✅ External ping OK (${extPing.provider})`);
|
|
1302
|
+
} else if (!args.flags['skip-verify']) {
|
|
1303
|
+
console.log('⚠️ External ping FAILED (server may not be publicly reachable yet).');
|
|
1304
|
+
console.log('Fix ingress (DNS/reverse proxy/firewall), then rerun with:');
|
|
1305
|
+
console.log(` a2a quickstart --hostname ${inviteHost} --port ${backendPort} --confirm-tiers --confirm-ingress`);
|
|
1306
|
+
console.log('');
|
|
1307
|
+
return;
|
|
1308
|
+
} else {
|
|
1309
|
+
console.log('⚠️ External ping FAILED (skipped via --skip-verify).');
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
if (!config.getOnboarding().verify_confirmed) {
|
|
1314
|
+
config.setOnboarding({
|
|
1315
|
+
step: 'verify',
|
|
1316
|
+
verify_confirmed: true
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
config.completeOnboarding();
|
|
1321
|
+
console.log('✅ Onboarding complete.');
|
|
1322
|
+
console.log('Next: a2a gui or a2a create or a2a server');
|
|
1011
1323
|
},
|
|
1012
1324
|
|
|
1013
1325
|
install: () => {
|
|
@@ -1125,15 +1437,15 @@ https://github.com/onthegonow/a2a_calling
|
|
|
1125
1437
|
}
|
|
1126
1438
|
},
|
|
1127
1439
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1440
|
+
onboard: (args) => {
|
|
1441
|
+
const { A2AConfig } = require('../src/lib/config');
|
|
1442
|
+
const { readContextFiles, generateDefaultManifest, saveManifest, MANIFEST_FILE } = require('../src/lib/disclosure');
|
|
1443
|
+
const config = new A2AConfig();
|
|
1444
|
+
|
|
1445
|
+
if (config.isOnboarded() && !args.flags.force) {
|
|
1446
|
+
console.log('\u2705 Onboarding already complete. Use --force to regenerate the disclosure manifest.');
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1137
1449
|
|
|
1138
1450
|
const workspaceDir = process.env.A2A_WORKSPACE || process.cwd();
|
|
1139
1451
|
console.log('\n\ud83d\ude80 A2A Onboarding\n' + '\u2550'.repeat(50) + '\n');
|
|
@@ -1159,15 +1471,13 @@ https://github.com/onthegonow/a2a_calling
|
|
|
1159
1471
|
|
|
1160
1472
|
const agentName = args.flags.name || config.getAgent().name || process.env.A2A_AGENT_NAME || '';
|
|
1161
1473
|
const hostname = args.flags.hostname || config.getAgent().hostname || process.env.A2A_HOSTNAME || '';
|
|
1162
|
-
|
|
1163
|
-
|
|
1474
|
+
if (agentName) config.setAgent({ name: agentName });
|
|
1475
|
+
if (hostname) config.setAgent({ hostname });
|
|
1164
1476
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
console.log(` Next: a2a quickstart or a2a server\n`);
|
|
1170
|
-
},
|
|
1477
|
+
console.log(`\n\u2705 Disclosure manifest generated.`);
|
|
1478
|
+
console.log(` Manifest: ${MANIFEST_FILE}`);
|
|
1479
|
+
console.log(' Next: a2a quickstart\n');
|
|
1480
|
+
},
|
|
1171
1481
|
|
|
1172
1482
|
help: () => {
|
|
1173
1483
|
console.log(`A2A Calling - Agent-to-Agent Communication
|
|
@@ -1194,11 +1504,13 @@ Contacts:
|
|
|
1194
1504
|
contacts add <url> Add a contact
|
|
1195
1505
|
--name, -n Agent name
|
|
1196
1506
|
--owner, -o Owner name
|
|
1507
|
+
--server-name Server label (optional)
|
|
1197
1508
|
--notes Notes about this contact
|
|
1198
1509
|
--tags Comma-separated tags
|
|
1199
1510
|
--link Link to token ID you gave them
|
|
1200
1511
|
contacts show <n> Show contact details + linked token
|
|
1201
1512
|
contacts edit <n> Edit contact metadata
|
|
1513
|
+
--server-name Server label (optional)
|
|
1202
1514
|
contacts link <n> <tok> Link a token to a contact
|
|
1203
1515
|
contacts ping <n> Ping contact, update status
|
|
1204
1516
|
contacts rm <n> Remove contact
|
|
@@ -1215,7 +1527,7 @@ Conversations:
|
|
|
1215
1527
|
conversations end <id> End and summarize conversation
|
|
1216
1528
|
|
|
1217
1529
|
Calling:
|
|
1218
|
-
call <contact|url> <msg> Call a
|
|
1530
|
+
call <contact|url> <msg> Call a contact (or invite URL)
|
|
1219
1531
|
ping <url> Check if agent is reachable
|
|
1220
1532
|
status <url> Get A2A status
|
|
1221
1533
|
gui Open the local dashboard GUI in a browser
|
|
@@ -1225,9 +1537,14 @@ Server:
|
|
|
1225
1537
|
server Start the A2A server
|
|
1226
1538
|
--port, -p Port to listen on (default: 3001)
|
|
1227
1539
|
|
|
1228
|
-
quickstart
|
|
1229
|
-
--
|
|
1230
|
-
--
|
|
1540
|
+
quickstart Deterministic onboarding (access → tiers → ingress → verify)
|
|
1541
|
+
--hostname Public hostname for remote access (e.g. myserver.com:443)
|
|
1542
|
+
--public-port Port to assume when --hostname omits a port (default: 443)
|
|
1543
|
+
--port A2A server port to run locally (default: 3001)
|
|
1544
|
+
--confirm-tiers Confirm tier topics/goals and continue
|
|
1545
|
+
--confirm-ingress Confirm reverse proxy/ingress is configured and continue
|
|
1546
|
+
--skip-verify Skip external reachability check (not recommended)
|
|
1547
|
+
--force Reset onboarding and start over
|
|
1231
1548
|
|
|
1232
1549
|
onboard Generate disclosure manifest from workspace context
|
|
1233
1550
|
--force Re-run even if already onboarded
|