@axintai/compiler 0.3.0 → 0.3.1

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 CHANGED
@@ -63,10 +63,10 @@ Axint is the fastest path from an AI coding tool to a shipped App Intent. **One
63
63
  - **Native type fidelity.** `int → Int`, `double → Double`, `float → Float`, `date → Date`, `url → URL`, `duration → Measurement<UnitDuration>`, `optional<T> → T?`. Default values and optionality are preserved end-to-end.
64
64
  - **Return-type-aware `perform()` signatures.** Every generated intent is a drop-in tool for Agent Siri and Shortcuts.
65
65
  - **Info.plist and .entitlements emit.** Axint writes the `NSAppIntentsDomains` plist fragment and the App Intents entitlement XML alongside your `.swift` file. Drop all three into Xcode and ship.
66
- - **MCP-native.** A bundled `axint-mcp` server exposes `axint_scaffold`, `axint_compile`, and `axint_validate` to any MCP client. Your AI coding agent can read your project, draft a TypeScript intent, compile it, and open a PR — without a human touching Xcode.
66
+ - **MCP-native.** A bundled `axint-mcp` server exposes five tools — `axint_scaffold`, `axint_compile`, `axint_validate`, `axint_list_templates`, and `axint_template` to any MCP client. Your AI coding agent can read your project, draft a TypeScript intent, compile it, and open a PR — without a human touching Xcode.
67
67
  - **Rust-grade diagnostics.** 16 diagnostic codes (`AX001`–`AX202`) with fix suggestions and color-coded output.
68
68
  - **Sub-millisecond compile.** A typical intent compiles in under a millisecond. The [axint.ai playground](https://axint.ai/#playground) runs the full compiler in your browser with zero server round-trip.
69
- - **152 tests.** Parser, validator, generator, emit paths, and sandbox — all covered.
69
+ - **155 tests.** Parser, validator, generator, emit paths, watch mode, and sandbox — all covered.
70
70
  - **Apache 2.0, no CLA.** Fork it, extend it, ship it.
71
71
 
72
72
  ---
@@ -120,6 +120,31 @@ ios/Intents/
120
120
 
121
121
  ---
122
122
 
123
+ ## Watch mode
124
+
125
+ For iterative development, `axint watch` recompiles on every save with sub-millisecond rebuilds:
126
+
127
+ ```bash
128
+ # Watch a single file
129
+ axint watch my-intent.ts --out ios/Intents/
130
+
131
+ # Watch a directory of intents
132
+ axint watch ./intents/ --out ios/Intents/ --emit-info-plist --emit-entitlements
133
+
134
+ # With swift-format
135
+ axint watch my-intent.ts --out ios/Intents/ --format
136
+
137
+ # Auto-run swift build after each compile
138
+ axint watch ./intents/ --out ios/Intents/ --swift-build
139
+
140
+ # Specify the Swift project root (defaults to --out parent)
141
+ axint watch ./intents/ --out ios/Sources/Intents/ --swift-build --swift-project ios/
142
+ ```
143
+
144
+ The watcher runs an initial compile pass, then re-triggers on file changes with a 150ms debounce. Errors are reported inline without killing the process — fix the file and it recompiles automatically. With `--swift-build`, each successful compile triggers `swift build` in the project directory so you get immediate feedback on whether the generated Swift compiles cleanly.
145
+
146
+ ---
147
+
123
148
  ## Compiled Swift output
124
149
 
125
150
  ```swift
@@ -183,13 +208,15 @@ Axint ships with `axint-mcp`, a Model Context Protocol server that exposes the c
183
208
  }
184
209
  ```
185
210
 
186
- Three tools are exposed:
211
+ Five tools are exposed:
187
212
 
188
- | Tool | What it does |
189
- | ----------------- | -------------------------------------------------------------------------- |
190
- | `axint_scaffold` | Generates a TypeScript intent from a natural-language description |
191
- | `axint_compile` | Runs the full pipeline and returns `.swift` + `.plist` + `.entitlements` |
192
- | `axint_validate` | Dry-run validation with line/column diagnostics |
213
+ | Tool | What it does |
214
+ | --------------------- | -------------------------------------------------------------------------- |
215
+ | `axint_scaffold` | Generates a TypeScript intent from a natural-language description |
216
+ | `axint_compile` | Runs the full pipeline and returns `.swift` + `.plist` + `.entitlements` |
217
+ | `axint_validate` | Dry-run validation with line/column diagnostics |
218
+ | `axint_list_templates`| Lists all bundled intent templates |
219
+ | `axint_template` | Returns the source of a specific bundled template |
193
220
 
194
221
  Once connected, your AI coding agent can read a Swift project, draft an intent, compile it, and open a PR — without a human touching Xcode.
195
222
 
@@ -272,7 +299,7 @@ axint/
272
299
  │ ├── mcp/ # MCP server (scaffold, compile, validate)
273
300
  │ ├── cli/ # axint CLI (Commander.js)
274
301
  │ └── templates/ # Intent template registry
275
- ├── tests/ # 117 vitest tests
302
+ ├── tests/ # 155 vitest tests
276
303
  ├── examples/ # Example intent definitions
277
304
  └── docs/ # Error reference, contributing, assets
278
305
  ```
@@ -300,10 +327,11 @@ See [`ROADMAP.md`](ROADMAP.md) for the full plan. Highlights:
300
327
  - [x] Info.plist and `.entitlements` emit (v0.2.0)
301
328
  - [x] Return-type-aware `perform()` (v0.2.0)
302
329
  - [x] MCP scaffold tool (v0.2.0)
303
- - [x] 152-test suite with snapshot coverage (v0.2.0)
304
- - [ ] Intent template library (v0.3.0)
305
- - [ ] `--watch` mode for live Swift preview
306
- - [ ] Xcode build plugin
330
+ - [x] 155-test suite with snapshot coverage (v0.2.0+)
331
+ - [x] Intent template library with 12+ templates (v0.3.0)
332
+ - [x] `--watch` mode with `--swift-build` for live recompilation (v0.3.0)
333
+ - [x] SPM build plugin — auto-compile during `swift build` (v0.3.0)
334
+ - [x] Registry at registry.axint.ai (v0.3.0)
307
335
  - [ ] GitHub template repo (`axint-starter`)
308
336
  - [ ] Axint Cloud (hosted compilation)
309
337
 
package/dist/cli/index.js CHANGED
@@ -2077,8 +2077,16 @@ var init_server = __esm({
2077
2077
  // src/cli/index.ts
2078
2078
  init_compiler();
2079
2079
  import { Command } from "commander";
2080
- import { readFileSync as readFileSync3, writeFileSync, mkdirSync, existsSync as existsSync3 } from "fs";
2080
+ import {
2081
+ readFileSync as readFileSync3,
2082
+ writeFileSync,
2083
+ mkdirSync,
2084
+ existsSync as existsSync3,
2085
+ watch as fsWatch,
2086
+ statSync
2087
+ } from "fs";
2081
2088
  import { resolve as resolve2, dirname as dirname2, basename } from "path";
2089
+ import { spawn as spawn4 } from "child_process";
2082
2090
  import { fileURLToPath as fileURLToPath2 } from "url";
2083
2091
 
2084
2092
  // src/core/eject.ts
@@ -2343,7 +2351,7 @@ Any agent that supports MCP can now call \`axint_compile\`, \`axint_validate\`,
2343
2351
  - Edit \`intents/${template}.ts\` \u2014 this is your App Intent source of truth.
2344
2352
  - Add more intents in the \`intents/\` folder.
2345
2353
  - Run \`axint templates\` to see every bundled starter.
2346
- - Read the docs at https://axint.ai/docs
2354
+ - Read the docs at https://github.com/agenticempire/axint#readme
2347
2355
 
2348
2356
  ---
2349
2357
 
@@ -2406,7 +2414,9 @@ program.command("init").description("Scaffold a new Axint project (zero-config,
2406
2414
  );
2407
2415
  }
2408
2416
  console.log();
2409
- console.log(` \x1B[2mDocs: https://axint.ai/docs\x1B[0m`);
2417
+ console.log(
2418
+ ` \x1B[2mDocs: https://github.com/agenticempire/axint#readme\x1B[0m`
2419
+ );
2410
2420
  console.log(
2411
2421
  ` \x1B[2mMCP: npx axint-mcp (add to Claude Code, Cursor, Windsurf)\x1B[0m`
2412
2422
  );
@@ -2731,7 +2741,7 @@ program.command("templates").description("List bundled intent templates").argume
2731
2741
  program.command("login").description("Authenticate with the Axint Registry via GitHub").action(async () => {
2732
2742
  const { homedir } = await import("os");
2733
2743
  const { join: join4 } = await import("path");
2734
- const { spawn: spawn4 } = await import("child_process");
2744
+ const { spawn: spawn5 } = await import("child_process");
2735
2745
  const configDir = join4(homedir(), ".axint");
2736
2746
  const credPath = join4(configDir, "credentials.json");
2737
2747
  const registryUrl = process.env.AXINT_REGISTRY_URL ?? "https://registry.axint.ai";
@@ -2760,7 +2770,7 @@ program.command("login").description("Authenticate with the Axint Registry via G
2760
2770
  console.log(` \x1B[2mWaiting for authorization\u2026\x1B[0m`);
2761
2771
  try {
2762
2772
  const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2763
- spawn4(openCmd, [verification_uri], { stdio: "ignore", detached: true }).unref();
2773
+ spawn5(openCmd, [verification_uri], { stdio: "ignore", detached: true }).unref();
2764
2774
  } catch {
2765
2775
  }
2766
2776
  const pollInterval = (interval ?? 5) * 1e3;
@@ -3006,6 +3016,302 @@ program.command("add").description("Install a template from the Axint Registry")
3006
3016
  process.exit(1);
3007
3017
  }
3008
3018
  });
3019
+ program.command("search").description("Search the Axint Registry for intent templates").argument("[query]", "Search term (lists popular packages if omitted)").option("--limit <n>", "Max results", "20").option("--json", "Output as JSON").action(
3020
+ async (query, options) => {
3021
+ const registryUrl = process.env.AXINT_REGISTRY_URL ?? "https://registry.axint.ai";
3022
+ const limit = Math.max(1, Math.min(100, parseInt(options.limit, 10) || 20));
3023
+ console.log();
3024
+ if (!options.json) {
3025
+ console.log(
3026
+ ` \x1B[38;5;208m\u25C6\x1B[0m \x1B[1mAxint\x1B[0m \xB7 search ${query ? `"${query}"` : ""}`
3027
+ );
3028
+ console.log();
3029
+ }
3030
+ try {
3031
+ const params = new URLSearchParams({ limit: String(limit) });
3032
+ if (query) params.set("q", query);
3033
+ const res = await fetch(`${registryUrl}/api/v1/search?${params}`, {
3034
+ headers: { "X-Axint-Version": VERSION }
3035
+ });
3036
+ if (!res.ok) {
3037
+ console.error(`\x1B[31merror:\x1B[0m Search failed (HTTP ${res.status})`);
3038
+ process.exit(1);
3039
+ }
3040
+ const data = await res.json();
3041
+ if (options.json) {
3042
+ console.log(JSON.stringify(data, null, 2));
3043
+ return;
3044
+ }
3045
+ if (data.results.length === 0) {
3046
+ console.log(` No packages found`);
3047
+ console.log();
3048
+ return;
3049
+ }
3050
+ for (const pkg3 of data.results) {
3051
+ const downloads = pkg3.downloads > 0 ? `\u25BC ${pkg3.downloads}` : "";
3052
+ const dl = downloads ? ` \x1B[2m${downloads}\x1B[0m` : "";
3053
+ console.log(
3054
+ ` \x1B[38;5;208m\u25C6\x1B[0m ${pkg3.package_name.padEnd(30)} ${pkg3.description.substring(0, 35).padEnd(35)}${dl}`
3055
+ );
3056
+ }
3057
+ console.log();
3058
+ console.log(
3059
+ ` ${data.results.length} package${data.results.length === 1 ? "" : "s"} found`
3060
+ );
3061
+ console.log();
3062
+ console.log(
3063
+ ` \x1B[2mInstall:\x1B[0m axint add ${data.results[0]?.package_name ?? "@namespace/slug"}`
3064
+ );
3065
+ console.log();
3066
+ } catch (err) {
3067
+ console.error(`\x1B[31merror:\x1B[0m ${err.message ?? err}`);
3068
+ process.exit(1);
3069
+ }
3070
+ }
3071
+ );
3072
+ async function runSwiftBuild2(projectPath) {
3073
+ return new Promise((resolve3) => {
3074
+ const t0 = performance.now();
3075
+ const proc = spawn4("swift", ["build"], {
3076
+ cwd: projectPath,
3077
+ stdio: ["ignore", "inherit", "inherit"]
3078
+ });
3079
+ proc.on("close", (code) => {
3080
+ if (code === 0) {
3081
+ const dt = (performance.now() - t0).toFixed(0);
3082
+ console.log();
3083
+ console.log(`\x1B[38;5;208m\u2500 swift build\x1B[0m`);
3084
+ console.log(`\x1B[32m\u2713\x1B[0m Build succeeded \x1B[90m(${dt}ms)\x1B[0m`);
3085
+ console.log();
3086
+ resolve3(true);
3087
+ } else {
3088
+ console.log();
3089
+ console.log(`\x1B[38;5;208m\u2500 swift build\x1B[0m`);
3090
+ console.log(`\x1B[31m\u2717\x1B[0m Build failed (exit code: ${code})`);
3091
+ console.log("\x1B[90mContinuing to watch for changes\u2026\x1B[0m");
3092
+ console.log();
3093
+ resolve3(false);
3094
+ }
3095
+ });
3096
+ proc.on("error", (err) => {
3097
+ console.log();
3098
+ console.log(`\x1B[38;5;208m\u2500 swift build\x1B[0m`);
3099
+ console.error(`\x1B[31m\u2717\x1B[0m Error: ${err.message}`);
3100
+ console.log("\x1B[90mContinuing to watch for changes\u2026\x1B[0m");
3101
+ console.log();
3102
+ resolve3(false);
3103
+ });
3104
+ });
3105
+ }
3106
+ program.command("watch").description("Watch intent files and recompile on change").argument("<file>", "Path to a TypeScript intent file or directory of intents").option("-o, --out <dir>", "Output directory for generated Swift", ".").option("--no-validate", "Skip validation of generated Swift").option("--emit-info-plist", "Emit Info.plist fragments alongside Swift files").option("--emit-entitlements", "Emit entitlements fragments alongside Swift files").option("--format", "Pipe generated Swift through swift-format").option(
3107
+ "--strict-format",
3108
+ "Fail if swift-format is missing or errors (implies --format)"
3109
+ ).option(
3110
+ "--swift-build",
3111
+ "Run `swift build` in the project after successful compilation"
3112
+ ).option(
3113
+ "--swift-project <path>",
3114
+ "Path to the Swift project root (defaults to --out parent directory)"
3115
+ ).action(
3116
+ async (file, options) => {
3117
+ const target = resolve2(file);
3118
+ const isDir = existsSync3(target) && statSync(target).isDirectory();
3119
+ const filesToWatch = [];
3120
+ if (isDir) {
3121
+ const { readdirSync } = await import("fs");
3122
+ for (const entry of readdirSync(target)) {
3123
+ if (entry.endsWith(".ts") && !entry.endsWith(".d.ts")) {
3124
+ filesToWatch.push(resolve2(target, entry));
3125
+ }
3126
+ }
3127
+ if (filesToWatch.length === 0) {
3128
+ console.error(`\x1B[31merror:\x1B[0m No .ts files found in ${target}`);
3129
+ process.exit(1);
3130
+ }
3131
+ } else {
3132
+ if (!existsSync3(target)) {
3133
+ console.error(`\x1B[31merror:\x1B[0m File not found: ${target}`);
3134
+ process.exit(1);
3135
+ }
3136
+ filesToWatch.push(target);
3137
+ }
3138
+ const swiftProjectPath = options.swiftProject ?? dirname2(resolve2(options.out));
3139
+ function compileOne(filePath) {
3140
+ const t0 = performance.now();
3141
+ const result = compileFile(filePath, {
3142
+ outDir: options.out,
3143
+ validate: options.validate,
3144
+ emitInfoPlist: options.emitInfoPlist,
3145
+ emitEntitlements: options.emitEntitlements
3146
+ });
3147
+ for (const d of result.diagnostics) {
3148
+ const prefix = d.severity === "error" ? "\x1B[31merror\x1B[0m" : d.severity === "warning" ? "\x1B[33mwarning\x1B[0m" : "\x1B[36minfo\x1B[0m";
3149
+ console.error(` ${prefix}[${d.code}]: ${d.message}`);
3150
+ if (d.file) console.error(` --> ${d.file}${d.line ? `:${d.line}` : ""}`);
3151
+ if (d.suggestion) console.error(` = help: ${d.suggestion}`);
3152
+ }
3153
+ if (!result.success || !result.output) {
3154
+ const errors = result.diagnostics.filter((d) => d.severity === "error").length;
3155
+ console.error(`\x1B[31m\u2717\x1B[0m ${basename(filePath)} \u2014 ${errors} error(s)`);
3156
+ return false;
3157
+ }
3158
+ const swiftCode = result.output.swiftCode;
3159
+ if (options.format || options.strictFormat) {
3160
+ }
3161
+ const outPath = resolve2(result.output.outputPath);
3162
+ mkdirSync(dirname2(outPath), { recursive: true });
3163
+ writeFileSync(outPath, swiftCode, "utf-8");
3164
+ if (options.emitInfoPlist && result.output.infoPlistFragment) {
3165
+ const plistPath = outPath.replace(/\.swift$/, ".plist.fragment.xml");
3166
+ writeFileSync(plistPath, result.output.infoPlistFragment, "utf-8");
3167
+ }
3168
+ if (options.emitEntitlements && result.output.entitlementsFragment) {
3169
+ const entPath = outPath.replace(/\.swift$/, ".entitlements.fragment.xml");
3170
+ writeFileSync(entPath, result.output.entitlementsFragment, "utf-8");
3171
+ }
3172
+ const dt = (performance.now() - t0).toFixed(1);
3173
+ console.log(
3174
+ `\x1B[32m\u2713\x1B[0m ${result.output.ir.name} \u2192 ${outPath} \x1B[90m(${dt}ms)\x1B[0m`
3175
+ );
3176
+ return true;
3177
+ }
3178
+ async function compileWithFormat(filePath) {
3179
+ if (!options.format && !options.strictFormat) {
3180
+ return compileOne(filePath);
3181
+ }
3182
+ const t0 = performance.now();
3183
+ const result = compileFile(filePath, {
3184
+ outDir: options.out,
3185
+ validate: options.validate,
3186
+ emitInfoPlist: options.emitInfoPlist,
3187
+ emitEntitlements: options.emitEntitlements
3188
+ });
3189
+ for (const d of result.diagnostics) {
3190
+ const prefix = d.severity === "error" ? "\x1B[31merror\x1B[0m" : d.severity === "warning" ? "\x1B[33mwarning\x1B[0m" : "\x1B[36minfo\x1B[0m";
3191
+ console.error(` ${prefix}[${d.code}]: ${d.message}`);
3192
+ if (d.file) console.error(` --> ${d.file}${d.line ? `:${d.line}` : ""}`);
3193
+ if (d.suggestion) console.error(` = help: ${d.suggestion}`);
3194
+ }
3195
+ if (!result.success || !result.output) {
3196
+ const errors = result.diagnostics.filter((d) => d.severity === "error").length;
3197
+ console.error(`\x1B[31m\u2717\x1B[0m ${basename(filePath)} \u2014 ${errors} error(s)`);
3198
+ return false;
3199
+ }
3200
+ let swiftCode = result.output.swiftCode;
3201
+ try {
3202
+ const { formatSwift: formatSwift2 } = await Promise.resolve().then(() => (init_format(), format_exports));
3203
+ const fmt = await formatSwift2(swiftCode, { strict: options.strictFormat });
3204
+ if (fmt.ran) swiftCode = fmt.formatted;
3205
+ } catch (fmtErr) {
3206
+ if (options.strictFormat) {
3207
+ console.error(`\x1B[31merror:\x1B[0m ${fmtErr.message}`);
3208
+ return false;
3209
+ }
3210
+ }
3211
+ const outPath = resolve2(result.output.outputPath);
3212
+ mkdirSync(dirname2(outPath), { recursive: true });
3213
+ writeFileSync(outPath, swiftCode, "utf-8");
3214
+ if (options.emitInfoPlist && result.output.infoPlistFragment) {
3215
+ writeFileSync(
3216
+ outPath.replace(/\.swift$/, ".plist.fragment.xml"),
3217
+ result.output.infoPlistFragment,
3218
+ "utf-8"
3219
+ );
3220
+ }
3221
+ if (options.emitEntitlements && result.output.entitlementsFragment) {
3222
+ writeFileSync(
3223
+ outPath.replace(/\.swift$/, ".entitlements.fragment.xml"),
3224
+ result.output.entitlementsFragment,
3225
+ "utf-8"
3226
+ );
3227
+ }
3228
+ const dt = (performance.now() - t0).toFixed(1);
3229
+ console.log(
3230
+ `\x1B[32m\u2713\x1B[0m ${result.output.ir.name} \u2192 ${outPath} \x1B[90m(${dt}ms)\x1B[0m`
3231
+ );
3232
+ return true;
3233
+ }
3234
+ console.log(`\x1B[1maxint watch\x1B[0m \u2014 ${filesToWatch.length} file(s)
3235
+ `);
3236
+ let ok = 0;
3237
+ let fail = 0;
3238
+ for (const f of filesToWatch) {
3239
+ if (await compileWithFormat(f)) {
3240
+ ok++;
3241
+ } else {
3242
+ fail++;
3243
+ }
3244
+ }
3245
+ console.log();
3246
+ if (fail > 0) {
3247
+ console.log(
3248
+ `\x1B[33m\u26A0\x1B[0m ${ok} compiled, ${fail} failed \u2014 watching for changes\u2026
3249
+ `
3250
+ );
3251
+ } else {
3252
+ console.log(`\x1B[32m\u2713\x1B[0m ${ok} compiled \u2014 watching for changes\u2026
3253
+ `);
3254
+ if (options.swiftBuild) {
3255
+ await runSwiftBuild2(swiftProjectPath);
3256
+ }
3257
+ }
3258
+ const pending = /* @__PURE__ */ new Map();
3259
+ const DEBOUNCE_MS = 150;
3260
+ let batchInProgress = false;
3261
+ function onFileChange(filePath) {
3262
+ const existing = pending.get(filePath);
3263
+ if (existing) clearTimeout(existing);
3264
+ pending.set(
3265
+ filePath,
3266
+ setTimeout(async () => {
3267
+ pending.delete(filePath);
3268
+ if (batchInProgress) return;
3269
+ batchInProgress = true;
3270
+ try {
3271
+ const now = (/* @__PURE__ */ new Date()).toLocaleTimeString();
3272
+ console.log(`\x1B[90m[${now}]\x1B[0m ${basename(filePath)} changed`);
3273
+ const compiled = await compileWithFormat(filePath);
3274
+ console.log();
3275
+ if (compiled && options.swiftBuild) {
3276
+ await runSwiftBuild2(swiftProjectPath);
3277
+ }
3278
+ } finally {
3279
+ batchInProgress = false;
3280
+ }
3281
+ }, DEBOUNCE_MS)
3282
+ );
3283
+ }
3284
+ if (isDir) {
3285
+ const dirWatcher = fsWatch(target, { persistent: true }, (_event, filename) => {
3286
+ if (filename && typeof filename === "string" && filename.endsWith(".ts") && !filename.endsWith(".d.ts")) {
3287
+ onFileChange(resolve2(target, filename));
3288
+ }
3289
+ });
3290
+ dirWatcher.on("error", (err) => {
3291
+ console.error(`\x1B[31m\u2717\x1B[0m watcher error: ${err.message}`);
3292
+ });
3293
+ } else {
3294
+ const parentDir = dirname2(target);
3295
+ const targetBase = basename(target);
3296
+ const fileWatcher = fsWatch(
3297
+ parentDir,
3298
+ { persistent: true },
3299
+ (_event, filename) => {
3300
+ if (filename === targetBase) {
3301
+ onFileChange(target);
3302
+ }
3303
+ }
3304
+ );
3305
+ fileWatcher.on("error", (err) => {
3306
+ console.error(`\x1B[31m\u2717\x1B[0m watcher error: ${err.message}`);
3307
+ });
3308
+ }
3309
+ process.on("SIGINT", () => {
3310
+ console.log("\n\x1B[90mStopped watching.\x1B[0m");
3311
+ process.exit(0);
3312
+ });
3313
+ }
3314
+ );
3009
3315
  program.command("mcp").description(
3010
3316
  "Start the Axint MCP server (stdio) for Claude Code, Cursor, Windsurf, Zed, or any MCP client"
3011
3317
  ).action(async () => {