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 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 { tryBindPort, findAvailablePort, isPortListening } = require('../src/lib/port-scanner');
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
- function parsePort(raw, fallback) {
1107
- const parsed = Number.parseInt(String(raw || '').trim(), 10);
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
- // ── Step 1 of 4: Setting up A2A server ──────────────────
1115
- console.log('\nStep 1 of 4: Setting up A2A server\n');
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
- // If user specified a port, try that first
1120
- let serverPort;
1121
- let usingAlternatePort = false;
1122
-
1123
- if (preferredPort) {
1124
- console.log(` 1a. Checking preferred port ${preferredPort}...`);
1125
- const preferredResult = await tryBindPort(preferredPort);
1126
- if (preferredResult.ok) {
1127
- console.log(` Port ${preferredPort} available.`);
1128
- serverPort = preferredPort;
1129
- usingAlternatePort = preferredPort !== 80;
1130
- } else if (preferredResult.code === 'EACCES') {
1131
- console.log(` Port ${preferredPort} requires elevated privileges.`);
1132
- console.log(' Rerun with: sudo npm install -g a2acalling\n');
1133
- process.exit(1);
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
- console.log(` Port ${preferredPort} is in use. Scanning for alternatives...`);
1136
- const candidates = [];
1137
- for (let p = 3001; p < 3101; p++) candidates.push(p);
1138
- serverPort = await findAvailablePort(candidates);
1139
- if (!serverPort) {
1140
- console.log(' Could not find a bindable port. Rerun with elevated privileges:');
1141
- console.log(' sudo npm install -g a2acalling');
1142
- process.exit(1);
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
- } else {
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
- const candidates = [];
1166
- for (let p = 3001; p < 3101; p++) candidates.push(p);
1167
- serverPort = await findAvailablePort(candidates);
1321
+ printSection('Hostname Configuration');
1322
+ const ipResult = await getExternalIp();
1323
+ const externalIp = ipResult.ip || null;
1324
+ let publicHost = `localhost:${serverPort}`;
1168
1325
 
1169
- if (!serverPort) {
1170
- console.log(' Could not find a bindable port. Rerun with elevated privileges:');
1171
- console.log(' sudo npm install -g a2acalling');
1172
- process.exit(1);
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
- console.log(` Port ${serverPort} available.`);
1175
- usingAlternatePort = true;
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
- // Start server
1180
- console.log(` Starting A2A server on port ${serverPort}...`);
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
- async function startServer(port) {
1183
- const listening = await isPortListening(port, '127.0.0.1', { timeoutMs: 250 });
1184
- if (listening.listening) return { started: false, existing: true };
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(port) },
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
- await new Promise(r => setTimeout(r, 300));
1193
- return { started: true, pid: child.pid };
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
- // Store server PID for cleanup
1218
- if (serverResult.pid) {
1219
- config.setOnboarding({ server_pid: serverResult.pid, server_port: serverPort });
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
- // Detect external IP
1223
- const ipResult = await getExternalIp();
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.\n');
1245
- console.log(` Option B: Open port ${serverPort} in your firewall.`);
1246
- console.log(' This requires the owner to manually allow inbound traffic on');
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(' Verify externally:');
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
- // ── Step 2 of 4: Configure disclosure topics ────────────
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2acalling",
3
- "version": "0.6.9",
3
+ "version": "0.6.10",
4
4
  "description": "Agent-to-agent calling for OpenClaw - A2A agent communication",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -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.