a2acalling 0.6.50 → 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,6 +38,7 @@ const ONBOARDING_EXEMPT = new Set([
37
38
  'dashboard',
38
39
  'server',
39
40
  'setup',
41
+ 'app',
40
42
  'install',
41
43
  'skills'
42
44
  ]);
@@ -168,6 +170,100 @@ function findNativeApp() {
168
170
  return null;
169
171
  }
170
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
+
171
267
  async function findLocalServerPort(preferredPorts = []) {
172
268
  const http = require('http');
173
269
 
@@ -2159,6 +2255,74 @@ a2a add "${inviteUrl}" "${ownerText || 'friend'}" && a2a call "${ownerText || 'f
2159
2255
  require('../scripts/install-openclaw.js');
2160
2256
  },
2161
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
+
2162
2326
  uninstall: async (args) => {
2163
2327
  const fs = require('fs');
2164
2328
  const path = require('path');
@@ -2737,6 +2901,12 @@ Calling:
2737
2901
  status <url> Get A2A status
2738
2902
  gui Open the local dashboard GUI in a browser
2739
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
2740
2910
 
2741
2911
  Server:
2742
2912
  server Start the A2A server
@@ -2773,6 +2943,8 @@ Examples:
2773
2943
  a2a contacts link Alice tok_abc123
2774
2944
  a2a call Alice "Hello!"
2775
2945
  a2a conversations show conv_abc123
2946
+ a2a app status
2947
+ a2a app install --force
2776
2948
  a2a server --port 3001
2777
2949
  `);
2778
2950
  }
@@ -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);