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.
- package/README.md +187 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +33 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +360 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/architecture.d.ts +16 -0
- package/dist/tools/architecture.js +198 -0
- package/dist/tools/architecture.js.map +1 -0
- package/dist/tools/build-check.d.ts +23 -0
- package/dist/tools/build-check.js +162 -0
- package/dist/tools/build-check.js.map +1 -0
- package/dist/tools/build.d.ts +14 -0
- package/dist/tools/build.js +101 -0
- package/dist/tools/build.js.map +1 -0
- package/dist/tools/diff-core.d.ts +24 -0
- package/dist/tools/diff-core.js +88 -0
- package/dist/tools/diff-core.js.map +1 -0
- package/dist/tools/idf-build.d.ts +10 -0
- package/dist/tools/idf-build.js +155 -0
- package/dist/tools/idf-build.js.map +1 -0
- package/dist/tools/input.d.ts +36 -0
- package/dist/tools/input.js +61 -0
- package/dist/tools/input.js.map +1 -0
- package/dist/tools/log.d.ts +16 -0
- package/dist/tools/log.js +49 -0
- package/dist/tools/log.js.map +1 -0
- package/dist/tools/repos.d.ts +12 -0
- package/dist/tools/repos.js +63 -0
- package/dist/tools/repos.js.map +1 -0
- package/dist/tools/scaffold.d.ts +15 -0
- package/dist/tools/scaffold.js +192 -0
- package/dist/tools/scaffold.js.map +1 -0
- package/dist/tools/screenshot.d.ts +24 -0
- package/dist/tools/screenshot.js +80 -0
- package/dist/tools/screenshot.js.map +1 -0
- package/dist/tools/settings.d.ts +25 -0
- package/dist/tools/settings.js +48 -0
- package/dist/tools/settings.js.map +1 -0
- package/dist/tools/stats.d.ts +18 -0
- package/dist/tools/stats.js +31 -0
- package/dist/tools/stats.js.map +1 -0
- package/dist/tools/symbols.d.ts +20 -0
- package/dist/tools/symbols.js +157 -0
- package/dist/tools/symbols.js.map +1 -0
- package/dist/tools/test.d.ts +24 -0
- package/dist/tools/test.js +227 -0
- package/dist/tools/test.js.map +1 -0
- package/dist/utils/exec.d.ts +58 -0
- package/dist/utils/exec.js +292 -0
- package/dist/utils/exec.js.map +1 -0
- package/dist/utils/git.d.ts +10 -0
- package/dist/utils/git.js +29 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/remote-client.d.ts +17 -0
- package/dist/utils/remote-client.js +94 -0
- package/dist/utils/remote-client.js.map +1 -0
- package/package.json +21 -0
- package/server.json +23 -0
- package/src/config.ts +45 -0
- package/src/index.ts +484 -0
- package/src/tools/architecture.ts +260 -0
- package/src/tools/build-check.ts +178 -0
- package/src/tools/build.ts +130 -0
- package/src/tools/diff-core.ts +130 -0
- package/src/tools/idf-build.ts +182 -0
- package/src/tools/input.ts +80 -0
- package/src/tools/log.ts +75 -0
- package/src/tools/repos.ts +75 -0
- package/src/tools/scaffold.ts +229 -0
- package/src/tools/screenshot.ts +100 -0
- package/src/tools/settings.ts +68 -0
- package/src/tools/stats.ts +38 -0
- package/src/tools/symbols.ts +185 -0
- package/src/tools/test.ts +264 -0
- package/src/utils/exec.ts +376 -0
- package/src/utils/git.ts +45 -0
- package/src/utils/remote-client.ts +107 -0
- 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"}
|