a2acalling 0.6.49 → 0.6.51

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
@@ -11,6 +11,7 @@
11
11
  * a2a call <url> <msg> Call a contact (or invite URL)
12
12
  * a2a ping <url> Ping an invite URL
13
13
  * a2a gui Open the local dashboard GUI in a browser
14
+ * a2a app <action> Manage native macOS app (status/install/uninstall)
14
15
  * a2a setup Auto setup (gateway-aware dashboard install)
15
16
  * a2a uninstall Stop server and remove local A2A config
16
17
  */
@@ -37,7 +38,9 @@ const ONBOARDING_EXEMPT = new Set([
37
38
  'dashboard',
38
39
  'server',
39
40
  'setup',
40
- 'install'
41
+ 'app',
42
+ 'install',
43
+ 'skills'
41
44
  ]);
42
45
 
43
46
  function isOnboarded() {
@@ -167,6 +170,100 @@ function findNativeApp() {
167
170
  return null;
168
171
  }
169
172
 
173
+ function getNativeAppPaths() {
174
+ return {
175
+ appDir: path.join(os.homedir(), 'Applications'),
176
+ appPath: path.join(os.homedir(), 'Applications', 'A2A Callbook.app')
177
+ };
178
+ }
179
+
180
+ function parseInstalledNativeAppVersion(appPath) {
181
+ if (!appPath) return null;
182
+ const plistPath = path.join(appPath, 'Contents', 'Info.plist');
183
+ if (!fs.existsSync(plistPath)) return null;
184
+ try {
185
+ const plist = fs.readFileSync(plistPath, 'utf8');
186
+ const m = plist.match(/<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/);
187
+ return m && m[1] ? m[1].trim() : null;
188
+ } catch (_) {
189
+ return null;
190
+ }
191
+ }
192
+
193
+ function installNativeMacApp(options = {}) {
194
+ if (os.platform() !== 'darwin') {
195
+ return { success: false, skipped: 'not_macos' };
196
+ }
197
+ const quiet = Boolean(options.quiet);
198
+ const force = Boolean(options.force);
199
+ const version = require('../package.json').version;
200
+ const { appDir, appPath } = getNativeAppPaths();
201
+ const installedVersion = parseInstalledNativeAppVersion(appPath);
202
+ if (!force && installedVersion === version) {
203
+ return { success: true, installed: false, version, appPath, reason: 'already_current' };
204
+ }
205
+
206
+ const tarUrl = `https://github.com/onthegonow/a2a_calling/releases/download/v${version}/A2A-Callbook-${version}.app.tar.gz`;
207
+ const tmpFile = path.join(os.tmpdir(), `a2a-callbook-${version}.app.tar.gz`);
208
+ const { execFileSync } = require('child_process');
209
+
210
+ try {
211
+ fs.mkdirSync(appDir, { recursive: true });
212
+ execFileSync('curl', ['-fL', '-o', tmpFile, tarUrl], { timeout: 120000, stdio: quiet ? 'ignore' : 'inherit' });
213
+ if (!fs.existsSync(tmpFile) || fs.statSync(tmpFile).size < 1000) {
214
+ return { success: false, error: 'download_failed' };
215
+ }
216
+ if (fs.existsSync(appPath)) {
217
+ fs.rmSync(appPath, { recursive: true, force: true });
218
+ }
219
+ execFileSync('tar', ['-xzf', tmpFile, '-C', appDir], { timeout: 60000, stdio: quiet ? 'ignore' : 'inherit' });
220
+ try { fs.unlinkSync(tmpFile); } catch (_) {}
221
+ return { success: true, installed: true, version, appPath };
222
+ } catch (err) {
223
+ try { fs.unlinkSync(tmpFile); } catch (_) {}
224
+ return { success: false, error: err.message || 'install_failed' };
225
+ }
226
+ }
227
+
228
+ function uninstallNativeMacApp() {
229
+ if (os.platform() !== 'darwin') {
230
+ return { success: false, skipped: 'not_macos' };
231
+ }
232
+ const candidates = [
233
+ path.join(os.homedir(), 'Applications', 'A2A Callbook.app'),
234
+ '/Applications/A2A Callbook.app'
235
+ ];
236
+ const existing = candidates.filter((candidate) => {
237
+ try {
238
+ return fs.existsSync(candidate);
239
+ } catch (_) {
240
+ return false;
241
+ }
242
+ });
243
+ if (existing.length === 0) {
244
+ return { success: true, removed: false, appPath: candidates[0] };
245
+ }
246
+
247
+ const removed = [];
248
+ const failed = [];
249
+ for (const appPath of existing) {
250
+ try {
251
+ fs.rmSync(appPath, { recursive: true, force: true });
252
+ removed.push(appPath);
253
+ } catch (err) {
254
+ failed.push({ appPath, error: err && err.message ? err.message : 'uninstall_failed' });
255
+ }
256
+ }
257
+ if (failed.length > 0) {
258
+ return {
259
+ success: false,
260
+ error: failed.map((f) => `${f.appPath}: ${f.error}`).join('; '),
261
+ appPath: removed[0] || existing[0]
262
+ };
263
+ }
264
+ return { success: true, removed: true, appPath: removed[0] || existing[0] };
265
+ }
266
+
170
267
  async function findLocalServerPort(preferredPorts = []) {
171
268
  const http = require('http');
172
269
 
@@ -710,6 +807,8 @@ const commands = {
710
807
 
711
808
  // Get objectives from disclosure
712
809
  const objectives = tierTopics.objectives || [];
810
+ const timeoutMsRaw = args.flags['timeout-ms'] || args.flags.timeout_ms;
811
+ const timeoutMs = timeoutMsRaw ? Number.parseInt(String(timeoutMsRaw), 10) : null;
713
812
 
714
813
  const { token, record } = store.create({
715
814
  name: args.flags.name || args.flags.n || 'unnamed',
@@ -720,7 +819,8 @@ const commands = {
720
819
  notify: args.flags.notify || 'all',
721
820
  maxCalls,
722
821
  allowedTopics,
723
- allowedGoals: objectives.map(o => o.objective || o)
822
+ allowedGoals: objectives.map(o => o.objective || o),
823
+ timeoutMs
724
824
  });
725
825
 
726
826
  const resolvedHost = await resolveInviteHostname();
@@ -759,6 +859,7 @@ const commands = {
759
859
  console.log(`Disclosure: ${record.disclosure}`);
760
860
  console.log(`Notify: ${record.notify}`);
761
861
  console.log(`Max calls: ${record.max_calls || 'unlimited'}`);
862
+ if (record.timeout_ms) console.log(`Turn timeout: ${record.timeout_ms}ms`);
762
863
  if (linkContact) console.log(`Linked to: ${linkContact}`);
763
864
  console.log(`\nTo revoke: a2a revoke ${record.id}`);
764
865
  console.log(`\n${'─'.repeat(50)}`);
@@ -1355,11 +1456,15 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
1355
1456
 
1356
1457
  // Build owner context from config for summarizer
1357
1458
  let ownerContext = {};
1459
+ let configTurnTimeoutMs = null;
1358
1460
  try {
1359
1461
  const { A2AConfig } = require('../src/lib/config');
1360
1462
  const config = new A2AConfig();
1361
1463
  const configAll = config.getAll();
1362
1464
  const tierGoals = configAll.tiers?.public?.goals || [];
1465
+ configTurnTimeoutMs = configAll.defaults?.turnTimeoutMs
1466
+ || configAll.defaults?.turn_timeout_ms
1467
+ || null;
1363
1468
  ownerContext = {
1364
1469
  goals: tierGoals,
1365
1470
  agentName: agentContext.name,
@@ -1378,6 +1483,7 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
1378
1483
  disclosure,
1379
1484
  minTurns,
1380
1485
  maxTurns,
1486
+ configTurnTimeoutMs,
1381
1487
  ownerContext,
1382
1488
  onTurn: (info) => {
1383
1489
  const preview = info.messagePreview.length >= 80
@@ -2149,6 +2255,74 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
2149
2255
  require('../scripts/install-openclaw.js');
2150
2256
  },
2151
2257
 
2258
+ app: (args) => {
2259
+ const action = String(args._[1] || 'status').trim().toLowerCase();
2260
+ const force = Boolean(args.flags.force || args.flags.f);
2261
+ const quiet = Boolean(args.flags.quiet || args.flags.q);
2262
+ const pkgVersion = require('../package.json').version;
2263
+
2264
+ if (action === 'status') {
2265
+ const installedAppPath = findNativeApp();
2266
+ const preferredPath = getNativeAppPaths().appPath;
2267
+ const version = installedAppPath ? parseInstalledNativeAppVersion(installedAppPath) : null;
2268
+ console.log('A2A Native App Status\n');
2269
+ console.log(` Platform: ${os.platform()}`);
2270
+ console.log(` CLI version: ${pkgVersion}`);
2271
+ if (os.platform() !== 'darwin') {
2272
+ console.log(' Native app: Not supported on this platform');
2273
+ return;
2274
+ }
2275
+ console.log(` Installed: ${installedAppPath ? 'yes' : 'no'}`);
2276
+ console.log(` App path: ${installedAppPath || preferredPath}`);
2277
+ console.log(` App version: ${version || '(unknown)'}`);
2278
+ if (!installedAppPath) {
2279
+ console.log('\nInstall with: a2a app install');
2280
+ }
2281
+ return;
2282
+ }
2283
+
2284
+ if (action === 'install') {
2285
+ const result = installNativeMacApp({ force, quiet });
2286
+ if (result.skipped === 'not_macos') {
2287
+ console.error('Native app install is only available on macOS.');
2288
+ process.exit(1);
2289
+ }
2290
+ if (!result.success) {
2291
+ console.error(`Native app install failed: ${result.error || 'unknown error'}`);
2292
+ process.exit(1);
2293
+ }
2294
+ if (result.reason === 'already_current') {
2295
+ console.log(`Native app already installed at current version (${result.version}).`);
2296
+ console.log(`Path: ${result.appPath}`);
2297
+ return;
2298
+ }
2299
+ console.log(`Native app installed (v${result.version}).`);
2300
+ console.log(`Path: ${result.appPath}`);
2301
+ return;
2302
+ }
2303
+
2304
+ if (action === 'uninstall') {
2305
+ const result = uninstallNativeMacApp();
2306
+ if (result.skipped === 'not_macos') {
2307
+ console.error('Native app uninstall is only available on macOS.');
2308
+ process.exit(1);
2309
+ }
2310
+ if (!result.success) {
2311
+ console.error(`Native app uninstall failed: ${result.error || 'unknown error'}`);
2312
+ process.exit(1);
2313
+ }
2314
+ if (!result.removed) {
2315
+ console.log('Native app is not installed.');
2316
+ return;
2317
+ }
2318
+ console.log(`Native app removed: ${result.appPath}`);
2319
+ return;
2320
+ }
2321
+
2322
+ console.error('Usage: a2a app <status|install|uninstall> [--force] [--quiet]');
2323
+ process.exit(1);
2324
+ },
2325
+
2152
2326
  uninstall: async (args) => {
2153
2327
  const fs = require('fs');
2154
2328
  const path = require('path');
@@ -2625,6 +2799,46 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
2625
2799
  return commands.quickstart(args);
2626
2800
  },
2627
2801
 
2802
+ skills: (args) => {
2803
+ const { installSkills, SKILL_FILES } = require('../scripts/install-skills');
2804
+ const check = args.flags.check || args.flags.c;
2805
+ const force = args.flags.force;
2806
+ const targetDir = process.cwd();
2807
+
2808
+ if (check) {
2809
+ console.log('A2A skills for this project:\n');
2810
+ for (const file of SKILL_FILES) {
2811
+ const destPath = path.join(targetDir, file.dest);
2812
+ const exists = fs.existsSync(destPath);
2813
+ const icon = exists ? ' \u2713' : ' \u2717';
2814
+ console.log(`${icon} ${file.dest}${exists ? ' (installed)' : ' (not installed)'}`);
2815
+ }
2816
+ console.log(`\nRun "a2a skills" to install missing files.`);
2817
+ return;
2818
+ }
2819
+
2820
+ const result = installSkills(targetDir, { force });
2821
+
2822
+ if (result.installed.length) {
2823
+ console.log(`\n Installed ${result.installed.length} A2A skill file(s):\n`);
2824
+ result.installed.forEach(f => console.log(` + ${f}`));
2825
+ }
2826
+ if (result.skipped.length) {
2827
+ console.log(`\n Skipped ${result.skipped.length} unchanged file(s)`);
2828
+ }
2829
+ if (result.errors.length) {
2830
+ console.error(`\n Errors:`);
2831
+ result.errors.forEach(e => console.error(` ! ${e.file}: ${e.error}`));
2832
+ }
2833
+
2834
+ if (result.installed.length === 0 && result.skipped.length > 0) {
2835
+ console.log('\n All skills already installed. Use --force to overwrite.\n');
2836
+ } else if (result.installed.length > 0) {
2837
+ console.log('\n Skills ready. In Claude Code, type /a2a- to see available commands.');
2838
+ console.log(' In Codex CLI, A2A instructions are in .codex/AGENTS.md\n');
2839
+ }
2840
+ },
2841
+
2628
2842
  version: () => {
2629
2843
  const pkg = require('../package.json');
2630
2844
  console.log(pkg.version);
@@ -2645,6 +2859,7 @@ Commands:
2645
2859
  --disclosure, -d Disclosure level (public, minimal, none)
2646
2860
  --notify Owner notification (all, summary, none)
2647
2861
  --max-calls Maximum invocations (default: 100)
2862
+ --timeout-ms Per-token Claude turn timeout in milliseconds
2648
2863
  --link, -l Auto-link to contact name
2649
2864
 
2650
2865
  list List active tokens
@@ -2686,6 +2901,12 @@ Calling:
2686
2901
  status <url> Get A2A status
2687
2902
  gui Open the local dashboard GUI in a browser
2688
2903
  --tab, -t Optional: contacts|calls|logs|settings|invites
2904
+ app Manage native macOS app
2905
+ status Show native app installation status (default)
2906
+ install Install/update native app from GitHub release
2907
+ --force, -f Reinstall even when current version is present
2908
+ --quiet, -q Suppress download/extract output
2909
+ uninstall Remove native app from ~/Applications
2689
2910
 
2690
2911
  Server:
2691
2912
  server Start the A2A server
@@ -2710,6 +2931,9 @@ Server:
2710
2931
  uninstall Stop server and remove local config/DB
2711
2932
  --keep-config Preserve config/DB (for reinstall)
2712
2933
  --force Skip confirmation prompt
2934
+ skills Install Claude Code + Codex CLI skills
2935
+ --check, -c Show what would be installed
2936
+ --force Overwrite existing files
2713
2937
  version Show installed package version
2714
2938
 
2715
2939
  Examples:
@@ -2719,6 +2943,8 @@ Examples:
2719
2943
  a2a contacts link Alice tok_abc123
2720
2944
  a2a call Alice "Hello!"
2721
2945
  a2a conversations show conv_abc123
2946
+ a2a app status
2947
+ a2a app install --force
2722
2948
  a2a server --port 3001
2723
2949
  `);
2724
2950
  }
package/docs/protocol.md CHANGED
@@ -388,6 +388,48 @@ module.exports = function (test, assert, helpers) {
388
388
  };
389
389
  ```
390
390
 
391
+ ## CLI Skills (Claude Code & Codex)
392
+
393
+ A2A ships with slash commands for Claude Code and agent instructions for Codex CLI.
394
+
395
+ ### Installation
396
+
397
+ ```bash
398
+ a2a skills # Install into current project
399
+ a2a skills --check # See what would be installed
400
+ a2a skills --force # Overwrite existing files
401
+ ```
402
+
403
+ Skills are also installed automatically on `npm install -g a2acalling`.
404
+
405
+ ### Claude Code Commands
406
+
407
+ | Command | Description |
408
+ |---------|-------------|
409
+ | `/a2a-call <contact> <msg>` | Call another agent (multi-turn) |
410
+ | `/a2a-invite [name] [--tier]` | Create invite token |
411
+ | `/a2a-contacts [add\|show\|ping\|rm]` | Manage contacts |
412
+ | `/a2a-status` | Server and agent health dashboard |
413
+ | `/a2a-setup` | First-time setup and onboarding |
414
+
415
+ Files installed to: `.claude/commands/a2a-*.md`
416
+
417
+ ### Codex CLI
418
+
419
+ A2A agent instructions are installed to `.codex/AGENTS.md`. Codex reads this file automatically to understand available A2A commands, permission tiers, and workflows.
420
+
421
+ ### Manual Installation
422
+
423
+ If the automatic install didn't work, copy the files manually:
424
+
425
+ ```bash
426
+ # Claude Code commands
427
+ cp node_modules/a2acalling/.claude/commands/a2a-*.md .claude/commands/
428
+
429
+ # Codex instructions
430
+ cp node_modules/a2acalling/.codex/AGENTS.md .codex/AGENTS.md
431
+ ```
432
+
391
433
  ## Future Protocol Extensions (v1+)
392
434
 
393
435
  - **Capability advertisement**: Agents declare what they can help with
@@ -71,7 +71,7 @@
71
71
  Server not running
72
72
  </p>
73
73
  <p class="port-info" id="last-port">No a2a server found on common ports</p>
74
- <button id="btn-start">Start Server</button>
74
+ <button id="btn-start">How to start server</button>
75
75
  <button id="btn-retry" class="secondary">Retry</button>
76
76
  <p id="error-detail"></p>
77
77
  </div>
@@ -120,15 +120,10 @@
120
120
  }
121
121
 
122
122
  document.getElementById('btn-start')?.addEventListener('click', async () => {
123
- try {
124
- await invoke('start_server');
125
- // Wait for server to boot, then retry
126
- setTimeout(checkServer, 2000);
127
- } catch (err) {
128
- const detail = document.getElementById('error-detail');
129
- detail.textContent = `Failed to start: ${err}`;
130
- detail.style.display = 'block';
131
- }
123
+ const detail = document.getElementById('error-detail');
124
+ // v1 design decision: app links/status only; no app-owned server lifecycle management yet.
125
+ detail.textContent = 'Run `a2a server` (or `a2a quickstart`) in Terminal, then click Retry.';
126
+ detail.style.display = 'block';
132
127
  });
133
128
 
134
129
  document.getElementById('btn-retry')?.addEventListener('click', checkServer);