xcode-cli 1.0.5 → 1.0.6

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.
@@ -23,8 +23,18 @@ export async function installSkill(rootDir: string): Promise<void> {
23
23
  const targetFile = path.join(targetDir, SKILL_FILENAME);
24
24
 
25
25
  await fs.mkdir(targetDir, { recursive: true });
26
- await fs.copyFile(source, targetFile);
27
- console.log(`Installed skill: ${targetFile}`);
26
+
27
+ // Remove existing file or symlink so we can (re)create the symlink cleanly.
28
+ try {
29
+ await fs.unlink(targetFile);
30
+ } catch {
31
+ // ignore — file didn't exist yet
32
+ }
33
+
34
+ // Use a symlink so the skill automatically reflects package upgrades
35
+ // without requiring the user to re-run `skill install`.
36
+ await fs.symlink(source, targetFile);
37
+ console.log(`Installed skill: ${targetFile} -> ${source}`);
28
38
  }
29
39
 
30
40
  export async function uninstallSkill(rootDir: string): Promise<void> {
@@ -32,7 +42,8 @@ export async function uninstallSkill(rootDir: string): Promise<void> {
32
42
  const targetFile = path.join(targetDir, SKILL_FILENAME);
33
43
 
34
44
  try {
35
- await fs.access(targetFile);
45
+ // lstat works for both symlinks and regular files
46
+ await fs.lstat(targetFile);
36
47
  } catch {
37
48
  console.log(`Skill not found at ${targetFile}`);
38
49
  return;
package/src/xcode.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { execFile } from 'node:child_process';
3
+ import { promisify } from 'node:util';
2
4
  import { Command } from 'commander';
3
5
  import { createRuntime, createServerProxy, describeConnectionIssue } from 'mcporter';
4
6
  import type { CallResult } from 'mcporter';
7
+
8
+ const execFileAsync = promisify(execFile);
5
9
  import { printResult, unwrapResult } from './xcode-output.ts';
6
10
  import { copyPreviewToOutput, findPreviewPath } from './xcode-preview.ts';
7
11
  import { parseTestSpecifier, type ParsedTestSpecifier } from './xcode-test.ts';
@@ -464,8 +468,24 @@ program
464
468
  });
465
469
 
466
470
  program
467
- .command('run <toolName>')
468
- .description('Run any MCP tool directly with JSON args')
471
+ .command('run')
472
+ .description('Build and run the active scheme (like Cmd+R in Xcode)')
473
+ .action(async () => {
474
+ await triggerXcodeKeystroke('r', 'command down');
475
+ console.log('Run triggered');
476
+ });
477
+
478
+ program
479
+ .command('run-without-build')
480
+ .description('Run without building the active scheme (like Ctrl+Cmd+R in Xcode)')
481
+ .action(async () => {
482
+ await triggerXcodeKeystroke('r', 'command down, control down');
483
+ console.log('Run Without Build triggered');
484
+ });
485
+
486
+ program
487
+ .command('call <toolName>')
488
+ .description('Call any MCP tool directly with JSON args')
469
489
  .requiredOption('--args <json>', 'JSON object with tool arguments')
470
490
  .action(async (toolName: string, options: { args: string }) => {
471
491
  await withClient(async (ctx) => {
@@ -479,6 +499,8 @@ applyCommandOrder(program, [
479
499
  'status',
480
500
  'build',
481
501
  'build-log',
502
+ 'run',
503
+ 'run-without-build',
482
504
  'test',
483
505
  'issues',
484
506
  'file-issues',
@@ -496,7 +518,7 @@ applyCommandOrder(program, [
496
518
  'snippet',
497
519
  'doc',
498
520
  'tools',
499
- 'run',
521
+ 'call',
500
522
  ]);
501
523
 
502
524
  program.parseAsync(process.argv).catch((error) => {
@@ -513,6 +535,16 @@ program.parseAsync(process.argv).catch((error) => {
513
535
  process.exit(1);
514
536
  });
515
537
 
538
+ async function triggerXcodeKeystroke(key: string, modifiers: string): Promise<void> {
539
+ const script = `tell application "Xcode" to activate\ntell application "System Events"\n tell process "Xcode"\n keystroke "${key}" using {${modifiers}}\n end tell\nend tell`;
540
+ try {
541
+ await execFileAsync('osascript', ['-e', script]);
542
+ } catch (error) {
543
+ const message = error instanceof Error ? error.message : String(error);
544
+ throw new Error(`Failed to trigger Xcode action: ${message}\nEnsure Xcode is open and Accessibility access is granted to Terminal/iTerm.`);
545
+ }
546
+ }
547
+
516
548
  async function withClient(handler: (ctx: ClientContext) => Promise<void>) {
517
549
  const root = program.opts<CommonOpts>();
518
550
  const endpoint = root.url ?? process.env.XCODE_CLI_URL ?? DEFAULT_URL;