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,150 @@
1
+ import type { Command } from "commander";
2
+ import { readFile } from "node:fs/promises";
3
+ import { z } from "zod";
4
+
5
+ import { defineCommand } from "../core/command-definition.ts";
6
+ import { runtimeError } from "../core/errors.ts";
7
+ import { callDaemonTool } from "../runtime/daemon-host.ts";
8
+ import {
9
+ printCommandResult,
10
+ printVerboseTool,
11
+ toCommandData,
12
+ } from "../runtime/output.ts";
13
+ import { resolveTabIdentifier } from "../runtime/tab-resolver.ts";
14
+
15
+ type ToolCommandResult = {
16
+ structuredContent?: unknown;
17
+ text: string;
18
+ };
19
+
20
+ type ToolCommandDefinitionInput<TOptions> = {
21
+ buildArguments: (options: TOptions) => Promise<Record<string, unknown>> | Record<string, unknown>;
22
+ configure?: (command: Command) => void;
23
+ description: string;
24
+ path: readonly [string, ...string[]];
25
+ requiresTab?: boolean;
26
+ toolName: string;
27
+ optionsSchema: z.ZodType<TOptions>;
28
+ };
29
+
30
+ const toolCommandResultSchema = z.object({
31
+ structuredContent: z.unknown().optional(),
32
+ text: z.string().default(""),
33
+ });
34
+
35
+ function defaultToolText(result: ToolCommandResult): string {
36
+ const text = result.text.trimEnd();
37
+ if (text.length > 0) {
38
+ return text;
39
+ }
40
+ if (result.structuredContent !== undefined) {
41
+ return JSON.stringify(result.structuredContent, null, 2);
42
+ }
43
+ return "";
44
+ }
45
+
46
+ function readTabIdentifierOption(options: unknown): string | undefined {
47
+ if (!options || typeof options !== "object") {
48
+ return undefined;
49
+ }
50
+ if (!("tabIdentifier" in options)) {
51
+ return undefined;
52
+ }
53
+ const candidate = options.tabIdentifier;
54
+ if (typeof candidate !== "string") {
55
+ return undefined;
56
+ }
57
+ return candidate;
58
+ }
59
+
60
+ export function addTabIdentifierOption(command: Command): void {
61
+ command.option("--tab-identifier <id>", "Active Xcode window tab identifier.");
62
+ }
63
+
64
+ export function collectRepeatedStrings(
65
+ value: string,
66
+ previous: string[] | undefined,
67
+ ): string[] {
68
+ return [...(previous ?? []), value];
69
+ }
70
+
71
+ export function parseKeyValuePairs(values: string[]): Record<string, string>[] {
72
+ const result: Record<string, string>[] = [];
73
+ let current: Record<string, string> = {};
74
+ for (const value of values) {
75
+ const separatorIndex = value.indexOf("=");
76
+ if (separatorIndex <= 0) {
77
+ throw runtimeError(`Invalid key=value input: ${value}`);
78
+ }
79
+ const key = value.slice(0, separatorIndex);
80
+ const parsedValue = value.slice(separatorIndex + 1);
81
+ current[key] = parsedValue;
82
+ if (current.targetName && current.testIdentifier) {
83
+ result.push(current);
84
+ current = {};
85
+ }
86
+ }
87
+ if (Object.keys(current).length > 0) {
88
+ throw runtimeError("Incomplete test specifier. Expected both targetName and testIdentifier.");
89
+ }
90
+ return result;
91
+ }
92
+
93
+ export async function resolveWriteContent(input: {
94
+ content?: string;
95
+ contentFile?: string;
96
+ }): Promise<string> {
97
+ if (typeof input.content === "string") {
98
+ return input.content;
99
+ }
100
+ if (typeof input.contentFile === "string") {
101
+ return readFile(input.contentFile, "utf8");
102
+ }
103
+ throw runtimeError("Expected --content or --content-file.");
104
+ }
105
+
106
+ export function requireYes(
107
+ input: { condition: boolean; commandName: string; yes?: boolean },
108
+ ): void {
109
+ if (input.condition && input.yes) {
110
+ return;
111
+ }
112
+ if (input.condition) {
113
+ throw runtimeError(`Command requires --yes: ${input.commandName}`);
114
+ }
115
+ }
116
+
117
+ export function createToolCommand<TOptions>(input: ToolCommandDefinitionInput<TOptions>) {
118
+ return defineCommand({
119
+ path: input.path,
120
+ description: input.description,
121
+ toolName: input.toolName,
122
+ configure: input.configure,
123
+ optionsSchema: input.optionsSchema,
124
+ run: async ({ commandPath, globals, options }) => {
125
+ const toolArguments = await input.buildArguments(options);
126
+ if (input.requiresTab && typeof toolArguments.tabIdentifier !== "string") {
127
+ toolArguments.tabIdentifier = await resolveTabIdentifier({
128
+ explicitTabIdentifier: readTabIdentifierOption(options),
129
+ });
130
+ }
131
+ printVerboseTool(globals, input.toolName);
132
+ const result = toolCommandResultSchema.parse(
133
+ await callDaemonTool({
134
+ name: input.toolName,
135
+ arguments: toolArguments,
136
+ }),
137
+ );
138
+ printCommandResult({
139
+ commandPath,
140
+ globals,
141
+ text: defaultToolText(result),
142
+ data: toCommandData(result),
143
+ tabIdentifier: typeof toolArguments.tabIdentifier === "string"
144
+ ? toolArguments.tabIdentifier
145
+ : undefined,
146
+ tool: input.toolName,
147
+ });
148
+ },
149
+ });
150
+ }
@@ -0,0 +1,43 @@
1
+ import { z } from "zod";
2
+
3
+ import { defineCommand } from "../core/command-definition.ts";
4
+ import { callDaemonTool } from "../runtime/daemon-host.ts";
5
+ import { setLastSeenWindows } from "../runtime/daemon-state.ts";
6
+ import {
7
+ printCommandResult,
8
+ printVerboseTool,
9
+ toCommandData,
10
+ } from "../runtime/output.ts";
11
+ import {
12
+ readWindowsFromToolResult,
13
+ xcodeWindowsToolResultSchema,
14
+ } from "../runtime/xcode-windows.ts";
15
+
16
+ export const windowsListCommand = defineCommand({
17
+ path: ["windows", "list"],
18
+ description: "List open Xcode windows.",
19
+ toolName: "XcodeListWindows",
20
+ optionsSchema: z.object({}),
21
+ run: async ({ commandPath, globals }) => {
22
+ printVerboseTool(globals, "XcodeListWindows");
23
+ const result = xcodeWindowsToolResultSchema.parse(
24
+ await callDaemonTool({
25
+ name: "XcodeListWindows",
26
+ arguments: {},
27
+ }),
28
+ );
29
+ const windows = readWindowsFromToolResult(result);
30
+ await setLastSeenWindows(windows);
31
+ printCommandResult({
32
+ commandPath,
33
+ globals,
34
+ text: result.text.trimEnd(),
35
+ data: windows.length > 0
36
+ ? {
37
+ windows,
38
+ }
39
+ : toCommandData(result),
40
+ tool: "XcodeListWindows",
41
+ });
42
+ },
43
+ });
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+
3
+ import { defineCommand } from "../core/command-definition.ts";
4
+ import { setActiveTabIdentifier } from "../runtime/daemon-state.ts";
5
+ import { printCommandResult } from "../runtime/output.ts";
6
+
7
+ const windowsUseOptionsSchema = z.object({
8
+ tabIdentifier: z.string().trim().min(1),
9
+ });
10
+
11
+ export const windowsUseCommand = defineCommand({
12
+ path: ["windows", "use"],
13
+ description: "Select the active Xcode window tab.",
14
+ configure: (command) => {
15
+ command.option("--tab-identifier <id>", "Active Xcode window tab identifier.");
16
+ },
17
+ optionsSchema: windowsUseOptionsSchema,
18
+ run: async ({ commandPath, globals, options }) => {
19
+ await setActiveTabIdentifier(options.tabIdentifier);
20
+ printCommandResult({
21
+ commandPath,
22
+ globals,
23
+ text: `Active Xcode tab set to ${options.tabIdentifier}.`,
24
+ tabIdentifier: options.tabIdentifier,
25
+ data: {
26
+ tabIdentifier: options.tabIdentifier,
27
+ },
28
+ });
29
+ },
30
+ });
@@ -0,0 +1,460 @@
1
+ import type { Command } from "commander";
2
+ import { z } from "zod";
3
+
4
+ import {
5
+ addTabIdentifierOption,
6
+ collectRepeatedStrings,
7
+ createToolCommand,
8
+ parseKeyValuePairs,
9
+ requireYes,
10
+ resolveWriteContent,
11
+ } from "./tool-command.ts";
12
+
13
+ const severitySchema = z.enum(["error", "warning", "remark"]);
14
+ const outputModeSchema = z.enum(["content", "files_with_matches", "count"]);
15
+ const optionalTabIdentifierShape = {
16
+ tabIdentifier: z.string().trim().min(1).optional(),
17
+ };
18
+ const yesAndOptionalTabShape = {
19
+ yes: z.boolean().default(false),
20
+ ...optionalTabIdentifierShape,
21
+ };
22
+ const sourceFileTimeoutShape = {
23
+ sourceFilePath: z.string().trim().min(1),
24
+ timeout: z.coerce.number().positive().optional(),
25
+ ...optionalTabIdentifierShape,
26
+ };
27
+ const tabOnlyCommandSpecs = [
28
+ {
29
+ description: "List tests from the active test plan.",
30
+ path: ["tests", "list"] as const,
31
+ toolName: "GetTestList",
32
+ },
33
+ {
34
+ description: "Run all tests from the active test plan.",
35
+ path: ["tests", "run-all"] as const,
36
+ toolName: "RunAllTests",
37
+ },
38
+ ];
39
+ const filteredCommandSpecs = [
40
+ {
41
+ description: "Read the current or latest build log.",
42
+ path: ["build", "log"] as const,
43
+ toolName: "GetBuildLog",
44
+ },
45
+ {
46
+ description: "List navigator issues.",
47
+ path: ["issues", "list"] as const,
48
+ toolName: "XcodeListNavigatorIssues",
49
+ },
50
+ ];
51
+
52
+ function createTabOnlyCommand(input: (typeof tabOnlyCommandSpecs)[number]) {
53
+ return createToolCommand({
54
+ path: input.path,
55
+ description: input.description,
56
+ toolName: input.toolName,
57
+ requiresTab: true,
58
+ optionsSchema: z.object(optionalTabIdentifierShape),
59
+ configure: addTabIdentifierOption,
60
+ buildArguments: (options) => options,
61
+ });
62
+ }
63
+
64
+ function createFilteredCommand(input: (typeof filteredCommandSpecs)[number]) {
65
+ return createToolCommand({
66
+ path: input.path,
67
+ description: input.description,
68
+ toolName: input.toolName,
69
+ requiresTab: true,
70
+ optionsSchema: z.object({
71
+ glob: z.string().trim().min(1).optional(),
72
+ pattern: z.string().trim().min(1).optional(),
73
+ severity: severitySchema.optional(),
74
+ ...optionalTabIdentifierShape,
75
+ }),
76
+ configure: (command) => {
77
+ command.option("--glob <glob>", "Filter results by glob.");
78
+ command.option("--pattern <regex>", "Filter results by regex.");
79
+ command.option("--severity <level>", "Minimum issue severity.");
80
+ addTabIdentifierOption(command);
81
+ },
82
+ buildArguments: (options) => options,
83
+ });
84
+ }
85
+
86
+ function configureSourceFileTimeoutCommand(command: Command, input: {
87
+ includePreviewDefinitionIndex?: boolean;
88
+ sourceHelp: string;
89
+ }): void {
90
+ command.requiredOption("--source-file-path <path>", input.sourceHelp);
91
+ if (input.includePreviewDefinitionIndex) {
92
+ command.option(
93
+ "--preview-definition-index-in-file <n>",
94
+ "Zero-based preview definition index.",
95
+ );
96
+ }
97
+ command.option("--timeout <seconds>", "Execution timeout in seconds.");
98
+ addTabIdentifierOption(command);
99
+ }
100
+
101
+ const docsSearchCommand = createToolCommand({
102
+ path: ["docs", "search"],
103
+ description: "Search Apple documentation through Xcode MCP.",
104
+ toolName: "DocumentationSearch",
105
+ optionsSchema: z.object({
106
+ query: z.string().trim().min(1),
107
+ framework: z.array(z.string()).default([]),
108
+ }),
109
+ configure: (command) => {
110
+ command.requiredOption("--query <text>", "Search query text.");
111
+ command.option(
112
+ "--framework <name>",
113
+ "Restrict search to a framework.",
114
+ collectRepeatedStrings,
115
+ [],
116
+ );
117
+ },
118
+ buildArguments: (options) => ({
119
+ query: options.query,
120
+ frameworks: options.framework,
121
+ }),
122
+ });
123
+
124
+ const snippetExecuteCommand = createToolCommand({
125
+ path: ["snippet", "execute"],
126
+ description: "Execute a Swift snippet in Xcode source context.",
127
+ toolName: "ExecuteSnippet",
128
+ requiresTab: true,
129
+ optionsSchema: z.object({
130
+ codeSnippet: z.string().min(1),
131
+ ...sourceFileTimeoutShape,
132
+ }),
133
+ configure: (command) => {
134
+ command.requiredOption("--code-snippet <swift>", "Swift snippet to execute.");
135
+ configureSourceFileTimeoutCommand(command, {
136
+ sourceHelp: "Source file path.",
137
+ });
138
+ },
139
+ buildArguments: (options) => ({
140
+ codeSnippet: options.codeSnippet,
141
+ sourceFilePath: options.sourceFilePath,
142
+ timeout: options.timeout,
143
+ tabIdentifier: options.tabIdentifier,
144
+ }),
145
+ });
146
+
147
+ const previewRenderCommand = createToolCommand({
148
+ path: ["preview", "render"],
149
+ description: "Render a SwiftUI preview.",
150
+ toolName: "RenderPreview",
151
+ requiresTab: true,
152
+ optionsSchema: z.object({
153
+ previewDefinitionIndexInFile: z.coerce.number().int().nonnegative().optional(),
154
+ ...sourceFileTimeoutShape,
155
+ }),
156
+ configure: (command) => {
157
+ configureSourceFileTimeoutCommand(command, {
158
+ includePreviewDefinitionIndex: true,
159
+ sourceHelp: "Preview source file path.",
160
+ });
161
+ },
162
+ buildArguments: (options) => options,
163
+ });
164
+
165
+ const testsRunSomeCommand = createToolCommand({
166
+ path: ["tests", "run-some"],
167
+ description: "Run selected tests from the active test plan.",
168
+ toolName: "RunSomeTests",
169
+ requiresTab: true,
170
+ optionsSchema: z.object({
171
+ test: z.array(z.string()).min(2),
172
+ ...optionalTabIdentifierShape,
173
+ }),
174
+ configure: (command) => {
175
+ command.option(
176
+ "--test <key=value>",
177
+ "Repeatable test specifier field.",
178
+ collectRepeatedStrings,
179
+ [],
180
+ );
181
+ addTabIdentifierOption(command);
182
+ },
183
+ buildArguments: (options) => ({
184
+ tabIdentifier: options.tabIdentifier,
185
+ tests: parseKeyValuePairs(options.test),
186
+ }),
187
+ });
188
+
189
+ const filesGlobCommand = createToolCommand({
190
+ path: ["files", "glob"],
191
+ description: "Glob Xcode project files.",
192
+ toolName: "XcodeGlob",
193
+ requiresTab: true,
194
+ optionsSchema: z.object({
195
+ path: z.string().trim().min(1).optional(),
196
+ pattern: z.string().trim().min(1).optional(),
197
+ ...optionalTabIdentifierShape,
198
+ }),
199
+ configure: (command) => {
200
+ command.option("--path <path>", "Project navigator path.");
201
+ command.option("--pattern <glob>", "Glob pattern.");
202
+ addTabIdentifierOption(command);
203
+ },
204
+ buildArguments: (options) => options,
205
+ });
206
+
207
+ const filesGrepCommand = createToolCommand({
208
+ path: ["files", "grep"],
209
+ description: "Grep Xcode project files.",
210
+ toolName: "XcodeGrep",
211
+ requiresTab: true,
212
+ optionsSchema: z.object({
213
+ glob: z.string().trim().min(1).optional(),
214
+ headLimit: z.coerce.number().int().positive().optional(),
215
+ ignoreCase: z.boolean().default(false),
216
+ linesAfter: z.coerce.number().int().nonnegative().optional(),
217
+ linesBefore: z.coerce.number().int().nonnegative().optional(),
218
+ linesContext: z.coerce.number().int().nonnegative().optional(),
219
+ multiline: z.boolean().default(false),
220
+ outputMode: outputModeSchema.optional(),
221
+ path: z.string().trim().min(1).optional(),
222
+ pattern: z.string().trim().min(1),
223
+ showLineNumbers: z.boolean().default(false),
224
+ type: z.string().trim().min(1).optional(),
225
+ ...optionalTabIdentifierShape,
226
+ }),
227
+ configure: (command) => {
228
+ command.requiredOption("--pattern <regex>", "Content regex.");
229
+ command.option("--glob <glob>", "Filter files by glob.");
230
+ command.option("--head-limit <n>", "Limit matches.");
231
+ command.option("--ignore-case", "Case-insensitive search.");
232
+ command.option("--lines-after <n>", "Lines of trailing context.");
233
+ command.option("--lines-before <n>", "Lines of leading context.");
234
+ command.option("--lines-context <n>", "Lines of surrounding context.");
235
+ command.option("--multiline", "Enable multiline regex.");
236
+ command.option("--output-mode <mode>", "Output mode.");
237
+ command.option("--path <path>", "Project navigator path.");
238
+ command.option("--show-line-numbers", "Show line numbers.");
239
+ command.option("--type <type>", "File type filter.");
240
+ addTabIdentifierOption(command);
241
+ },
242
+ buildArguments: (options) => options,
243
+ });
244
+
245
+ const filesLsCommand = createToolCommand({
246
+ path: ["files", "ls"],
247
+ description: "List Xcode project files.",
248
+ toolName: "XcodeLS",
249
+ requiresTab: true,
250
+ optionsSchema: z.object({
251
+ ignore: z.array(z.string()).default([]),
252
+ path: z.string().trim().min(1),
253
+ recursive: z.boolean().default(false),
254
+ ...optionalTabIdentifierShape,
255
+ }),
256
+ configure: (command) => {
257
+ command.requiredOption("--path <path>", "Project navigator path.");
258
+ command.option("--ignore <pattern>", "Ignore pattern.", collectRepeatedStrings, []);
259
+ command.option("--recursive", "Recurse through directories.");
260
+ addTabIdentifierOption(command);
261
+ },
262
+ buildArguments: (options) => options,
263
+ });
264
+
265
+ const filesMvCommand = createToolCommand({
266
+ path: ["files", "mv"],
267
+ description: "Move a file or group in Xcode.",
268
+ toolName: "XcodeMV",
269
+ requiresTab: true,
270
+ optionsSchema: z.object({
271
+ destinationPath: z.string().trim().min(1),
272
+ operation: z.string().trim().min(1).optional(),
273
+ overwriteExisting: z.boolean().default(false),
274
+ sourcePath: z.string().trim().min(1),
275
+ yes: z.boolean().default(false),
276
+ ...optionalTabIdentifierShape,
277
+ }),
278
+ configure: (command) => {
279
+ command.requiredOption("--source-path <path>", "Source path.");
280
+ command.requiredOption("--destination-path <path>", "Destination path.");
281
+ command.option("--operation <operation>", "Move operation.");
282
+ command.option("--overwrite-existing", "Overwrite an existing destination.");
283
+ command.option("--yes", "Confirm the overwrite.");
284
+ addTabIdentifierOption(command);
285
+ },
286
+ buildArguments: (options) => {
287
+ requireYes({
288
+ condition: options.overwriteExisting,
289
+ commandName: "files mv --overwrite-existing",
290
+ yes: options.yes,
291
+ });
292
+ return options;
293
+ },
294
+ });
295
+
296
+ const filesMkdirCommand = createToolCommand({
297
+ path: ["files", "mkdir"],
298
+ description: "Create a directory in Xcode.",
299
+ toolName: "XcodeMakeDir",
300
+ requiresTab: true,
301
+ optionsSchema: z.object({
302
+ directoryPath: z.string().trim().min(1),
303
+ ...optionalTabIdentifierShape,
304
+ }),
305
+ configure: (command) => {
306
+ command.requiredOption("--directory-path <path>", "Directory path.");
307
+ addTabIdentifierOption(command);
308
+ },
309
+ buildArguments: (options) => options,
310
+ });
311
+
312
+ const filesRmCommand = createToolCommand({
313
+ path: ["files", "rm"],
314
+ description: "Remove files or groups in Xcode.",
315
+ toolName: "XcodeRM",
316
+ requiresTab: true,
317
+ optionsSchema: z.object({
318
+ deleteFiles: z.boolean().default(false),
319
+ path: z.string().trim().min(1),
320
+ recursive: z.boolean().default(false),
321
+ ...yesAndOptionalTabShape,
322
+ }),
323
+ configure: (command) => {
324
+ command.requiredOption("--path <path>", "Path to remove.");
325
+ command.option("--delete-files", "Delete underlying files.");
326
+ command.option("--recursive", "Remove recursively.");
327
+ command.option("--yes", "Confirm removal.");
328
+ addTabIdentifierOption(command);
329
+ },
330
+ buildArguments: (options) => {
331
+ requireYes({
332
+ condition: true,
333
+ commandName: "files rm",
334
+ yes: options.yes,
335
+ });
336
+ return options;
337
+ },
338
+ });
339
+
340
+ const filesReadCommand = createToolCommand({
341
+ path: ["files", "read"],
342
+ description: "Read a file in Xcode.",
343
+ toolName: "XcodeRead",
344
+ requiresTab: true,
345
+ optionsSchema: z.object({
346
+ filePath: z.string().trim().min(1),
347
+ limit: z.coerce.number().int().positive().optional(),
348
+ offset: z.coerce.number().int().nonnegative().optional(),
349
+ ...optionalTabIdentifierShape,
350
+ }),
351
+ configure: (command) => {
352
+ command.requiredOption("--file-path <path>", "File path.");
353
+ command.option("--limit <n>", "Line limit.");
354
+ command.option("--offset <line>", "Line offset.");
355
+ addTabIdentifierOption(command);
356
+ },
357
+ buildArguments: (options) => options,
358
+ });
359
+
360
+ const issuesFileCommand = createToolCommand({
361
+ path: ["issues", "file"],
362
+ description: "Refresh issues for one file.",
363
+ toolName: "XcodeRefreshCodeIssuesInFile",
364
+ requiresTab: true,
365
+ optionsSchema: z.object({
366
+ filePath: z.string().trim().min(1),
367
+ ...optionalTabIdentifierShape,
368
+ }),
369
+ configure: (command) => {
370
+ command.requiredOption("--file-path <path>", "File path.");
371
+ addTabIdentifierOption(command);
372
+ },
373
+ buildArguments: (options) => options,
374
+ });
375
+
376
+ const filesUpdateCommand = createToolCommand({
377
+ path: ["files", "update"],
378
+ description: "Replace text in a file through Xcode.",
379
+ toolName: "XcodeUpdate",
380
+ requiresTab: true,
381
+ optionsSchema: z.object({
382
+ filePath: z.string().trim().min(1),
383
+ newString: z.string(),
384
+ oldString: z.string(),
385
+ replaceAll: z.boolean().default(false),
386
+ ...yesAndOptionalTabShape,
387
+ }),
388
+ configure: (command) => {
389
+ command.requiredOption("--file-path <path>", "File path.");
390
+ command.requiredOption("--old-string <text>", "Text to replace.");
391
+ command.requiredOption("--new-string <text>", "Replacement text.");
392
+ command.option("--replace-all", "Replace every match.");
393
+ command.option("--yes", "Confirm update.");
394
+ addTabIdentifierOption(command);
395
+ },
396
+ buildArguments: (options) => {
397
+ requireYes({
398
+ condition: true,
399
+ commandName: "files update",
400
+ yes: options.yes,
401
+ });
402
+ return options;
403
+ },
404
+ });
405
+
406
+ const filesWriteCommand = createToolCommand({
407
+ path: ["files", "write"],
408
+ description: "Write file content through Xcode.",
409
+ toolName: "XcodeWrite",
410
+ requiresTab: true,
411
+ optionsSchema: z
412
+ .object({
413
+ content: z.string().optional(),
414
+ contentFile: z.string().trim().min(1).optional(),
415
+ filePath: z.string().trim().min(1),
416
+ ...yesAndOptionalTabShape,
417
+ })
418
+ .refine((value) => value.content || value.contentFile, {
419
+ message: "Expected --content or --content-file.",
420
+ path: ["content"],
421
+ }),
422
+ configure: (command) => {
423
+ command.requiredOption("--file-path <path>", "File path.");
424
+ command.option("--content <text>", "Content to write.");
425
+ command.option("--content-file <path>", "Read content from a file.");
426
+ command.option("--yes", "Confirm write.");
427
+ addTabIdentifierOption(command);
428
+ },
429
+ buildArguments: async (options) => {
430
+ requireYes({
431
+ condition: true,
432
+ commandName: "files write",
433
+ yes: options.yes,
434
+ });
435
+ return {
436
+ filePath: options.filePath,
437
+ content: await resolveWriteContent(options),
438
+ tabIdentifier: options.tabIdentifier,
439
+ };
440
+ },
441
+ });
442
+
443
+ export const xcodeToolCommandDefinitions = [
444
+ docsSearchCommand,
445
+ snippetExecuteCommand,
446
+ ...filteredCommandSpecs.map(createFilteredCommand),
447
+ ...tabOnlyCommandSpecs.map(createTabOnlyCommand),
448
+ previewRenderCommand,
449
+ testsRunSomeCommand,
450
+ filesGlobCommand,
451
+ filesGrepCommand,
452
+ filesLsCommand,
453
+ filesMvCommand,
454
+ filesMkdirCommand,
455
+ filesRmCommand,
456
+ filesReadCommand,
457
+ issuesFileCommand,
458
+ filesUpdateCommand,
459
+ filesWriteCommand,
460
+ ];
@@ -0,0 +1,3 @@
1
+ export const EXIT_SUCCESS = 0;
2
+ export const EXIT_RUNTIME_ERROR = 1;
3
+ export const EXIT_USAGE_ERROR = 2;