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 CHANGED
@@ -299,17 +299,22 @@ function readWorkspaceContext(baseDir = process.cwd()) {
299
299
  }
300
300
 
301
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)`);
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(' ⚠️ No memory/*.md files');
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
- const context = readWorkspaceContext(process.env.A2A_WORKSPACE || process.cwd());
1235
- const availableFiles = getDisclosurePromptFiles(context);
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(availableFiles));
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(`\n Recommended: ${recommendedPort}`);
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
- console.log(' External ping check:\n curl -s ' + verifyUrl);
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 2 of 4: Configure disclosure topics
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(availableFiles));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2acalling",
3
- "version": "0.6.11",
3
+ "version": "0.6.12",
4
4
  "description": "Agent-to-agent calling for OpenClaw - A2A agent communication",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,54 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // Only run for global installs; skip in CI, dev, and Docker builds.
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 attached to /dev/tty so prompts and output are visible
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: [tty.fdIn, tty.fdOut, tty.fdOut],
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
- 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) {}
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);