@mobileaidev/ai-app-bridge 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -4,9 +4,11 @@
4
4
  npm install -g @mobileaidev/ai-app-bridge
5
5
 
6
6
  ai-app-bridge status --package-name io.github.mobileaidev.aiappbridge.sample
7
- ai-app-bridge tree --package-name io.github.mobileaidev.aiappbridge.sample
8
- ai-app-bridge install-apk --package-name io.github.mobileaidev.aiappbridge.sample --apk-path app-debug.apk
9
- ai-app-bridge screenshot --package-name io.github.mobileaidev.aiappbridge.sample
7
+ ai-app-bridge tree --package-name io.github.mobileaidev.aiappbridge.sample
8
+ ai-app-bridge install-apk --package-name io.github.mobileaidev.aiappbridge.sample --apk-path app-debug.apk
9
+ ai-app-bridge launch-app --package-name io.github.mobileaidev.aiappbridge.sample
10
+ ai-app-bridge launch-activity --package-name io.github.mobileaidev.aiappbridge.sample --activity .MainActivity --extra route=/home
11
+ ai-app-bridge screenshot --package-name io.github.mobileaidev.aiappbridge.sample
10
12
  ai-app-bridge input-text --package-name io.github.mobileaidev.aiappbridge.sample --text "中文输入" --hide-keyboard
11
13
  ai-app-bridge network --package-name io.github.mobileaidev.aiappbridge.sample --compact --url-filter /api/
12
14
  ai-app-bridge webview-network --package-name io.github.mobileaidev.aiappbridge.sample --duration-ms 3000
@@ -25,5 +27,11 @@ When `screenshot` or `smoke` runs without `--out-file`, the CLI writes a unique
25
27
  PNG under `build/ai_app_bridge_artifacts` instead of reusing a stable filename
26
28
  or creating files in the project root.
27
29
  It keeps the newest 20 generated screenshots for each command prefix. Use
28
- `--artifact-dir` to choose that directory, or `--out-file` when a fixed path is
29
- intentional.
30
+ `--artifact-dir` to choose that directory, or `--out-file` when a fixed path is
31
+ intentional.
32
+
33
+ `launch-app` queries Android LAUNCHER activities before starting the app. If a
34
+ debug dependency exposes multiple launcher entries, it returns
35
+ `launcher_ambiguous` with the candidates instead of guessing. Use
36
+ `launch-activity` or `launch-app --activity/--component` to choose the intended
37
+ entry point explicitly.
@@ -68,10 +68,12 @@ Device/action commands:
68
68
  keyboard-state Read Android soft keyboard visibility from dumpsys.
69
69
  hide-keyboard Hide the Android soft keyboard when it is visible.
70
70
 
71
- App/permission commands:
72
- install-apk Install an APK and assist device-side installer screens.
73
- launch-native-test Launch the debug native Android bridge test Activity.
74
- launch-flutter Launch the Flutter Activity.
71
+ App/permission commands:
72
+ install-apk Install an APK and assist device-side installer screens.
73
+ launch-app Launch the target package LAUNCHER Activity.
74
+ launch-activity Launch an explicit Android Activity component.
75
+ launch-native-test Launch the debug native Android bridge test Activity.
76
+ launch-flutter Launch the Flutter Activity.
75
77
  permission-state Read Android runtime permission state.
76
78
  permission-grant Grant an Android runtime permission.
77
79
  permission-revoke Revoke an Android runtime permission.
@@ -92,7 +94,13 @@ Options:
92
94
  --adb-timeout-ms <ms> Timeout for ADB subprocesses.
93
95
  --out-file <path> Screenshot output path.
94
96
  --artifact-dir <path> Directory for generated screenshot/artifact defaults.
95
- --apk-path <path> APK path used by install-apk.
97
+ --apk-path <path> APK path used by install-apk.
98
+ --activity <name> Activity class for launch-activity or launch-app override.
99
+ --component <pkg/act> Explicit Android component for launch-activity.
100
+ --action <name> Intent action for launch-activity.
101
+ --category <name> Intent category for launch-activity; may be repeated.
102
+ --data <uri> Intent data URI for launch-activity.
103
+ --extra <key=value> String intent extra for launch-activity; may be repeated.
96
104
  --text <text> Text used by Unicode-safe bridge input commands.
97
105
  --value <text> Text value used by h5-input or flutter-h5-input.
98
106
  --selector <css> CSS selector used by H5 commands.
@@ -319,11 +327,15 @@ async function runCommand(command, options, ctx) {
319
327
  return appopsSet(ctx, requiredString(options.op, 'op'), requiredString(options.mode, 'mode'));
320
328
  case 'tap-uia-text':
321
329
  return tapUiaText(ctx, requiredString(options.targetText, 'targetText'), options);
322
- case 'permission-dialog':
323
- return permissionDialog(ctx, options);
324
- case 'launch-native-test':
325
- return launchNativeTest(ctx);
326
- case 'launch-flutter':
330
+ case 'permission-dialog':
331
+ return permissionDialog(ctx, options);
332
+ case 'launch-app':
333
+ return launchApp(ctx, options);
334
+ case 'launch-activity':
335
+ return launchActivity(ctx, options);
336
+ case 'launch-native-test':
337
+ return launchNativeTest(ctx);
338
+ case 'launch-flutter':
327
339
  return launchFlutter(ctx, options.initialRoute || '');
328
340
  case 'smoke':
329
341
  return smoke(ctx, options);
@@ -347,16 +359,25 @@ function parseArgs(argv) {
347
359
  const rawName = arg.slice(2);
348
360
  const name = rawName.replace(/-([a-z])/g, (_, value) => value.toUpperCase());
349
361
  const next = argv[index + 1];
350
- if (next === undefined || next.startsWith('--')) {
351
- options[name] = true;
352
- continue;
353
- }
354
- options[name] = next;
355
- index += 1;
356
- }
357
- return { command, options };
358
- }
359
-
362
+ if (next === undefined || next.startsWith('--')) {
363
+ appendOption(options, name, true);
364
+ continue;
365
+ }
366
+ appendOption(options, name, next);
367
+ index += 1;
368
+ }
369
+ return { command, options };
370
+ }
371
+
372
+ function appendOption(options, name, value) {
373
+ if (name === 'extra' || name === 'category') {
374
+ if (!Array.isArray(options[name])) options[name] = [];
375
+ options[name].push(value);
376
+ return;
377
+ }
378
+ options[name] = value;
379
+ }
380
+
360
381
  async function adb(ctx, args, { binary = false } = {}) {
361
382
  const allArgs = adbArgs(ctx, args);
362
383
  return new Promise((resolve, reject) => {
@@ -3664,13 +3685,17 @@ async function resolveLogcatPid(ctx, options) {
3664
3685
  return '';
3665
3686
  }
3666
3687
 
3667
- function filterLogcat(text, options) {
3668
- const tags = splitCsv(options.tag || options.tags);
3669
- const grep = options.grep ? String(options.grep) : '';
3670
- const grepCaseSensitive = Boolean(options.grepCaseSensitive);
3671
- const minLevel = priorityValue(options.level || options.minLevel || '');
3672
- const pid = options.pid ? String(options.pid) : '';
3673
- const lines = String(text || '').split(/\r?\n/);
3688
+ function filterLogcat(text, options) {
3689
+ const tags = splitCsv(options.tag || options.tags);
3690
+ const grep = options.grep ? String(options.grep) : '';
3691
+ const grepCaseSensitive = Boolean(options.grepCaseSensitive);
3692
+ const minLevel = priorityValue(options.level || options.minLevel || '');
3693
+ const pid = options.pid ? String(options.pid) : '';
3694
+ const requiresAppPid = options.appPid || options.packagePid || options.pid === 'current';
3695
+ if (requiresAppPid && !pid) {
3696
+ return '';
3697
+ }
3698
+ const lines = String(text || '').split(/\r?\n/);
3674
3699
  const filtered = [];
3675
3700
  let previousIncluded = false;
3676
3701
  for (const line of lines) {
@@ -3753,19 +3778,134 @@ function adbFollow(ctx, args, durationMs) {
3753
3778
  });
3754
3779
  }
3755
3780
 
3756
- async function launchNativeTest(ctx) {
3757
- const component = `${ctx.packageName}/${ctx.nativeActivity}`;
3758
- await adb(ctx, ['shell', 'am', 'start', '-n', component]);
3759
- return { ok: true, transport: 'adb', component };
3760
- }
3761
-
3762
- async function launchFlutter(ctx, initialRoute) {
3763
- const component = `${ctx.packageName}/${ctx.flutterActivity}`;
3764
- const args = ['shell', 'am', 'start', '-n', component];
3765
- if (initialRoute) args.push('-e', 'ai_app_initial_route', initialRoute);
3766
- await adb(ctx, args);
3767
- return { ok: true, transport: 'adb', component, initialRoute };
3768
- }
3781
+ async function launchApp(ctx, options = {}) {
3782
+ if (options.component || options.activity) {
3783
+ return launchActivity(ctx, options);
3784
+ }
3785
+
3786
+ const candidates = await launcherActivityCandidates(ctx);
3787
+ if (candidates.length === 0) {
3788
+ return {
3789
+ ok: false,
3790
+ error: 'launcher_not_found',
3791
+ packageName: ctx.packageName,
3792
+ launcherCandidates: candidates,
3793
+ };
3794
+ }
3795
+ if (candidates.length > 1) {
3796
+ return {
3797
+ ok: false,
3798
+ error: 'launcher_ambiguous',
3799
+ packageName: ctx.packageName,
3800
+ launcherCandidates: candidates,
3801
+ suggestion: 'Pass --component or --activity to choose the intended launcher Activity.',
3802
+ };
3803
+ }
3804
+
3805
+ return startActivity(ctx, candidates[0], options, {
3806
+ packageName: ctx.packageName,
3807
+ launcherCandidates: candidates,
3808
+ });
3809
+ }
3810
+
3811
+ async function launchActivity(ctx, options = {}) {
3812
+ const component = normalizeActivityComponent(
3813
+ ctx.packageName,
3814
+ options.component || requiredString(options.activity, 'activity'),
3815
+ );
3816
+ return startActivity(ctx, component, options, { packageName: ctx.packageName });
3817
+ }
3818
+
3819
+ async function launchNativeTest(ctx) {
3820
+ const component = normalizeActivityComponent(ctx.packageName, ctx.nativeActivity);
3821
+ return startActivity(ctx, component, {}, { packageName: ctx.packageName });
3822
+ }
3823
+
3824
+ async function launchFlutter(ctx, initialRoute) {
3825
+ const component = normalizeActivityComponent(ctx.packageName, ctx.flutterActivity);
3826
+ const options = initialRoute ? { extra: [`ai_app_initial_route=${initialRoute}`] } : {};
3827
+ const result = await startActivity(ctx, component, options, { packageName: ctx.packageName });
3828
+ return { ...result, initialRoute };
3829
+ }
3830
+
3831
+ async function launcherActivityCandidates(ctx) {
3832
+ const result = await adb(ctx, [
3833
+ 'shell',
3834
+ 'cmd',
3835
+ 'package',
3836
+ 'query-activities',
3837
+ '--brief',
3838
+ '-a',
3839
+ 'android.intent.action.MAIN',
3840
+ '-c',
3841
+ 'android.intent.category.LAUNCHER',
3842
+ ctx.packageName,
3843
+ ]);
3844
+ return parseLauncherActivityCandidates(result.stdout);
3845
+ }
3846
+
3847
+ function parseLauncherActivityCandidates(stdout) {
3848
+ const candidates = [];
3849
+ for (const line of String(stdout || '').split(/\r?\n/)) {
3850
+ const candidate = line.trim();
3851
+ if (/^[A-Za-z0-9_.$]+\/[A-Za-z0-9_.$]+$/.test(candidate) && !candidates.includes(candidate)) {
3852
+ candidates.push(candidate);
3853
+ }
3854
+ }
3855
+ return candidates;
3856
+ }
3857
+
3858
+ function normalizeActivityComponent(packageName, activityOrComponent) {
3859
+ const value = requiredString(activityOrComponent, 'activity');
3860
+ if (value.includes('/')) return value;
3861
+ return `${packageName}/${value}`;
3862
+ }
3863
+
3864
+ async function startActivity(ctx, component, options = {}, extraResult = {}) {
3865
+ const args = buildAmStartArgs(component, options);
3866
+ const result = await adb(ctx, args);
3867
+ return {
3868
+ ok: true,
3869
+ transport: 'adb',
3870
+ component,
3871
+ ...extraResult,
3872
+ stdout: result.stdout.trim(),
3873
+ stderr: result.stderr.trim(),
3874
+ };
3875
+ }
3876
+
3877
+ function buildAmStartArgs(component, options = {}) {
3878
+ const args = ['shell', 'am', 'start'];
3879
+ if (options.action) args.push('-a', options.action);
3880
+ for (const category of optionList(options.category)) {
3881
+ args.push('-c', category);
3882
+ }
3883
+ if (options.data) args.push('-d', options.data);
3884
+ for (const extra of parseStartExtras(options.extra)) {
3885
+ args.push('-e', extra.key, extra.value);
3886
+ }
3887
+ args.push('-n', component);
3888
+ return args;
3889
+ }
3890
+
3891
+ function parseStartExtras(rawExtras) {
3892
+ return optionList(rawExtras).map((rawExtra) => {
3893
+ const value = String(rawExtra);
3894
+ const separator = value.indexOf('=');
3895
+ if (separator <= 0) {
3896
+ throw new Error('extra must use key=value');
3897
+ }
3898
+ return {
3899
+ key: value.slice(0, separator),
3900
+ value: value.slice(separator + 1),
3901
+ };
3902
+ });
3903
+ }
3904
+
3905
+ function optionList(value) {
3906
+ if (value === undefined || value === null || value === false || value === '') return [];
3907
+ return Array.isArray(value) ? value : [value];
3908
+ }
3769
3909
 
3770
3910
  async function smoke(ctx, options) {
3771
3911
  const summary = {
@@ -4131,9 +4271,10 @@ module.exports = {
4131
4271
  compactUiaTree,
4132
4272
  defaultArtifactDirectory,
4133
4273
  defaultArtifactPath,
4134
- findFlutterNode,
4135
- findTappableNodeByText,
4136
- firstErrorLine,
4274
+ findFlutterNode,
4275
+ findTappableNodeByText,
4276
+ filterLogcat,
4277
+ firstErrorLine,
4137
4278
  flutterNodePoint,
4138
4279
  flutterPhysicalViewport,
4139
4280
  helpText,
@@ -4141,10 +4282,14 @@ module.exports = {
4141
4282
  isAdbInputTextSafe,
4142
4283
  isLikelyInstallerSurface,
4143
4284
  nodeTapState,
4144
- normalizeBridgeError,
4145
- parseWebViewDevToolsSockets,
4146
- parseKeyboardState,
4147
- parseUiaBounds,
4285
+ normalizeBridgeError,
4286
+ normalizeActivityComponent,
4287
+ parseWebViewDevToolsSockets,
4288
+ parseArgs,
4289
+ parseKeyboardState,
4290
+ parseLauncherActivityCandidates,
4291
+ parseStartExtras,
4292
+ parseUiaBounds,
4148
4293
  parseUiaViewport,
4149
4294
  parseComponentFromWindowLine,
4150
4295
  parseForegroundWindow,
package/bin/mcp-server.js CHANGED
@@ -219,8 +219,10 @@ function toolDefinitions() {
219
219
  installerTimeoutMs: { type: 'number', description: 'Maximum time to keep assisting installer screens after adb install exits. Defaults to 90000 ms.' },
220
220
  intervalMs: { type: 'number', description: 'Installer polling interval. Defaults to 700 ms.' },
221
221
  }, ['apkPath']),
222
- bridgeTool('launch_native_test', 'Launch the debug native Android bridge test Activity.'),
223
- bridgeTool('launch_flutter', 'Launch the Flutter Activity, optionally with an initial route.'),
222
+ bridgeTool('launch_app', 'Launch the target package LAUNCHER Activity. If multiple launcher Activities exist, returns launcher_ambiguous with candidates unless activity or component is explicit.', launchProperties()),
223
+ bridgeTool('launch_activity', 'Launch an explicit Android Activity component, optionally with action/data/category/string extras.', launchProperties()),
224
+ bridgeTool('launch_native_test', 'Launch the debug native Android bridge test Activity.'),
225
+ bridgeTool('launch_flutter', 'Launch the Flutter Activity, optionally with an initial route.'),
224
226
  bridgeTool('tap', 'Tap device coordinates through ADB.', {
225
227
  tapX: { type: 'number' },
226
228
  tapY: { type: 'number' },
@@ -233,12 +235,12 @@ function toolDefinitions() {
233
235
  targetText: { type: 'string' },
234
236
  timeoutSec: { type: 'number' },
235
237
  }, ['targetText']),
236
- bridgeTool('input_text', 'Set native Android text through the in-app bridge. Use this for Chinese/Unicode; do not use raw adb shell input text for non-ASCII text.', {
238
+ bridgeTool('input_text', 'Set native Android text through the in-app bridge. Use this for Chinese/Unicode; always pass packageName so the tool targets the intended app.', {
237
239
  text: { type: 'string', description: 'Text to set in the focused or coordinate-matched native EditText.' },
238
240
  tapX: { type: 'number', description: 'Optional X coordinate used to choose a native EditText target.' },
239
241
  tapY: { type: 'number', description: 'Optional Y coordinate used to choose a native EditText target.' },
240
242
  hideKeyboard: { type: 'boolean', description: 'Hide the soft keyboard after setting text.' },
241
- }, ['text']),
243
+ }, ['text', 'packageName']),
242
244
  bridgeTool('keyboard_state', 'Read Android soft-keyboard visibility from dumpsys input_method.'),
243
245
  bridgeTool('hide_keyboard', 'Hide the Android soft keyboard when it is visible.', {
244
246
  force: { type: 'boolean', description: 'Send keyboard-dismiss keys even when the visibility probe says the keyboard is hidden.' },
@@ -287,14 +289,38 @@ function toolDefinitions() {
287
289
  ];
288
290
  }
289
291
 
290
- function bridgeTool(name, description, properties = {}, required = []) {
291
- return {
292
- name,
293
- description,
294
- inputSchema: baseSchema(properties, required),
295
- };
296
- }
297
-
292
+ function bridgeTool(name, description, properties = {}, required = []) {
293
+ return {
294
+ name,
295
+ description,
296
+ inputSchema: baseSchema(properties, required),
297
+ };
298
+ }
299
+
300
+ function launchProperties() {
301
+ return {
302
+ activity: { type: 'string', description: 'Activity class, such as .MainActivity or com.example.MainActivity.' },
303
+ component: { type: 'string', description: 'Explicit Android component, such as com.example/.MainActivity.' },
304
+ action: { type: 'string', description: 'Intent action for explicit Activity launch.' },
305
+ category: {
306
+ oneOf: [
307
+ { type: 'string' },
308
+ { type: 'array', items: { type: 'string' } },
309
+ ],
310
+ description: 'Intent category or categories.',
311
+ },
312
+ data: { type: 'string', description: 'Intent data URI.' },
313
+ extra: {
314
+ oneOf: [
315
+ { type: 'string' },
316
+ { type: 'array', items: { type: 'string' } },
317
+ { type: 'object', additionalProperties: { type: 'string' } },
318
+ ],
319
+ description: 'String extras. Use key=value strings or an object of string values.',
320
+ },
321
+ };
322
+ }
323
+
298
324
  function baseSchema(extraProperties = {}, extraRequired = []) {
299
325
  return {
300
326
  type: 'object',
@@ -324,11 +350,14 @@ function h5TargetSchema() {
324
350
  };
325
351
  }
326
352
 
327
- async function callTool(name, args) {
328
- if (name === 'run_smoke') {
329
- return runSmoke(args);
330
- }
331
- const commandMap = {
353
+ async function callTool(name, args) {
354
+ if (name === 'run_smoke') {
355
+ return runSmoke(args);
356
+ }
357
+ if (name === 'input_text' && !args.packageName) {
358
+ return toolText('packageName is required for input_text so text input is routed to the intended app bridge.', true);
359
+ }
360
+ const commandMap = {
332
361
  flutter_tree: 'flutter-tree',
333
362
  h5_dom: 'h5-dom',
334
363
  h5_eval: 'h5-eval',
@@ -346,9 +375,11 @@ async function callTool(name, args) {
346
375
  tap_flutter_text: 'tap-flutter-text',
347
376
  input_flutter_text: 'input-flutter-text',
348
377
  uia_tree: 'uia-tree',
349
- install_apk: 'install-apk',
350
- launch_native_test: 'launch-native-test',
351
- launch_flutter: 'launch-flutter',
378
+ install_apk: 'install-apk',
379
+ launch_app: 'launch-app',
380
+ launch_activity: 'launch-activity',
381
+ launch_native_test: 'launch-native-test',
382
+ launch_flutter: 'launch-flutter',
352
383
  tap_text: 'tap-text',
353
384
  wait_text: 'wait-text',
354
385
  input_text: 'input-text',
@@ -371,8 +402,14 @@ async function callTool(name, args) {
371
402
  async function runBridge(command, args) {
372
403
  const cliArgs = [cliScript, command];
373
404
  addCommonArgs(cliArgs, args);
374
- addArg(cliArgs, 'initial-route', args.initialRoute);
375
- addArg(cliArgs, 'out-file', args.outFile);
405
+ addArg(cliArgs, 'initial-route', args.initialRoute);
406
+ addArg(cliArgs, 'activity', args.activity);
407
+ addArg(cliArgs, 'component', args.component);
408
+ addArg(cliArgs, 'action', args.action);
409
+ addRepeatedArg(cliArgs, 'category', args.category);
410
+ addArg(cliArgs, 'data', args.data);
411
+ addExtraArgs(cliArgs, args.extra);
412
+ addArg(cliArgs, 'out-file', args.outFile);
376
413
  addArg(cliArgs, 'artifact-dir', args.artifactDir || defaultArtifactDirFor(command, args));
377
414
  addArg(cliArgs, 'apk-path', args.apkPath);
378
415
  addArg(cliArgs, 'tap-x', args.tapX);
@@ -459,13 +496,35 @@ function addCommonArgs(cliArgs, args) {
459
496
  addArg(cliArgs, 'package-name', args.packageName);
460
497
  }
461
498
 
462
- function addArg(cliArgs, name, value) {
463
- if (value === undefined || value === null || value === '' || value === false) {
464
- return;
465
- }
466
- cliArgs.push(`--${name}`, String(value));
467
- }
468
-
499
+ function addArg(cliArgs, name, value) {
500
+ if (value === undefined || value === null || value === '' || value === false) {
501
+ return;
502
+ }
503
+ cliArgs.push(`--${name}`, String(value));
504
+ }
505
+
506
+ function addRepeatedArg(cliArgs, name, value) {
507
+ if (Array.isArray(value)) {
508
+ for (const item of value) addArg(cliArgs, name, item);
509
+ return;
510
+ }
511
+ addArg(cliArgs, name, value);
512
+ }
513
+
514
+ function addExtraArgs(cliArgs, value) {
515
+ if (Array.isArray(value)) {
516
+ for (const item of value) addArg(cliArgs, 'extra', item);
517
+ return;
518
+ }
519
+ if (value && typeof value === 'object') {
520
+ for (const [key, extraValue] of Object.entries(value)) {
521
+ addArg(cliArgs, 'extra', `${key}=${extraValue}`);
522
+ }
523
+ return;
524
+ }
525
+ addArg(cliArgs, 'extra', value);
526
+ }
527
+
469
528
  function runProcess(cliArgs) {
470
529
  return new Promise((resolve) => {
471
530
  const child = spawn(nodeBinary, cliArgs, {
@@ -490,12 +549,23 @@ function runProcess(cliArgs) {
490
549
  code === 0 ? '' : `exitCode: ${code}`,
491
550
  retryWithPackageNameHint(cliArgs, stdout, stderr, code),
492
551
  ].filter(Boolean).join('\n\n');
493
- resolve(toolText(text || 'ok', code !== 0));
494
- });
495
- });
496
- }
497
-
498
- function retryWithPackageNameHint(cliArgs, stdout, stderr, code) {
552
+ resolve(toolText(text || emptyProcessText(cliArgs), code !== 0));
553
+ });
554
+ });
555
+ }
556
+
557
+ function emptyProcessText(cliArgs) {
558
+ const command = cliArgs[1] || '';
559
+ if (command === 'logcat' && cliArgs.includes('--app-pid')) {
560
+ return 'logcat: no matching lines for current app pid';
561
+ }
562
+ if (command === 'logcat') {
563
+ return 'logcat: no matching lines';
564
+ }
565
+ return 'ok';
566
+ }
567
+
568
+ function retryWithPackageNameHint(cliArgs, stdout, stderr, code) {
499
569
  if (code === 0 || cliArgs.includes('--package-name') || cliArgs.includes('--port')) {
500
570
  return '';
501
571
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mobileaidev/ai-app-bridge",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Desktop CLI and MCP server for AI App Bridge.",
5
5
  "repository": {
6
6
  "type": "git",