a2acalling 0.6.9 → 0.6.10
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 +284 -108
- package/package.json +1 -1
- package/scripts/postinstall.js +10 -0
package/bin/cli.js
CHANGED
|
@@ -200,16 +200,159 @@ function parseArgs(argv) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
async function promptYesNo(question) {
|
|
203
|
+
const defaultValue = question.includes('[Y/n]') || question.includes('[y/N]') || question.includes('[Y/N]')
|
|
204
|
+
? question.includes('[Y/n]') || question.includes('[y/n]')
|
|
205
|
+
? true
|
|
206
|
+
: false
|
|
207
|
+
: true;
|
|
208
|
+
|
|
209
|
+
if (!isInteractiveShell()) {
|
|
210
|
+
return defaultValue;
|
|
211
|
+
}
|
|
212
|
+
|
|
203
213
|
return await new Promise(resolve => {
|
|
204
214
|
const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
|
|
205
215
|
rl.question(question, (answer) => {
|
|
206
216
|
rl.close();
|
|
207
217
|
const normalized = String(answer || '').trim().toLowerCase();
|
|
218
|
+
if (!normalized) return resolve(defaultValue);
|
|
208
219
|
resolve(normalized === 'y' || normalized === 'yes');
|
|
209
220
|
});
|
|
210
221
|
});
|
|
211
222
|
}
|
|
212
223
|
|
|
224
|
+
function isInteractiveShell() {
|
|
225
|
+
return Boolean(process.stdin && process.stdout && process.stdin.isTTY && process.stdout.isTTY);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function promptText(question, defaultValue = '') {
|
|
229
|
+
if (!isInteractiveShell()) {
|
|
230
|
+
return defaultValue;
|
|
231
|
+
}
|
|
232
|
+
return await new Promise(resolve => {
|
|
233
|
+
const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
|
|
234
|
+
rl.question(question, (answer) => {
|
|
235
|
+
rl.close();
|
|
236
|
+
const cleaned = String(answer || '').trim();
|
|
237
|
+
resolve(cleaned || defaultValue);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function parsePort(raw, fallback = null) {
|
|
243
|
+
const parsed = Number.parseInt(String(raw || '').trim(), 10);
|
|
244
|
+
if (Number.isFinite(parsed) && parsed > 0 && parsed <= 65535) {
|
|
245
|
+
return parsed;
|
|
246
|
+
}
|
|
247
|
+
return fallback;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function printStepHeader(label) {
|
|
251
|
+
const clean = String(label || '').trim();
|
|
252
|
+
const innerWidth = Math.max(62, clean.length + 12);
|
|
253
|
+
const padding = Math.max(0, innerWidth - clean.length);
|
|
254
|
+
const left = Math.floor(padding / 2);
|
|
255
|
+
const right = Math.max(0, padding - left);
|
|
256
|
+
console.log('\n' + '╔' + '═'.repeat(innerWidth) + '╗');
|
|
257
|
+
console.log(`║${' '.repeat(left)}${clean}${' '.repeat(right)}║`);
|
|
258
|
+
console.log('╚' + '═'.repeat(innerWidth) + '╝');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function printSection(title) {
|
|
262
|
+
console.log('\n━━━ ' + title + ' ━━━');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function readWorkspaceContext(baseDir = process.cwd()) {
|
|
266
|
+
const base = baseDir || process.cwd();
|
|
267
|
+
const workspaceFiles = {
|
|
268
|
+
USER: { filename: 'USER.md' },
|
|
269
|
+
SOUL: { filename: 'SOUL.md' },
|
|
270
|
+
HEARTBEAT: { filename: 'HEARTBEAT.md' },
|
|
271
|
+
SKILL: { filename: 'SKILL.md' },
|
|
272
|
+
CLAUDE: { filename: 'CLAUDE.md' }
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const found = {};
|
|
276
|
+
for (const key of Object.keys(workspaceFiles)) {
|
|
277
|
+
found[key] = fs.existsSync(path.join(base, workspaceFiles[key].filename));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const memoryDir = path.join(base, 'memory');
|
|
281
|
+
let memoryCount = 0;
|
|
282
|
+
if (fs.existsSync(memoryDir)) {
|
|
283
|
+
try {
|
|
284
|
+
memoryCount = fs.readdirSync(memoryDir).filter(item => item.endsWith('.md')).length;
|
|
285
|
+
} catch (err) {
|
|
286
|
+
memoryCount = 0;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
found.MEMORY = memoryCount;
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
workspace: base,
|
|
293
|
+
found,
|
|
294
|
+
memoryCount
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function printWorkspaceScan(context) {
|
|
299
|
+
console.log('Scanning workspace for context...');
|
|
300
|
+
console.log(` ${context.found.USER ? '✅' : '⚠️ '} ${context.found.USER ? 'Found USER.md' : 'No USER.md'}${context.found.USER ? ' — identity hints found' : ''}`);
|
|
301
|
+
console.log(` ${context.found.SOUL ? '✅' : '⚠️ '} ${context.found.SOUL ? 'Found SOUL.md' : 'No SOUL.md'}${context.found.SOUL ? ' — personality notes available' : ''}`);
|
|
302
|
+
console.log(` ${context.found.HEARTBEAT ? '✅' : '⚠️ '} ${context.found.HEARTBEAT ? 'Found HEARTBEAT.md' : 'No HEARTBEAT.md (skipped)'}`);
|
|
303
|
+
console.log(` ${context.found.SKILL ? '✅' : '⚠️ '} ${context.found.SKILL ? 'Found SKILL.md' : 'No SKILL.md (skipped)'}`
|
|
304
|
+
);
|
|
305
|
+
console.log(` ${context.found.CLAUDE ? '✅' : '⚠️ '} ${context.found.CLAUDE ? 'Found CLAUDE.md' : 'No CLAUDE.md (skipped)'}`);
|
|
306
|
+
if (context.memoryCount > 0) {
|
|
307
|
+
console.log(` ✅ Found ${context.memoryCount} memory file(s)`);
|
|
308
|
+
} else {
|
|
309
|
+
console.log(' ⚠️ No memory/*.md files');
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function getDisclosurePromptFiles(context) {
|
|
314
|
+
return {
|
|
315
|
+
'USER.md': context.found.USER,
|
|
316
|
+
'SOUL.md': context.found.SOUL,
|
|
317
|
+
'HEARTBEAT.md': context.found.HEARTBEAT,
|
|
318
|
+
'SKILL.md': context.found.SKILL,
|
|
319
|
+
'CLAUDE.md': context.found.CLAUDE,
|
|
320
|
+
'memory/*.md': context.memoryCount > 0
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function inspectPorts(preferredPort = null) {
|
|
325
|
+
const candidates = [];
|
|
326
|
+
if (preferredPort) {
|
|
327
|
+
candidates.push(preferredPort);
|
|
328
|
+
}
|
|
329
|
+
candidates.push(80);
|
|
330
|
+
for (let p = 3001; p < 3021; p += 1) {
|
|
331
|
+
if (!candidates.includes(p)) candidates.push(p);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const { tryBindPort } = require('../src/lib/port-scanner');
|
|
335
|
+
const results = [];
|
|
336
|
+
for (const port of candidates) {
|
|
337
|
+
const r = await tryBindPort(port);
|
|
338
|
+
results.push({
|
|
339
|
+
port,
|
|
340
|
+
available: Boolean(r.ok),
|
|
341
|
+
blocked: !r.ok && r.code === 'EACCES',
|
|
342
|
+
code: r.code || null
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
return results;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function summarizePortResults(portResults) {
|
|
349
|
+
return portResults.map(item => {
|
|
350
|
+
if (item.available) return `Port ${item.port}: available ✓`;
|
|
351
|
+
if (item.blocked) return `Port ${item.port}: requires elevated privileges`;
|
|
352
|
+
return `Port ${item.port}: in use`;
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
213
356
|
async function handleDisclosureSubmit(args, commandLabel = 'onboard') {
|
|
214
357
|
const submitRaw = args.flags.submit;
|
|
215
358
|
if (!submitRaw) return false;
|
|
@@ -1064,11 +1207,12 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1064
1207
|
|
|
1065
1208
|
quickstart: async (args) => {
|
|
1066
1209
|
const { A2AConfig } = require('../src/lib/config');
|
|
1067
|
-
const {
|
|
1210
|
+
const { isPortListening } = require('../src/lib/port-scanner');
|
|
1068
1211
|
const { buildExtractionPrompt } = require('../src/lib/disclosure');
|
|
1069
1212
|
const { getExternalIp } = require('../src/lib/external-ip');
|
|
1070
1213
|
|
|
1071
1214
|
const config = new A2AConfig();
|
|
1215
|
+
const interactive = isInteractiveShell();
|
|
1072
1216
|
|
|
1073
1217
|
if (await handleDisclosureSubmit(args, 'quickstart')) {
|
|
1074
1218
|
return;
|
|
@@ -1084,6 +1228,9 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1084
1228
|
return;
|
|
1085
1229
|
}
|
|
1086
1230
|
|
|
1231
|
+
const context = readWorkspaceContext(process.env.A2A_WORKSPACE || process.cwd());
|
|
1232
|
+
const availableFiles = getDisclosurePromptFiles(context);
|
|
1233
|
+
|
|
1087
1234
|
// If server is already running and awaiting disclosure, skip to Step 2
|
|
1088
1235
|
let currentStep = 'not_started';
|
|
1089
1236
|
try {
|
|
@@ -1097,100 +1244,150 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1097
1244
|
if (currentStep === 'awaiting_disclosure' && !args.flags.force) {
|
|
1098
1245
|
console.log('\nStep 1 already complete. Server is running.\n');
|
|
1099
1246
|
console.log('Step 2 of 4: Configure disclosure topics\n');
|
|
1100
|
-
console.log(buildExtractionPrompt());
|
|
1247
|
+
console.log(buildExtractionPrompt(availableFiles));
|
|
1101
1248
|
console.log('\n Read your workspace files, extract topics, and present to your owner for review.');
|
|
1102
1249
|
console.log(" Then submit with: a2a quickstart --submit '<json>'\n");
|
|
1103
1250
|
return;
|
|
1104
1251
|
}
|
|
1105
1252
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
if (Number.isFinite(parsed) && parsed > 0 && parsed <= 65535) {
|
|
1109
|
-
return parsed;
|
|
1110
|
-
}
|
|
1111
|
-
return fallback;
|
|
1112
|
-
}
|
|
1253
|
+
printStepHeader('🤝 A2A Calling — First-Time Setup');
|
|
1254
|
+
printWorkspaceScan(context);
|
|
1113
1255
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1256
|
+
const continueSetup = await promptYesNo('Continue with setup? [Y/n] ');
|
|
1257
|
+
if (!continueSetup) {
|
|
1258
|
+
console.log('\nSetup cancelled.\n');
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1116
1261
|
|
|
1262
|
+
printSection('Port Configuration');
|
|
1117
1263
|
const preferredPort = parsePort(args.flags.port || args.flags.p, null);
|
|
1264
|
+
const candidates = await inspectPorts(preferredPort);
|
|
1265
|
+
const availableCandidates = candidates.filter(c => c.available);
|
|
1266
|
+
const recommendedPort = availableCandidates.length ? availableCandidates[0].port : null;
|
|
1118
1267
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
if (
|
|
1124
|
-
console.
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1268
|
+
summarizePortResults(candidates).forEach(line => {
|
|
1269
|
+
console.log(` ${line}`);
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
if (!recommendedPort) {
|
|
1273
|
+
console.error(' Could not find a bindable port in the scan range.');
|
|
1274
|
+
console.error(' Re-run with --port <number> after freeing one of these ports.\n');
|
|
1275
|
+
process.exit(1);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
console.log(`\n Recommended: ${recommendedPort}`);
|
|
1279
|
+
let serverPort = recommendedPort;
|
|
1280
|
+
const portChoice = await promptText(`Use port ${recommendedPort}? [Y/n/custom]: `, 'y');
|
|
1281
|
+
if (!interactive) {
|
|
1282
|
+
// explicit default for non-interactive mode
|
|
1283
|
+
serverPort = recommendedPort;
|
|
1284
|
+
} else if (!['', 'y', 'Y', 'yes', 'YES', 'ye'].includes(String(portChoice).trim())) {
|
|
1285
|
+
if (/^(n|no|custom|c)$/i.test(String(portChoice).trim())) {
|
|
1286
|
+
let customPort = null;
|
|
1287
|
+
while (customPort === null) {
|
|
1288
|
+
const raw = await promptText('Enter a custom port number: ', String(recommendedPort));
|
|
1289
|
+
const parsed = parsePort(raw, null);
|
|
1290
|
+
if (!parsed) {
|
|
1291
|
+
console.log(' Invalid port. Enter a value between 1 and 65535.');
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1294
|
+
const checked = await (async () => {
|
|
1295
|
+
const scan = await inspectPorts(parsed);
|
|
1296
|
+
return scan[0];
|
|
1297
|
+
})();
|
|
1298
|
+
if (!checked.available) {
|
|
1299
|
+
console.log(` Port ${parsed} is unavailable (${checked.code || 'in use'}).`);
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
customPort = parsed;
|
|
1303
|
+
}
|
|
1304
|
+
serverPort = customPort;
|
|
1134
1305
|
} else {
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1306
|
+
const parsed = parsePort(portChoice, null);
|
|
1307
|
+
if (parsed) {
|
|
1308
|
+
const checked = await (async () => {
|
|
1309
|
+
const scan = await inspectPorts(parsed);
|
|
1310
|
+
return scan[0];
|
|
1311
|
+
})();
|
|
1312
|
+
if (!checked.available) {
|
|
1313
|
+
console.log(` Port ${parsed} is unavailable (${checked.code || 'in use'}).`);
|
|
1314
|
+
} else {
|
|
1315
|
+
serverPort = parsed;
|
|
1316
|
+
}
|
|
1143
1317
|
}
|
|
1144
|
-
console.log(` Port ${serverPort} available.`);
|
|
1145
|
-
usingAlternatePort = true;
|
|
1146
1318
|
}
|
|
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...');
|
|
1319
|
+
}
|
|
1164
1320
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1321
|
+
printSection('Hostname Configuration');
|
|
1322
|
+
const ipResult = await getExternalIp();
|
|
1323
|
+
const externalIp = ipResult.ip || null;
|
|
1324
|
+
let publicHost = `localhost:${serverPort}`;
|
|
1168
1325
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1326
|
+
if (externalIp) {
|
|
1327
|
+
const detectedHost = serverPort === 80 ? externalIp : `${externalIp}:${serverPort}`;
|
|
1328
|
+
console.log(` Detected external IP: ${detectedHost}`);
|
|
1329
|
+
if (interactive) {
|
|
1330
|
+
const hostChoiceRaw = await promptText(
|
|
1331
|
+
'How should other agents reach you?\n'
|
|
1332
|
+
+ ' 1. Use IP directly\n'
|
|
1333
|
+
+ ' 2. Enter a domain name\n'
|
|
1334
|
+
+ ' 3. Skip (configure later)\n'
|
|
1335
|
+
+ 'Choice [1/2/3]: ',
|
|
1336
|
+
'1'
|
|
1337
|
+
);
|
|
1338
|
+
const hostChoice = String(hostChoiceRaw || '').trim();
|
|
1339
|
+
if (hostChoice === '2') {
|
|
1340
|
+
const manualHost = await promptText('Enter your public hostname: ', '');
|
|
1341
|
+
if (manualHost) publicHost = String(manualHost).trim();
|
|
1342
|
+
} else if (hostChoice === '3') {
|
|
1343
|
+
publicHost = process.env.A2A_HOSTNAME || `localhost:${serverPort}`;
|
|
1344
|
+
} else {
|
|
1345
|
+
publicHost = detectedHost;
|
|
1173
1346
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1347
|
+
} else {
|
|
1348
|
+
publicHost = detectedHost;
|
|
1176
1349
|
}
|
|
1350
|
+
} else if (interactive) {
|
|
1351
|
+
const hostChoiceRaw = await promptText(
|
|
1352
|
+
'External IP unavailable.\nHow should other agents reach you?\n'
|
|
1353
|
+
+ ' 1. Enter a domain name\n'
|
|
1354
|
+
+ ' 2. Skip (use localhost)\n'
|
|
1355
|
+
+ 'Choice [1/2]: ',
|
|
1356
|
+
'2'
|
|
1357
|
+
);
|
|
1358
|
+
const hostChoice = String(hostChoiceRaw || '').trim();
|
|
1359
|
+
if (hostChoice === '1') {
|
|
1360
|
+
const manualHost = await promptText('Enter your public hostname: ', '');
|
|
1361
|
+
if (manualHost) publicHost = String(manualHost).trim();
|
|
1362
|
+
}
|
|
1363
|
+
} else if (ipResult.error) {
|
|
1364
|
+
console.log(` External IP lookup failed: ${ipResult.error}`);
|
|
1177
1365
|
}
|
|
1178
1366
|
|
|
1179
|
-
|
|
1180
|
-
console.log(
|
|
1367
|
+
printSection('Starting Server');
|
|
1368
|
+
console.log(' Configuration summary:');
|
|
1369
|
+
console.log(` Port: ${serverPort}`);
|
|
1370
|
+
console.log(` Public host: ${publicHost}`);
|
|
1371
|
+
const startServer = await promptYesNo('Start the A2A server now? [Y/n] ');
|
|
1372
|
+
if (!startServer) {
|
|
1373
|
+
console.log('\nServer not started. Run with:\n a2a server --port <port> --hostname <host>\n');
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1181
1376
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1377
|
+
// Start server
|
|
1378
|
+
const isAlreadyListening = await isPortListening(serverPort, '127.0.0.1', { timeoutMs: 250 });
|
|
1379
|
+
let serverPid = null;
|
|
1380
|
+
if (!isAlreadyListening.listening) {
|
|
1185
1381
|
const serverScript = path.join(__dirname, '../src/server.js');
|
|
1186
1382
|
const child = spawn(process.execPath, [serverScript], {
|
|
1187
|
-
env: { ...process.env, PORT: String(
|
|
1383
|
+
env: { ...process.env, PORT: String(serverPort) },
|
|
1188
1384
|
detached: true,
|
|
1189
1385
|
stdio: 'ignore'
|
|
1190
1386
|
});
|
|
1387
|
+
serverPid = child.pid;
|
|
1191
1388
|
child.unref();
|
|
1192
|
-
|
|
1193
|
-
|
|
1389
|
+
} else {
|
|
1390
|
+
console.log(' Existing server detected on this port.');
|
|
1194
1391
|
}
|
|
1195
1392
|
|
|
1196
1393
|
async function waitForServer(port) {
|
|
@@ -1202,64 +1399,43 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1202
1399
|
return false;
|
|
1203
1400
|
}
|
|
1204
1401
|
|
|
1205
|
-
const serverResult = await startServer(serverPort);
|
|
1206
|
-
if (serverResult.existing) {
|
|
1207
|
-
console.log(' Existing server detected on this port.');
|
|
1208
|
-
}
|
|
1209
1402
|
const serverUp = await waitForServer(serverPort);
|
|
1210
1403
|
if (!serverUp) {
|
|
1211
1404
|
console.log(' Server failed to start. Check logs and retry:');
|
|
1212
1405
|
console.log(` PORT=${serverPort} node ${path.join(__dirname, '../src/server.js')}`);
|
|
1213
1406
|
process.exit(1);
|
|
1214
1407
|
}
|
|
1215
|
-
console.log(' Server running.\n');
|
|
1216
1408
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
config.setOnboarding({ server_pid:
|
|
1409
|
+
if (serverPid) {
|
|
1410
|
+
console.log(' Server started.');
|
|
1411
|
+
config.setOnboarding({ server_pid: serverPid, server_port: serverPort });
|
|
1412
|
+
} else {
|
|
1413
|
+
console.log(' Using existing server.');
|
|
1220
1414
|
}
|
|
1415
|
+
console.log(' ✅ A2A server is running');
|
|
1221
1416
|
|
|
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');
|
|
1417
|
+
if (serverPort !== 80 && externalIp) {
|
|
1418
|
+
console.log('\n External access required because port 80 is not in use.');
|
|
1241
1419
|
console.log(' Option A (recommended): Set up a reverse proxy (HTTP or HTTPS).');
|
|
1242
1420
|
console.log(` Configure your web server to forward /api/a2a/* to localhost:${serverPort}.`);
|
|
1243
1421
|
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');
|
|
1422
|
+
console.log(' A reverse proxy avoids firewall changes entirely.');
|
|
1423
|
+
console.log(`\n Option B: Open port ${serverPort} in your firewall (e.g. ufw allow ${serverPort}).`);
|
|
1424
|
+
console.log(' Most users prefer not to modify firewall settings.\n');
|
|
1249
1425
|
}
|
|
1250
|
-
|
|
1251
1426
|
if (externalIp) {
|
|
1252
1427
|
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');
|
|
1428
|
+
console.log(' External ping check:\n curl -s ' + verifyUrl);
|
|
1256
1429
|
}
|
|
1257
1430
|
|
|
1431
|
+
// Save server config and advance onboarding
|
|
1432
|
+
config.setAgent({ hostname: publicHost });
|
|
1258
1433
|
config.setOnboarding({ step: 'awaiting_disclosure' });
|
|
1259
1434
|
|
|
1260
|
-
//
|
|
1435
|
+
// Step 2 of 4: Configure disclosure topics
|
|
1436
|
+
printSection('Disclosure Topic Extraction');
|
|
1261
1437
|
console.log('Step 2 of 4: Configure disclosure topics\n');
|
|
1262
|
-
console.log(buildExtractionPrompt());
|
|
1438
|
+
console.log(buildExtractionPrompt(availableFiles));
|
|
1263
1439
|
console.log('\n Read your workspace files, extract topics, and present to your owner for review.');
|
|
1264
1440
|
console.log(" Then submit with: a2a quickstart --submit '<json>'\n");
|
|
1265
1441
|
},
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -6,6 +6,16 @@ if (process.env.DOCKER) process.exit(0);
|
|
|
6
6
|
if (process.env.npm_config_global !== 'true') process.exit(0);
|
|
7
7
|
|
|
8
8
|
const { spawnSync } = require('child_process');
|
|
9
|
+
const isInteractive = Boolean(process.stdout && process.stderr && process.stdin &&
|
|
10
|
+
process.stdout.isTTY && process.stderr.isTTY && process.stdin.isTTY);
|
|
11
|
+
|
|
12
|
+
function warnSuppressedOutput() {
|
|
13
|
+
if (!isInteractive) {
|
|
14
|
+
console.warn('\n⚠️ Output may be suppressed. Run \'a2a quickstart\' manually if you don\'t see prompts.');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
warnSuppressedOutput();
|
|
9
19
|
|
|
10
20
|
// Launch quickstart directly — stdio: 'inherit' forces foreground output
|
|
11
21
|
// even when npm v10+ suppresses postinstall stdout by default.
|