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.
- 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 +35 -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,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
|
|
468
|
-
.description('
|
|
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
|
-
'
|
|
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;
|