a2acalling 0.6.9 → 0.6.11
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/bin/cli.js +297 -108
- package/package.json +1 -1
- package/scripts/postinstall.js +62 -11
- package/docs/plans/2026-02-14-agent-driven-disclosure-extraction.md +0 -986
package/bin/cli.js
CHANGED
|
@@ -200,16 +200,162 @@ function parseArgs(argv) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
async function promptYesNo(question) {
|
|
203
|
+
const q = String(question || '');
|
|
204
|
+
// Support both bracket and paren styles: [Y/n], (y/N), etc.
|
|
205
|
+
// Convention: uppercase letter is the default when user presses Enter.
|
|
206
|
+
const defaultValue = q.includes('y/N')
|
|
207
|
+
? false
|
|
208
|
+
: q.includes('Y/n')
|
|
209
|
+
? true
|
|
210
|
+
: true;
|
|
211
|
+
|
|
212
|
+
if (!isInteractiveShell()) {
|
|
213
|
+
return defaultValue;
|
|
214
|
+
}
|
|
215
|
+
|
|
203
216
|
return await new Promise(resolve => {
|
|
204
217
|
const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
|
|
205
218
|
rl.question(question, (answer) => {
|
|
206
219
|
rl.close();
|
|
207
220
|
const normalized = String(answer || '').trim().toLowerCase();
|
|
221
|
+
if (!normalized) return resolve(defaultValue);
|
|
208
222
|
resolve(normalized === 'y' || normalized === 'yes');
|
|
209
223
|
});
|
|
210
224
|
});
|
|
211
225
|
}
|
|
212
226
|
|
|
227
|
+
function isInteractiveShell() {
|
|
228
|
+
return Boolean(process.stdin && process.stdout && process.stdin.isTTY && process.stdout.isTTY);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function promptText(question, defaultValue = '') {
|
|
232
|
+
if (!isInteractiveShell()) {
|
|
233
|
+
return defaultValue;
|
|
234
|
+
}
|
|
235
|
+
return await new Promise(resolve => {
|
|
236
|
+
const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
|
|
237
|
+
rl.question(question, (answer) => {
|
|
238
|
+
rl.close();
|
|
239
|
+
const cleaned = String(answer || '').trim();
|
|
240
|
+
resolve(cleaned || defaultValue);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function parsePort(raw, fallback = null) {
|
|
246
|
+
const parsed = Number.parseInt(String(raw || '').trim(), 10);
|
|
247
|
+
if (Number.isFinite(parsed) && parsed > 0 && parsed <= 65535) {
|
|
248
|
+
return parsed;
|
|
249
|
+
}
|
|
250
|
+
return fallback;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function printStepHeader(label) {
|
|
254
|
+
const clean = String(label || '').trim();
|
|
255
|
+
const innerWidth = Math.max(62, clean.length + 12);
|
|
256
|
+
const padding = Math.max(0, innerWidth - clean.length);
|
|
257
|
+
const left = Math.floor(padding / 2);
|
|
258
|
+
const right = Math.max(0, padding - left);
|
|
259
|
+
console.log('\n' + '╔' + '═'.repeat(innerWidth) + '╗');
|
|
260
|
+
console.log(`║${' '.repeat(left)}${clean}${' '.repeat(right)}║`);
|
|
261
|
+
console.log('╚' + '═'.repeat(innerWidth) + '╝');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function printSection(title) {
|
|
265
|
+
console.log('\n━━━ ' + title + ' ━━━');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function readWorkspaceContext(baseDir = process.cwd()) {
|
|
269
|
+
const base = baseDir || process.cwd();
|
|
270
|
+
const workspaceFiles = {
|
|
271
|
+
USER: { filename: 'USER.md' },
|
|
272
|
+
SOUL: { filename: 'SOUL.md' },
|
|
273
|
+
HEARTBEAT: { filename: 'HEARTBEAT.md' },
|
|
274
|
+
SKILL: { filename: 'SKILL.md' },
|
|
275
|
+
CLAUDE: { filename: 'CLAUDE.md' }
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const found = {};
|
|
279
|
+
for (const key of Object.keys(workspaceFiles)) {
|
|
280
|
+
found[key] = fs.existsSync(path.join(base, workspaceFiles[key].filename));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const memoryDir = path.join(base, 'memory');
|
|
284
|
+
let memoryCount = 0;
|
|
285
|
+
if (fs.existsSync(memoryDir)) {
|
|
286
|
+
try {
|
|
287
|
+
memoryCount = fs.readdirSync(memoryDir).filter(item => item.endsWith('.md')).length;
|
|
288
|
+
} catch (err) {
|
|
289
|
+
memoryCount = 0;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
found.MEMORY = memoryCount;
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
workspace: base,
|
|
296
|
+
found,
|
|
297
|
+
memoryCount
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function printWorkspaceScan(context) {
|
|
302
|
+
console.log('Scanning workspace for context...');
|
|
303
|
+
console.log(` ${context.found.USER ? '✅' : '⚠️ '} ${context.found.USER ? 'Found USER.md' : 'No USER.md'}${context.found.USER ? ' — identity hints found' : ''}`);
|
|
304
|
+
console.log(` ${context.found.SOUL ? '✅' : '⚠️ '} ${context.found.SOUL ? 'Found SOUL.md' : 'No SOUL.md'}${context.found.SOUL ? ' — personality notes available' : ''}`);
|
|
305
|
+
console.log(` ${context.found.HEARTBEAT ? '✅' : '⚠️ '} ${context.found.HEARTBEAT ? 'Found HEARTBEAT.md' : 'No HEARTBEAT.md (skipped)'}`);
|
|
306
|
+
console.log(` ${context.found.SKILL ? '✅' : '⚠️ '} ${context.found.SKILL ? 'Found SKILL.md' : 'No SKILL.md (skipped)'}`
|
|
307
|
+
);
|
|
308
|
+
console.log(` ${context.found.CLAUDE ? '✅' : '⚠️ '} ${context.found.CLAUDE ? 'Found CLAUDE.md' : 'No CLAUDE.md (skipped)'}`);
|
|
309
|
+
if (context.memoryCount > 0) {
|
|
310
|
+
console.log(` ✅ Found ${context.memoryCount} memory file(s)`);
|
|
311
|
+
} else {
|
|
312
|
+
console.log(' ⚠️ No memory/*.md files');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function getDisclosurePromptFiles(context) {
|
|
317
|
+
return {
|
|
318
|
+
'USER.md': context.found.USER,
|
|
319
|
+
'SOUL.md': context.found.SOUL,
|
|
320
|
+
'HEARTBEAT.md': context.found.HEARTBEAT,
|
|
321
|
+
'SKILL.md': context.found.SKILL,
|
|
322
|
+
'CLAUDE.md': context.found.CLAUDE,
|
|
323
|
+
'memory/*.md': context.memoryCount > 0
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function inspectPorts(preferredPort = null) {
|
|
328
|
+
const candidates = [];
|
|
329
|
+
if (preferredPort) {
|
|
330
|
+
candidates.push(preferredPort);
|
|
331
|
+
}
|
|
332
|
+
candidates.push(80);
|
|
333
|
+
for (let p = 3001; p < 3021; p += 1) {
|
|
334
|
+
if (!candidates.includes(p)) candidates.push(p);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const { tryBindPort } = require('../src/lib/port-scanner');
|
|
338
|
+
const results = [];
|
|
339
|
+
for (const port of candidates) {
|
|
340
|
+
const r = await tryBindPort(port);
|
|
341
|
+
results.push({
|
|
342
|
+
port,
|
|
343
|
+
available: Boolean(r.ok),
|
|
344
|
+
blocked: !r.ok && r.code === 'EACCES',
|
|
345
|
+
code: r.code || null
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
return results;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function summarizePortResults(portResults) {
|
|
352
|
+
return portResults.map(item => {
|
|
353
|
+
if (item.available) return `Port ${item.port}: available ✓`;
|
|
354
|
+
if (item.blocked) return `Port ${item.port}: requires elevated privileges`;
|
|
355
|
+
return `Port ${item.port}: in use`;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
213
359
|
async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
|
|
214
360
|
const submitRaw = args.flags.submit;
|
|
215
361
|
if (!submitRaw) return false;
|
|
@@ -1064,11 +1210,12 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1064
1210
|
|
|
1065
1211
|
quickstart: async (args) => {
|
|
1066
1212
|
const { A2AConfig } = require('../src/lib/config');
|
|
1067
|
-
const {
|
|
1213
|
+
const { isPortListening } = require('../src/lib/port-scanner');
|
|
1068
1214
|
const { buildExtractionPrompt } = require('../src/lib/disclosure');
|
|
1069
1215
|
const { getExternalIp } = require('../src/lib/external-ip');
|
|
1070
1216
|
|
|
1071
1217
|
const config = new A2AConfig();
|
|
1218
|
+
const interactive = isInteractiveShell();
|
|
1072
1219
|
|
|
1073
1220
|
if (await handleDisclosureSubmit(args, 'quickstart')) {
|
|
1074
1221
|
return;
|
|
@@ -1084,6 +1231,9 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1084
1231
|
return;
|
|
1085
1232
|
}
|
|
1086
1233
|
|
|
1234
|
+
const context = readWorkspaceContext(process.env.A2A_WORKSPACE || process.cwd());
|
|
1235
|
+
const availableFiles = getDisclosurePromptFiles(context);
|
|
1236
|
+
|
|
1087
1237
|
// If server is already running and awaiting disclosure, skip to Step 2
|
|
1088
1238
|
let currentStep = 'not_started';
|
|
1089
1239
|
try {
|
|
@@ -1097,100 +1247,160 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1097
1247
|
if (currentStep === 'awaiting_disclosure' && !args.flags.force) {
|
|
1098
1248
|
console.log('\nStep 1 already complete. Server is running.\n');
|
|
1099
1249
|
console.log('Step 2 of 4: Configure disclosure topics\n');
|
|
1100
|
-
console.log(buildExtractionPrompt());
|
|
1250
|
+
console.log(buildExtractionPrompt(availableFiles));
|
|
1101
1251
|
console.log('\n Read your workspace files, extract topics, and present to your owner for review.');
|
|
1102
1252
|
console.log(" Then submit with: a2a quickstart --submit '<json>'\n");
|
|
1103
1253
|
return;
|
|
1104
1254
|
}
|
|
1105
1255
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
if (Number.isFinite(parsed) && parsed > 0 && parsed <= 65535) {
|
|
1109
|
-
return parsed;
|
|
1110
|
-
}
|
|
1111
|
-
return fallback;
|
|
1112
|
-
}
|
|
1256
|
+
printStepHeader('🤝 A2A Calling — First-Time Setup');
|
|
1257
|
+
printWorkspaceScan(context);
|
|
1113
1258
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1259
|
+
const continueSetup = await promptYesNo('Continue with setup? [Y/n] ');
|
|
1260
|
+
if (!continueSetup) {
|
|
1261
|
+
console.log('\nSetup cancelled.\n');
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1116
1264
|
|
|
1265
|
+
printSection('Port Configuration');
|
|
1117
1266
|
const preferredPort = parsePort(args.flags.port || args.flags.p, null);
|
|
1267
|
+
const candidates = await inspectPorts(preferredPort);
|
|
1268
|
+
const availableCandidates = candidates.filter(c => c.available);
|
|
1269
|
+
const recommendedPort = availableCandidates.length ? availableCandidates[0].port : null;
|
|
1118
1270
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
if (
|
|
1124
|
-
console.
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1271
|
+
summarizePortResults(candidates).forEach(line => {
|
|
1272
|
+
console.log(` ${line}`);
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
if (!recommendedPort) {
|
|
1276
|
+
console.error(' Could not find a bindable port in the scan range.');
|
|
1277
|
+
console.error(' Re-run with --port <number> after freeing one of these ports.\n');
|
|
1278
|
+
process.exit(1);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
console.log(`\n Recommended: ${recommendedPort}`);
|
|
1282
|
+
let serverPort = recommendedPort;
|
|
1283
|
+
const portChoice = await promptText(`Use port ${recommendedPort}? [Y/n/custom]: `, 'y');
|
|
1284
|
+
if (!interactive) {
|
|
1285
|
+
// explicit default for non-interactive mode
|
|
1286
|
+
serverPort = recommendedPort;
|
|
1287
|
+
} else if (!['', 'y', 'Y', 'yes', 'YES', 'ye'].includes(String(portChoice).trim())) {
|
|
1288
|
+
if (/^(n|no|custom|c)$/i.test(String(portChoice).trim())) {
|
|
1289
|
+
let customPort = null;
|
|
1290
|
+
while (customPort === null) {
|
|
1291
|
+
const raw = await promptText('Enter a custom port number: ', String(recommendedPort));
|
|
1292
|
+
const parsed = parsePort(raw, null);
|
|
1293
|
+
if (!parsed) {
|
|
1294
|
+
console.log(' Invalid port. Enter a value between 1 and 65535.');
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
const checked = await (async () => {
|
|
1298
|
+
const scan = await inspectPorts(parsed);
|
|
1299
|
+
return scan[0];
|
|
1300
|
+
})();
|
|
1301
|
+
if (!checked.available) {
|
|
1302
|
+
console.log(` Port ${parsed} is unavailable (${checked.code || 'in use'}).`);
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1305
|
+
customPort = parsed;
|
|
1306
|
+
}
|
|
1307
|
+
serverPort = customPort;
|
|
1134
1308
|
} else {
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1309
|
+
const parsed = parsePort(portChoice, null);
|
|
1310
|
+
if (parsed) {
|
|
1311
|
+
const checked = await (async () => {
|
|
1312
|
+
const scan = await inspectPorts(parsed);
|
|
1313
|
+
return scan[0];
|
|
1314
|
+
})();
|
|
1315
|
+
if (!checked.available) {
|
|
1316
|
+
console.log(` Port ${parsed} is unavailable (${checked.code || 'in use'}).`);
|
|
1317
|
+
} else {
|
|
1318
|
+
serverPort = parsed;
|
|
1319
|
+
}
|
|
1143
1320
|
}
|
|
1144
|
-
console.log(` Port ${serverPort} available.`);
|
|
1145
|
-
usingAlternatePort = true;
|
|
1146
1321
|
}
|
|
1147
|
-
}
|
|
1148
|
-
// Default: check port 80 first, then scan
|
|
1149
|
-
console.log(' 1a. Checking port 80...');
|
|
1150
|
-
const port80Result = await tryBindPort(80);
|
|
1151
|
-
|
|
1152
|
-
if (port80Result.ok) {
|
|
1153
|
-
console.log(' Port 80 available.');
|
|
1154
|
-
serverPort = 80;
|
|
1155
|
-
} else if (port80Result.code === 'EACCES') {
|
|
1156
|
-
console.log(' Port 80 is available but requires elevated privileges.');
|
|
1157
|
-
console.log(' A2A needs to bind to a port to function. Rerun with:');
|
|
1158
|
-
console.log(' sudo npm install -g a2acalling\n');
|
|
1159
|
-
console.log(' Onboarding cannot continue without a bound port.');
|
|
1160
|
-
process.exit(1);
|
|
1161
|
-
} else {
|
|
1162
|
-
console.log(' Port 80 is in use by another process.');
|
|
1163
|
-
console.log(' 1b. Scanning for available port...');
|
|
1322
|
+
}
|
|
1164
1323
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1324
|
+
printSection('Hostname Configuration');
|
|
1325
|
+
const ipResult = await getExternalIp();
|
|
1326
|
+
const externalIp = ipResult.ip || null;
|
|
1327
|
+
let publicHost = `localhost:${serverPort}`;
|
|
1168
1328
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1329
|
+
if (externalIp) {
|
|
1330
|
+
const detectedHost = serverPort === 80 ? externalIp : `${externalIp}:${serverPort}`;
|
|
1331
|
+
console.log(` Detected external IP: ${detectedHost}`);
|
|
1332
|
+
if (interactive) {
|
|
1333
|
+
const hostChoiceRaw = await promptText(
|
|
1334
|
+
'How should other agents reach you?\n'
|
|
1335
|
+
+ ' 1. Use IP directly\n'
|
|
1336
|
+
+ ' 2. Enter a domain name\n'
|
|
1337
|
+
+ ' 3. Skip (configure later)\n'
|
|
1338
|
+
+ 'Choice [1/2/3]: ',
|
|
1339
|
+
'1'
|
|
1340
|
+
);
|
|
1341
|
+
const hostChoice = String(hostChoiceRaw || '').trim();
|
|
1342
|
+
if (hostChoice === '2') {
|
|
1343
|
+
const manualHost = await promptText('Enter your public hostname: ', '');
|
|
1344
|
+
if (manualHost) publicHost = String(manualHost).trim();
|
|
1345
|
+
} else if (hostChoice === '3') {
|
|
1346
|
+
publicHost = process.env.A2A_HOSTNAME || `localhost:${serverPort}`;
|
|
1347
|
+
} else {
|
|
1348
|
+
publicHost = detectedHost;
|
|
1173
1349
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1350
|
+
} else {
|
|
1351
|
+
publicHost = detectedHost;
|
|
1176
1352
|
}
|
|
1353
|
+
} else if (interactive) {
|
|
1354
|
+
const hostChoiceRaw = await promptText(
|
|
1355
|
+
'External IP unavailable.\nHow should other agents reach you?\n'
|
|
1356
|
+
+ ' 1. Enter a domain name\n'
|
|
1357
|
+
+ ' 2. Skip (use localhost)\n'
|
|
1358
|
+
+ 'Choice [1/2]: ',
|
|
1359
|
+
'2'
|
|
1360
|
+
);
|
|
1361
|
+
const hostChoice = String(hostChoiceRaw || '').trim();
|
|
1362
|
+
if (hostChoice === '1') {
|
|
1363
|
+
const manualHost = await promptText('Enter your public hostname: ', '');
|
|
1364
|
+
if (manualHost) publicHost = String(manualHost).trim();
|
|
1365
|
+
}
|
|
1366
|
+
} else if (ipResult.error) {
|
|
1367
|
+
console.log(` External IP lookup failed: ${ipResult.error}`);
|
|
1177
1368
|
}
|
|
1178
1369
|
|
|
1179
|
-
|
|
1180
|
-
console.log(
|
|
1370
|
+
printSection('Starting Server');
|
|
1371
|
+
console.log(' Configuration summary:');
|
|
1372
|
+
console.log(` Port: ${serverPort}`);
|
|
1373
|
+
console.log(` Public host: ${publicHost}`);
|
|
1374
|
+
|
|
1375
|
+
if (!interactive) {
|
|
1376
|
+
console.log('\n Non-interactive mode detected (no TTY).');
|
|
1377
|
+
console.log(' Not starting the server automatically.\n');
|
|
1378
|
+
console.log(' Next steps:');
|
|
1379
|
+
console.log(' 1. Re-run in a terminal: a2a quickstart');
|
|
1380
|
+
console.log(` 2. Or start manually: a2a server --port ${serverPort}\n`);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1181
1383
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1384
|
+
const startServer = await promptYesNo('Start the A2A server now? [Y/n] ');
|
|
1385
|
+
if (!startServer) {
|
|
1386
|
+
console.log('\nServer not started. Run with:\n a2a server --port <port> --hostname <host>\n');
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// Start server
|
|
1391
|
+
const isAlreadyListening = await isPortListening(serverPort, '127.0.0.1', { timeoutMs: 250 });
|
|
1392
|
+
let serverPid = null;
|
|
1393
|
+
if (!isAlreadyListening.listening) {
|
|
1185
1394
|
const serverScript = path.join(__dirname, '../src/server.js');
|
|
1186
1395
|
const child = spawn(process.execPath, [serverScript], {
|
|
1187
|
-
env: { ...process.env, PORT: String(
|
|
1396
|
+
env: { ...process.env, PORT: String(serverPort) },
|
|
1188
1397
|
detached: true,
|
|
1189
1398
|
stdio: 'ignore'
|
|
1190
1399
|
});
|
|
1400
|
+
serverPid = child.pid;
|
|
1191
1401
|
child.unref();
|
|
1192
|
-
|
|
1193
|
-
|
|
1402
|
+
} else {
|
|
1403
|
+
console.log(' Existing server detected on this port.');
|
|
1194
1404
|
}
|
|
1195
1405
|
|
|
1196
1406
|
async function waitForServer(port) {
|
|
@@ -1202,64 +1412,43 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1202
1412
|
return false;
|
|
1203
1413
|
}
|
|
1204
1414
|
|
|
1205
|
-
const serverResult = await startServer(serverPort);
|
|
1206
|
-
if (serverResult.existing) {
|
|
1207
|
-
console.log(' Existing server detected on this port.');
|
|
1208
|
-
}
|
|
1209
1415
|
const serverUp = await waitForServer(serverPort);
|
|
1210
1416
|
if (!serverUp) {
|
|
1211
1417
|
console.log(' Server failed to start. Check logs and retry:');
|
|
1212
1418
|
console.log(` PORT=${serverPort} node ${path.join(__dirname, '../src/server.js')}`);
|
|
1213
1419
|
process.exit(1);
|
|
1214
1420
|
}
|
|
1215
|
-
console.log(' Server running.\n');
|
|
1216
1421
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
config.setOnboarding({ server_pid:
|
|
1422
|
+
if (serverPid) {
|
|
1423
|
+
console.log(' Server started.');
|
|
1424
|
+
config.setOnboarding({ server_pid: serverPid, server_port: serverPort });
|
|
1425
|
+
} else {
|
|
1426
|
+
console.log(' Using existing server.');
|
|
1220
1427
|
}
|
|
1428
|
+
console.log(' ✅ A2A server is running');
|
|
1221
1429
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
if (!ipResult.ip) {
|
|
1225
|
-
console.log(' Warning: Could not detect external IP address.');
|
|
1226
|
-
console.log(' Set your hostname via environment variable and re-run:');
|
|
1227
|
-
console.log(` A2A_HOSTNAME=YOUR_IP${serverPort !== 80 ? ':' + serverPort : ''} a2a quickstart --force\n`);
|
|
1228
|
-
}
|
|
1229
|
-
const externalIp = ipResult.ip || null;
|
|
1230
|
-
const publicHost = externalIp
|
|
1231
|
-
? (serverPort === 80 ? externalIp : `${externalIp}:${serverPort}`)
|
|
1232
|
-
: `localhost:${serverPort}`;
|
|
1233
|
-
|
|
1234
|
-
// Save server config
|
|
1235
|
-
config.setAgent({ hostname: publicHost });
|
|
1236
|
-
|
|
1237
|
-
if (usingAlternatePort) {
|
|
1238
|
-
console.log(' External access required.');
|
|
1239
|
-
console.log(' Something is already bound to port 80 on this machine.');
|
|
1240
|
-
console.log(' Two options to make your A2A server reachable:\n');
|
|
1430
|
+
if (serverPort !== 80 && externalIp) {
|
|
1431
|
+
console.log('\n External access required because port 80 is not in use.');
|
|
1241
1432
|
console.log(' Option A (recommended): Set up a reverse proxy (HTTP or HTTPS).');
|
|
1242
1433
|
console.log(` Configure your web server to forward /api/a2a/* to localhost:${serverPort}.`);
|
|
1243
1434
|
console.log(' If you serve HTTPS on port 443, proxy from there instead.');
|
|
1244
|
-
console.log(' A reverse proxy avoids firewall changes entirely
|
|
1245
|
-
console.log(
|
|
1246
|
-
console.log('
|
|
1247
|
-
console.log(` port ${serverPort} (e.g. ufw allow ${serverPort}, or cloud provider security group).`);
|
|
1248
|
-
console.log(' Most users prefer not to modify firewall settings.\n');
|
|
1435
|
+
console.log(' A reverse proxy avoids firewall changes entirely.');
|
|
1436
|
+
console.log(`\n Option B: Open port ${serverPort} in your firewall (e.g. ufw allow ${serverPort}).`);
|
|
1437
|
+
console.log(' Most users prefer not to modify firewall settings.\n');
|
|
1249
1438
|
}
|
|
1250
|
-
|
|
1251
1439
|
if (externalIp) {
|
|
1252
1440
|
const verifyUrl = `http://${publicHost}/api/a2a/ping`;
|
|
1253
|
-
console.log('
|
|
1254
|
-
console.log(` curl -s ${verifyUrl}`);
|
|
1255
|
-
console.log(' Or ask your owner to check: https://canyouseeme.org/\n');
|
|
1441
|
+
console.log(' External ping check:\n curl -s ' + verifyUrl);
|
|
1256
1442
|
}
|
|
1257
1443
|
|
|
1444
|
+
// Save server config and advance onboarding
|
|
1445
|
+
config.setAgent({ hostname: publicHost });
|
|
1258
1446
|
config.setOnboarding({ step: 'awaiting_disclosure' });
|
|
1259
1447
|
|
|
1260
|
-
//
|
|
1448
|
+
// Step 2 of 4: Configure disclosure topics
|
|
1449
|
+
printSection('Disclosure Topic Extraction');
|
|
1261
1450
|
console.log('Step 2 of 4: Configure disclosure topics\n');
|
|
1262
|
-
console.log(buildExtractionPrompt());
|
|
1451
|
+
console.log(buildExtractionPrompt(availableFiles));
|
|
1263
1452
|
console.log('\n Read your workspace files, extract topics, and present to your owner for review.');
|
|
1264
1453
|
console.log(" Then submit with: a2a quickstart --submit '<json>'\n");
|
|
1265
1454
|
},
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -5,22 +5,73 @@ if (process.env.CI || process.env.CONTINUOUS_INTEGRATION) process.exit(0);
|
|
|
5
5
|
if (process.env.DOCKER) process.exit(0);
|
|
6
6
|
if (process.env.npm_config_global !== 'true') process.exit(0);
|
|
7
7
|
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
8
10
|
const { spawnSync } = require('child_process');
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
function openDevTty() {
|
|
13
|
+
if (process.env.A2A_POSTINSTALL_DISABLE_TTY === '1') return null;
|
|
14
|
+
if (process.platform === 'win32') return null;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// npm may pipe lifecycle stdio even when the user ran npm in a terminal.
|
|
18
|
+
// /dev/tty lets us talk to the actual interactive terminal when present.
|
|
19
|
+
const fdIn = fs.openSync('/dev/tty', 'r');
|
|
20
|
+
const fdOut = fs.openSync('/dev/tty', 'w');
|
|
21
|
+
return { fdIn, fdOut };
|
|
22
|
+
} catch (err) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const initCwd = process.env.INIT_CWD || process.env.HOME || process.cwd();
|
|
28
|
+
const tty = openDevTty();
|
|
29
|
+
|
|
30
|
+
if (!tty) {
|
|
31
|
+
// Do NOT auto-start a background server in non-interactive installs.
|
|
32
|
+
console.warn('\n⚠️ A2A Calling installed.');
|
|
33
|
+
console.warn(' Setup requires an interactive terminal.');
|
|
34
|
+
console.warn(' Next: a2a quickstart\n');
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function writeTty(message) {
|
|
39
|
+
try {
|
|
40
|
+
fs.writeSync(tty.fdOut, String(message));
|
|
41
|
+
} catch (err) {
|
|
42
|
+
// ignore
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const cliPath = path.join(__dirname, '..', 'bin', 'cli.js');
|
|
47
|
+
|
|
48
|
+
// Launch quickstart attached to /dev/tty so prompts and output are visible
|
|
49
|
+
// even when npm suppresses postinstall output.
|
|
50
|
+
const result = spawnSync(process.execPath, [cliPath, 'quickstart'], {
|
|
51
|
+
stdio: [tty.fdIn, tty.fdOut, tty.fdOut],
|
|
52
|
+
cwd: initCwd,
|
|
53
|
+
env: {
|
|
54
|
+
...process.env,
|
|
55
|
+
A2A_WORKSPACE: process.env.A2A_WORKSPACE || initCwd
|
|
56
|
+
}
|
|
16
57
|
});
|
|
17
58
|
|
|
18
|
-
if (result.error
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
59
|
+
if (result.error) {
|
|
60
|
+
writeTty('\nCould not auto-launch onboarding.\n');
|
|
61
|
+
writeTty(`Reason: ${result.error.message}\n`);
|
|
62
|
+
writeTty('\nRun manually: a2a quickstart\n');
|
|
63
|
+
try {
|
|
64
|
+
fs.closeSync(tty.fdIn);
|
|
65
|
+
fs.closeSync(tty.fdOut);
|
|
66
|
+
} catch (err) {}
|
|
23
67
|
process.exit(0); // don't fail the install
|
|
24
68
|
}
|
|
25
69
|
|
|
70
|
+
try {
|
|
71
|
+
fs.closeSync(tty.fdIn);
|
|
72
|
+
fs.closeSync(tty.fdOut);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
// Best-effort cleanup.
|
|
75
|
+
}
|
|
76
|
+
|
|
26
77
|
process.exit(result.status || 0);
|