xcode-cli 1.0.5 → 1.0.7
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 +22 -51
- package/package.json +1 -1
- package/skills/xcode-cli/SKILL.md +33 -117
- package/src/mcpbridge.ts +500 -500
- package/src/xcode-skill.ts +14 -3
- package/src/xcode.ts +38 -3
package/src/xcode-skill.ts
CHANGED
|
@@ -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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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,13 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
2
5
|
import { Command } from 'commander';
|
|
3
6
|
import { createRuntime, createServerProxy, describeConnectionIssue } from 'mcporter';
|
|
4
7
|
import type { CallResult } from 'mcporter';
|
|
8
|
+
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
5
10
|
import { printResult, unwrapResult } from './xcode-output.ts';
|
|
6
11
|
import { copyPreviewToOutput, findPreviewPath } from './xcode-preview.ts';
|
|
7
12
|
import { parseTestSpecifier, type ParsedTestSpecifier } from './xcode-test.ts';
|
|
8
13
|
import { renderLsTree } from './xcode-tree.ts';
|
|
9
14
|
import type { CommonOpts, ClientContext } from './xcode-types.ts';
|
|
10
15
|
|
|
16
|
+
const { version } = createRequire(import.meta.url)('../package.json');
|
|
11
17
|
const SERVER_NAME = 'xcode-tools';
|
|
12
18
|
const DEFAULT_PORT = '48321';
|
|
13
19
|
const DEFAULT_URL = `http://localhost:${DEFAULT_PORT}/mcp`;
|
|
@@ -16,6 +22,7 @@ const program = new Command();
|
|
|
16
22
|
program
|
|
17
23
|
.name('xcode-cli')
|
|
18
24
|
.description('Friendly Xcode MCP CLI for browsing, editing, building, and testing projects.')
|
|
25
|
+
.version(version, '-v, --version')
|
|
19
26
|
.option('--url <url>', `MCP endpoint (default: ${DEFAULT_URL})`)
|
|
20
27
|
.option('--tab <tabIdentifier>', 'Default tab identifier for commands that need it')
|
|
21
28
|
.option('-t, --timeout <ms>', 'Call timeout in milliseconds', '60000')
|
|
@@ -464,8 +471,24 @@ program
|
|
|
464
471
|
});
|
|
465
472
|
|
|
466
473
|
program
|
|
467
|
-
.command('run
|
|
468
|
-
.description('
|
|
474
|
+
.command('run')
|
|
475
|
+
.description('Build and run the active scheme (like Cmd+R in Xcode)')
|
|
476
|
+
.action(async () => {
|
|
477
|
+
await triggerXcodeKeystroke('r', 'command down');
|
|
478
|
+
console.log('Run triggered');
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
program
|
|
482
|
+
.command('run-without-build')
|
|
483
|
+
.description('Run without building the active scheme (like Ctrl+Cmd+R in Xcode)')
|
|
484
|
+
.action(async () => {
|
|
485
|
+
await triggerXcodeKeystroke('r', 'command down, control down');
|
|
486
|
+
console.log('Run Without Build triggered');
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
program
|
|
490
|
+
.command('call <toolName>')
|
|
491
|
+
.description('Call any MCP tool directly with JSON args')
|
|
469
492
|
.requiredOption('--args <json>', 'JSON object with tool arguments')
|
|
470
493
|
.action(async (toolName: string, options: { args: string }) => {
|
|
471
494
|
await withClient(async (ctx) => {
|
|
@@ -479,6 +502,8 @@ applyCommandOrder(program, [
|
|
|
479
502
|
'status',
|
|
480
503
|
'build',
|
|
481
504
|
'build-log',
|
|
505
|
+
'run',
|
|
506
|
+
'run-without-build',
|
|
482
507
|
'test',
|
|
483
508
|
'issues',
|
|
484
509
|
'file-issues',
|
|
@@ -496,7 +521,7 @@ applyCommandOrder(program, [
|
|
|
496
521
|
'snippet',
|
|
497
522
|
'doc',
|
|
498
523
|
'tools',
|
|
499
|
-
'
|
|
524
|
+
'call',
|
|
500
525
|
]);
|
|
501
526
|
|
|
502
527
|
program.parseAsync(process.argv).catch((error) => {
|
|
@@ -513,6 +538,16 @@ program.parseAsync(process.argv).catch((error) => {
|
|
|
513
538
|
process.exit(1);
|
|
514
539
|
});
|
|
515
540
|
|
|
541
|
+
async function triggerXcodeKeystroke(key: string, modifiers: string): Promise<void> {
|
|
542
|
+
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`;
|
|
543
|
+
try {
|
|
544
|
+
await execFileAsync('osascript', ['-e', script]);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
547
|
+
throw new Error(`Failed to trigger Xcode action: ${message}\nEnsure Xcode is open and Accessibility access is granted to Terminal/iTerm.`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
516
551
|
async function withClient(handler: (ctx: ClientContext) => Promise<void>) {
|
|
517
552
|
const root = program.opts<CommonOpts>();
|
|
518
553
|
const endpoint = root.url ?? process.env.XCODE_CLI_URL ?? DEFAULT_URL;
|