crosspad-mcp-server 4.0.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 (80) hide show
  1. package/README.md +187 -0
  2. package/dist/config.d.ts +10 -0
  3. package/dist/config.js +33 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +360 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/tools/architecture.d.ts +16 -0
  9. package/dist/tools/architecture.js +198 -0
  10. package/dist/tools/architecture.js.map +1 -0
  11. package/dist/tools/build-check.d.ts +23 -0
  12. package/dist/tools/build-check.js +162 -0
  13. package/dist/tools/build-check.js.map +1 -0
  14. package/dist/tools/build.d.ts +14 -0
  15. package/dist/tools/build.js +101 -0
  16. package/dist/tools/build.js.map +1 -0
  17. package/dist/tools/diff-core.d.ts +24 -0
  18. package/dist/tools/diff-core.js +88 -0
  19. package/dist/tools/diff-core.js.map +1 -0
  20. package/dist/tools/idf-build.d.ts +10 -0
  21. package/dist/tools/idf-build.js +155 -0
  22. package/dist/tools/idf-build.js.map +1 -0
  23. package/dist/tools/input.d.ts +36 -0
  24. package/dist/tools/input.js +61 -0
  25. package/dist/tools/input.js.map +1 -0
  26. package/dist/tools/log.d.ts +16 -0
  27. package/dist/tools/log.js +49 -0
  28. package/dist/tools/log.js.map +1 -0
  29. package/dist/tools/repos.d.ts +12 -0
  30. package/dist/tools/repos.js +63 -0
  31. package/dist/tools/repos.js.map +1 -0
  32. package/dist/tools/scaffold.d.ts +15 -0
  33. package/dist/tools/scaffold.js +192 -0
  34. package/dist/tools/scaffold.js.map +1 -0
  35. package/dist/tools/screenshot.d.ts +24 -0
  36. package/dist/tools/screenshot.js +80 -0
  37. package/dist/tools/screenshot.js.map +1 -0
  38. package/dist/tools/settings.d.ts +25 -0
  39. package/dist/tools/settings.js +48 -0
  40. package/dist/tools/settings.js.map +1 -0
  41. package/dist/tools/stats.d.ts +18 -0
  42. package/dist/tools/stats.js +31 -0
  43. package/dist/tools/stats.js.map +1 -0
  44. package/dist/tools/symbols.d.ts +20 -0
  45. package/dist/tools/symbols.js +157 -0
  46. package/dist/tools/symbols.js.map +1 -0
  47. package/dist/tools/test.d.ts +24 -0
  48. package/dist/tools/test.js +227 -0
  49. package/dist/tools/test.js.map +1 -0
  50. package/dist/utils/exec.d.ts +58 -0
  51. package/dist/utils/exec.js +292 -0
  52. package/dist/utils/exec.js.map +1 -0
  53. package/dist/utils/git.d.ts +10 -0
  54. package/dist/utils/git.js +29 -0
  55. package/dist/utils/git.js.map +1 -0
  56. package/dist/utils/remote-client.d.ts +17 -0
  57. package/dist/utils/remote-client.js +94 -0
  58. package/dist/utils/remote-client.js.map +1 -0
  59. package/package.json +21 -0
  60. package/server.json +23 -0
  61. package/src/config.ts +45 -0
  62. package/src/index.ts +484 -0
  63. package/src/tools/architecture.ts +260 -0
  64. package/src/tools/build-check.ts +178 -0
  65. package/src/tools/build.ts +130 -0
  66. package/src/tools/diff-core.ts +130 -0
  67. package/src/tools/idf-build.ts +182 -0
  68. package/src/tools/input.ts +80 -0
  69. package/src/tools/log.ts +75 -0
  70. package/src/tools/repos.ts +75 -0
  71. package/src/tools/scaffold.ts +229 -0
  72. package/src/tools/screenshot.ts +100 -0
  73. package/src/tools/settings.ts +68 -0
  74. package/src/tools/stats.ts +38 -0
  75. package/src/tools/symbols.ts +185 -0
  76. package/src/tools/test.ts +264 -0
  77. package/src/utils/exec.ts +376 -0
  78. package/src/utils/git.ts +45 -0
  79. package/src/utils/remote-client.ts +107 -0
  80. package/tsconfig.json +16 -0
@@ -0,0 +1,10 @@
1
+ import { OnLine } from "../utils/exec.js";
2
+ export interface IdfBuildResult {
3
+ success: boolean;
4
+ duration_seconds: number;
5
+ errors: string[];
6
+ warnings: string[];
7
+ tail: string[];
8
+ auto_reconfigured?: boolean;
9
+ }
10
+ export declare function crosspadIdfBuild(mode: "build" | "fullclean" | "clean", onLine?: OnLine): Promise<IdfBuildResult>;
@@ -0,0 +1,155 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { CROSSPAD_IDF_ROOT } from "../config.js";
4
+ import { runWithIdf, runWithIdfStream } from "../utils/exec.js";
5
+ /**
6
+ * Detect app directories that have REGISTER_APP in their sources but are NOT
7
+ * listed in the auto-generated app_registry_init.cpp. This means CMake hasn't
8
+ * seen them yet (file(GLOB) only runs at configure time).
9
+ */
10
+ function detectUnregisteredApps() {
11
+ const appsDir = path.join(CROSSPAD_IDF_ROOT, "main", "app");
12
+ const registryFile = path.join(appsDir, "app_registry_init.cpp");
13
+ if (!fs.existsSync(registryFile))
14
+ return [];
15
+ const registryContent = fs.readFileSync(registryFile, "utf-8");
16
+ const unregistered = [];
17
+ let entries;
18
+ try {
19
+ entries = fs.readdirSync(appsDir, { withFileTypes: true });
20
+ }
21
+ catch {
22
+ return [];
23
+ }
24
+ for (const entry of entries) {
25
+ if (!entry.isDirectory())
26
+ continue;
27
+ const appCmake = path.join(appsDir, entry.name, "CMakeLists.txt");
28
+ if (!fs.existsSync(appCmake))
29
+ continue;
30
+ // Scan .cpp files in this app dir for REGISTER_APP
31
+ const appDir = path.join(appsDir, entry.name);
32
+ let hasRegisterApp = false;
33
+ let appName = "";
34
+ try {
35
+ for (const file of fs.readdirSync(appDir)) {
36
+ if (!file.endsWith(".cpp"))
37
+ continue;
38
+ const content = fs.readFileSync(path.join(appDir, file), "utf-8");
39
+ const match = content.match(/REGISTER_APP\((\w+)/);
40
+ if (match) {
41
+ hasRegisterApp = true;
42
+ appName = match[1];
43
+ break;
44
+ }
45
+ }
46
+ }
47
+ catch {
48
+ continue;
49
+ }
50
+ if (hasRegisterApp && appName) {
51
+ // Check if this app is in the registry
52
+ const registerFn = `_register_${appName}_app`;
53
+ if (!registryContent.includes(registerFn)) {
54
+ unregistered.push(appName);
55
+ }
56
+ }
57
+ }
58
+ return unregistered;
59
+ }
60
+ function parseErrors(output) {
61
+ const errors = [];
62
+ for (const line of output.split("\n")) {
63
+ const trimmed = line.trim();
64
+ if (!trimmed)
65
+ continue;
66
+ if (/:\d+:\d+: (?:fatal )?error:/.test(trimmed)) {
67
+ errors.push(trimmed);
68
+ }
69
+ else if (/^.*ld.*: error:/.test(trimmed) || /undefined reference to/.test(trimmed)) {
70
+ errors.push(trimmed);
71
+ }
72
+ else if (/^CMake Error/i.test(trimmed)) {
73
+ errors.push(trimmed);
74
+ }
75
+ else if (/FAILED:/.test(trimmed) && !trimmed.startsWith("[")) {
76
+ errors.push(trimmed);
77
+ }
78
+ }
79
+ return errors.slice(0, 30);
80
+ }
81
+ function parseWarnings(output) {
82
+ const warnings = [];
83
+ for (const line of output.split("\n")) {
84
+ const trimmed = line.trim();
85
+ if (!trimmed)
86
+ continue;
87
+ if (/:\d+:\d+: warning:/.test(trimmed)) {
88
+ warnings.push(trimmed);
89
+ }
90
+ }
91
+ return warnings.slice(0, 20);
92
+ }
93
+ function getTail(output, n) {
94
+ return output.split("\n").filter(l => l.trim()).slice(-n);
95
+ }
96
+ async function runIdfCmd(cmd, onLine, timeoutMs) {
97
+ if (onLine) {
98
+ const r = await runWithIdfStream(cmd, CROSSPAD_IDF_ROOT, onLine, timeoutMs);
99
+ return r;
100
+ }
101
+ return runWithIdf(cmd, CROSSPAD_IDF_ROOT, timeoutMs);
102
+ }
103
+ export async function crosspadIdfBuild(mode, onLine) {
104
+ const startTime = Date.now();
105
+ let autoReconfigured = false;
106
+ // Auto-detect unregistered apps — if found, escalate to fullclean
107
+ if (mode === "build") {
108
+ const unregistered = detectUnregisteredApps();
109
+ if (unregistered.length > 0) {
110
+ onLine?.("stdout", `[idf] Detected ${unregistered.length} unregistered app(s): ${unregistered.join(", ")} — running fullclean`);
111
+ mode = "fullclean";
112
+ autoReconfigured = true;
113
+ }
114
+ }
115
+ if (mode === "fullclean") {
116
+ onLine?.("stdout", "[idf] Running idf.py fullclean...");
117
+ const r = await runIdfCmd("idf.py fullclean", onLine, 60_000);
118
+ if (!r.success) {
119
+ const combined = r.stdout + "\n" + r.stderr;
120
+ return {
121
+ success: false,
122
+ duration_seconds: (Date.now() - startTime) / 1000,
123
+ errors: parseErrors(combined),
124
+ warnings: [],
125
+ tail: getTail(combined, 20),
126
+ auto_reconfigured: autoReconfigured,
127
+ };
128
+ }
129
+ }
130
+ if (mode === "clean") {
131
+ const buildDir = path.join(CROSSPAD_IDF_ROOT, "build");
132
+ if (fs.existsSync(buildDir)) {
133
+ onLine?.("stdout", "[idf] Removing build directory...");
134
+ fs.rmSync(buildDir, { recursive: true, force: true });
135
+ }
136
+ }
137
+ onLine?.("stdout", "[idf] Building...");
138
+ const r = await runIdfCmd("idf.py build", onLine, 600_000);
139
+ const combined = r.stdout + "\n" + r.stderr;
140
+ const errors = parseErrors(combined);
141
+ const warnings = parseWarnings(combined);
142
+ const result = {
143
+ success: r.success,
144
+ duration_seconds: (Date.now() - startTime) / 1000,
145
+ errors,
146
+ warnings,
147
+ tail: getTail(combined, r.success ? 10 : 30),
148
+ };
149
+ if (autoReconfigured) {
150
+ result.auto_reconfigured = true;
151
+ }
152
+ onLine?.("stdout", `[idf] Build ${result.success ? "succeeded" : "FAILED"} in ${result.duration_seconds.toFixed(1)}s`);
153
+ return result;
154
+ }
155
+ //# sourceMappingURL=idf-build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idf-build.js","sourceRoot":"","sources":["../../src/tools/idf-build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * MCP tool: send input events to the running CrossPad simulator.
3
+ * Supports: click, pad_press/release, encoder_rotate/press/release, key.
4
+ */
5
+ export type InputAction = {
6
+ action: "click";
7
+ x: number;
8
+ y: number;
9
+ } | {
10
+ action: "pad_press";
11
+ pad: number;
12
+ velocity?: number;
13
+ } | {
14
+ action: "pad_release";
15
+ pad: number;
16
+ } | {
17
+ action: "encoder_rotate";
18
+ delta: number;
19
+ } | {
20
+ action: "encoder_press";
21
+ } | {
22
+ action: "encoder_release";
23
+ } | {
24
+ action: "key";
25
+ keycode: number;
26
+ };
27
+ export interface InputResult {
28
+ success: boolean;
29
+ action: string;
30
+ response?: Record<string, unknown>;
31
+ error?: string;
32
+ }
33
+ /**
34
+ * Send a single input event to the simulator.
35
+ */
36
+ export declare function crosspadInput(input: InputAction): Promise<InputResult>;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * MCP tool: send input events to the running CrossPad simulator.
3
+ * Supports: click, pad_press/release, encoder_rotate/press/release, key.
4
+ */
5
+ import { sendRemoteCommand, isSimulatorRunning } from "../utils/remote-client.js";
6
+ /**
7
+ * Send a single input event to the simulator.
8
+ */
9
+ export async function crosspadInput(input) {
10
+ const running = await isSimulatorRunning();
11
+ if (!running) {
12
+ return {
13
+ success: false,
14
+ action: input.action,
15
+ error: "Simulator is not running. Use crosspad_run to start it.",
16
+ };
17
+ }
18
+ try {
19
+ let cmd;
20
+ switch (input.action) {
21
+ case "click":
22
+ cmd = { cmd: "click", x: input.x, y: input.y };
23
+ break;
24
+ case "pad_press":
25
+ cmd = { cmd: "pad_press", pad: input.pad, velocity: input.velocity ?? 127 };
26
+ break;
27
+ case "pad_release":
28
+ cmd = { cmd: "pad_release", pad: input.pad };
29
+ break;
30
+ case "encoder_rotate":
31
+ cmd = { cmd: "encoder_rotate", delta: input.delta };
32
+ break;
33
+ case "encoder_press":
34
+ cmd = { cmd: "encoder_press" };
35
+ break;
36
+ case "encoder_release":
37
+ cmd = { cmd: "encoder_release" };
38
+ break;
39
+ case "key":
40
+ cmd = { cmd: "key", keycode: input.keycode };
41
+ break;
42
+ default:
43
+ return { success: false, action: "unknown", error: "Unknown action" };
44
+ }
45
+ const resp = await sendRemoteCommand(cmd);
46
+ return {
47
+ success: resp.ok === true,
48
+ action: input.action,
49
+ response: resp,
50
+ error: resp.ok ? undefined : resp.error,
51
+ };
52
+ }
53
+ catch (err) {
54
+ return {
55
+ success: false,
56
+ action: input.action,
57
+ error: err.message,
58
+ };
59
+ }
60
+ }
61
+ //# sourceMappingURL=input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/tools/input.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAkB,MAAM,2BAA2B,CAAC;AAkBlG;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAkB;IACpD,MAAM,OAAO,GAAG,MAAM,kBAAkB,EAAE,CAAC;IAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,KAAK,EAAE,yDAAyD;SACjE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,IAAI,GAA4B,CAAC;QAEjC,QAAQ,KAAK,CAAC,MAAM,EAAE,CAAC;YACrB,KAAK,OAAO;gBACV,GAAG,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC/C,MAAM;YACR,KAAK,WAAW;gBACd,GAAG,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,GAAG,EAAE,CAAC;gBAC5E,MAAM;YACR,KAAK,aAAa;gBAChB,GAAG,GAAG,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC;gBAC7C,MAAM;YACR,KAAK,gBAAgB;gBACnB,GAAG,GAAG,EAAE,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;gBACpD,MAAM;YACR,KAAK,eAAe;gBAClB,GAAG,GAAG,EAAE,GAAG,EAAE,eAAe,EAAE,CAAC;gBAC/B,MAAM;YACR,KAAK,iBAAiB;gBACpB,GAAG,GAAG,EAAE,GAAG,EAAE,iBAAiB,EAAE,CAAC;gBACjC,MAAM;YACR,KAAK,KAAK;gBACR,GAAG,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;gBAC7C,MAAM;YACR;gBACE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAC1E,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAC1C,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,EAAE,KAAK,IAAI;YACzB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,QAAQ,EAAE,IAA+B;YACzC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE,IAAI,CAAC,KAAgB;SACpD,CAAC;IACJ,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,KAAK,EAAE,GAAG,CAAC,OAAO;SACnB,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { OnLine } from "../utils/exec.js";
2
+ export interface LogResult {
3
+ success: boolean;
4
+ exe_path: string;
5
+ stdout: string;
6
+ stderr: string;
7
+ exit_code: number | null;
8
+ duration_seconds: number;
9
+ truncated: boolean;
10
+ }
11
+ /**
12
+ * Launch main.exe, capture stdout/stderr for up to `timeout_seconds`,
13
+ * then kill the process and return the output.
14
+ * Streams lines in real-time via onLine callback.
15
+ */
16
+ export declare function crosspadLog(timeoutSeconds?: number, maxLines?: number, onLine?: OnLine): Promise<LogResult>;
@@ -0,0 +1,49 @@
1
+ import fs from "fs";
2
+ import { BIN_EXE, CROSSPAD_PC_ROOT } from "../config.js";
3
+ import { runBuildStream } from "../utils/exec.js";
4
+ /**
5
+ * Launch main.exe, capture stdout/stderr for up to `timeout_seconds`,
6
+ * then kill the process and return the output.
7
+ * Streams lines in real-time via onLine callback.
8
+ */
9
+ export async function crosspadLog(timeoutSeconds = 5, maxLines = 200, onLine) {
10
+ if (!fs.existsSync(BIN_EXE)) {
11
+ return {
12
+ success: false,
13
+ exe_path: BIN_EXE,
14
+ stdout: "",
15
+ stderr: `${BIN_EXE} not found — build first`,
16
+ exit_code: null,
17
+ duration_seconds: 0,
18
+ truncated: false,
19
+ };
20
+ }
21
+ onLine?.("stdout", `[crosspad] Launching ${BIN_EXE} (capturing for ${timeoutSeconds}s)...`);
22
+ const result = await runBuildStream(`"${BIN_EXE}"`, CROSSPAD_PC_ROOT, onLine ?? (() => { }), timeoutSeconds * 1000);
23
+ // Truncate to maxLines
24
+ let stdout = result.stdout;
25
+ let stderr = result.stderr;
26
+ let truncated = false;
27
+ const stdoutLines = stdout.split("\n");
28
+ if (stdoutLines.length > maxLines) {
29
+ stdout = stdoutLines.slice(0, maxLines).join("\n");
30
+ truncated = true;
31
+ }
32
+ const stderrLines = stderr.split("\n");
33
+ if (stderrLines.length > maxLines) {
34
+ stderr = stderrLines.slice(0, maxLines).join("\n");
35
+ truncated = true;
36
+ }
37
+ // exitCode -1 = killed by timeout (expected)
38
+ const exitCode = result.exitCode === -1 ? null : result.exitCode;
39
+ return {
40
+ success: exitCode === 0 || exitCode === null,
41
+ exe_path: BIN_EXE,
42
+ stdout,
43
+ stderr,
44
+ exit_code: exitCode,
45
+ duration_seconds: Math.round(result.durationMs / 100) / 10,
46
+ truncated,
47
+ };
48
+ }
49
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/tools/log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,cAAc,EAAU,MAAM,kBAAkB,CAAC;AAY1D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,iBAAyB,CAAC,EAC1B,WAAmB,GAAG,EACtB,MAAe;IAEf,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,QAAQ,EAAE,OAAO;YACjB,MAAM,EAAE,EAAE;YACV,MAAM,EAAE,GAAG,OAAO,0BAA0B;YAC5C,SAAS,EAAE,IAAI;YACf,gBAAgB,EAAE,CAAC;YACnB,SAAS,EAAE,KAAK;SACjB,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,CAAC,QAAQ,EAAE,wBAAwB,OAAO,mBAAmB,cAAc,OAAO,CAAC,CAAC;IAE5F,MAAM,MAAM,GAAG,MAAM,cAAc,CACjC,IAAI,OAAO,GAAG,EACd,gBAAgB,EAChB,MAAM,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,EACpB,cAAc,GAAG,IAAI,CACtB,CAAC;IAEF,uBAAuB;IACvB,IAAI,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC3B,IAAI,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC3B,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,WAAW,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;QAClC,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,SAAS,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,WAAW,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;QAClC,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,SAAS,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,6CAA6C;IAC7C,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;IAEjE,OAAO;QACL,OAAO,EAAE,QAAQ,KAAK,CAAC,IAAI,QAAQ,KAAK,IAAI;QAC5C,QAAQ,EAAE,OAAO;QACjB,MAAM;QACN,MAAM;QACN,SAAS,EAAE,QAAQ;QACnB,gBAAgB,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,GAAG,GAAG,CAAC,GAAG,EAAE;QAC1D,SAAS;KACV,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { RepoStatus } from "../utils/git.js";
2
+ export interface SubmoduleSync {
3
+ pinned: string | null;
4
+ local_head: string | null;
5
+ in_sync: boolean;
6
+ }
7
+ export interface ReposStatusResult {
8
+ repos: RepoStatus[];
9
+ crosspad_pc_mode: "dev-mode" | "submodule-mode" | "unknown";
10
+ submodule_sync: Record<string, SubmoduleSync>;
11
+ }
12
+ export declare function crosspadReposStatus(): ReposStatusResult;
@@ -0,0 +1,63 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { REPOS, CROSSPAD_PC_ROOT } from "../config.js";
4
+ import { getRepoStatus, getSubmodulePin, getHead } from "../utils/git.js";
5
+ function detectMode() {
6
+ const corePath = path.join(CROSSPAD_PC_ROOT, "crosspad-core");
7
+ try {
8
+ const stat = fs.lstatSync(corePath);
9
+ // Windows junctions report as symlinks in Node.js
10
+ if (stat.isSymbolicLink())
11
+ return "dev-mode";
12
+ // Check for .git file (submodule) or .git dir
13
+ const gitPath = path.join(corePath, ".git");
14
+ if (fs.existsSync(gitPath))
15
+ return "submodule-mode";
16
+ return "unknown";
17
+ }
18
+ catch {
19
+ return "unknown";
20
+ }
21
+ }
22
+ export function crosspadReposStatus() {
23
+ const repos = [];
24
+ for (const [name, repoPath] of Object.entries(REPOS)) {
25
+ try {
26
+ if (fs.existsSync(repoPath)) {
27
+ repos.push(getRepoStatus(name, repoPath));
28
+ }
29
+ else {
30
+ repos.push({
31
+ name,
32
+ path: repoPath,
33
+ branch: "",
34
+ head: "",
35
+ dirtyFiles: [`(repo not found at ${repoPath})`],
36
+ });
37
+ }
38
+ }
39
+ catch (err) {
40
+ repos.push({
41
+ name,
42
+ path: repoPath,
43
+ branch: "",
44
+ head: "",
45
+ dirtyFiles: [`(error: ${err.message})`],
46
+ });
47
+ }
48
+ }
49
+ const mode = detectMode();
50
+ // Submodule sync info
51
+ const submoduleSync = {};
52
+ for (const sub of ["crosspad-core", "crosspad-gui"]) {
53
+ const pinned = getSubmodulePin(CROSSPAD_PC_ROOT, sub);
54
+ const localHead = REPOS[sub] ? getHead(REPOS[sub]) : null;
55
+ submoduleSync[sub] = {
56
+ pinned,
57
+ local_head: localHead,
58
+ in_sync: pinned !== null && localHead !== null && localHead.startsWith(pinned),
59
+ };
60
+ }
61
+ return { repos, crosspad_pc_mode: mode, submodule_sync: submoduleSync };
62
+ }
63
+ //# sourceMappingURL=repos.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repos.js","sourceRoot":"","sources":["../../src/tools/repos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,OAAO,EAAc,MAAM,iBAAiB,CAAC;AActF,SAAS,UAAU;IACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACpC,kDAAkD;QAClD,IAAI,IAAI,CAAC,cAAc,EAAE;YAAE,OAAO,UAAU,CAAC;QAC7C,8CAA8C;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC5C,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,gBAAgB,CAAC;QACpD,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,MAAM,KAAK,GAAiB,EAAE,CAAC;IAE/B,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC;YACH,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC5B,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC5C,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC;oBACT,IAAI;oBACJ,IAAI,EAAE,QAAQ;oBACd,MAAM,EAAE,EAAE;oBACV,IAAI,EAAE,EAAE;oBACR,UAAU,EAAE,CAAC,sBAAsB,QAAQ,GAAG,CAAC;iBAChD,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI;gBACJ,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,EAAE;gBACV,IAAI,EAAE,EAAE;gBACR,UAAU,EAAE,CAAC,WAAW,GAAG,CAAC,OAAO,GAAG,CAAC;aACxC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAE1B,sBAAsB;IACtB,MAAM,aAAa,GAAkC,EAAE,CAAC;IACxD,KAAK,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,cAAc,CAAC,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,eAAe,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;QACtD,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC1D,aAAa,CAAC,GAAG,CAAC,GAAG;YACnB,MAAM;YACN,UAAU,EAAE,SAAS;YACrB,OAAO,EAAE,MAAM,KAAK,IAAI,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC;SAC/E,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface ScaffoldParams {
2
+ name: string;
3
+ display_name?: string;
4
+ has_pad_logic?: boolean;
5
+ icon?: string;
6
+ }
7
+ export interface ScaffoldResult {
8
+ files: Record<string, string>;
9
+ cmake_patch: {
10
+ file: string;
11
+ after_pattern: string;
12
+ content: string;
13
+ };
14
+ }
15
+ export declare function crosspadScaffoldApp(params: ScaffoldParams): ScaffoldResult;
@@ -0,0 +1,192 @@
1
+ export function crosspadScaffoldApp(params) {
2
+ const { name, display_name = name, has_pad_logic = false, icon = "CrossPad_Logo_110w.png", } = params;
3
+ const lower = name.toLowerCase();
4
+ const upper = name.toUpperCase();
5
+ const dir = `src/apps/${lower}`;
6
+ const files = {};
7
+ // --- CMakeLists.txt ---
8
+ const sources = [`\${CMAKE_CURRENT_SOURCE_DIR}/${name}App.cpp`];
9
+ if (has_pad_logic) {
10
+ sources.push(`\${CMAKE_CURRENT_SOURCE_DIR}/${name}PadLogic.cpp`);
11
+ }
12
+ files[`${dir}/CMakeLists.txt`] = `# ${display_name} app sources
13
+ set(${upper}_APP_SOURCES
14
+ ${sources.join("\n ")}
15
+ PARENT_SCOPE
16
+ )
17
+ `;
18
+ // --- App header ---
19
+ files[`${dir}/${name}App.hpp`] = `#pragma once
20
+
21
+ #include "lvgl.h"
22
+
23
+ class App;
24
+
25
+ lv_obj_t* ${name}_create(lv_obj_t* parent, App* app);
26
+ void ${name}_destroy(lv_obj_t* app_obj);
27
+ `;
28
+ // --- App implementation ---
29
+ let appCpp = `/**
30
+ * @file ${name}App.cpp
31
+ * @brief ${display_name} app — LVGL GUI
32
+ */
33
+
34
+ #include "${name}App.hpp"
35
+ #include "pc_stubs/PcApp.hpp"
36
+ #include "pc_stubs/pc_platform.h"
37
+
38
+ #include <crosspad/app/AppRegistry.hpp>
39
+ #include <crosspad/pad/PadManager.hpp>
40
+ #include "crosspad-gui/components/app_lifecycle.h"
41
+ #include "crosspad-gui/platform/IGuiPlatform.h"
42
+ #include "crosspad_app.hpp"
43
+
44
+ #include "lvgl.h"
45
+
46
+ #include <cstdio>
47
+ `;
48
+ if (has_pad_logic) {
49
+ appCpp += `#include "${name}PadLogic.hpp"
50
+ #include <memory>
51
+
52
+ static std::shared_ptr<${name}PadLogic> s_padLogic;
53
+ `;
54
+ }
55
+ appCpp += `
56
+ static App* s_app = nullptr;
57
+
58
+ /* ── App create / destroy ────────────────────────────────────────────── */
59
+
60
+ lv_obj_t* ${name}_create(lv_obj_t* parent, App* a)
61
+ {
62
+ s_app = a;
63
+ lv_obj_t* cont = lv_obj_create(parent);
64
+ lv_obj_set_size(cont, lv_pct(100), lv_pct(100));
65
+ lv_obj_set_style_bg_color(cont, lv_color_black(), 0);
66
+ lv_obj_set_style_bg_opa(cont, LV_OPA_COVER, 0);
67
+ lv_obj_set_style_pad_all(cont, 4, 0);
68
+ lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);
69
+ lv_obj_set_style_pad_row(cont, 2, 0);
70
+ lv_obj_remove_flag(cont, LV_OBJ_FLAG_SCROLLABLE);
71
+
72
+ /* ── Title bar with close button ─────────────────────────── */
73
+ lv_obj_t* titleBar = lv_obj_create(cont);
74
+ lv_obj_set_size(titleBar, lv_pct(100), 24);
75
+ lv_obj_set_style_bg_opa(titleBar, LV_OPA_TRANSP, 0);
76
+ lv_obj_set_style_border_width(titleBar, 0, 0);
77
+ lv_obj_set_style_pad_all(titleBar, 0, 0);
78
+ lv_obj_remove_flag(titleBar, LV_OBJ_FLAG_SCROLLABLE);
79
+
80
+ lv_obj_t* titleLabel = lv_label_create(titleBar);
81
+ lv_label_set_text(titleLabel, "${display_name}");
82
+ lv_obj_set_style_text_color(titleLabel, lv_color_white(), 0);
83
+ lv_obj_set_style_text_font(titleLabel, &lv_font_montserrat_14, 0);
84
+ lv_obj_align(titleLabel, LV_ALIGN_LEFT_MID, 4, 0);
85
+
86
+ lv_obj_t* closeBtn = lv_button_create(titleBar);
87
+ lv_obj_set_size(closeBtn, 28, 20);
88
+ lv_obj_align(closeBtn, LV_ALIGN_RIGHT_MID, -2, 0);
89
+ lv_obj_set_style_bg_color(closeBtn, lv_color_hex(0x662222), 0);
90
+ lv_obj_set_style_bg_color(closeBtn, lv_color_hex(0xAA3333), LV_STATE_PRESSED);
91
+ lv_obj_set_style_radius(closeBtn, 4, 0);
92
+ lv_obj_set_style_shadow_width(closeBtn, 0, 0);
93
+ lv_obj_t* closeLbl = lv_label_create(closeBtn);
94
+ lv_label_set_text(closeLbl, "X");
95
+ lv_obj_set_style_text_font(closeLbl, &lv_font_montserrat_12, 0);
96
+ lv_obj_center(closeLbl);
97
+ lv_obj_add_event_cb(closeBtn, [](lv_event_t*) {
98
+ if (s_app) crosspad_gui::app_request_close(s_app);
99
+ }, LV_EVENT_CLICKED, nullptr);
100
+ `;
101
+ if (has_pad_logic) {
102
+ appCpp += `
103
+ /* ── Register pad logic ──────────────────────────────────── */
104
+ s_padLogic = std::make_shared<${name}PadLogic>();
105
+ crosspad::getPadManager().registerPadLogic("${name}", s_padLogic);
106
+
107
+ if (a) {
108
+ a->setOnShow([](lv_obj_t*) {
109
+ crosspad::getPadManager().setActivePadLogic("${name}");
110
+ crosspad_app_update_pad_icon();
111
+ });
112
+ a->setOnHide([](lv_obj_t*) {
113
+ crosspad::getPadManager().setActivePadLogic("");
114
+ crosspad_app_update_pad_icon();
115
+ });
116
+ }
117
+ `;
118
+ }
119
+ appCpp += `
120
+ /* ── TODO: Add your UI here ──────────────────────────────── */
121
+
122
+ printf("[${name}] App created\\n");
123
+ return cont;
124
+ }
125
+
126
+ void ${name}_destroy(lv_obj_t* app_obj)
127
+ {
128
+ `;
129
+ if (has_pad_logic) {
130
+ appCpp += ` crosspad::getPadManager().setActivePadLogic("");
131
+ crosspad::getPadManager().unregisterPadLogic("${name}");
132
+ crosspad_app_update_pad_icon();
133
+ s_padLogic.reset();
134
+ `;
135
+ }
136
+ appCpp += ` s_app = nullptr;
137
+ lv_obj_delete_async(app_obj);
138
+ printf("[${name}] App destroyed\\n");
139
+ }
140
+
141
+ /* ── App registration ────────────────────────────────────────────────── */
142
+
143
+ void _register_${name}_app() {
144
+ static char icon_path[256];
145
+ snprintf(icon_path, sizeof(icon_path), "%s${icon}",
146
+ crosspad_gui::getGuiPlatform().assetPathPrefix());
147
+
148
+ static const crosspad::AppEntry entry = {
149
+ "${name}", icon_path, ${name}_create, ${name}_destroy,
150
+ nullptr, nullptr, nullptr, nullptr, 0
151
+ };
152
+ crosspad::AppRegistry::getInstance().registerApp(entry);
153
+ }
154
+ `;
155
+ files[`${dir}/${name}App.cpp`] = appCpp;
156
+ // --- Pad logic (optional) ---
157
+ if (has_pad_logic) {
158
+ files[`${dir}/${name}PadLogic.hpp`] = `#pragma once
159
+
160
+ #include <crosspad/pad/IPadLogicHandler.hpp>
161
+
162
+ class ${name}PadLogic : public crosspad::IPadLogicHandler {
163
+ public:
164
+ void onPadPressed(uint8_t padIndex, uint8_t velocity) override;
165
+ void onPadReleased(uint8_t padIndex) override;
166
+ };
167
+ `;
168
+ files[`${dir}/${name}PadLogic.cpp`] = `#include "${name}PadLogic.hpp"
169
+ #include <cstdio>
170
+
171
+ void ${name}PadLogic::onPadPressed(uint8_t padIndex, uint8_t velocity)
172
+ {
173
+ printf("[${name}PadLogic] Pad %d pressed (vel=%d)\\n", padIndex, velocity);
174
+ // TODO: Implement pad press logic
175
+ }
176
+
177
+ void ${name}PadLogic::onPadReleased(uint8_t padIndex)
178
+ {
179
+ printf("[${name}PadLogic] Pad %d released\\n", padIndex);
180
+ // TODO: Implement pad release logic
181
+ }
182
+ `;
183
+ }
184
+ // --- CMake patch instructions ---
185
+ const cmakePatch = {
186
+ file: "CMakeLists.txt",
187
+ after_pattern: "add_subdirectory(src/apps/",
188
+ content: `add_subdirectory(${dir})\nlist(APPEND MAIN_SOURCES \${${upper}_APP_SOURCES})`,
189
+ };
190
+ return { files, cmake_patch: cmakePatch };
191
+ }
192
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/tools/scaffold.ts"],"names":[],"mappings":"AAgBA,MAAM,UAAU,mBAAmB,CAAC,MAAsB;IACxD,MAAM,EACJ,IAAI,EACJ,YAAY,GAAG,IAAI,EACnB,aAAa,GAAG,KAAK,EACrB,IAAI,GAAG,wBAAwB,GAChC,GAAG,MAAM,CAAC;IAEX,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,MAAM,GAAG,GAAG,YAAY,KAAK,EAAE,CAAC;IAEhC,MAAM,KAAK,GAA2B,EAAE,CAAC;IAEzC,yBAAyB;IACzB,MAAM,OAAO,GAAG,CAAC,gCAAgC,IAAI,SAAS,CAAC,CAAC;IAChE,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,CAAC,IAAI,CAAC,gCAAgC,IAAI,cAAc,CAAC,CAAC;IACnE,CAAC;IAED,KAAK,CAAC,GAAG,GAAG,iBAAiB,CAAC,GAAG,KAAK,YAAY;MAC9C,KAAK;MACL,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC;;;CAG3B,CAAC;IAEA,qBAAqB;IACrB,KAAK,CAAC,GAAG,GAAG,IAAI,IAAI,SAAS,CAAC,GAAG;;;;;;YAMvB,IAAI;OACT,IAAI;CACV,CAAC;IAEA,6BAA6B;IAC7B,IAAI,MAAM,GAAG;WACJ,IAAI;YACH,YAAY;;;YAGZ,IAAI;;;;;;;;;;;;;CAaf,CAAC;IAEA,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,IAAI,aAAa,IAAI;;;yBAGN,IAAI;CAC5B,CAAC;IACA,CAAC;IAED,MAAM,IAAI;;;;;YAKA,IAAI;;;;;;;;;;;;;;;;;;;;;qCAqBqB,YAAY;;;;;;;;;;;;;;;;;;;CAmBhD,CAAC;IAEA,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,IAAI;;oCAEsB,IAAI;kDACU,IAAI;;;;2DAIK,IAAI;;;;;;;;CAQ9D,CAAC;IACA,CAAC;IAED,MAAM,IAAI;;;eAGG,IAAI;;;;OAIZ,IAAI;;CAEV,CAAC;IAEA,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,IAAI;oDACsC,IAAI;;;CAGvD,CAAC;IACA,CAAC;IAED,MAAM,IAAI;;eAEG,IAAI;;;;;iBAKF,IAAI;;gDAE2B,IAAI;;;;WAIzC,IAAI,iBAAiB,IAAI,YAAY,IAAI;;;;;CAKnD,CAAC;IAEA,KAAK,CAAC,GAAG,GAAG,IAAI,IAAI,SAAS,CAAC,GAAG,MAAM,CAAC;IAExC,+BAA+B;IAC/B,IAAI,aAAa,EAAE,CAAC;QAClB,KAAK,CAAC,GAAG,GAAG,IAAI,IAAI,cAAc,CAAC,GAAG;;;;QAIlC,IAAI;;;;;CAKX,CAAC;QAEE,KAAK,CAAC,GAAG,GAAG,IAAI,IAAI,cAAc,CAAC,GAAG,aAAa,IAAI;;;OAGpD,IAAI;;eAEI,IAAI;;;;OAIZ,IAAI;;eAEI,IAAI;;;CAGlB,CAAC;IACA,CAAC;IAED,mCAAmC;IACnC,MAAM,UAAU,GAAG;QACjB,IAAI,EAAE,gBAAgB;QACtB,aAAa,EAAE,4BAA4B;QAC3C,OAAO,EAAE,oBAAoB,GAAG,kCAAkC,KAAK,gBAAgB;KACxF,CAAC;IAEF,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;AAC5C,CAAC"}