a2acalling 0.6.5 → 0.6.6
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 +13 -0
- package/SKILL.md +29 -1
- package/bin/cli.js +826 -439
- package/docs/plans/2026-02-14-agent-driven-disclosure-extraction.md +986 -0
- package/package.json +2 -1
- package/scripts/postinstall.js +25 -0
- package/src/lib/disclosure.js +171 -7
package/bin/cli.js
CHANGED
|
@@ -12,11 +12,42 @@
|
|
|
12
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
|
+
* a2a uninstall Stop server and remove local A2A config
|
|
15
16
|
*/
|
|
16
17
|
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const readline = require('readline');
|
|
22
|
+
const { spawn } = require('child_process');
|
|
17
23
|
const { TokenStore } = require('../src/lib/tokens');
|
|
18
24
|
const { A2AClient } = require('../src/lib/client');
|
|
19
25
|
|
|
26
|
+
const CONFIG_DIR = process.env.A2A_CONFIG_DIR || process.env.OPENCLAW_CONFIG_DIR || path.join(os.homedir(), '.config', 'openclaw');
|
|
27
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, 'a2a-config.json');
|
|
28
|
+
const ONBOARDING_EXEMPT = new Set([
|
|
29
|
+
'quickstart',
|
|
30
|
+
'help',
|
|
31
|
+
'version',
|
|
32
|
+
'update',
|
|
33
|
+
'uninstall',
|
|
34
|
+
'onboard',
|
|
35
|
+
'gui',
|
|
36
|
+
'dashboard',
|
|
37
|
+
'server',
|
|
38
|
+
'setup',
|
|
39
|
+
'install'
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
function isOnboarded() {
|
|
43
|
+
try {
|
|
44
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
45
|
+
return config.onboarding?.version === 2 && config.onboarding?.step === 'complete';
|
|
46
|
+
} catch (err) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
20
51
|
// Lazy load conversation store (requires better-sqlite3)
|
|
21
52
|
let convStore = null;
|
|
22
53
|
function getConvStore() {
|
|
@@ -40,20 +71,20 @@ function getConvStore() {
|
|
|
40
71
|
|
|
41
72
|
const store = new TokenStore();
|
|
42
73
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
74
|
+
function enforceOnboarding(command) {
|
|
75
|
+
if (ONBOARDING_EXEMPT.has(command)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!isOnboarded()) {
|
|
80
|
+
console.log('\n⚠️ A2A not configured yet.');
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log('Run this first:');
|
|
83
|
+
console.log(' a2a quickstart --hostname YOUR_DOMAIN:PORT');
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log('Example:');
|
|
86
|
+
console.log(' a2a quickstart --hostname myserver.com:3001');
|
|
87
|
+
process.exit(1);
|
|
57
88
|
}
|
|
58
89
|
}
|
|
59
90
|
|
|
@@ -71,8 +102,6 @@ function formatTimeAgo(date) {
|
|
|
71
102
|
}
|
|
72
103
|
|
|
73
104
|
function openInBrowser(url) {
|
|
74
|
-
const { spawn } = require('child_process');
|
|
75
|
-
|
|
76
105
|
const platform = process.platform;
|
|
77
106
|
let cmd = null;
|
|
78
107
|
let args = [];
|
|
@@ -162,6 +191,17 @@ function parseArgs(argv) {
|
|
|
162
191
|
return args;
|
|
163
192
|
}
|
|
164
193
|
|
|
194
|
+
async function promptYesNo(question) {
|
|
195
|
+
return await new Promise(resolve => {
|
|
196
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
197
|
+
rl.question(question, (answer) => {
|
|
198
|
+
rl.close();
|
|
199
|
+
const normalized = String(answer || '').trim().toLowerCase();
|
|
200
|
+
resolve(normalized === 'y' || normalized === 'yes');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
165
205
|
async function resolveInviteHostname() {
|
|
166
206
|
const { resolveInviteHost } = require('../src/lib/invite-host');
|
|
167
207
|
|
|
@@ -184,7 +224,6 @@ async function resolveInviteHostname() {
|
|
|
184
224
|
// Commands
|
|
185
225
|
const commands = {
|
|
186
226
|
create: async (args) => {
|
|
187
|
-
checkOnboarding('create');
|
|
188
227
|
// Parse max-calls: number, 'unlimited', or default (unlimited)
|
|
189
228
|
let maxCalls = null; // Default: unlimited
|
|
190
229
|
if (args.flags['max-calls']) {
|
|
@@ -753,7 +792,6 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
753
792
|
},
|
|
754
793
|
|
|
755
794
|
call: async (args) => {
|
|
756
|
-
checkOnboarding('call');
|
|
757
795
|
let target = args._[1];
|
|
758
796
|
const message = args._.slice(2).join(' ') || args.flags.message || args.flags.m;
|
|
759
797
|
|
|
@@ -902,497 +940,705 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
902
940
|
|
|
903
941
|
quickstart: async (args) => {
|
|
904
942
|
const http = require('http');
|
|
905
|
-
const https = require('https');
|
|
906
943
|
const { A2AConfig } = require('../src/lib/config');
|
|
907
|
-
const
|
|
944
|
+
const { isPortListening } = require('../src/lib/port-scanner');
|
|
945
|
+
const {
|
|
946
|
+
readContextFiles,
|
|
947
|
+
generateDefaultManifest,
|
|
948
|
+
saveManifest
|
|
949
|
+
} = require('../src/lib/disclosure');
|
|
908
950
|
const {
|
|
909
951
|
normalizeHostInput,
|
|
910
952
|
splitHostPort,
|
|
911
953
|
isLocalOrUnroutableHost
|
|
912
954
|
} = 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
955
|
|
|
917
|
-
const workspaceDir = process.env.A2A_WORKSPACE || process.cwd();
|
|
918
956
|
const config = new A2AConfig();
|
|
957
|
+
const workspaceDir = process.env.A2A_WORKSPACE || process.cwd();
|
|
919
958
|
|
|
920
959
|
if (args.flags.force) {
|
|
921
960
|
config.resetOnboarding();
|
|
922
961
|
}
|
|
923
962
|
|
|
924
|
-
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
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
|
|
963
|
+
function parsePort(raw, fallback) {
|
|
964
|
+
const parsed = Number.parseInt(String(raw || '').trim(), 10);
|
|
965
|
+
if (Number.isFinite(parsed) && parsed > 0 && parsed <= 65535) {
|
|
966
|
+
return parsed;
|
|
936
967
|
}
|
|
937
|
-
return
|
|
968
|
+
return fallback;
|
|
938
969
|
}
|
|
939
970
|
|
|
940
|
-
function
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
parsed = new URL(url);
|
|
945
|
-
} catch (err) {
|
|
946
|
-
reject(new Error('invalid_url'));
|
|
947
|
-
return;
|
|
971
|
+
function uniqueNonEmpty(values, limit = 80) {
|
|
972
|
+
const normalizeValue = (value) => {
|
|
973
|
+
if (typeof value === 'string') {
|
|
974
|
+
return String(value || '').trim();
|
|
948
975
|
}
|
|
949
|
-
|
|
950
|
-
|
|
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
|
-
// Try direct access first. In practice this is the most reliable signal:
|
|
988
|
-
// it avoids flaky third-party proxies and catches obvious scheme/port mistakes.
|
|
989
|
-
try {
|
|
990
|
-
const direct = await fetchUrlText(targetUrl, 2500);
|
|
991
|
-
return { ok: looksLikePong(direct.body), provider: 'direct', statusCode: direct.statusCode };
|
|
992
|
-
} catch (err) {
|
|
993
|
-
// Fall back to remote fetch providers below.
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
const providers = [
|
|
997
|
-
{
|
|
998
|
-
name: 'allorigins',
|
|
999
|
-
buildUrl: () => {
|
|
1000
|
-
const u = new URL('https://api.allorigins.win/raw');
|
|
1001
|
-
u.searchParams.set('url', targetUrl);
|
|
1002
|
-
return u.toString();
|
|
1003
|
-
}
|
|
1004
|
-
},
|
|
1005
|
-
{
|
|
1006
|
-
name: 'jina',
|
|
1007
|
-
buildUrl: () => `https://r.jina.ai/${targetUrl}`
|
|
976
|
+
if (value && typeof value === 'object' && !Array.isArray(value) && 'topic' in value) {
|
|
977
|
+
return String(value.topic || '').trim();
|
|
1008
978
|
}
|
|
1009
|
-
|
|
979
|
+
return '';
|
|
980
|
+
};
|
|
1010
981
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
982
|
+
const out = [];
|
|
983
|
+
const seen = new Set();
|
|
984
|
+
for (const value of values) {
|
|
985
|
+
const text = normalizeValue(value);
|
|
986
|
+
if (!text) continue;
|
|
987
|
+
const key = text.toLowerCase();
|
|
988
|
+
if (seen.has(key)) continue;
|
|
989
|
+
seen.add(key);
|
|
990
|
+
out.push(text);
|
|
991
|
+
if (out.length >= limit) break;
|
|
1021
992
|
}
|
|
1022
|
-
return
|
|
993
|
+
return out;
|
|
1023
994
|
}
|
|
1024
995
|
|
|
1025
|
-
function
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
996
|
+
function normalizeTopicRecord(raw) {
|
|
997
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
998
|
+
return {
|
|
999
|
+
topic: String(raw.topic || '').trim(),
|
|
1000
|
+
detail: String(raw.detail || '').trim()
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
return {
|
|
1004
|
+
topic: String(raw || '').trim(),
|
|
1005
|
+
detail: ''
|
|
1006
|
+
};
|
|
1032
1007
|
}
|
|
1033
1008
|
|
|
1034
|
-
function
|
|
1009
|
+
function uniqueTopicRecords(values, limit = 80) {
|
|
1035
1010
|
const out = [];
|
|
1036
1011
|
const seen = new Set();
|
|
1037
|
-
for (const
|
|
1038
|
-
const
|
|
1039
|
-
if (!
|
|
1040
|
-
|
|
1041
|
-
seen.
|
|
1042
|
-
|
|
1012
|
+
for (const value of values) {
|
|
1013
|
+
const item = normalizeTopicRecord(value);
|
|
1014
|
+
if (!item.topic) continue;
|
|
1015
|
+
const key = item.topic.toLowerCase();
|
|
1016
|
+
if (seen.has(key)) continue;
|
|
1017
|
+
seen.add(key);
|
|
1018
|
+
out.push(item);
|
|
1043
1019
|
if (out.length >= limit) break;
|
|
1044
1020
|
}
|
|
1045
1021
|
return out;
|
|
1046
1022
|
}
|
|
1047
1023
|
|
|
1048
|
-
function
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
.split('\n')
|
|
1054
|
-
.map(l => l.trim())
|
|
1055
|
-
.filter(l => l.startsWith('-') || l.startsWith('*'))
|
|
1056
|
-
.map(l => l.replace(/^[\\s\\-\\*]+/, '').trim())
|
|
1057
|
-
.filter(Boolean);
|
|
1024
|
+
function sanitizeSectionItems(values, limit = 80) {
|
|
1025
|
+
return uniqueTopicRecords(values, limit).map(item => ({
|
|
1026
|
+
topic: item.topic,
|
|
1027
|
+
detail: item.detail || ''
|
|
1028
|
+
}));
|
|
1058
1029
|
}
|
|
1059
1030
|
|
|
1060
|
-
function
|
|
1061
|
-
|
|
1062
|
-
if (!t) return fallback;
|
|
1063
|
-
const items = []
|
|
1064
|
-
.concat(Array.isArray(t.lead_with) ? t.lead_with : [])
|
|
1065
|
-
.concat(Array.isArray(t.discuss_freely) ? t.discuss_freely : [])
|
|
1066
|
-
.concat(Array.isArray(t.deflect) ? t.deflect : []);
|
|
1067
|
-
const topics = items.map(x => (x && x.topic) ? x.topic : '').filter(Boolean);
|
|
1068
|
-
return topics.length ? topics : fallback;
|
|
1031
|
+
function cloneDraft(draft = {}) {
|
|
1032
|
+
return JSON.parse(JSON.stringify(draft));
|
|
1069
1033
|
}
|
|
1070
1034
|
|
|
1071
|
-
function
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
|
|
1035
|
+
function makeDraft(manifest) {
|
|
1036
|
+
const src = (manifest && manifest.topics) ? manifest.topics : {};
|
|
1037
|
+
return {
|
|
1038
|
+
public: {
|
|
1039
|
+
lead_with: sanitizeSectionItems((src.public && src.public.lead_with) || [], 60),
|
|
1040
|
+
discuss_freely: sanitizeSectionItems((src.public && src.public.discuss_freely) || [], 60),
|
|
1041
|
+
deflect: sanitizeSectionItems((src.public && src.public.deflect) || [], 60)
|
|
1042
|
+
},
|
|
1043
|
+
friends: {
|
|
1044
|
+
lead_with: sanitizeSectionItems((src.friends && src.friends.lead_with) || [], 60),
|
|
1045
|
+
discuss_freely: sanitizeSectionItems((src.friends && src.friends.discuss_freely) || [], 60),
|
|
1046
|
+
deflect: sanitizeSectionItems((src.friends && src.friends.deflect) || [], 60)
|
|
1047
|
+
},
|
|
1048
|
+
family: {
|
|
1049
|
+
lead_with: sanitizeSectionItems((src.family && src.family.lead_with) || [], 60),
|
|
1050
|
+
discuss_freely: sanitizeSectionItems((src.family && src.family.discuss_freely) || [], 60),
|
|
1051
|
+
deflect: sanitizeSectionItems((src.family && src.family.deflect) || [], 60)
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1075
1055
|
|
|
1076
|
-
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1056
|
+
function summarizeLine(content, maxLen = 60) {
|
|
1057
|
+
const text = String(content || '').split('\n').map((line) => line.trim()).find((line) => {
|
|
1058
|
+
return line && !line.startsWith('#') && !line.startsWith('---') && line.length <= 220;
|
|
1059
|
+
});
|
|
1060
|
+
if (!text) {
|
|
1061
|
+
return 'found';
|
|
1062
|
+
}
|
|
1063
|
+
return text.length > maxLen ? `${text.slice(0, maxLen)}...` : text;
|
|
1064
|
+
}
|
|
1079
1065
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1066
|
+
function countMemoryDocs(root) {
|
|
1067
|
+
try {
|
|
1068
|
+
const dir = path.join(root, 'memory');
|
|
1069
|
+
if (!fs.existsSync(dir)) return 0;
|
|
1070
|
+
return fs.readdirSync(dir).filter(name => name.endsWith('.md')).length;
|
|
1071
|
+
} catch (err) {
|
|
1072
|
+
return 0;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1084
1075
|
|
|
1085
|
-
|
|
1086
|
-
const
|
|
1087
|
-
|
|
1076
|
+
function renderWorkspaceScan(contextFiles) {
|
|
1077
|
+
const memoryCount = countMemoryDocs(workspaceDir);
|
|
1078
|
+
console.log('\n🔍 Scanning workspace for context...\n');
|
|
1079
|
+
console.log('Found:');
|
|
1080
|
+
const rows = [
|
|
1081
|
+
{ label: 'USER.md', found: Boolean(contextFiles.user), note: summarizeLine(contextFiles.user, 72) },
|
|
1082
|
+
{ label: 'SOUL.md', found: Boolean(contextFiles.soul), note: summarizeLine(contextFiles.soul, 72) },
|
|
1083
|
+
{ label: 'HEARTBEAT.md', found: Boolean(contextFiles.heartbeat), note: 'contains agent tasks, not disclosure topics' },
|
|
1084
|
+
{ label: 'SKILL.md', found: Boolean(contextFiles.skill), note: null },
|
|
1085
|
+
{ label: 'memory/*.md', found: memoryCount > 0, note: `${memoryCount} file${memoryCount === 1 ? '' : 's'}` }
|
|
1086
|
+
];
|
|
1088
1087
|
|
|
1089
|
-
const
|
|
1088
|
+
for (const row of rows) {
|
|
1089
|
+
const check = row.found ? '✅' : '❌';
|
|
1090
|
+
const note = row.found && row.note ? ` — ${row.note}` : '';
|
|
1091
|
+
const skip = row.label === 'HEARTBEAT.md' && row.found ? ' (skipped)' : '';
|
|
1092
|
+
console.log(` ${check} ${row.label}${skip}${note}`);
|
|
1093
|
+
}
|
|
1094
|
+
console.log('');
|
|
1095
|
+
}
|
|
1090
1096
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1097
|
+
function sectionLabel(sectionName) {
|
|
1098
|
+
if (sectionName === 'lead_with') return 'Lead with';
|
|
1099
|
+
if (sectionName === 'discuss_freely') return 'Discuss freely';
|
|
1100
|
+
return 'Deflect';
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function flattenDraft(draft) {
|
|
1104
|
+
const flat = [];
|
|
1105
|
+
let index = 1;
|
|
1106
|
+
['public', 'friends', 'family'].forEach((tier) => {
|
|
1107
|
+
['lead_with', 'discuss_freely', 'deflect'].forEach((section) => {
|
|
1108
|
+
(draft[tier][section] || []).forEach((item, itemIndex) => {
|
|
1109
|
+
flat.push({
|
|
1110
|
+
index,
|
|
1111
|
+
tier,
|
|
1112
|
+
section,
|
|
1113
|
+
item,
|
|
1114
|
+
itemIndex,
|
|
1115
|
+
list: draft[tier][section]
|
|
1116
|
+
});
|
|
1117
|
+
index += 1;
|
|
1118
|
+
});
|
|
1119
|
+
});
|
|
1120
|
+
});
|
|
1121
|
+
return flat;
|
|
1096
1122
|
}
|
|
1097
1123
|
|
|
1098
|
-
function
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1124
|
+
function renderDraft(draft, neverDisclose) {
|
|
1125
|
+
console.log('\n📋 Proposed Permission Tiers');
|
|
1126
|
+
console.log('═'.repeat(60));
|
|
1127
|
+
|
|
1128
|
+
let index = 1;
|
|
1129
|
+
const titleByTier = {
|
|
1130
|
+
public: 'PUBLIC (anyone can see):',
|
|
1131
|
+
friends: 'FRIENDS (trusted contacts):',
|
|
1132
|
+
family: 'FAMILY (inner circle):'
|
|
1103
1133
|
};
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1134
|
+
|
|
1135
|
+
['public', 'friends', 'family'].forEach((tier) => {
|
|
1136
|
+
console.log(`\n${titleByTier[tier]}`);
|
|
1137
|
+
['lead_with', 'discuss_freely', 'deflect'].forEach((section) => {
|
|
1138
|
+
console.log(` ${sectionLabel(section)}:`);
|
|
1139
|
+
const list = draft[tier][section] || [];
|
|
1140
|
+
if (list.length === 0) {
|
|
1141
|
+
console.log(' (none)');
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
list.forEach((item) => {
|
|
1145
|
+
const detail = item.detail ? ` — ${item.detail}` : '';
|
|
1146
|
+
console.log(` ${index}. ${item.topic}${detail}`);
|
|
1147
|
+
index += 1;
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
console.log('\nNEVER DISCLOSE:');
|
|
1153
|
+
const staticNever = (neverDisclose || ['API keys', 'Other users\' data', 'Financial figures']);
|
|
1154
|
+
staticNever.forEach((item) => console.log(` • ${item}`));
|
|
1155
|
+
console.log('═'.repeat(60));
|
|
1156
|
+
return flattenDraft(draft);
|
|
1112
1157
|
}
|
|
1113
1158
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
disc.saveManifest(generated);
|
|
1131
|
-
manifest = generated;
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
} catch (err) {
|
|
1135
|
-
// Non-fatal: onboarding can proceed even if manifest fails.
|
|
1136
|
-
contextFiles = {};
|
|
1137
|
-
manifest = {};
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
console.log('\nA2A deterministic onboarding');
|
|
1141
|
-
console.log('──────────────────────────');
|
|
1142
|
-
|
|
1143
|
-
// ── Step 2: Owner dashboard access (local + optional remote) ─
|
|
1144
|
-
config.setOnboarding({ step: 'access' });
|
|
1145
|
-
|
|
1146
|
-
const hostnameFlagRaw = args.flags.hostname !== undefined ? String(args.flags.hostname) : '';
|
|
1147
|
-
const normalizedHostname = normalizeHostInput(hostnameFlagRaw);
|
|
1148
|
-
|
|
1149
|
-
// Invite host controls the a2a:// hostname we hand out (and remote dashboard pairing URL).
|
|
1150
|
-
let inviteHost = '';
|
|
1151
|
-
if (normalizedHostname) {
|
|
1152
|
-
const parsed = splitHostPort(normalizedHostname);
|
|
1153
|
-
const publicPortRaw = args.flags['public-port'] || args.flags.publicPort || process.env.A2A_PUBLIC_PORT || 443;
|
|
1154
|
-
const publicPort = Number.parseInt(String(publicPortRaw), 10);
|
|
1155
|
-
inviteHost = parsed.port
|
|
1156
|
-
? normalizedHostname
|
|
1157
|
-
: `${parsed.hostname}:${(Number.isFinite(publicPort) && publicPort > 0 && publicPort <= 65535) ? publicPort : 443}`;
|
|
1158
|
-
config.setAgent({ hostname: inviteHost });
|
|
1159
|
-
} else {
|
|
1160
|
-
const existing = normalizeHostInput((config.getAgent() || {}).hostname || '');
|
|
1161
|
-
inviteHost = existing || `localhost:${backendPort}`;
|
|
1162
|
-
if (!existing) {
|
|
1163
|
-
config.setAgent({ hostname: inviteHost });
|
|
1164
|
-
}
|
|
1159
|
+
function parseSections(target) {
|
|
1160
|
+
if (!target) return null;
|
|
1161
|
+
const [tierRaw, sectionRaw] = String(target).toLowerCase().split('.');
|
|
1162
|
+
if (!tierRaw || !sectionRaw) return null;
|
|
1163
|
+
if (!['public', 'friends', 'family'].includes(tierRaw)) return null;
|
|
1164
|
+
|
|
1165
|
+
const section = {
|
|
1166
|
+
lead: 'lead_with',
|
|
1167
|
+
lead_with: 'lead_with',
|
|
1168
|
+
discuss: 'discuss_freely',
|
|
1169
|
+
discuss_freely: 'discuss_freely',
|
|
1170
|
+
deflect: 'deflect'
|
|
1171
|
+
}[sectionRaw];
|
|
1172
|
+
|
|
1173
|
+
if (!section) return null;
|
|
1174
|
+
return { tier: tierRaw, section };
|
|
1165
1175
|
}
|
|
1166
1176
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1177
|
+
function splitCommand(input) {
|
|
1178
|
+
const raw = String(input || '').trim();
|
|
1179
|
+
if (!raw) return [];
|
|
1180
|
+
const match = raw.match(/"([^"]*)"|'([^']*)'|`([^`]*)`|\S+/g);
|
|
1181
|
+
if (!match) return [];
|
|
1182
|
+
return match.map((token) => {
|
|
1183
|
+
if ((token.startsWith('"') && token.endsWith('"')) ||
|
|
1184
|
+
(token.startsWith("'") && token.endsWith("'")) ||
|
|
1185
|
+
(token.startsWith('`') && token.endsWith('`'))) {
|
|
1186
|
+
return token.slice(1, -1);
|
|
1187
|
+
}
|
|
1188
|
+
return token;
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1173
1191
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1192
|
+
function findByIndex(draft, index) {
|
|
1193
|
+
const target = flattenDraft(draft).find(item => item.index === index);
|
|
1194
|
+
return target || null;
|
|
1195
|
+
}
|
|
1177
1196
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
+
function readNameFromUserContext(content) {
|
|
1198
|
+
const lines = String(content || '').split('\n');
|
|
1199
|
+
for (const line of lines) {
|
|
1200
|
+
const trimmed = String(line || '').trim();
|
|
1201
|
+
if (!trimmed) continue;
|
|
1202
|
+
|
|
1203
|
+
const nameMatch = trimmed.match(/^\*{0,2}Name:\*{0,2}\s*(.+)$/i);
|
|
1204
|
+
if (nameMatch && nameMatch[1]) {
|
|
1205
|
+
return String(nameMatch[1]).trim();
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
if (/^(owner|ownername):/i.test(trimmed)) {
|
|
1209
|
+
const ownerMatch = trimmed.replace(/^[^:]+:\s*/, '');
|
|
1210
|
+
if (ownerMatch) return ownerMatch.trim();
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
if (trimmed.startsWith('-') || trimmed.startsWith('*') || trimmed.startsWith('#')) {
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
if (/^[A-Za-z][\w\-,.\s]{2,}$/i.test(trimmed)) {
|
|
1218
|
+
const candidate = trimmed.split('|')[0].split('\t')[0].trim();
|
|
1219
|
+
if (candidate && candidate.length <= 80) {
|
|
1220
|
+
return candidate;
|
|
1221
|
+
}
|
|
1197
1222
|
}
|
|
1198
1223
|
}
|
|
1224
|
+
return '';
|
|
1199
1225
|
}
|
|
1200
1226
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
const parseFreeTextList = (raw) => {
|
|
1207
|
-
if (raw === undefined || raw === null || raw === true) return [];
|
|
1208
|
-
const text = String(raw || '').trim();
|
|
1209
|
-
if (!text) return [];
|
|
1210
|
-
return text
|
|
1211
|
-
.split(/[\n,]+/g)
|
|
1212
|
-
.map(s => s.trim())
|
|
1213
|
-
.filter(Boolean);
|
|
1214
|
-
};
|
|
1215
|
-
|
|
1216
|
-
const promptLine = async (question) => {
|
|
1217
|
-
const readline = require('readline');
|
|
1218
|
-
return await new Promise(resolve => {
|
|
1219
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1220
|
-
rl.question(question, (answer) => {
|
|
1221
|
-
rl.close();
|
|
1222
|
-
resolve(String(answer || '').trim());
|
|
1223
|
-
});
|
|
1224
|
-
});
|
|
1225
|
-
};
|
|
1226
|
-
|
|
1227
|
-
// Optional owner override: Friends tier topics/interests (most important tier).
|
|
1228
|
-
const interactive = Boolean(
|
|
1229
|
-
args.flags.interactive ||
|
|
1230
|
-
args.flags['ask-friends-topics'] ||
|
|
1231
|
-
args.flags.askFriendsTopics
|
|
1232
|
-
);
|
|
1233
|
-
let friendsTopicsOverride = parseFreeTextList(args.flags['friends-topics'] || args.flags.friendsTopics);
|
|
1234
|
-
const noWorkspaceContext = !contextFiles.user && !contextFiles.heartbeat && !contextFiles.soul &&
|
|
1235
|
-
!contextFiles.memory && !contextFiles.claude;
|
|
1236
|
-
const shouldPromptFriendsTopics = (interactive || noWorkspaceContext) &&
|
|
1237
|
-
friendsTopicsOverride.length === 0 &&
|
|
1238
|
-
process.stdin.isTTY &&
|
|
1239
|
-
process.stdout.isTTY;
|
|
1240
|
-
if (shouldPromptFriendsTopics) {
|
|
1241
|
-
const suggested = (recommendations.friends.topics || []).slice(0, 12).join(', ');
|
|
1242
|
-
const answer = await promptLine(`Friends-tier topics/interests (comma-separated).\nSuggested: ${suggested}\n> `);
|
|
1243
|
-
friendsTopicsOverride = parseFreeTextList(answer);
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
if (friendsTopicsOverride.length > 0) {
|
|
1247
|
-
const normalized = uniqueNonEmpty(friendsTopicsOverride.map(slugify).filter(Boolean), 24);
|
|
1248
|
-
recommendations.friends.topics = uniqueNonEmpty(
|
|
1249
|
-
[...(recommendations.public.topics || []), ...normalized],
|
|
1250
|
-
24
|
|
1251
|
-
);
|
|
1252
|
-
recommendations.family.topics = uniqueNonEmpty(
|
|
1253
|
-
[...(recommendations.friends.topics || []), ...(recommendations.family.topics || [])],
|
|
1254
|
-
30
|
|
1255
|
-
);
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
try {
|
|
1259
|
-
config.setTier('public', recommendations.public);
|
|
1260
|
-
config.setTier('friends', recommendations.friends);
|
|
1261
|
-
config.setTier('family', recommendations.family);
|
|
1262
|
-
} catch (err) {
|
|
1263
|
-
console.error('\n❌ Tier configuration validation failed.');
|
|
1264
|
-
console.error(` ${err.message}`);
|
|
1265
|
-
if (err.hint) {
|
|
1266
|
-
console.error(` Hint: ${err.hint}`);
|
|
1267
|
-
}
|
|
1268
|
-
console.error('');
|
|
1269
|
-
process.exit(1);
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
printTierSummary(recommendations);
|
|
1273
|
-
|
|
1274
|
-
config.setOnboarding({
|
|
1275
|
-
step: 'tiers',
|
|
1276
|
-
tiers_confirmed: true
|
|
1277
|
-
});
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
// ── Step 4: Port scan + reverse proxy guidance (if needed) ──
|
|
1281
|
-
console.log('\n4️⃣ Port scan + reverse proxy');
|
|
1282
|
-
console.log(`Invite host: ${inviteHost}`);
|
|
1283
|
-
console.log(`Expected ping URL: ${expectedPingUrl}\n`);
|
|
1284
|
-
|
|
1285
|
-
const expectsReverseProxy = Boolean(
|
|
1286
|
-
(invitePort === 80 && backendPort !== 80) ||
|
|
1287
|
-
((!invitePort || invitePort === 443) && backendPort !== 443)
|
|
1288
|
-
);
|
|
1227
|
+
function flattenTopicStrings(section) {
|
|
1228
|
+
return uniqueNonEmpty((section || []).map(item => String(item && item.topic || '').trim()), 200)
|
|
1229
|
+
.filter(Boolean);
|
|
1230
|
+
}
|
|
1289
1231
|
|
|
1290
|
-
|
|
1291
|
-
const
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
if (port80Ping.ok) {
|
|
1297
|
-
console.log(' ✅ serves /api/a2a/ping (A2A detected on :80)');
|
|
1298
|
-
} else if (port80Listening.listening) {
|
|
1299
|
-
console.log(` ⚠️ has a listener (${port80Listening.code || 'in_use'})`);
|
|
1300
|
-
} else if (!port80Bind.ok && port80Bind.code === 'EACCES') {
|
|
1301
|
-
console.log(' ⚠️ appears free but is not bindable by this user (EACCES)');
|
|
1302
|
-
} else if (port80Bind.ok) {
|
|
1303
|
-
console.log(' ✅ free and bindable by this user');
|
|
1304
|
-
} else {
|
|
1305
|
-
console.log(` ⚠️ not bindable (${port80Bind.code || 'unknown'})`);
|
|
1232
|
+
async function editLoop(draft, neverDisclose, reloadManifest) {
|
|
1233
|
+
const shouldPrompt = process.stdin.isTTY && process.stdout.isTTY;
|
|
1234
|
+
if (!shouldPrompt) {
|
|
1235
|
+
console.log('\n⏩ Non-interactive shell detected. Proceeding with proposed topics.');
|
|
1236
|
+
renderDraft(draft, neverDisclose);
|
|
1237
|
+
return draft;
|
|
1306
1238
|
}
|
|
1307
1239
|
|
|
1308
|
-
console.log('\
|
|
1309
|
-
console.log(
|
|
1310
|
-
console.log(
|
|
1311
|
-
console.log(
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1240
|
+
console.log('\nEdit commands:');
|
|
1241
|
+
console.log(' move N to TIER.SECTION — Move topic #N to a section');
|
|
1242
|
+
console.log(' remove N — Remove topic #N');
|
|
1243
|
+
console.log(' add TIER.SECTION "Topic" "Detail" — Add a topic');
|
|
1244
|
+
console.log(' edit N topic "new" — Edit topic #N label');
|
|
1245
|
+
console.log(' edit N detail "new" — Edit topic #N detail');
|
|
1246
|
+
console.log(' reset — Rescan workspace and regenerate');
|
|
1247
|
+
console.log(' done — Save and continue\n');
|
|
1248
|
+
|
|
1249
|
+
let done = false;
|
|
1250
|
+
renderDraft(draft, neverDisclose);
|
|
1251
|
+
|
|
1252
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1253
|
+
return await new Promise((resolve) => {
|
|
1254
|
+
const finish = () => {
|
|
1255
|
+
if (!done) {
|
|
1256
|
+
done = true;
|
|
1257
|
+
resolve(draft);
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
|
|
1261
|
+
const prompt = () => {
|
|
1262
|
+
rl.question('Your choice: ', (answer) => {
|
|
1263
|
+
const parts = splitCommand(answer);
|
|
1264
|
+
const command = String(parts[0] || '').toLowerCase();
|
|
1265
|
+
if (!parts.length) {
|
|
1266
|
+
renderDraft(draft, neverDisclose);
|
|
1267
|
+
return prompt();
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
if (command === 'done') {
|
|
1271
|
+
rl.close();
|
|
1272
|
+
return finish();
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
if (command === 'reset') {
|
|
1276
|
+
draft = cloneDraft(reloadManifest());
|
|
1277
|
+
renderDraft(draft, neverDisclose);
|
|
1278
|
+
return prompt();
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
if (command === 'remove') {
|
|
1282
|
+
const target = findByIndex(draft, Number.parseInt(parts[1], 10));
|
|
1283
|
+
if (!target) {
|
|
1284
|
+
console.log(`Could not find topic #${parts[1]}.`);
|
|
1285
|
+
} else {
|
|
1286
|
+
target.list.splice(target.itemIndex, 1);
|
|
1287
|
+
console.log(`Removed topic #${parts[1]}.`);
|
|
1288
|
+
}
|
|
1289
|
+
renderDraft(draft, neverDisclose);
|
|
1290
|
+
return prompt();
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
if (command === 'move') {
|
|
1294
|
+
const target = findByIndex(draft, Number.parseInt(parts[1], 10));
|
|
1295
|
+
const destination = parseSections(parts[2] === 'to' ? parts[3] : parts[2]);
|
|
1296
|
+
if (!target) {
|
|
1297
|
+
console.log(`Could not find topic #${parts[1]}.`);
|
|
1298
|
+
} else if (!destination) {
|
|
1299
|
+
console.log('Invalid target. Use format: move N to friends.lead');
|
|
1300
|
+
} else {
|
|
1301
|
+
target.list.splice(target.itemIndex, 1);
|
|
1302
|
+
draft[destination.tier][destination.section].push(target.item);
|
|
1303
|
+
console.log(`Moved topic #${parts[1]} to ${destination.tier}.${destination.section}`);
|
|
1304
|
+
}
|
|
1305
|
+
renderDraft(draft, neverDisclose);
|
|
1306
|
+
return prompt();
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
if (command === 'add') {
|
|
1310
|
+
const destination = parseSections(parts[1]);
|
|
1311
|
+
const topic = parts[2];
|
|
1312
|
+
const detail = parts[3] || '';
|
|
1313
|
+
if (!destination || !topic) {
|
|
1314
|
+
console.log('Add format: add TIER.SECTION "Topic" "Detail"');
|
|
1315
|
+
} else {
|
|
1316
|
+
draft[destination.tier][destination.section].push({ topic, detail });
|
|
1317
|
+
console.log(`Added topic to ${destination.tier}.${destination.section}.`);
|
|
1318
|
+
}
|
|
1319
|
+
renderDraft(draft, neverDisclose);
|
|
1320
|
+
return prompt();
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
if (command === 'edit') {
|
|
1324
|
+
const target = findByIndex(draft, Number.parseInt(parts[1], 10));
|
|
1325
|
+
const field = String(parts[2] || '').toLowerCase();
|
|
1326
|
+
const value = parts[3] || '';
|
|
1327
|
+
if (!target || !field || !['topic', 'detail'].includes(field)) {
|
|
1328
|
+
console.log('Edit format: edit N topic "new" | edit N detail "new"');
|
|
1329
|
+
} else {
|
|
1330
|
+
target.item[field] = value;
|
|
1331
|
+
console.log(`Updated topic #${parts[1]} ${field}.`);
|
|
1332
|
+
}
|
|
1333
|
+
renderDraft(draft, neverDisclose);
|
|
1334
|
+
return prompt();
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
console.log('Unknown command.');
|
|
1338
|
+
renderDraft(draft, neverDisclose);
|
|
1339
|
+
return prompt();
|
|
1340
|
+
});
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1343
|
+
rl.on('close', finish);
|
|
1344
|
+
prompt();
|
|
1345
|
+
});
|
|
1321
1346
|
}
|
|
1322
1347
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1348
|
+
async function probePing(port) {
|
|
1349
|
+
return await new Promise((resolve) => {
|
|
1350
|
+
const req = http.request({
|
|
1351
|
+
hostname: '127.0.0.1',
|
|
1352
|
+
port,
|
|
1353
|
+
path: '/api/a2a/ping',
|
|
1354
|
+
method: 'GET',
|
|
1355
|
+
timeout: 1200
|
|
1356
|
+
}, (res) => {
|
|
1357
|
+
let body = '';
|
|
1358
|
+
res.setEncoding('utf8');
|
|
1359
|
+
res.on('data', chunk => { body += String(chunk || ''); });
|
|
1360
|
+
res.on('end', () => {
|
|
1361
|
+
const ok = body.includes('"pong":true') || body.includes('"pong": true');
|
|
1362
|
+
resolve({ ok, statusCode: res.statusCode || 0, body });
|
|
1363
|
+
});
|
|
1364
|
+
});
|
|
1365
|
+
req.on('error', () => resolve({ ok: false }));
|
|
1366
|
+
req.on('timeout', () => {
|
|
1367
|
+
req.destroy(new Error('timeout'));
|
|
1368
|
+
resolve({ ok: false });
|
|
1369
|
+
});
|
|
1370
|
+
req.end();
|
|
1327
1371
|
});
|
|
1328
1372
|
}
|
|
1329
1373
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1374
|
+
async function waitForLocalServer(port) {
|
|
1375
|
+
for (let i = 0; i < 18; i++) {
|
|
1376
|
+
const listening = await isPortListening(port, '127.0.0.1', { timeoutMs: 250 });
|
|
1377
|
+
if (!listening.listening) {
|
|
1378
|
+
await new Promise(r => setTimeout(r, 250));
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1332
1381
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1382
|
+
const probe = await probePing(port);
|
|
1383
|
+
if (probe.ok) {
|
|
1384
|
+
return true;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
async function startServer(port) {
|
|
1391
|
+
const listening = await isPortListening(port, '127.0.0.1', { timeoutMs: 250 });
|
|
1392
|
+
if (listening.listening) {
|
|
1393
|
+
return false;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
const serverScript = path.join(__dirname, '../src/server.js');
|
|
1397
|
+
const child = spawn(process.execPath, [serverScript], {
|
|
1398
|
+
env: {
|
|
1399
|
+
...process.env,
|
|
1400
|
+
PORT: String(port),
|
|
1401
|
+
A2A_WORKSPACE: workspaceDir
|
|
1402
|
+
},
|
|
1403
|
+
detached: true,
|
|
1404
|
+
stdio: 'ignore'
|
|
1405
|
+
});
|
|
1406
|
+
child.unref();
|
|
1407
|
+
await new Promise(r => setTimeout(r, 300));
|
|
1408
|
+
return true;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
function looksLikePong(body) {
|
|
1412
|
+
try {
|
|
1413
|
+
const parsed = JSON.parse(String(body || ''));
|
|
1414
|
+
if (parsed && parsed.pong === true) return true;
|
|
1415
|
+
} catch (e) {}
|
|
1416
|
+
return String(body || '').includes('"pong":true') || String(body || '').includes('"pong": true');
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// Step 1: discover context
|
|
1420
|
+
const contextFiles = (() => {
|
|
1421
|
+
try {
|
|
1422
|
+
return readContextFiles(workspaceDir);
|
|
1423
|
+
} catch (err) {
|
|
1424
|
+
return {};
|
|
1425
|
+
}
|
|
1426
|
+
})();
|
|
1427
|
+
|
|
1428
|
+
renderWorkspaceScan(contextFiles);
|
|
1429
|
+
|
|
1430
|
+
const backendPort = parsePort(args.flags.port || args.flags.p || process.env.A2A_PORT || process.env.PORT, 3001);
|
|
1431
|
+
const hostFlag = normalizeHostInput(
|
|
1432
|
+
args.flags.hostname !== undefined
|
|
1433
|
+
? String(args.flags.hostname)
|
|
1434
|
+
: (config.getAgent().hostname || `localhost:${backendPort}`)
|
|
1435
|
+
);
|
|
1436
|
+
const parsedHost = splitHostPort(hostFlag || `localhost:${backendPort}`);
|
|
1437
|
+
const inviteHost = parsedHost.port
|
|
1438
|
+
? `${parsedHost.hostname}:${parsedHost.port}`
|
|
1439
|
+
: `${parsedHost.hostname || 'localhost'}:${backendPort}`;
|
|
1440
|
+
|
|
1441
|
+
// Step 2: seed draft from workspace context
|
|
1442
|
+
let manifest = generateDefaultManifest(contextFiles);
|
|
1443
|
+
let draft = makeDraft(manifest);
|
|
1444
|
+
const neverDisclose = uniqueNonEmpty(manifest.never_disclose || [
|
|
1445
|
+
'API keys',
|
|
1446
|
+
'Other users\' data',
|
|
1447
|
+
'Financial figures'
|
|
1448
|
+
], 30);
|
|
1449
|
+
|
|
1450
|
+
draft = await editLoop(draft, neverDisclose, () => {
|
|
1451
|
+
try {
|
|
1452
|
+
const refreshedContext = readContextFiles(workspaceDir);
|
|
1453
|
+
const freshManifest = generateDefaultManifest(refreshedContext);
|
|
1454
|
+
manifest = freshManifest;
|
|
1455
|
+
return makeDraft(freshManifest);
|
|
1456
|
+
} catch (err) {
|
|
1457
|
+
return draft;
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
const finalManifest = {
|
|
1462
|
+
version: 1,
|
|
1463
|
+
generated_at: manifest.generated_at || new Date().toISOString(),
|
|
1464
|
+
updated_at: new Date().toISOString(),
|
|
1465
|
+
topics: {
|
|
1466
|
+
public: {
|
|
1467
|
+
lead_with: sanitizeSectionItems(draft.public.lead_with, 80),
|
|
1468
|
+
discuss_freely: sanitizeSectionItems(draft.public.discuss_freely, 80),
|
|
1469
|
+
deflect: sanitizeSectionItems(draft.public.deflect, 80)
|
|
1470
|
+
},
|
|
1471
|
+
friends: {
|
|
1472
|
+
lead_with: sanitizeSectionItems(draft.friends.lead_with, 80),
|
|
1473
|
+
discuss_freely: sanitizeSectionItems(draft.friends.discuss_freely, 80),
|
|
1474
|
+
deflect: sanitizeSectionItems(draft.friends.deflect, 80)
|
|
1475
|
+
},
|
|
1476
|
+
family: {
|
|
1477
|
+
lead_with: sanitizeSectionItems(draft.family.lead_with, 80),
|
|
1478
|
+
discuss_freely: sanitizeSectionItems(draft.family.discuss_freely, 80),
|
|
1479
|
+
deflect: sanitizeSectionItems(draft.family.deflect, 80)
|
|
1480
|
+
}
|
|
1481
|
+
},
|
|
1482
|
+
never_disclose: neverDisclose,
|
|
1483
|
+
personality_notes: manifest.personality_notes || ''
|
|
1484
|
+
};
|
|
1485
|
+
|
|
1486
|
+
// Keep config in sync with the edited disclosure.
|
|
1487
|
+
try {
|
|
1488
|
+
config.setTier('public', {
|
|
1489
|
+
topics: flattenTopicStrings([...finalManifest.topics.public.lead_with, ...finalManifest.topics.public.discuss_freely, ...finalManifest.topics.public.deflect]),
|
|
1490
|
+
disclosure: 'public'
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
config.setTier('friends', {
|
|
1494
|
+
topics: flattenTopicStrings([
|
|
1495
|
+
...finalManifest.topics.public.lead_with,
|
|
1496
|
+
...finalManifest.topics.public.discuss_freely,
|
|
1497
|
+
...finalManifest.topics.public.deflect,
|
|
1498
|
+
...finalManifest.topics.friends.lead_with,
|
|
1499
|
+
...finalManifest.topics.friends.discuss_freely,
|
|
1500
|
+
...finalManifest.topics.friends.deflect
|
|
1501
|
+
]),
|
|
1502
|
+
disclosure: 'minimal'
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
config.setTier('family', {
|
|
1506
|
+
topics: flattenTopicStrings([
|
|
1507
|
+
...finalManifest.topics.public.lead_with,
|
|
1508
|
+
...finalManifest.topics.public.discuss_freely,
|
|
1509
|
+
...finalManifest.topics.public.deflect,
|
|
1510
|
+
...finalManifest.topics.friends.lead_with,
|
|
1511
|
+
...finalManifest.topics.friends.discuss_freely,
|
|
1512
|
+
...finalManifest.topics.friends.deflect,
|
|
1513
|
+
...finalManifest.topics.family.lead_with,
|
|
1514
|
+
...finalManifest.topics.family.discuss_freely,
|
|
1515
|
+
...finalManifest.topics.family.deflect
|
|
1516
|
+
]),
|
|
1517
|
+
disclosure: 'minimal'
|
|
1518
|
+
});
|
|
1519
|
+
|
|
1520
|
+
saveManifest(finalManifest);
|
|
1521
|
+
config.setOnboarding({ step: 'tiers', tiers_confirmed: true });
|
|
1522
|
+
} catch (err) {
|
|
1523
|
+
console.error('\n❌ Failed to save tier updates.');
|
|
1524
|
+
console.error(` ${err.message}`);
|
|
1525
|
+
throw err;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
console.log('\n🚀 Starting A2A server...');
|
|
1529
|
+
console.log(`Port: ${backendPort}`);
|
|
1530
|
+
console.log(`Hostname: ${inviteHost}`);
|
|
1531
|
+
|
|
1532
|
+
const started = await startServer(backendPort);
|
|
1533
|
+
const localRunning = await waitForLocalServer(backendPort);
|
|
1534
|
+
if (!localRunning) {
|
|
1535
|
+
console.log('⚠️ Local server not reachable. Start it manually and retry if needed:');
|
|
1536
|
+
console.log(` A2A_HOSTNAME="${inviteHost}" a2a server --port ${backendPort}`);
|
|
1335
1537
|
} else {
|
|
1336
|
-
|
|
1337
|
-
if (
|
|
1338
|
-
console.log(
|
|
1339
|
-
} else {
|
|
1340
|
-
console.log(`External IP lookup failed: ${external && external.error ? external.error : 'unknown_error'}`);
|
|
1538
|
+
console.log('✅ Server running!');
|
|
1539
|
+
if (started) {
|
|
1540
|
+
console.log('🟢 Local server started automatically.');
|
|
1341
1541
|
}
|
|
1342
1542
|
}
|
|
1343
1543
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
console.log('Skipping external reachability check: invite host looks local/unroutable.');
|
|
1368
|
-
} else {
|
|
1369
|
-
const extPing = await externalPingCheck(expectedPingUrl);
|
|
1370
|
-
if (extPing.ok) {
|
|
1371
|
-
console.log(`✅ External ping OK (${extPing.provider})`);
|
|
1372
|
-
} else if (args.flags['skip-verify']) {
|
|
1373
|
-
console.log('⚠️ External ping FAILED (skipped via --skip-verify).');
|
|
1374
|
-
} else if (args.flags['confirm-ingress']) {
|
|
1375
|
-
console.log('⚠️ External ping FAILED (continuing due to --confirm-ingress).');
|
|
1376
|
-
} else {
|
|
1377
|
-
console.log('⚠️ External ping FAILED (server may not be publicly reachable yet).');
|
|
1378
|
-
console.log('Fix ingress (DNS/reverse proxy/firewall), then rerun. If you want to proceed anyway:');
|
|
1379
|
-
console.log(` a2a quickstart --hostname ${inviteHost} --port ${backendPort} --confirm-ingress`);
|
|
1380
|
-
console.log(` a2a quickstart --hostname ${inviteHost} --port ${backendPort} --skip-verify`);
|
|
1381
|
-
console.log('');
|
|
1382
|
-
return;
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
if (!config.getOnboarding().verify_confirmed) {
|
|
1387
|
-
config.setOnboarding({
|
|
1388
|
-
step: 'verify',
|
|
1389
|
-
verify_confirmed: true
|
|
1544
|
+
const dashboard = `http://127.0.0.1:${backendPort}/dashboard/`;
|
|
1545
|
+
|
|
1546
|
+
const hostSplit = splitHostPort(inviteHost);
|
|
1547
|
+
const isPrivateHost = isLocalOrUnroutableHost(hostSplit.hostname);
|
|
1548
|
+
const expectedPingUrl = `${isPrivateHost ? 'http' : (hostSplit.port === 443 ? 'https' : 'http')}://${inviteHost}/api/a2a/ping`;
|
|
1549
|
+
|
|
1550
|
+
if (isPrivateHost) {
|
|
1551
|
+
console.log('✅ External ping OK (local testing host)');
|
|
1552
|
+
} else {
|
|
1553
|
+
const external = await new Promise(resolve => {
|
|
1554
|
+
const req = http.get(expectedPingUrl, (res) => {
|
|
1555
|
+
let body = '';
|
|
1556
|
+
res.setEncoding('utf8');
|
|
1557
|
+
res.on('data', chunk => { body += chunk; });
|
|
1558
|
+
res.on('end', () => {
|
|
1559
|
+
resolve({ ok: looksLikePong(body), statusCode: res.statusCode || 0, body });
|
|
1560
|
+
});
|
|
1561
|
+
});
|
|
1562
|
+
req.on('error', () => resolve({ ok: false }));
|
|
1563
|
+
req.setTimeout(1500, () => {
|
|
1564
|
+
req.destroy(new Error('timeout'));
|
|
1565
|
+
resolve({ ok: false });
|
|
1566
|
+
});
|
|
1390
1567
|
});
|
|
1568
|
+
|
|
1569
|
+
if (!external.ok && !args.flags['confirm-ingress'] && !args.flags['skip-verify']) {
|
|
1570
|
+
console.log('⚠️ External ping FAILED. Fix host/reachability and rerun quickstart, or use --skip-verify.');
|
|
1571
|
+
console.log(` a2a quickstart --hostname ${inviteHost} --port ${backendPort} --skip-verify`);
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
if (!external.ok) {
|
|
1576
|
+
console.log('⚠️ External ping FAILED (continuing).');
|
|
1577
|
+
} else {
|
|
1578
|
+
console.log(`✅ External ping OK (${expectedPingUrl})`);
|
|
1579
|
+
}
|
|
1391
1580
|
}
|
|
1392
1581
|
|
|
1582
|
+
console.log(`Dashboard: ${dashboard}`);
|
|
1583
|
+
|
|
1584
|
+
// Step 5: generate first invite
|
|
1585
|
+
const publicTopicsForInvite = flattenTopicStrings([
|
|
1586
|
+
...draft.public.lead_with,
|
|
1587
|
+
...draft.public.discuss_freely
|
|
1588
|
+
]);
|
|
1589
|
+
const goalItems = ['grow-network', 'find-collaborators', 'build-in-public'];
|
|
1590
|
+
|
|
1591
|
+
const ownerName = args.flags.owner || config.getAgent().name || readNameFromUserContext(contextFiles.user) || 'Someone';
|
|
1592
|
+
const peerName = args.flags.name || 'my-agent';
|
|
1593
|
+
|
|
1594
|
+
config.setAgent({ name: ownerName, hostname: inviteHost });
|
|
1595
|
+
|
|
1596
|
+
const { token, record } = store.create({
|
|
1597
|
+
name: peerName,
|
|
1598
|
+
owner: ownerName,
|
|
1599
|
+
permissions: 'public',
|
|
1600
|
+
disclosure: 'minimal',
|
|
1601
|
+
expires: 'never',
|
|
1602
|
+
maxCalls: null,
|
|
1603
|
+
allowedTopics: publicTopicsForInvite,
|
|
1604
|
+
allowedGoals: goalItems,
|
|
1605
|
+
notify: 'all'
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
const inviteUrl = `a2a://${inviteHost}/${token}`;
|
|
1609
|
+
const topicLine = publicTopicsForInvite.length > 0 ? publicTopicsForInvite.slice(0, 6).join(' · ') : 'chat';
|
|
1610
|
+
const goalLine = goalItems.join(' · ');
|
|
1611
|
+
|
|
1612
|
+
console.log('\n📞 Your first invite (public tier):\n');
|
|
1613
|
+
console.log('─'.repeat(60));
|
|
1614
|
+
const inviteText = `📞🗣️ **Agent-to-Agent Call Invite**
|
|
1615
|
+
|
|
1616
|
+
👤 **${ownerName}** would like your agent to call **${peerName}**
|
|
1617
|
+
|
|
1618
|
+
💬 ${topicLine}
|
|
1619
|
+
🎯 ${goalLine}
|
|
1620
|
+
|
|
1621
|
+
${inviteUrl}
|
|
1622
|
+
|
|
1623
|
+
── setup ──
|
|
1624
|
+
npm i -g a2acalling && a2a add "${inviteUrl}" "${peerName}" && a2a call "${peerName}" "Hello!"
|
|
1625
|
+
https://github.com/onthegonow/a2a_calling`;
|
|
1626
|
+
console.log(inviteText);
|
|
1627
|
+
console.log('─'.repeat(60));
|
|
1628
|
+
console.log('Share this invite to let other agents call you!\n');
|
|
1629
|
+
|
|
1393
1630
|
config.completeOnboarding();
|
|
1394
|
-
|
|
1395
|
-
console.log('
|
|
1631
|
+
|
|
1632
|
+
console.log('✅ A2A setup complete!\n');
|
|
1633
|
+
console.log('Your agent is now:');
|
|
1634
|
+
console.log(` • Listening on ${inviteHost}`);
|
|
1635
|
+
console.log(' • Ready to receive calls');
|
|
1636
|
+
console.log(` • Configured with ${Object.keys(finalManifest.topics).length} permission tiers`);
|
|
1637
|
+
console.log('\nNext steps:');
|
|
1638
|
+
console.log(' a2a invite friends — Create a friends-tier invite');
|
|
1639
|
+
console.log(' a2a contacts — View your contacts');
|
|
1640
|
+
console.log(' a2a gui — Open the dashboard\n');
|
|
1641
|
+
console.log('Happy calling! 🤝');
|
|
1396
1642
|
},
|
|
1397
1643
|
|
|
1398
1644
|
install: () => {
|
|
@@ -1403,6 +1649,136 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1403
1649
|
require('../scripts/install-openclaw.js');
|
|
1404
1650
|
},
|
|
1405
1651
|
|
|
1652
|
+
uninstall: async (args) => {
|
|
1653
|
+
const fs = require('fs');
|
|
1654
|
+
const path = require('path');
|
|
1655
|
+
const { spawnSync } = require('child_process');
|
|
1656
|
+
|
|
1657
|
+
const keepConfig = Boolean(args.flags['keep-config'] || args.flags.keepConfig);
|
|
1658
|
+
const force = Boolean(args.flags.force || args.flags.f);
|
|
1659
|
+
|
|
1660
|
+
const configDir = process.env.A2A_CONFIG_DIR ||
|
|
1661
|
+
process.env.OPENCLAW_CONFIG_DIR ||
|
|
1662
|
+
path.join(process.env.HOME || '/tmp', '.config', 'openclaw');
|
|
1663
|
+
|
|
1664
|
+
const configFile = path.join(configDir, 'a2a-config.json');
|
|
1665
|
+
const disclosureFile = path.join(configDir, 'a2a-disclosure.json');
|
|
1666
|
+
const tokensFile = path.join(configDir, 'a2a-tokens.json');
|
|
1667
|
+
const dbFile = path.join(configDir, 'a2a-conversations.db');
|
|
1668
|
+
const logsDbFile = path.join(configDir, 'a2a-logs.db');
|
|
1669
|
+
const callbookDbFile = path.join(configDir, 'a2a-callbook.db');
|
|
1670
|
+
|
|
1671
|
+
console.log(`\n🗑️ A2A Uninstall`);
|
|
1672
|
+
console.log('─────────────────\n');
|
|
1673
|
+
|
|
1674
|
+
if (!keepConfig && !force) {
|
|
1675
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1676
|
+
console.error('Refusing to prompt without a TTY. Re-run with --force to confirm uninstall.');
|
|
1677
|
+
process.exit(1);
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
const existing = [configFile, disclosureFile, tokensFile, dbFile, logsDbFile, callbookDbFile].filter(f => fs.existsSync(f));
|
|
1681
|
+
const list = existing.length ? existing.map(f => ` - ${f}`).join('\n') : ' (no local config/database files found)';
|
|
1682
|
+
const ok = await promptYesNo(
|
|
1683
|
+
`This will stop the pm2 process "a2a" and delete:\n${list}\nProceed? (y/N) `
|
|
1684
|
+
);
|
|
1685
|
+
if (!ok) {
|
|
1686
|
+
console.log('\nCancelled.\n');
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
function pm2Exists() {
|
|
1692
|
+
const res = spawnSync('pm2', ['--version'], { stdio: 'ignore', timeout: 4000 });
|
|
1693
|
+
if (res.error && res.error.code === 'ENOENT') return false;
|
|
1694
|
+
return res.status === 0;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
function pm2HasProcess(name) {
|
|
1698
|
+
const res = spawnSync('pm2', ['describe', name], { encoding: 'utf8', timeout: 6000 });
|
|
1699
|
+
if (res.error && res.error.code === 'ENOENT') return false;
|
|
1700
|
+
return res.status === 0;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function pm2StopAndDelete(name) {
|
|
1704
|
+
if (!pm2Exists()) return { ok: true, skipped: true };
|
|
1705
|
+
if (!pm2HasProcess(name)) return { ok: true, skipped: true };
|
|
1706
|
+
|
|
1707
|
+
const stop = spawnSync('pm2', ['stop', name], { encoding: 'utf8', timeout: 8000 });
|
|
1708
|
+
if (stop.status !== 0) {
|
|
1709
|
+
const msg = (stop.stderr || stop.stdout || '').trim();
|
|
1710
|
+
return { ok: false, error: msg || 'pm2 stop failed' };
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
const del = spawnSync('pm2', ['delete', name], { encoding: 'utf8', timeout: 8000 });
|
|
1714
|
+
if (del.status !== 0) {
|
|
1715
|
+
const msg = (del.stderr || del.stdout || '').trim();
|
|
1716
|
+
return { ok: false, error: msg || 'pm2 delete failed' };
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
return { ok: true };
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
function rmFileSafe(filePath) {
|
|
1723
|
+
try {
|
|
1724
|
+
fs.rmSync(filePath, { force: true });
|
|
1725
|
+
return { ok: true };
|
|
1726
|
+
} catch (err) {
|
|
1727
|
+
return { ok: false, error: err.message };
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
process.stdout.write('Stopping server... ');
|
|
1732
|
+
const stopped = pm2StopAndDelete('a2a');
|
|
1733
|
+
if (!stopped.ok) {
|
|
1734
|
+
console.log('❌');
|
|
1735
|
+
console.error(` ${stopped.error}`);
|
|
1736
|
+
process.exit(1);
|
|
1737
|
+
}
|
|
1738
|
+
console.log('✅');
|
|
1739
|
+
|
|
1740
|
+
let configOk = true;
|
|
1741
|
+
let dbOk = true;
|
|
1742
|
+
|
|
1743
|
+
if (!keepConfig) {
|
|
1744
|
+
process.stdout.write('Removing config... ');
|
|
1745
|
+
const c1 = rmFileSafe(configFile);
|
|
1746
|
+
const c2 = rmFileSafe(disclosureFile);
|
|
1747
|
+
const c3 = rmFileSafe(tokensFile);
|
|
1748
|
+
configOk = Boolean(c1.ok && c2.ok && c3.ok);
|
|
1749
|
+
console.log(configOk ? '✅' : '❌');
|
|
1750
|
+
if (!configOk) {
|
|
1751
|
+
if (!c1.ok) console.error(` ${configFile}: ${c1.error}`);
|
|
1752
|
+
if (!c2.ok) console.error(` ${disclosureFile}: ${c2.error}`);
|
|
1753
|
+
if (!c3.ok) console.error(` ${tokensFile}: ${c3.error}`);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
process.stdout.write('Removing database... ');
|
|
1757
|
+
const d1 = rmFileSafe(dbFile);
|
|
1758
|
+
const d2 = rmFileSafe(logsDbFile);
|
|
1759
|
+
const d3 = rmFileSafe(callbookDbFile);
|
|
1760
|
+
dbOk = Boolean(d1.ok && d2.ok && d3.ok);
|
|
1761
|
+
console.log(dbOk ? '✅' : '❌');
|
|
1762
|
+
if (!dbOk) {
|
|
1763
|
+
if (!d1.ok) console.error(` ${dbFile}: ${d1.error}`);
|
|
1764
|
+
if (!d2.ok) console.error(` ${logsDbFile}: ${d2.error}`);
|
|
1765
|
+
if (!d3.ok) console.error(` ${callbookDbFile}: ${d3.error}`);
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
if (!configOk || !dbOk) {
|
|
1769
|
+
process.exit(1);
|
|
1770
|
+
}
|
|
1771
|
+
} else {
|
|
1772
|
+
console.log('Removing config... ⏭️');
|
|
1773
|
+
console.log('Removing database... ⏭️');
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
console.log('\nTo complete removal:');
|
|
1777
|
+
console.log(' npm uninstall -g a2acalling\n');
|
|
1778
|
+
console.log(`Config preserved: ${keepConfig ? 'yes' : 'no'}`);
|
|
1779
|
+
console.log(`Location: ${configDir}`);
|
|
1780
|
+
},
|
|
1781
|
+
|
|
1406
1782
|
update: async (args) => {
|
|
1407
1783
|
const { execSync } = require('child_process');
|
|
1408
1784
|
const path = require('path');
|
|
@@ -1578,6 +1954,11 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1578
1954
|
console.log(" a2a onboard --submit '<json>'\n");
|
|
1579
1955
|
},
|
|
1580
1956
|
|
|
1957
|
+
version: () => {
|
|
1958
|
+
const pkg = require('../package.json');
|
|
1959
|
+
console.log(pkg.version);
|
|
1960
|
+
},
|
|
1961
|
+
|
|
1581
1962
|
help: () => {
|
|
1582
1963
|
console.log(`A2A Calling - Agent-to-Agent Communication
|
|
1583
1964
|
|
|
@@ -1657,6 +2038,10 @@ Server:
|
|
|
1657
2038
|
|
|
1658
2039
|
install Install A2A for OpenClaw
|
|
1659
2040
|
setup Auto setup (gateway-aware dashboard install)
|
|
2041
|
+
uninstall Stop server and remove local config/DB
|
|
2042
|
+
--keep-config Preserve config/DB (for reinstall)
|
|
2043
|
+
--force Skip confirmation prompt
|
|
2044
|
+
version Show installed package version
|
|
1660
2045
|
|
|
1661
2046
|
Examples:
|
|
1662
2047
|
a2a create --name "bappybot" --owner "Benjamin Pollack" --expires 7d
|
|
@@ -1680,6 +2065,8 @@ if (!commands[command]) {
|
|
|
1680
2065
|
process.exit(1);
|
|
1681
2066
|
}
|
|
1682
2067
|
|
|
2068
|
+
enforceOnboarding(command);
|
|
2069
|
+
|
|
1683
2070
|
// Handle async commands
|
|
1684
2071
|
const result = commands[command](args);
|
|
1685
2072
|
if (result instanceof Promise) {
|