a2acalling 0.6.11 → 0.6.12
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 +86 -45
- package/package.json +1 -1
- package/scripts/postinstall.js +33 -52
package/bin/cli.js
CHANGED
|
@@ -299,17 +299,22 @@ function readWorkspaceContext(baseDir = process.cwd()) {
|
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
function printWorkspaceScan(context) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
302
|
+
const fileLabels = {
|
|
303
|
+
USER: 'USER.md — identity hints',
|
|
304
|
+
SOUL: 'SOUL.md — personality notes',
|
|
305
|
+
HEARTBEAT: 'HEARTBEAT.md — scheduled tasks',
|
|
306
|
+
SKILL: 'SKILL.md — capabilities',
|
|
307
|
+
CLAUDE: 'CLAUDE.md — agent instructions'
|
|
308
|
+
};
|
|
309
|
+
const found = Object.entries(fileLabels)
|
|
310
|
+
.filter(([key]) => context.found[key])
|
|
311
|
+
.map(([, label]) => label);
|
|
312
|
+
if (context.memoryCount > 0) found.push(`${context.memoryCount} memory file(s)`);
|
|
313
|
+
|
|
314
|
+
if (found.length) {
|
|
315
|
+
console.log(`Workspace context: ${found.join(', ')}`);
|
|
311
316
|
} else {
|
|
312
|
-
console.log('
|
|
317
|
+
console.log('Workspace context: no context files found (disclosure topics will be minimal)');
|
|
313
318
|
}
|
|
314
319
|
}
|
|
315
320
|
|
|
@@ -1208,6 +1213,33 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1208
1213
|
require('../src/server.js');
|
|
1209
1214
|
},
|
|
1210
1215
|
|
|
1216
|
+
// ==========================================================================
|
|
1217
|
+
// quickstart — full onboarding flow
|
|
1218
|
+
//
|
|
1219
|
+
// OBJECTIVE: Walk an installing agent (or human) through every step needed
|
|
1220
|
+
// to get A2A Calling running, in a single pass. The postinstall hook calls
|
|
1221
|
+
// this automatically, so `npm install -g a2acalling` completes the entire
|
|
1222
|
+
// setup without any follow-up commands.
|
|
1223
|
+
//
|
|
1224
|
+
// Every prompt auto-accepts its default when there is no TTY, so the full
|
|
1225
|
+
// flow completes unattended in non-interactive environments (agents).
|
|
1226
|
+
//
|
|
1227
|
+
// STEPS:
|
|
1228
|
+
// Step 1 — Port selection: scan for an available port and bind it.
|
|
1229
|
+
// Step 2 — Hostname detection: look up the external IP so remote agents
|
|
1230
|
+
// know where to reach this instance.
|
|
1231
|
+
// Step 3 — Server start: launch the A2A server as a detached process,
|
|
1232
|
+
// confirm it's listening, save config, and print a verify hint.
|
|
1233
|
+
// Step 4 — Disclosure prompt: output a full agent-readable prompt that
|
|
1234
|
+
// instructs the agent to scan its own workspace files, extract
|
|
1235
|
+
// tiered disclosure topics, and submit them back via
|
|
1236
|
+
// `a2a quickstart --submit '<json>'`.
|
|
1237
|
+
//
|
|
1238
|
+
// The disclosure prompt does NOT pre-scan files itself — it tells the agent
|
|
1239
|
+
// which files to look for (USER.md, SOUL.md, etc.) and lets the agent read
|
|
1240
|
+
// them with its own tools. This is intentional: the installer runs in a
|
|
1241
|
+
// subprocess where it has no access to the agent's file-reading capabilities.
|
|
1242
|
+
// ==========================================================================
|
|
1211
1243
|
quickstart: async (args) => {
|
|
1212
1244
|
const { A2AConfig } = require('../src/lib/config');
|
|
1213
1245
|
const { isPortListening } = require('../src/lib/port-scanner');
|
|
@@ -1217,6 +1249,8 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1217
1249
|
const config = new A2AConfig();
|
|
1218
1250
|
const interactive = isInteractiveShell();
|
|
1219
1251
|
|
|
1252
|
+
// Handle `quickstart --submit '<json>'` — this is the agent calling back
|
|
1253
|
+
// after it has scanned its workspace and built the disclosure JSON.
|
|
1220
1254
|
if (await handleDisclosureSubmit(args, 'quickstart')) {
|
|
1221
1255
|
return;
|
|
1222
1256
|
}
|
|
@@ -1231,10 +1265,9 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1231
1265
|
return;
|
|
1232
1266
|
}
|
|
1233
1267
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
// If server is already running and awaiting disclosure, skip to Step 2
|
|
1268
|
+
// Resume point: if the server is already running and we're waiting for the
|
|
1269
|
+
// agent to submit disclosure topics, skip straight to the disclosure prompt.
|
|
1270
|
+
// This happens when the agent re-runs quickstart after a previous partial run.
|
|
1238
1271
|
let currentStep = 'not_started';
|
|
1239
1272
|
try {
|
|
1240
1273
|
const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
@@ -1247,42 +1280,46 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1247
1280
|
if (currentStep === 'awaiting_disclosure' && !args.flags.force) {
|
|
1248
1281
|
console.log('\nStep 1 already complete. Server is running.\n');
|
|
1249
1282
|
console.log('Step 2 of 4: Configure disclosure topics\n');
|
|
1250
|
-
console.log(buildExtractionPrompt(
|
|
1283
|
+
console.log(buildExtractionPrompt());
|
|
1251
1284
|
console.log('\n Read your workspace files, extract topics, and present to your owner for review.');
|
|
1252
1285
|
console.log(" Then submit with: a2a quickstart --submit '<json>'\n");
|
|
1253
1286
|
return;
|
|
1254
1287
|
}
|
|
1255
1288
|
|
|
1256
1289
|
printStepHeader('🤝 A2A Calling — First-Time Setup');
|
|
1257
|
-
printWorkspaceScan(context);
|
|
1258
1290
|
|
|
1291
|
+
// Interactive: ask for confirmation. Non-interactive: auto-accepts (Y).
|
|
1259
1292
|
const continueSetup = await promptYesNo('Continue with setup? [Y/n] ');
|
|
1260
1293
|
if (!continueSetup) {
|
|
1261
1294
|
console.log('\nSetup cancelled.\n');
|
|
1262
1295
|
return;
|
|
1263
1296
|
}
|
|
1264
1297
|
|
|
1298
|
+
// ── Step 1: Port selection ───────────────────────────────────────────
|
|
1299
|
+
// Scan ports 80, 3001-3020 and pick the first available one.
|
|
1300
|
+
// Only show the selected port — agents don't need to see every candidate.
|
|
1301
|
+
// Interactive users can override with a custom port; non-interactive
|
|
1302
|
+
// auto-accepts the recommendation.
|
|
1265
1303
|
printSection('Port Configuration');
|
|
1266
1304
|
const preferredPort = parsePort(args.flags.port || args.flags.p, null);
|
|
1267
1305
|
const candidates = await inspectPorts(preferredPort);
|
|
1268
1306
|
const availableCandidates = candidates.filter(c => c.available);
|
|
1269
1307
|
const recommendedPort = availableCandidates.length ? availableCandidates[0].port : null;
|
|
1270
1308
|
|
|
1271
|
-
summarizePortResults(candidates).forEach(line => {
|
|
1272
|
-
console.log(` ${line}`);
|
|
1273
|
-
});
|
|
1274
|
-
|
|
1275
1309
|
if (!recommendedPort) {
|
|
1276
1310
|
console.error(' Could not find a bindable port in the scan range.');
|
|
1277
1311
|
console.error(' Re-run with --port <number> after freeing one of these ports.\n');
|
|
1312
|
+
if (interactive) {
|
|
1313
|
+
console.log(' Ports scanned:');
|
|
1314
|
+
summarizePortResults(candidates).forEach(line => console.log(` ${line}`));
|
|
1315
|
+
}
|
|
1278
1316
|
process.exit(1);
|
|
1279
1317
|
}
|
|
1280
1318
|
|
|
1281
|
-
console.log(
|
|
1319
|
+
console.log(` Selected port: ${recommendedPort}`);
|
|
1282
1320
|
let serverPort = recommendedPort;
|
|
1283
1321
|
const portChoice = await promptText(`Use port ${recommendedPort}? [Y/n/custom]: `, 'y');
|
|
1284
1322
|
if (!interactive) {
|
|
1285
|
-
// explicit default for non-interactive mode
|
|
1286
1323
|
serverPort = recommendedPort;
|
|
1287
1324
|
} else if (!['', 'y', 'Y', 'yes', 'YES', 'ye'].includes(String(portChoice).trim())) {
|
|
1288
1325
|
if (/^(n|no|custom|c)$/i.test(String(portChoice).trim())) {
|
|
@@ -1321,6 +1358,10 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1321
1358
|
}
|
|
1322
1359
|
}
|
|
1323
1360
|
|
|
1361
|
+
// ── Step 2: Hostname detection ───────────────────────────────────────
|
|
1362
|
+
// Look up the machine's external IP so invite URLs point to a routable
|
|
1363
|
+
// address. Non-interactive: auto-uses the detected IP. Interactive: lets
|
|
1364
|
+
// the user choose IP, domain, or skip.
|
|
1324
1365
|
printSection('Hostname Configuration');
|
|
1325
1366
|
const ipResult = await getExternalIp();
|
|
1326
1367
|
const externalIp = ipResult.ip || null;
|
|
@@ -1367,27 +1408,23 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1367
1408
|
console.log(` External IP lookup failed: ${ipResult.error}`);
|
|
1368
1409
|
}
|
|
1369
1410
|
|
|
1411
|
+
// ── Step 3: Server start ─────────────────────────────────────────────
|
|
1412
|
+
// Launch the A2A Express server as a detached background process, wait
|
|
1413
|
+
// for it to bind, then save the config with the server PID and port.
|
|
1414
|
+
// Non-interactive: auto-starts. Interactive: asks for confirmation.
|
|
1415
|
+
// Also prints a one-line networking hint (reverse proxy or firewall)
|
|
1416
|
+
// and a curl command the agent can use to verify external reachability.
|
|
1370
1417
|
printSection('Starting Server');
|
|
1371
1418
|
console.log(' Configuration summary:');
|
|
1372
1419
|
console.log(` Port: ${serverPort}`);
|
|
1373
1420
|
console.log(` Public host: ${publicHost}`);
|
|
1374
1421
|
|
|
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
|
-
}
|
|
1383
|
-
|
|
1384
1422
|
const startServer = await promptYesNo('Start the A2A server now? [Y/n] ');
|
|
1385
1423
|
if (!startServer) {
|
|
1386
1424
|
console.log('\nServer not started. Run with:\n a2a server --port <port> --hostname <host>\n');
|
|
1387
1425
|
return;
|
|
1388
1426
|
}
|
|
1389
1427
|
|
|
1390
|
-
// Start server
|
|
1391
1428
|
const isAlreadyListening = await isPortListening(serverPort, '127.0.0.1', { timeoutMs: 250 });
|
|
1392
1429
|
let serverPid = null;
|
|
1393
1430
|
if (!isAlreadyListening.listening) {
|
|
@@ -1427,28 +1464,32 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1427
1464
|
}
|
|
1428
1465
|
console.log(' ✅ A2A server is running');
|
|
1429
1466
|
|
|
1430
|
-
if (serverPort !== 80 && externalIp) {
|
|
1431
|
-
console.log('\n External access required because port 80 is not in use.');
|
|
1432
|
-
console.log(' Option A (recommended): Set up a reverse proxy (HTTP or HTTPS).');
|
|
1433
|
-
console.log(` Configure your web server to forward /api/a2a/* to localhost:${serverPort}.`);
|
|
1434
|
-
console.log(' If you serve HTTPS on port 443, proxy from there instead.');
|
|
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');
|
|
1438
|
-
}
|
|
1439
1467
|
if (externalIp) {
|
|
1440
1468
|
const verifyUrl = `http://${publicHost}/api/a2a/ping`;
|
|
1441
|
-
|
|
1469
|
+
if (serverPort !== 80) {
|
|
1470
|
+
console.log(`\n Note: Port ${serverPort} must be reachable externally.`);
|
|
1471
|
+
console.log(` Reverse-proxy /api/a2a/* to localhost:${serverPort}, or open the port in your firewall.`);
|
|
1472
|
+
}
|
|
1473
|
+
console.log(` Verify: curl -s ${verifyUrl}`);
|
|
1442
1474
|
}
|
|
1443
1475
|
|
|
1444
|
-
// Save server config and advance onboarding
|
|
1476
|
+
// Save server config and advance onboarding state to awaiting_disclosure.
|
|
1445
1477
|
config.setAgent({ hostname: publicHost });
|
|
1446
1478
|
config.setOnboarding({ step: 'awaiting_disclosure' });
|
|
1447
1479
|
|
|
1448
|
-
// Step
|
|
1480
|
+
// ── Step 4: Disclosure prompt ────────────────────────────────────────
|
|
1481
|
+
// Output a full agent-readable prompt that tells the agent to:
|
|
1482
|
+
// 1. Scan its own workspace for USER.md, SOUL.md, etc.
|
|
1483
|
+
// 2. Extract tiered disclosure topics from those files
|
|
1484
|
+
// 3. Build the required JSON structure
|
|
1485
|
+
// 4. Submit it back via `a2a quickstart --submit '<json>'`
|
|
1486
|
+
//
|
|
1487
|
+
// IMPORTANT: We do NOT pre-scan files here. The installer runs in a
|
|
1488
|
+
// subprocess and has no access to the agent's file-reading tools. The
|
|
1489
|
+
// prompt lists which files to look for and the agent reads them itself.
|
|
1449
1490
|
printSection('Disclosure Topic Extraction');
|
|
1450
1491
|
console.log('Step 2 of 4: Configure disclosure topics\n');
|
|
1451
|
-
console.log(buildExtractionPrompt(
|
|
1492
|
+
console.log(buildExtractionPrompt());
|
|
1452
1493
|
console.log('\n Read your workspace files, extract topics, and present to your owner for review.');
|
|
1453
1494
|
console.log(" Then submit with: a2a quickstart --submit '<json>'\n");
|
|
1454
1495
|
},
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -1,54 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// npm postinstall hook — automatic onboarding launcher
|
|
5
|
+
//
|
|
6
|
+
// OBJECTIVE: When a user (or agent) runs `npm install -g a2acalling`, this
|
|
7
|
+
// script fires automatically and launches the full quickstart onboarding flow.
|
|
8
|
+
// The agent should never need a second command — install IS onboarding.
|
|
9
|
+
//
|
|
10
|
+
// KEY DESIGN DECISIONS:
|
|
11
|
+
//
|
|
12
|
+
// 1. No TTY gate. Agents are the primary users of this package and they
|
|
13
|
+
// run in non-interactive (piped) environments. Every prompt in the
|
|
14
|
+
// quickstart flow auto-accepts sensible defaults when there is no TTY,
|
|
15
|
+
// so the full onboarding completes unattended: port selection, hostname
|
|
16
|
+
// detection, server start, and disclosure prompt output.
|
|
17
|
+
//
|
|
18
|
+
// 2. stdio: 'inherit'. The onboarding output goes straight to whatever
|
|
19
|
+
// called npm — if that's a terminal the user sees it; if it's an agent
|
|
20
|
+
// the agent reads it. No /dev/tty tricks needed.
|
|
21
|
+
//
|
|
22
|
+
// 3. Never fail the install. If quickstart can't launch (e.g. missing node
|
|
23
|
+
// binary edge case), we print a hint and exit 0. A broken postinstall
|
|
24
|
+
// would prevent the package from installing at all.
|
|
25
|
+
//
|
|
26
|
+
// 4. Skip non-global installs, CI, and Docker builds. Local `npm install`
|
|
27
|
+
// in a project shouldn't trigger onboarding. CI and Docker are build
|
|
28
|
+
// environments, not runtime hosts.
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
4
31
|
if (process.env.CI || process.env.CONTINUOUS_INTEGRATION) process.exit(0);
|
|
5
32
|
if (process.env.DOCKER) process.exit(0);
|
|
6
33
|
if (process.env.npm_config_global !== 'true') process.exit(0);
|
|
7
34
|
|
|
8
|
-
const fs = require('fs');
|
|
9
35
|
const path = require('path');
|
|
10
36
|
const { spawnSync } = require('child_process');
|
|
11
37
|
|
|
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
38
|
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
39
|
const cliPath = path.join(__dirname, '..', 'bin', 'cli.js');
|
|
47
40
|
|
|
48
|
-
// Launch quickstart
|
|
49
|
-
// even when npm suppresses postinstall output.
|
|
41
|
+
// Launch quickstart — prompts auto-accept defaults when there's no TTY.
|
|
50
42
|
const result = spawnSync(process.execPath, [cliPath, 'quickstart'], {
|
|
51
|
-
stdio:
|
|
43
|
+
stdio: 'inherit',
|
|
52
44
|
cwd: initCwd,
|
|
53
45
|
env: {
|
|
54
46
|
...process.env,
|
|
@@ -57,21 +49,10 @@ const result = spawnSync(process.execPath, [cliPath, 'quickstart'], {
|
|
|
57
49
|
});
|
|
58
50
|
|
|
59
51
|
if (result.error) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
fs.closeSync(tty.fdIn);
|
|
65
|
-
fs.closeSync(tty.fdOut);
|
|
66
|
-
} catch (err) {}
|
|
52
|
+
console.error('\nCould not auto-launch onboarding.');
|
|
53
|
+
console.error(`Reason: ${result.error.message}`);
|
|
54
|
+
console.error('\nRun manually: a2a quickstart\n');
|
|
67
55
|
process.exit(0); // don't fail the install
|
|
68
56
|
}
|
|
69
57
|
|
|
70
|
-
try {
|
|
71
|
-
fs.closeSync(tty.fdIn);
|
|
72
|
-
fs.closeSync(tty.fdOut);
|
|
73
|
-
} catch (err) {
|
|
74
|
-
// Best-effort cleanup.
|
|
75
|
-
}
|
|
76
|
-
|
|
77
58
|
process.exit(result.status || 0);
|