xcode-mcli 0.1.0

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.
Files changed (42) hide show
  1. package/README.md +92 -0
  2. package/bin/xcode-mcli.ts +16 -0
  3. package/config/mcporter.json +11 -0
  4. package/package.json +57 -0
  5. package/skill/SKILL.md +70 -0
  6. package/skill/agents/openai.yaml +4 -0
  7. package/skill/references/api-reference.md +409 -0
  8. package/skill/references/apple-xcode-26.3.surface.json +1466 -0
  9. package/skill/references/compatibility.md +91 -0
  10. package/skill/references/setup.md +120 -0
  11. package/skill/references/troubleshooting.md +160 -0
  12. package/src/commands/daemon-restart.ts +22 -0
  13. package/src/commands/daemon-start.ts +22 -0
  14. package/src/commands/daemon-status.ts +29 -0
  15. package/src/commands/daemon-stop.ts +22 -0
  16. package/src/commands/index.ts +26 -0
  17. package/src/commands/project-build.ts +50 -0
  18. package/src/commands/setup.ts +26 -0
  19. package/src/commands/surface-snapshot.ts +45 -0
  20. package/src/commands/surface-verify.ts +46 -0
  21. package/src/commands/tool-command.ts +150 -0
  22. package/src/commands/windows-list.ts +43 -0
  23. package/src/commands/windows-use.ts +30 -0
  24. package/src/commands/xcode-tool-commands.ts +460 -0
  25. package/src/constants.ts +3 -0
  26. package/src/core/command-definition.ts +131 -0
  27. package/src/core/command-dispatch.ts +97 -0
  28. package/src/core/contracts.ts +25 -0
  29. package/src/core/errors.ts +52 -0
  30. package/src/core/package-version.ts +30 -0
  31. package/src/runtime/daemon-host-entry.ts +8 -0
  32. package/src/runtime/daemon-host.ts +455 -0
  33. package/src/runtime/daemon-state.ts +75 -0
  34. package/src/runtime/env.ts +37 -0
  35. package/src/runtime/mcp-jsonrpc.ts +114 -0
  36. package/src/runtime/output.ts +128 -0
  37. package/src/runtime/tab-resolver.ts +44 -0
  38. package/src/runtime/xcode-client.ts +192 -0
  39. package/src/runtime/xcode-surface.ts +166 -0
  40. package/src/runtime/xcode-tool-definition.ts +14 -0
  41. package/src/runtime/xcode-windows.ts +65 -0
  42. package/src/runtime/xcrun.ts +39 -0
@@ -0,0 +1,131 @@
1
+ import { Command } from "commander";
2
+ import { z } from "zod";
3
+ import type { output, ZodType } from "zod";
4
+
5
+ import type { CommandDefinition } from "./contracts.ts";
6
+ import { attachCommandMetadata, runtimeError, usageError } from "./errors.ts";
7
+
8
+ function commandPathToString(path: readonly string[]): string {
9
+ return path.join(" ");
10
+ }
11
+
12
+ function formatInvalidOptionsMessage(commandPath: readonly string[]): string {
13
+ return `Invalid options for command: ${commandPathToString(commandPath)}`;
14
+ }
15
+
16
+ function parseOptions<TSchema extends ZodType>(
17
+ commandPath: readonly string[],
18
+ optionsSchema: TSchema,
19
+ value: unknown,
20
+ ): output<TSchema> {
21
+ const result = optionsSchema.safeParse(value);
22
+ if (result.success) {
23
+ return result.data;
24
+ }
25
+ throw usageError(formatInvalidOptionsMessage(commandPath));
26
+ }
27
+
28
+ function readCommand(actionArgs: unknown[]): Command {
29
+ const candidate = actionArgs[actionArgs.length - 1];
30
+ if (candidate instanceof Command) {
31
+ return candidate;
32
+ }
33
+ throw runtimeError("Failed to read command context.");
34
+ }
35
+
36
+ const globalOptionsSchema = z.object({
37
+ json: z.boolean().default(false),
38
+ verbose: z.boolean().default(false),
39
+ });
40
+
41
+ function readGlobalOptions(command: Command) {
42
+ return globalOptionsSchema.parse(command.optsWithGlobals());
43
+ }
44
+
45
+ function addSharedGlobalOptions(command: Command): void {
46
+ command.option("--json", "Print command results as JSON.");
47
+ command.option("--verbose", "Print extra execution details.");
48
+ }
49
+
50
+ function registerCommandAction<TSchema extends ZodType>(input: {
51
+ command: Command;
52
+ definition: CommandDefinition<TSchema>;
53
+ projectDir: string;
54
+ }): void {
55
+ input.command.action(async (...actionArgs: unknown[]) => {
56
+ const command = readCommand(actionArgs);
57
+ const parsedOptions = parseOptions(
58
+ input.definition.path,
59
+ input.definition.optionsSchema,
60
+ command.optsWithGlobals(),
61
+ );
62
+ try {
63
+ await input.definition.run({
64
+ commandPath: input.definition.path,
65
+ globals: readGlobalOptions(command),
66
+ projectDir: input.projectDir,
67
+ options: parsedOptions,
68
+ });
69
+ } catch (error) {
70
+ throw attachCommandMetadata(error, {
71
+ commandName: commandPathToString(input.definition.path),
72
+ toolName: input.definition.toolName,
73
+ });
74
+ }
75
+ });
76
+ }
77
+
78
+ function findOrCreateGroupCommand(program: Command, groupName: string): Command {
79
+ const existingGroup = program.commands.find((candidate) => candidate.name() === groupName);
80
+ if (existingGroup) {
81
+ return existingGroup;
82
+ }
83
+ return program.command(groupName).description(`${groupName} commands.`).exitOverride();
84
+ }
85
+
86
+ function createLeafCommand(parent: Command, input: {
87
+ name: string;
88
+ description: string;
89
+ }): Command {
90
+ return parent.command(input.name).description(input.description).exitOverride();
91
+ }
92
+
93
+ function createRegisteredCommand(
94
+ program: Command,
95
+ definition: CommandDefinition<ZodType>,
96
+ ): Command {
97
+ const path = [...definition.path];
98
+ const leaf = path.pop();
99
+ if (!leaf) {
100
+ throw runtimeError("Command path cannot be empty.");
101
+ }
102
+ let parent = program;
103
+ for (const groupName of path) {
104
+ parent = findOrCreateGroupCommand(parent, groupName);
105
+ }
106
+ return createLeafCommand(parent, {
107
+ name: leaf,
108
+ description: definition.description,
109
+ });
110
+ }
111
+
112
+ export function registerCommand<TSchema extends ZodType>(input: {
113
+ program: Command;
114
+ definition: CommandDefinition<TSchema>;
115
+ projectDir: string;
116
+ }): void {
117
+ const command = createRegisteredCommand(input.program, input.definition);
118
+ addSharedGlobalOptions(command);
119
+ input.definition.configure?.(command);
120
+ registerCommandAction({
121
+ command,
122
+ definition: input.definition,
123
+ projectDir: input.projectDir,
124
+ });
125
+ }
126
+
127
+ export function defineCommand<TSchema extends ZodType>(
128
+ definition: CommandDefinition<TSchema>,
129
+ ): CommandDefinition<TSchema> {
130
+ return definition;
131
+ }
@@ -0,0 +1,97 @@
1
+ import { Command, CommanderError } from "commander";
2
+
3
+ import { commandDefinitions } from "../commands/index.ts";
4
+ import { registerCommand } from "./command-definition.ts";
5
+ import { usageError } from "./errors.ts";
6
+ import { readPackageVersion } from "./package-version.ts";
7
+
8
+ function hasJsonFlag(argv: string[]): boolean {
9
+ return argv.includes("--json");
10
+ }
11
+
12
+ async function createProgram(projectDir: string, argv: string[]): Promise<Command> {
13
+ const packageVersion = await readPackageVersion();
14
+ const jsonMode = hasJsonFlag(argv);
15
+ const program = new Command();
16
+ program
17
+ .name("xcode-mcli")
18
+ .description("Stable macOS CLI wrapper for Apple's Xcode MCP bridge.")
19
+ .helpOption("--help", "Display help for command.")
20
+ .version(packageVersion, "--version", "Show package version.")
21
+ .option("--json", "Print command results as JSON.")
22
+ .option("--verbose", "Print extra execution details.")
23
+ .option("--tab-identifier <id>", "Active Xcode window tab identifier.")
24
+ .configureOutput({
25
+ writeErr: (value) => {
26
+ if (jsonMode) {
27
+ return;
28
+ }
29
+ process.stderr.write(value);
30
+ },
31
+ })
32
+ .showSuggestionAfterError()
33
+ .exitOverride();
34
+ for (const definition of commandDefinitions) {
35
+ registerCommand({
36
+ program,
37
+ definition,
38
+ projectDir,
39
+ });
40
+ }
41
+ return program;
42
+ }
43
+
44
+ function isCommanderError(error: unknown): error is CommanderError {
45
+ return error instanceof CommanderError;
46
+ }
47
+
48
+ function isDisplayedHelpError(error: CommanderError): boolean {
49
+ return error.code === "commander.helpDisplayed" || error.message === "(outputHelp)";
50
+ }
51
+
52
+ function isDisplayedVersionError(error: CommanderError): boolean {
53
+ return error.code === "commander.version";
54
+ }
55
+
56
+ function isDisplayedOutputError(error: CommanderError): boolean {
57
+ return isDisplayedHelpError(error) || isDisplayedVersionError(error);
58
+ }
59
+
60
+ function toUsageMessage(error: CommanderError): string {
61
+ const message = error.message.trim();
62
+ if (message) {
63
+ return message;
64
+ }
65
+ return "Invalid command usage.";
66
+ }
67
+
68
+ function handleParseError(error: unknown): void {
69
+ if (!isCommanderError(error)) {
70
+ throw error;
71
+ }
72
+ if (isDisplayedOutputError(error)) {
73
+ return;
74
+ }
75
+ throw usageError(toUsageMessage(error));
76
+ }
77
+
78
+ async function parseProgram(program: Command, argv: string[]): Promise<void> {
79
+ await program.parseAsync(argv, { from: "user" });
80
+ }
81
+
82
+ export async function runXcodeMcli(argv: string[], projectDir: string): Promise<void> {
83
+ const program = await createProgram(projectDir, argv);
84
+ if (argv.length === 0) {
85
+ program.outputHelp();
86
+ return;
87
+ }
88
+ try {
89
+ await parseProgram(program, argv);
90
+ } catch (error) {
91
+ handleParseError(error);
92
+ }
93
+ }
94
+
95
+ export async function runXcodeMcliFromProcess(): Promise<void> {
96
+ await runXcodeMcli(process.argv.slice(2), process.cwd());
97
+ }
@@ -0,0 +1,25 @@
1
+ import type { Command } from "commander";
2
+ import type { output, ZodType } from "zod";
3
+
4
+ export type GlobalOptions = {
5
+ json: boolean;
6
+ verbose: boolean;
7
+ };
8
+
9
+ export type CommandRunContext<TOptions> = {
10
+ commandPath: CommandPath;
11
+ globals: GlobalOptions;
12
+ projectDir: string;
13
+ options: TOptions;
14
+ };
15
+
16
+ export type CommandPath = readonly [string, ...string[]];
17
+
18
+ export type CommandDefinition<TSchema extends ZodType = ZodType> = {
19
+ path: CommandPath;
20
+ description: string;
21
+ configure?: (command: Command) => void;
22
+ optionsSchema: TSchema;
23
+ toolName?: string;
24
+ run: (context: CommandRunContext<output<TSchema>>) => Promise<void>;
25
+ };
@@ -0,0 +1,52 @@
1
+ import { EXIT_RUNTIME_ERROR, EXIT_USAGE_ERROR } from "../constants.ts";
2
+
3
+ export class TemplateError extends Error {
4
+ public commandName?: string;
5
+ public details?: unknown;
6
+ public readonly exitCode: number;
7
+ public toolName?: string;
8
+
9
+ constructor(message: string, exitCode: number) {
10
+ super(message);
11
+ this.name = "TemplateError";
12
+ this.exitCode = exitCode;
13
+ }
14
+ }
15
+
16
+ export function usageError(message: string): TemplateError {
17
+ const error = new TemplateError(message, EXIT_USAGE_ERROR);
18
+ error.name = "UsageError";
19
+ return error;
20
+ }
21
+
22
+ export function runtimeError(message: string, details?: unknown): TemplateError {
23
+ const error = new TemplateError(message, EXIT_RUNTIME_ERROR);
24
+ error.name = "RuntimeError";
25
+ error.details = details;
26
+ return error;
27
+ }
28
+
29
+ export function toTemplateError(error: unknown): TemplateError {
30
+ if (error instanceof TemplateError) {
31
+ return error;
32
+ }
33
+ if (error instanceof Error) {
34
+ return runtimeError(error.message);
35
+ }
36
+ return runtimeError(String(error));
37
+ }
38
+
39
+ export function attachCommandMetadata(
40
+ error: unknown,
41
+ input: {
42
+ commandName: string;
43
+ toolName?: string;
44
+ },
45
+ ): TemplateError {
46
+ const wrapped = toTemplateError(error);
47
+ wrapped.commandName = input.commandName;
48
+ if (input.toolName) {
49
+ wrapped.toolName = input.toolName;
50
+ }
51
+ return wrapped;
52
+ }
@@ -0,0 +1,30 @@
1
+ import * as fs from "node:fs/promises";
2
+
3
+ import { runtimeError } from "./errors.ts";
4
+
5
+ const PACKAGE_JSON_PATH = new URL("../../package.json", import.meta.url);
6
+
7
+ const PACKAGE_VERSION_ERROR_MESSAGE =
8
+ "Failed to read package version. Set a valid version in package.json.";
9
+
10
+ export async function readPackageVersion(): Promise<string> {
11
+ let raw: string;
12
+ try {
13
+ raw = await fs.readFile(PACKAGE_JSON_PATH, "utf8");
14
+ } catch {
15
+ throw runtimeError(PACKAGE_VERSION_ERROR_MESSAGE);
16
+ }
17
+ let parsed: unknown;
18
+ try {
19
+ parsed = JSON.parse(raw);
20
+ } catch {
21
+ throw runtimeError(PACKAGE_VERSION_ERROR_MESSAGE);
22
+ }
23
+ if (typeof parsed === "object" && parsed && !Array.isArray(parsed)) {
24
+ const version = Reflect.get(parsed, "version");
25
+ if (typeof version === "string" && version.trim()) {
26
+ return version.trim();
27
+ }
28
+ }
29
+ throw runtimeError(PACKAGE_VERSION_ERROR_MESSAGE);
30
+ }
@@ -0,0 +1,8 @@
1
+ import { runDaemonHost } from "./daemon-host.ts";
2
+
3
+ try {
4
+ await runDaemonHost();
5
+ } catch (error) {
6
+ console.error(error);
7
+ process.exit(1);
8
+ }