nativeapptemplate-agent 0.1.1 → 0.2.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 (99) hide show
  1. package/CLAUDE.md +9 -7
  2. package/README.md +65 -21
  3. package/dist/agents/judge.d.ts +8 -0
  4. package/dist/agents/judge.d.ts.map +1 -1
  5. package/dist/agents/judge.js +100 -14
  6. package/dist/agents/judge.js.map +1 -1
  7. package/dist/agents/repair.d.ts +14 -0
  8. package/dist/agents/repair.d.ts.map +1 -0
  9. package/dist/agents/repair.js +103 -0
  10. package/dist/agents/repair.js.map +1 -0
  11. package/dist/agents/types.d.ts +34 -1
  12. package/dist/agents/types.d.ts.map +1 -1
  13. package/dist/dispatch.d.ts +21 -2
  14. package/dist/dispatch.d.ts.map +1 -1
  15. package/dist/dispatch.js +250 -14
  16. package/dist/dispatch.js.map +1 -1
  17. package/dist/env-bridge.d.ts +17 -0
  18. package/dist/env-bridge.d.ts.map +1 -0
  19. package/dist/env-bridge.js +211 -0
  20. package/dist/env-bridge.js.map +1 -0
  21. package/dist/index.d.ts +11 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +88 -3
  24. package/dist/index.js.map +1 -1
  25. package/dist/mcp.d.ts +5 -0
  26. package/dist/mcp.d.ts.map +1 -0
  27. package/dist/mcp.js +83 -0
  28. package/dist/mcp.js.map +1 -0
  29. package/dist/mobile.d.ts +30 -0
  30. package/dist/mobile.d.ts.map +1 -0
  31. package/dist/mobile.js +173 -0
  32. package/dist/mobile.js.map +1 -0
  33. package/dist/rails-lifecycle.d.ts +16 -0
  34. package/dist/rails-lifecycle.d.ts.map +1 -0
  35. package/dist/rails-lifecycle.js +184 -0
  36. package/dist/rails-lifecycle.js.map +1 -0
  37. package/dist/rename-overrides.d.ts +23 -0
  38. package/dist/rename-overrides.d.ts.map +1 -0
  39. package/dist/rename-overrides.js +58 -0
  40. package/dist/rename-overrides.js.map +1 -0
  41. package/dist/repair-loop.d.ts +25 -0
  42. package/dist/repair-loop.d.ts.map +1 -0
  43. package/dist/repair-loop.js +67 -0
  44. package/dist/repair-loop.js.map +1 -0
  45. package/dist/report/collect.d.ts +28 -0
  46. package/dist/report/collect.d.ts.map +1 -0
  47. package/dist/report/collect.js +117 -0
  48. package/dist/report/collect.js.map +1 -0
  49. package/dist/report/model.d.ts +49 -0
  50. package/dist/report/model.d.ts.map +1 -0
  51. package/dist/report/model.js +2 -0
  52. package/dist/report/model.js.map +1 -0
  53. package/dist/report/render.d.ts +3 -0
  54. package/dist/report/render.d.ts.map +1 -0
  55. package/dist/report/render.js +242 -0
  56. package/dist/report/render.js.map +1 -0
  57. package/dist/report/theme.d.ts +2 -0
  58. package/dist/report/theme.d.ts.map +1 -0
  59. package/dist/report/theme.js +115 -0
  60. package/dist/report/theme.js.map +1 -0
  61. package/dist/ruby.d.ts.map +1 -1
  62. package/dist/ruby.js +11 -2
  63. package/dist/ruby.js.map +1 -1
  64. package/dist/slug.d.ts +3 -0
  65. package/dist/slug.d.ts.map +1 -1
  66. package/dist/slug.js +30 -0
  67. package/dist/slug.js.map +1 -1
  68. package/dist/validation/capture.d.ts.map +1 -1
  69. package/dist/validation/capture.js +19 -2
  70. package/dist/validation/capture.js.map +1 -1
  71. package/dist/validation/index.d.ts +6 -0
  72. package/dist/validation/index.d.ts.map +1 -1
  73. package/dist/validation/index.js +3 -0
  74. package/dist/validation/index.js.map +1 -1
  75. package/dist/validation/launch.d.ts +10 -0
  76. package/dist/validation/launch.d.ts.map +1 -1
  77. package/dist/validation/launch.js +103 -5
  78. package/dist/validation/launch.js.map +1 -1
  79. package/dist/validation/layer1.d.ts.map +1 -1
  80. package/dist/validation/layer1.js +12 -4
  81. package/dist/validation/layer1.js.map +1 -1
  82. package/dist/validation/scenarios/queue.d.ts +12 -0
  83. package/dist/validation/scenarios/queue.d.ts.map +1 -0
  84. package/dist/validation/scenarios/queue.js +204 -0
  85. package/dist/validation/scenarios/queue.js.map +1 -0
  86. package/dist/validation/stage2-judge.d.ts +21 -0
  87. package/dist/validation/stage2-judge.d.ts.map +1 -0
  88. package/dist/validation/stage2-judge.js +204 -0
  89. package/dist/validation/stage2-judge.js.map +1 -0
  90. package/dist/validation/stage2.d.ts +73 -0
  91. package/dist/validation/stage2.d.ts.map +1 -0
  92. package/dist/validation/stage2.js +313 -0
  93. package/dist/validation/stage2.js.map +1 -0
  94. package/dist/version.d.ts +2 -0
  95. package/dist/version.d.ts.map +1 -0
  96. package/dist/version.js +17 -0
  97. package/dist/version.js.map +1 -0
  98. package/package.json +6 -3
  99. package/scripts/ruby/rename.rb +18 -8
package/dist/index.js CHANGED
@@ -1,23 +1,108 @@
1
1
  #!/usr/bin/env node
2
2
  import { realpathSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { spawn } from "node:child_process";
4
5
  import { dispatch } from "./dispatch.js";
5
6
  import { loadDotenvIfPresent } from "./env.js";
7
+ import { parseRenamePair } from "./rename-overrides.js";
8
+ import { projectNameToSlug, slugToPascal, isValidSlug } from "./slug.js";
6
9
  loadDotenvIfPresent();
10
+ export function parseArgs(argv) {
11
+ const specParts = [];
12
+ const report = {};
13
+ const renameOverrides = [];
14
+ let open = false;
15
+ let exitZero = false;
16
+ let projectName;
17
+ for (let i = 0; i < argv.length; i++) {
18
+ const arg = argv[i];
19
+ if (arg === undefined)
20
+ continue;
21
+ if (arg === "--no-report")
22
+ report.enabled = false;
23
+ else if (arg === "--report-open")
24
+ open = true;
25
+ else if (arg === "--exit-zero")
26
+ exitZero = true;
27
+ else if (arg.startsWith("--report-format=")) {
28
+ const value = arg.slice("--report-format=".length);
29
+ if (value === "html" || value === "json" || value === "both")
30
+ report.format = value;
31
+ }
32
+ else if (arg.startsWith("--report-embed=")) {
33
+ report.embed = arg.slice("--report-embed=".length) !== "false";
34
+ }
35
+ else if (arg === "--rename" || arg.startsWith("--rename=")) {
36
+ // Accept both `--rename From=To` (per ROADMAP) and `--rename=From=To`.
37
+ const raw = arg === "--rename" ? argv[++i] : arg.slice("--rename=".length);
38
+ const pair = parseRenamePair(raw);
39
+ if (pair)
40
+ renameOverrides.push(pair);
41
+ else
42
+ console.error(`warning: ignoring malformed --rename "${raw ?? ""}" (expected From=To, e.g. --rename Shop=Clinic)`);
43
+ }
44
+ else if (arg === "--project-name" || arg.startsWith("--project-name=")) {
45
+ const raw = (arg === "--project-name" ? argv[++i] : arg.slice("--project-name=".length))?.trim();
46
+ // Accept any of "Vet Clinic" / "VetClinic" / "vet-clinic" — the slug is
47
+ // derived in dispatch. Reject only if it yields no valid slug at all.
48
+ if (raw && isValidSlug(projectNameToSlug(raw)))
49
+ projectName = raw;
50
+ else
51
+ console.error(`warning: ignoring invalid --project-name "${raw ?? ""}" (e.g. --project-name="Vet Clinic")`);
52
+ }
53
+ else {
54
+ specParts.push(arg);
55
+ }
56
+ }
57
+ return { spec: specParts.join(" ").trim(), report, open, exitZero, renameOverrides, ...(projectName !== undefined ? { projectName } : {}) };
58
+ }
7
59
  export async function main(spec) {
8
- const input = spec ?? process.argv.slice(2).join(" ").trim();
60
+ const parsed = parseArgs(process.argv.slice(2));
61
+ const input = (spec ?? parsed.spec).trim();
9
62
  if (!input) {
10
- console.error('Usage: nativeapptemplate-agent "your spec here"');
63
+ console.error('Usage: nativeapptemplate-agent "your spec here" [--project-name="Vet Clinic"] [--rename From=To]... [--no-report] [--report-format=html|json|both] [--report-embed=true|false] [--report-open] [--exit-zero]');
11
64
  process.exitCode = 1;
12
65
  return;
13
66
  }
14
67
  console.log(`nativeapptemplate-agent: received spec: ${input}`);
15
68
  console.log('(tail tmp/trace/*.log in a tiled view via scripts/demo-tmux.sh)');
16
- const result = await dispatch(input);
69
+ const result = await dispatch(input, {
70
+ report: parsed.report,
71
+ renameOverrides: parsed.renameOverrides,
72
+ ...(parsed.projectName !== undefined ? { projectName: parsed.projectName } : {}),
73
+ });
74
+ if (parsed.projectName !== undefined) {
75
+ const finalSlug = result.report.meta.slug;
76
+ console.log(`project: ${slugToPascal(finalSlug)} (output out/${finalSlug}/)`);
77
+ }
78
+ for (const o of result.renameOverrideOutcomes) {
79
+ if (o.kind === "changed")
80
+ console.log(`override: ${o.from} → ${o.to} (overrode planner's "${o.was}")`);
81
+ else if (o.kind === "noop")
82
+ console.log(`override: ${o.from} → ${o.to} (already the planner's pick)`);
83
+ else {
84
+ const sources = result.report.domain.renamePlan.map((p) => p.from).join(", ");
85
+ console.error(`warning: --rename ${o.from}=${o.to} matched no planned rename — skipped (renamable: ${sources || "none"})`);
86
+ }
87
+ }
17
88
  console.log('');
18
89
  console.log('=== run complete ===');
19
90
  console.log(`result: ${result.summary}`);
20
91
  console.log(`overall: ${result.overallPass ? 'PASS' : 'FAIL'}`);
92
+ if (result.reportPaths.htmlPath) {
93
+ console.log(`report: file://${result.reportPaths.htmlPath}`);
94
+ if (parsed.open && process.platform === 'darwin') {
95
+ spawn('open', [result.reportPaths.htmlPath], { stdio: 'ignore', detached: true }).unref();
96
+ }
97
+ }
98
+ else if (result.reportPaths.jsonPath) {
99
+ console.log(`report: ${result.reportPaths.jsonPath}`);
100
+ }
101
+ // Non-zero exit on validation failure so CI / shell `&&` chains catch
102
+ // it. --exit-zero opts out (e.g. when you only want the report).
103
+ if (!result.overallPass && !parsed.exitZero) {
104
+ process.exitCode = 1;
105
+ }
21
106
  }
22
107
  // Entry guard: run main() when this file is the program entry point. Resolve
23
108
  // both sides to canonical (symlink-followed) paths so npm bin symlinks (e.g.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAE/C,mBAAmB,EAAE,CAAC;AAEtB,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAa;IACtC,MAAM,KAAK,GAAG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACjE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,2CAA2C,KAAK,EAAE,CAAC,CAAC;IAChE,OAAO,CAAC,GAAG,CAAC,iEAAiE,CAAC,CAAC;IAE/E,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC;IAErC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IACpC,OAAO,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AAClE,CAAC;AAED,6EAA6E;AAC7E,6EAA6E;AAC7E,+EAA+E;AAC/E,0EAA0E;AAC1E,0EAA0E;AAC1E,+BAA+B;AAC/B,IAAI,YAAY,EAAE,EAAE,CAAC;IACnB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,YAAY;IACnB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClD,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QACtC,OAAO,SAAS,KAAK,UAAU,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ export declare function createMcpServer(): McpServer;
4
+ export declare function main(): Promise<void>;
5
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAcpE,wBAAgB,eAAe,IAAI,SAAS,CAsD3C;AAED,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAK1C"}
package/dist/mcp.js ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ import { realpathSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z } from "zod";
7
+ import { dispatch } from "./dispatch.js";
8
+ import { loadDotenvIfPresent } from "./env.js";
9
+ import { readPackageVersion } from "./version.js";
10
+ // MCP surface (MONETIZATION.md §"MCP as a distribution surface"):
11
+ // thin wrapper around dispatch() so any MCP-compatible AI assistant
12
+ // (Claude Code, Cursor, Cline, Continue, Goose, ...) can invoke the
13
+ // agent as a tool. Same backend as the CLI; different wire format.
14
+ //
15
+ // Run via: `npx -y nativeapptemplate-agent-mcp` from an MCP client config.
16
+ export function createMcpServer() {
17
+ const server = new McpServer({
18
+ name: "nativeapptemplate-agent",
19
+ version: readPackageVersion(),
20
+ });
21
+ server.registerTool("generate_app", {
22
+ title: "Generate three-platform SaaS app",
23
+ description: "Generate a working three-platform SaaS app from a natural-language spec — Rails 8.1 API + SwiftUI iOS + Jetpack Compose Android. Validated end-to-end (rename completeness, build, vision judge). Output lands in ./out/<spec-slug>/{rails,ios,android}/. Each invocation runs ~3-5 minutes and consumes Anthropic API usage on the configured key, separate from the calling assistant's session.",
24
+ inputSchema: {
25
+ spec: z
26
+ .string()
27
+ .min(1)
28
+ .describe('Natural-language SaaS spec, e.g. "a walk-in queue for a barbershop"'),
29
+ renameOverrides: z
30
+ .array(z.object({ from: z.string().min(1), to: z.string().min(1) }))
31
+ .optional()
32
+ .describe('Optional manual rename overrides keyed on the substrate token (from = "Shop" | "Shopkeeper" | "ItemTag"), each replacing the planner\'s chosen target. Overrides that match no planned rename are ignored.'),
33
+ projectName: z
34
+ .string()
35
+ .optional()
36
+ .describe('Optional project name (e.g. "Vet Clinic" or "VetClinic"). Overrides the planner\'s name; the slug — which sets the output directory, DB prefix, and the Pascal project name across all three platforms — and the display name are derived from it. Ignored if it yields no valid slug.'),
37
+ },
38
+ }, async ({ spec, renameOverrides, projectName }) => {
39
+ const result = await dispatch(spec, {
40
+ ...(renameOverrides ? { renameOverrides } : {}),
41
+ ...(projectName !== undefined ? { projectName } : {}),
42
+ });
43
+ return {
44
+ content: [{ type: "text", text: result.summary }],
45
+ structuredContent: {
46
+ overallPass: result.overallPass,
47
+ summary: result.summary,
48
+ ...(result.visual ? { visual: result.visual } : {}),
49
+ report: result.report,
50
+ ...(result.reportPaths.htmlPath ? { reportHtmlPath: result.reportPaths.htmlPath } : {}),
51
+ ...(result.reportPaths.jsonPath ? { reportJsonPath: result.reportPaths.jsonPath } : {}),
52
+ },
53
+ isError: !result.overallPass,
54
+ };
55
+ });
56
+ return server;
57
+ }
58
+ export async function main() {
59
+ loadDotenvIfPresent();
60
+ const server = createMcpServer();
61
+ const transport = new StdioServerTransport();
62
+ await server.connect(transport);
63
+ }
64
+ if (isEntryPoint()) {
65
+ main().catch((err) => {
66
+ console.error(err);
67
+ process.exitCode = 1;
68
+ });
69
+ }
70
+ function isEntryPoint() {
71
+ const argv1 = process.argv[1];
72
+ if (!argv1)
73
+ return false;
74
+ try {
75
+ const modulePath = fileURLToPath(import.meta.url);
76
+ const argv1Real = realpathSync(argv1);
77
+ return argv1Real === modulePath;
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ export type MobilePlatform = "ios" | "android";
4
+ export type ScreenElement = {
5
+ readonly [key: string]: unknown;
6
+ };
7
+ export type Screenshot = {
8
+ readonly data: Buffer;
9
+ readonly mimeType: string;
10
+ };
11
+ export type MobileClient = {
12
+ callTool(name: string, args?: Record<string, unknown>): Promise<CallToolResult>;
13
+ useDevice(name: string | undefined): void;
14
+ listDevices(): Promise<readonly ScreenElement[]>;
15
+ listElements(): Promise<readonly ScreenElement[]>;
16
+ click(x: number, y: number): Promise<void>;
17
+ typeKeys(text: string, submit?: boolean): Promise<void>;
18
+ pressButton(button: string): Promise<void>;
19
+ takeScreenshot(): Promise<Screenshot>;
20
+ saveScreenshot(absolutePath: string): Promise<void>;
21
+ close(): Promise<void>;
22
+ };
23
+ export type MobileClientOptions = {
24
+ command?: string;
25
+ args?: readonly string[];
26
+ };
27
+ export declare function createMobileClient(opts?: MobileClientOptions): Promise<MobileClient>;
28
+ export declare function attachMobileClient(client: Client): MobileClient;
29
+ export declare function isStubMobile(): boolean;
30
+ //# sourceMappingURL=mobile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mobile.d.ts","sourceRoot":"","sources":["../src/mobile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAEnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAgBzE,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,SAAS,CAAC;AAE/C,MAAM,MAAM,aAAa,GAAG;IAI1B,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IAEvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAKhF,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IAC1C,WAAW,IAAI,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC,CAAC;IACjD,YAAY,IAAI,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC,CAAC;IAClD,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAK3C,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,cAAc,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IACtC,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAGhC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAAC;AAmBF,wBAAsB,kBAAkB,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,CAAC,CAY1F;AAKD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAE/D;AAED,wBAAgB,YAAY,IAAI,OAAO,CAKtC"}
package/dist/mobile.js ADDED
@@ -0,0 +1,173 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import { scrubbedEnv } from "./env.js";
4
+ const DEFAULT_COMMAND = "npx";
5
+ // Pinned to 0.0.54 (was @latest). mobile-mcp 0.0.55+ closes the stdio
6
+ // connection during the MCP `initialize` handshake with our pinned
7
+ // @modelcontextprotocol/sdk — reproduced 2026-05-23: 0.0.54 connects and
8
+ // lists tools; 0.0.55 and 0.0.56 both fail with "MCP error -32000:
9
+ // Connection closed" before any tool call. The wrapper's tool-shape parsing
10
+ // (envelope/prefix handling, `submit`/`device` args) is also written against
11
+ // 0.0.54. Re-validate the handshake + tool schemas (and likely bump the SDK)
12
+ // before moving this version forward.
13
+ const DEFAULT_ARGS = ["-y", "@mobilenext/mobile-mcp@0.0.54"];
14
+ // Lazy: spawns mobile-mcp on call, returns a typed client. close() shuts
15
+ // the subprocess down cleanly.
16
+ //
17
+ // Honors NATIVEAPPTEMPLATE_STUB_MOBILE=1 (and NATIVEAPPTEMPLATE_STUB_ALL=1)
18
+ // to short-circuit to a no-op client so unit tests don't need a sim or an
19
+ // npx download.
20
+ export async function createMobileClient(opts) {
21
+ if (isStubMobile())
22
+ return createStubMobileClient();
23
+ const transport = new StdioClientTransport({
24
+ command: opts?.command ?? DEFAULT_COMMAND,
25
+ args: [...(opts?.args ?? DEFAULT_ARGS)],
26
+ env: scrubbedEnv(),
27
+ stderr: "pipe",
28
+ });
29
+ const client = new Client({ name: "nativeapptemplate-agent-stage2", version: "0.1.0" });
30
+ await client.connect(transport);
31
+ return attachMobileClient(client);
32
+ }
33
+ // Wraps an already-connected MCP Client. Exported so tests can pair the
34
+ // wrapper with an in-memory MCP server impersonating mobile-mcp, without
35
+ // spawning npx. Production callers should use createMobileClient().
36
+ export function attachMobileClient(client) {
37
+ return wrapClient(client);
38
+ }
39
+ export function isStubMobile() {
40
+ return (process.env["NATIVEAPPTEMPLATE_STUB_ALL"] === "1" ||
41
+ process.env["NATIVEAPPTEMPLATE_STUB_MOBILE"] === "1");
42
+ }
43
+ // Tools that operate at the mobile-mcp server level rather than against
44
+ // a specific device. These don't take a `device` argument and would
45
+ // fail validation if we injected one.
46
+ const DEVICE_INDEPENDENT_TOOLS = new Set([
47
+ "mobile_list_available_devices",
48
+ ]);
49
+ function wrapClient(client) {
50
+ let activeDevice;
51
+ const callTool = async (name, args) => {
52
+ const merged = activeDevice !== undefined && !DEVICE_INDEPENDENT_TOOLS.has(name)
53
+ ? { device: activeDevice, ...(args ?? {}) }
54
+ : (args ?? {});
55
+ const result = (await client.callTool({
56
+ name,
57
+ arguments: merged,
58
+ }));
59
+ if (result.isError) {
60
+ throw new Error(`mobile-mcp ${name} failed: ${textOf(result)}`);
61
+ }
62
+ return result;
63
+ };
64
+ return {
65
+ callTool,
66
+ useDevice(name) {
67
+ activeDevice = name;
68
+ },
69
+ async listDevices() {
70
+ const result = await callTool("mobile_list_available_devices");
71
+ // mobile-mcp 0.0.54+ returns {"devices": [...]} (envelope object,
72
+ // not a bare array). Earlier behavior was a bare array. Accept
73
+ // either so the wrapper survives version drift in either direction.
74
+ const text = textOf(result);
75
+ if (!text)
76
+ return [];
77
+ try {
78
+ const parsed = JSON.parse(text);
79
+ if (Array.isArray(parsed))
80
+ return parsed;
81
+ if (parsed && typeof parsed === "object" && Array.isArray(parsed.devices)) {
82
+ return parsed.devices;
83
+ }
84
+ return [];
85
+ }
86
+ catch {
87
+ return [];
88
+ }
89
+ },
90
+ async listElements() {
91
+ const result = await callTool("mobile_list_elements_on_screen");
92
+ // mobile-mcp 0.0.54 returns "Found these elements on screen: [...]"
93
+ // — a human-readable text prefix followed by a JSON array, not
94
+ // pure JSON. Slice from the first '[' so JSON.parse sees just
95
+ // the array. Defensive against the prefix being absent in other
96
+ // versions too.
97
+ const text = textOf(result);
98
+ if (!text)
99
+ return [];
100
+ const arrayStart = text.indexOf("[");
101
+ if (arrayStart === -1)
102
+ return [];
103
+ try {
104
+ const parsed = JSON.parse(text.slice(arrayStart));
105
+ return Array.isArray(parsed) ? parsed : [];
106
+ }
107
+ catch {
108
+ return [];
109
+ }
110
+ },
111
+ async click(x, y) {
112
+ // mobile-mcp 0.0.54's iOS backend (mobilecli `io tap`) rejects
113
+ // non-integer coordinates. centerOf often produces .5 values for
114
+ // even-width elements; floor them at the tool boundary so callers
115
+ // don't have to think about it.
116
+ await callTool("mobile_click_on_screen_at_coordinates", {
117
+ x: Math.floor(x),
118
+ y: Math.floor(y),
119
+ });
120
+ },
121
+ async typeKeys(text, submit = false) {
122
+ // mobile-mcp 0.0.54 requires `submit: boolean` — passing only
123
+ // {text} fails Zod validation with "expected boolean, received
124
+ // undefined". Default false; opt in to true via the second
125
+ // arg when the caller wants the keyboard's return/submit key
126
+ // tapped after typing (form-submit / keyboard-dismiss).
127
+ await callTool("mobile_type_keys", { text, submit });
128
+ },
129
+ async pressButton(button) {
130
+ await callTool("mobile_press_button", { button });
131
+ },
132
+ async takeScreenshot() {
133
+ const result = await callTool("mobile_take_screenshot");
134
+ const image = (result.content ?? []).find((c) => c.type === "image");
135
+ if (!image || typeof image.data !== "string") {
136
+ throw new Error("mobile-mcp mobile_take_screenshot returned no image data");
137
+ }
138
+ return {
139
+ data: Buffer.from(image.data, "base64"),
140
+ mimeType: typeof image.mimeType === "string" ? image.mimeType : "image/png",
141
+ };
142
+ },
143
+ async saveScreenshot(absolutePath) {
144
+ await callTool("mobile_save_screenshot", { saveTo: absolutePath });
145
+ },
146
+ async close() {
147
+ await client.close();
148
+ },
149
+ };
150
+ }
151
+ function textOf(result) {
152
+ for (const c of result.content ?? []) {
153
+ if (c.type === "text" && typeof c.text === "string")
154
+ return c.text;
155
+ }
156
+ return "";
157
+ }
158
+ function createStubMobileClient() {
159
+ const stubResult = { content: [] };
160
+ return {
161
+ callTool: async () => stubResult,
162
+ useDevice: () => { },
163
+ listDevices: async () => [],
164
+ listElements: async () => [],
165
+ click: async () => { },
166
+ typeKeys: async (_text, _submit) => { },
167
+ pressButton: async () => { },
168
+ takeScreenshot: async () => ({ data: Buffer.alloc(0), mimeType: "image/png" }),
169
+ saveScreenshot: async () => { },
170
+ close: async () => { },
171
+ };
172
+ }
173
+ //# sourceMappingURL=mobile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mobile.js","sourceRoot":"","sources":["../src/mobile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ export type RailsHandle = {
3
+ readonly child: ChildProcess;
4
+ readonly url: string;
5
+ stop(): Promise<void>;
6
+ };
7
+ export type StartRailsInput = {
8
+ outDir: string;
9
+ host?: string;
10
+ port?: number;
11
+ readyTimeoutMs?: number;
12
+ shutdownGraceMs?: number;
13
+ };
14
+ export declare function startRails(input: StartRailsInput): Promise<RailsHandle>;
15
+ export declare function waitForReady(host: string, port: number, timeoutMs: number, childExitedEarly?: () => boolean): Promise<boolean>;
16
+ //# sourceMappingURL=rails-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rails-lifecycle.d.ts","sourceRoot":"","sources":["../src/rails-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAoB9D,MAAM,MAAM,WAAW,GAAG;IAGxB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IAIf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IAId,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAKF,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,CAkG7E;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,gBAAgB,CAAC,EAAE,MAAM,OAAO,GAC/B,OAAO,CAAC,OAAO,CAAC,CAgBlB"}