@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.4 → 0.1.2-dev.41
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 +116 -16
- package/cli/README.md +5 -5
- package/cli/bin/create-muscat-plugin.mjs +1 -1
- package/cli/bin/muscat-pk.mjs +20 -10
- package/cli/bundle-templates.mjs +28 -0
- package/cli/docker/Dockerfile.bridge +17 -0
- package/cli/docker/Dockerfile.cpp +22 -0
- package/cli/docker/Dockerfile.dev +14 -0
- package/cli/docker/Dockerfile.python +20 -0
- package/cli/docker/bridge-package.json +8 -0
- package/cli/src/bridge.mjs +92 -0
- package/cli/src/build.mjs +398 -36
- package/cli/src/check-package.mjs +8 -23
- package/cli/src/cpp.mjs +61 -0
- package/cli/src/dev.mjs +16 -3
- package/cli/src/devBack.mjs +239 -53
- package/cli/src/devUi.mjs +118 -15
- package/cli/src/esbuild.mjs +51 -10
- package/cli/src/manifest.mjs +193 -0
- package/cli/src/new.mjs +168 -37
- package/cli/src/preview.mjs +91 -0
- package/cli/src/python.mjs +122 -0
- package/cli/src/ros2.mjs +90 -0
- package/cli/src/socketBridge.mjs +129 -0
- package/cli/src/templates-embedded.mjs +3 -0
- package/cli/src/type.mjs +105 -0
- package/cli/src/uninstall.mjs +98 -0
- package/cli/src/update.mjs +259 -30
- package/cli/src/util.mjs +97 -7
- package/cli/templates/common/README.md +383 -9
- package/cli/templates/common/manifest.json +13 -13
- package/cli/templates/common/package.json +5 -11
- package/cli/templates/common/py/main.py +88 -0
- package/cli/templates/common/requirements.txt +4 -0
- package/cli/templates/common/tsconfig.json +1 -1
- package/cli/templates/common/{src → ui}/content.tsx +3 -2
- package/cli/templates/common/vite.config.ts +22 -13
- package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
- package/cli/templates/cpp/cpp/main.cpp +25 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/__init__.py +0 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +25 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/package.xml +10 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/resource/__PLUGIN_SLUG__ +0 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/setup.py +18 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +17 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +22 -0
- package/package.json +4 -4
- package/cli/src/pack.mjs +0 -58
- package/cli/templates/sdk/main.py +0 -21
- package/cli/templates/sdk/requirements.txt +0 -1
package/cli/src/ros2.mjs
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Shared ROS2 Docker helpers (Jazzy + Python 3.12 — matches the robot backend).
|
|
2
|
+
// Used by dev:back (interactive dev loop) and build (export install/ into the ZIP).
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
|
+
import { hasBin, log, pruneMuskatImages, resolveDockerfile } from "./util.mjs";
|
|
7
|
+
|
|
8
|
+
export const ROS2_DEV_IMAGE = "muscat-pk-ros2-dev";
|
|
9
|
+
|
|
10
|
+
/** First package directory under ros2_ws/src (the colcon package name). */
|
|
11
|
+
export function ros2PackageName(root) {
|
|
12
|
+
const srcDir = resolve(root, "ros2_ws/src");
|
|
13
|
+
if (!existsSync(srcDir)) return null;
|
|
14
|
+
const dirs = readdirSync(srcDir, { withFileTypes: true })
|
|
15
|
+
.filter((d) => d.isDirectory())
|
|
16
|
+
.map((d) => d.name);
|
|
17
|
+
return dirs[0] ?? null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function assertDockerReady() {
|
|
21
|
+
if (!hasBin("docker")) {
|
|
22
|
+
throw new Error("Docker not found. ROS2 needs Docker (Jazzy + Python 3.12 container).");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const ARCH_DOCKER_PLATFORM = {
|
|
27
|
+
amd64: "linux/amd64",
|
|
28
|
+
arm64: "linux/arm64",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Build the dev image from the kit's bundled Dockerfile (Docker layer-caches). */
|
|
32
|
+
export async function ensureImage(image = ROS2_DEV_IMAGE, { platform = null } = {}) {
|
|
33
|
+
const dockerfile = await resolveDockerfile("Dockerfile.dev");
|
|
34
|
+
log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
|
|
35
|
+
const platformFlags = platform ? ["--platform", platform] : [];
|
|
36
|
+
const r = spawnSync(
|
|
37
|
+
"docker",
|
|
38
|
+
["build", ...platformFlags, "-t", image, "-f", dockerfile, dirname(dockerfile)],
|
|
39
|
+
{ stdio: "inherit" },
|
|
40
|
+
);
|
|
41
|
+
if (r.status !== 0) throw new Error("docker build failed.");
|
|
42
|
+
pruneMuskatImages();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run `colcon build` inside the container, producing ros2_ws/install on the host
|
|
47
|
+
* (the workspace is mounted). symlink=true for dev (fast, edit-in-place);
|
|
48
|
+
* symlink=false for export builds that ship in the ZIP (real copies, relocatable).
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* arch=null → dev mode, builds to ros2_ws/install/ (native)
|
|
52
|
+
* arch="amd64"|"arm64" → export mode, builds to ros2_ws/install-{arch}/ using arch-specific image
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* symlink=true (dev): ros2_ws/install/ — local dev workspace, not for ZIP
|
|
56
|
+
* symlink=false, arch=null (rclpy export): dist/ros2_ws/install/
|
|
57
|
+
* symlink=false, arch="amd64"|"arm64" (rclcpp export): dist/ros2_ws/install-{arch}/
|
|
58
|
+
*/
|
|
59
|
+
export function colconBuild(root, { image = ROS2_DEV_IMAGE, symlink = false, arch = null } = {}) {
|
|
60
|
+
// paths are relative to ros2_ws/ inside the container
|
|
61
|
+
let installBase;
|
|
62
|
+
if (symlink) {
|
|
63
|
+
installBase = "install";
|
|
64
|
+
} else if (arch) {
|
|
65
|
+
installBase = `../dist/ros2_ws/install-${arch}`;
|
|
66
|
+
} else {
|
|
67
|
+
installBase = "../dist/ros2_ws/install";
|
|
68
|
+
}
|
|
69
|
+
const flag = symlink ? "--symlink-install" : "";
|
|
70
|
+
const script = [
|
|
71
|
+
"pip install -r requirements.txt --break-system-packages --ignore-installed -q || true",
|
|
72
|
+
"cd ros2_ws",
|
|
73
|
+
// ponytail: rm only on cross-arch; same-arch dev rebuilds keep the cache
|
|
74
|
+
...(arch ? ["rm -rf build"] : []),
|
|
75
|
+
`colcon build ${flag} --install-base ${installBase}`.trim(),
|
|
76
|
+
].join(" && ");
|
|
77
|
+
const label = arch
|
|
78
|
+
? `colcon build (${arch}) → dist/ros2_ws/install-${arch}/…`
|
|
79
|
+
: `colcon build${symlink ? " --symlink-install" : ""} (in container)…`;
|
|
80
|
+
log.step(label);
|
|
81
|
+
const actualImage = arch ? `${ROS2_DEV_IMAGE}-${arch}` : image;
|
|
82
|
+
const dockerPlatform = arch ? ARCH_DOCKER_PLATFORM[arch] : null;
|
|
83
|
+
const platformFlags = dockerPlatform ? ["--platform", dockerPlatform] : [];
|
|
84
|
+
const r = spawnSync(
|
|
85
|
+
"docker",
|
|
86
|
+
["run", "--rm", ...platformFlags, "-e", "PIP_ROOT_USER_ACTION=ignore", "-v", `${root}:/plugin`, "-w", "/plugin", actualImage, "bash", "-lc", script],
|
|
87
|
+
{ cwd: root, stdio: "inherit" },
|
|
88
|
+
);
|
|
89
|
+
if (r.status !== 0) throw new Error(`colcon build failed${arch ? ` (${arch})` : ""}.`);
|
|
90
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { Server } from "socket.io";
|
|
5
|
+
|
|
6
|
+
// Self-contained logger — this module also runs standalone inside the bridge
|
|
7
|
+
// Docker container, where ./util.mjs is not available.
|
|
8
|
+
const log = {
|
|
9
|
+
step: (...m) => console.log("›", ...m),
|
|
10
|
+
warn: (...m) => console.warn("⚠", ...m),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const DEFAULT_ACTION_TIMEOUT_MS = 10_000;
|
|
14
|
+
|
|
15
|
+
export function startSocketBridge({ port = 10000, host = "0.0.0.0" } = {}) {
|
|
16
|
+
const httpServer = createServer();
|
|
17
|
+
const io = new Server(httpServer, { cors: { origin: "*" } });
|
|
18
|
+
|
|
19
|
+
// topic → socketId (serve_register로 등록된 queryable)
|
|
20
|
+
const queryables = new Map();
|
|
21
|
+
// call_id → { timer, resolve } (action → serve_call → serve_reply 대기)
|
|
22
|
+
const pending = new Map();
|
|
23
|
+
|
|
24
|
+
function cleanupSocket(socket) {
|
|
25
|
+
for (const [topic, sid] of queryables) {
|
|
26
|
+
if (sid === socket.id) queryables.delete(topic);
|
|
27
|
+
}
|
|
28
|
+
for (const [id, p] of pending) {
|
|
29
|
+
if (p.sid !== socket.id) continue;
|
|
30
|
+
clearTimeout(p.timer);
|
|
31
|
+
p.resolve(undefined);
|
|
32
|
+
pending.delete(id);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
io.on("connection", (socket) => {
|
|
37
|
+
socket.on("sub", async (data = {}) => {
|
|
38
|
+
if (!data.topic) return;
|
|
39
|
+
await socket.join(`t:${data.topic}`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
socket.on("unsub", async (data = {}) => {
|
|
43
|
+
if (!data.topic) return;
|
|
44
|
+
await socket.leave(`t:${data.topic}`);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
socket.on("pub", (data = {}) => {
|
|
48
|
+
const { topic, payload } = data;
|
|
49
|
+
if (!topic) return;
|
|
50
|
+
io.to(`t:${topic}`).emit("msg", { topic, payload });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
socket.on("serve_register", (data = {}) => {
|
|
54
|
+
if (!data.topic) return;
|
|
55
|
+
queryables.set(data.topic, socket.id);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
socket.on("serve_unregister", (data = {}) => {
|
|
59
|
+
if (queryables.get(data.topic) === socket.id) queryables.delete(data.topic);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
socket.on("serve_reply", (data = {}) => {
|
|
63
|
+
const p = pending.get(data.id);
|
|
64
|
+
if (!p) return;
|
|
65
|
+
clearTimeout(p.timer);
|
|
66
|
+
p.resolve(data.payload);
|
|
67
|
+
pending.delete(data.id);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
socket.on("action", (data = {}, ack) => {
|
|
71
|
+
const { topic, payload } = data;
|
|
72
|
+
const targetSid = queryables.get(topic);
|
|
73
|
+
const target = targetSid && io.sockets.sockets.get(targetSid);
|
|
74
|
+
if (!target) {
|
|
75
|
+
if (typeof ack === "function") ack(undefined);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const id = randomUUID();
|
|
79
|
+
const timer = setTimeout(() => {
|
|
80
|
+
pending.delete(id);
|
|
81
|
+
if (typeof ack === "function") ack(undefined);
|
|
82
|
+
}, DEFAULT_ACTION_TIMEOUT_MS);
|
|
83
|
+
pending.set(id, {
|
|
84
|
+
sid: targetSid,
|
|
85
|
+
timer,
|
|
86
|
+
resolve: (v) => { if (typeof ack === "function") ack(v); },
|
|
87
|
+
});
|
|
88
|
+
target.emit("serve_call", { id, topic, payload });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
socket.on("disconnect", () => cleanupSocket(socket));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
httpServer.once("error", (err) => {
|
|
96
|
+
if (err.code === "EADDRINUSE") {
|
|
97
|
+
log.warn(`Socket.IO bridge already running on :${port} — skipping`);
|
|
98
|
+
io.close();
|
|
99
|
+
httpServer.close();
|
|
100
|
+
resolve({ close: () => {} });
|
|
101
|
+
} else {
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
httpServer.listen(port, host, () => {
|
|
107
|
+
log.step(`Socket.IO bridge listening on ws://127.0.0.1:${port}`);
|
|
108
|
+
|
|
109
|
+
const close = () => {
|
|
110
|
+
for (const p of pending.values()) {
|
|
111
|
+
clearTimeout(p.timer);
|
|
112
|
+
p.resolve(undefined);
|
|
113
|
+
}
|
|
114
|
+
pending.clear();
|
|
115
|
+
queryables.clear();
|
|
116
|
+
io.close();
|
|
117
|
+
httpServer.close();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
resolve({ close });
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// When executed directly (inside the bridge Docker container), start the server.
|
|
126
|
+
if (process.argv[1] && process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
127
|
+
const port = Number(process.env.PORT) || 10000;
|
|
128
|
+
startSocketBridge({ port });
|
|
129
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// AUTO-GENERATED by cli/bundle-templates.mjs — do not edit.
|
|
2
|
+
// ponytail: generated at publish time; not tracked in git.
|
|
3
|
+
export const TEMPLATES = {"common/README.md":"IyBfX1BMVUdJTl9OQU1FX18KCkEgTXVzY2F0IHBsdWdpbiBwcm9qZWN0LiBCdWlsZCwgZGV2ZWxvcCwgYW5kIHNoaXAgd2l0aCBgbXVzY2F0LXBrYCBDTEkuCgojIyBRdWljayBzdGFydAoKYGBgYmFzaAptdXNjYXQtcGsgZGV2ICAgICAgICAjIFVJIHByZXZpZXcgKyBiYWNrZW5kIHRvZ2V0aGVyCm11c2NhdC1wayBkZXY6dWkgICAgICMgVUkgcHJldmlldyBvbmx5Cm11c2NhdC1wayBkZXY6YmFjayAgICMgYmFja2VuZCBvbmx5IChQeXRob24gd2F0Y2hmaWxlcyAvIFJPUzIgRG9ja2VyKQptdXNjYXQtcGsgYnVpbGQgICAgICAjIGJ1aWxkICsgZGlzdHJpYnV0YWJsZSBaSVAKYGBgCgotLS0KCiMjIFByb2plY3Qgc3RydWN0dXJlCgpgYGAKX19QTFVHSU5fRElSX18vCuKUnOKUgCB1aS8K4pSCICAg4pSU4pSAIGNvbnRlbnQudHN4ICAgICAgIyBwbHVnaW4gVUkgZW50cnkgcG9pbnQK4pSc4pSAIHB5LwrilIIgICDilJTilIAgbWFpbi5weSAgICAgICAgICAjIFB5dGhvbiBiYWNrZW5kIGVudHJ5IHBvaW50CuKUnOKUgCBhc3NldHMvCuKUgiAgIOKUlOKUgCBpY29uLnN2ZyAgICAgICAgICMgaWNvbiByZWZlcmVuY2VkIGZyb20gbWFuaWZlc3QK4pSc4pSAIG1hbmlmZXN0Lmpzb24gICAgICAgICMgbWV0YWRhdGEsIGNvbnRyaWJ1dGlvbiBwb2ludHMsIHJ1bnRpbWUgY29uZmlnCuKUnOKUgCBSRUFETUUubWQgICAgICAgICAgICAjIHRoaXMgZmlsZSAoY29waWVkIHRvIGRpc3QvIG9uIGJ1aWxkKQrilJTilIAgcmVxdWlyZW1lbnRzLnR4dCAgICAgIyBQeXRob24gZGVwcyAoYXV0by1waW5uZWQgdG8gbGF0ZXN0IG9uIGJ1aWxkKQpgYGAKCj4gUk9TMiBwbHVnaW5zIGFkZGl0aW9uYWxseSBpbmNsdWRlIGEgYHJvczJfd3MvYCBkaXJlY3RvcnkuCgotLS0KCiMjIEZyb250ZW5kIChgdWkvY29udGVudC50c3hgKQoKIyMjIHVzZVJiKCkgaG9vawoKQWNjZXNzIHRoZSBjb21tdW5pY2F0aW9uIEFQSSBiZXR3ZWVuIHRoZSBwbHVnaW4gbW9kdWxlIGFuZCBVSSBmcm9tIGFueSBwbHVnaW4gY29tcG9uZW50LgoKYGBgdHN4CmltcG9ydCB7IHVzZVJiIH0gZnJvbSAiQHJhaW5ib3ctcm9ib3RpY3MvcGx1Z2luLXVpL3NkayI7CgpmdW5jdGlvbiBBcHAoKSB7CiAgY29uc3QgcmIgPSB1c2VSYigpOwp9CmBgYAoKIyMjIHJiIEFQSQoKfCBNZXRob2QgfCBEZXNjcmlwdGlvbiB8CnwtLS18LS0tfAp8IGByYi5wdWIodG9waWMsIHBheWxvYWQpYCB8IFB1Ymxpc2ggYSBtZXNzYWdlIHRvIGEgdG9waWMgfAp8IGByYi5zdWIodG9waWMsIGhhbmRsZXIpYCB8IFN1YnNjcmliZSB0byBhIHRvcGljIOKAlCByZXR1cm5zIGB7IHVuZGVjbGFyZSgpIH1gIHwKfCBgcmIuYWN0aW9uKHRvcGljLCBwYXlsb2FkPylgIHwgU2VuZCBhIHJlcXVlc3QgYW5kIGF3YWl0IGEgcmVwbHkgfAp8IGByYi5zZXJ2ZSh0b3BpYywgaGFuZGxlcilgIHwgSGFuZGxlIGluY29taW5nIHJlcXVlc3RzIGZyb20gdGhlIGJhY2tlbmQgfAp8IGByYi50b2FzdChtZXNzYWdlLCB0eXBlPylgIHwgSG9zdCB0b2FzdCBub3RpZmljYXRpb24gKGAiaW5mbyIgwrcgInN1Y2Nlc3MiIMK3ICJ3YXJuaW5nIiDCtyAiZXJyb3IiYCkgfAoKQWxsIG1ldGhvZHMgc3VwcG9ydCBHZW5lcmljIFR5cGVzOgoKYGBgdHN4CmNvbnN0IHN0YXR1cyA9IGF3YWl0IHJiLmFjdGlvbjx7IG9rOiBib29sZWFuIH0sIHsgaWQ6IG51bWJlciB9PigKICAiZ2V0LXN0YXR1cyIsCiAgeyBpZDogMSB9LAopOwoKY29uc3Qgc3ViID0gYXdhaXQgcmIuc3ViPHsgdmFsdWU6IG51bWJlciB9Pigic2Vuc29yL2RhdGEiLCAoZGF0YSkgPT4gewogIGNvbnNvbGUubG9nKGRhdGEudmFsdWUpOwp9KTsKc3ViLnVuZGVjbGFyZSgpOyAvLyBjbGVhbnVwCmBgYAoKIyMjIFRvcGljIG5hbWVzcGFjZQoKVG9waWNzIGFyZSBhdXRvbWF0aWNhbGx5IG5hbWVzcGFjZWQgYnkgcGx1Z2luIElEIHRvIHByZXZlbnQgY29sbGlzaW9ucyBhY3Jvc3MgcGx1Z2lucy4KCmBgYAoic2Vuc29yL2RhdGEiICAg4oaSICBwbHVnaW4ve3BsdWdpbklkfS9zZW5zb3IvZGF0YSAgIChhdXRvLW5hbWVzcGFjZWQpCiIvYnJvYWRjYXN0L3giICDihpIgIGJyb2FkY2FzdC94ICAgICAgICAgICAgICAgICAgICAobGVhZGluZyAvID0gYWJzb2x1dGUpCmBgYAoKIyMjIFVJIGNvbXBvbmVudHMKCkltcG9ydCBNdXNjYXQgY29tcG9uZW50cyBmcm9tIGBAcmFpbmJvdy1yb2JvdGljcy9wbHVnaW4tdWkvcmVhY3RgLiBUaGUgcGx1Z2luIGVtaXRzCmByYi0qYCB0YWdzOyB0aGUgaG9zdCByZW5kZXJzIHRoZSBhY3R1YWwgY29tcG9uZW50cy4KCmBgYHRzeAppbXBvcnQgeyBjb25uZWN0IH0gZnJvbSAiQHJhaW5ib3ctcm9ib3RpY3MvcGx1Z2luLXVpL2Nvbm5lY3QiOwppbXBvcnQgeyBCdXR0b24sIFNsaWRlciwgVGV4dCwgU3dpdGNoIH0gZnJvbSAiQHJhaW5ib3ctcm9ib3RpY3MvcGx1Z2luLXVpL3JlYWN0IjsKaW1wb3J0IHsgdXNlUmIgfSBmcm9tICJAcmFpbmJvdy1yb2JvdGljcy9wbHVnaW4tdWkvc2RrIjsKCmZ1bmN0aW9uIEFwcCgpIHsKICBjb25zdCByYiA9IHVzZVJiKCk7CiAgcmV0dXJuIDxCdXR0b24gbGFiZWw9IlJ1biIgb25QcmVzcz17KCkgPT4gcmIuYWN0aW9uKCJydW4iKX0gLz47Cn0KCmNvbm5lY3QoQXBwKTsKYGBgCgotLS0KCiMjIEJhY2tlbmQgKGBweS9tYWluLnB5YCkKClRoZSBiYWNrZW5kIGNvbW11bmljYXRlcyB3aXRoIHRoZSBmcm9udGVuZCB1c2luZyBgcmFpbmJvdy5yYl9zZGsuUkJQbHVnaW5DYWxsYC4KYG11c2NhdC1wayBkZXZgIHN0YXJ0cyB0aGUgU29ja2V0LklPIGJyaWRnZSBpbiBEb2NrZXIgKHB1Ymxpc2hlZCBvbiBwb3J0IDEwMDAwKSBhbmQKcnVucyB0aGUgYmFja2VuZCBjb250YWluZXIgc2hhcmluZyBpdHMgbmV0d29yaywgc28gdGhlIFNESyBjb25uZWN0cyBhdCBsb2NhbGhvc3Q6MTAwMDAuCmBweS9tYWluLnB5YCBzaG91bGQgY29udGFpbiBwbHVnaW4gbG9naWMgb25seSBhbmQgbXVzdCBub3Qgc3RhcnQgaXRzIG93biBzZXJ2ZXIuCgojIyMgUHl0aG9uIHBsdWdpbgoKYGBgcHl0aG9uCmltcG9ydCB0aW1lCmZyb20gcmFpbmJvdy5yYl9zZGsgaW1wb3J0IFJCQW1yU0RLLCBSQk1hbmlwdWxhdGVTREssIFJCUGx1Z2luQ2FsbAoKcGx1Z2luID0gUkJQbHVnaW5DYWxsKCkKCgpwbHVnaW4ucHVibGlzaCgic3RhdGUiLCB7InJlYWR5IjogVHJ1ZX0pCgoKQHBsdWdpbi5zdWJzY3JpYmUoImV2ZW50IikKZGVmIG9uX3VpX2V2ZW50KHBheWxvYWQ9Tm9uZSk6CiAgICBwcmludChwYXlsb2FkKQoKCkBwbHVnaW4uc2VydmUoInRvb2wvZWNobyIpCmRlZiBlY2hvKHBheWxvYWQ9Tm9uZSk6CiAgICByZXR1cm4geyJlY2hvIjogcGF5bG9hZH0KCgpkZWYgY2FsbF9mcm9udGVuZCgpOgogICAgcmVzID0gcGx1Z2luLmNhbGwoInRvb2wvcGluZyIsIHBheWxvYWQ9eyJoZWxsbyI6ICJwbHVnaW4ifSkKICAgIHByaW50KHJlcykKCgpkZWYgbW92ZV9qX2V4YW1wbGUoKToKICAgIGFtciA9IFJCQW1yU0RLKCkKICAgIG1hbmlwdWxhdGUgPSBSQk1hbmlwdWxhdGVTREsoKQogICAgcmV0dXJuIG1hbmlwdWxhdGUubW92ZS5jYWxsX21vdmVfaigKICAgICAgICByb2JvdF9tb2RlbD0iQzUwMDkyMCIsCiAgICAgICAgdGFyZ2V0PXsidGFyX3ZhbHVlcyI6IFswLCAwLCAwLCAwLCAwLCAwXSwgInRhcl9mcmFtZSI6IDAsICJ0YXJfdW5pdCI6IDB9LAogICAgKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICB3aGlsZSBUcnVlOgogICAgICAgIHRpbWUuc2xlZXAoMSkKYGBgCgojIyMgUk9TMiBwbHVnaW4KClRoZSBub2RlIGlzIGxhdW5jaGVkIHdpdGggYC0tcm9zLWFyZ3MgLXIgX19uczo9L3tzbHVnfWAgYXV0b21hdGljYWxseSBieSBgZGV2OmJhY2tgLgoKYGBgcHl0aG9uCiMgcm9zMl93cy9zcmMvX19QTFVHSU5fU0xVR19fL19fUExVR0lOX1NMVUdfXy9ub2RlLnB5CmltcG9ydCByY2xweQpmcm9tIHJjbHB5Lm5vZGUgaW1wb3J0IE5vZGUKZnJvbSBzdGRfbXNncy5tc2cgaW1wb3J0IEZsb2F0MzIKCmNsYXNzIF9fUExVR0lOX0NMQVNTX19Ob2RlKE5vZGUpOgogICAgZGVmIF9faW5pdF9fKHNlbGYpOgogICAgICAgIHN1cGVyKCkuX19pbml0X18oIl9fUExVR0lOX1NMVUdfX19ub2RlIikKICAgICAgICAjIHJlbGF0aXZlIHRvcGljIG5hbWUg4oCUIF9fbnMgcHJlZml4IGFwcGxpZWQgYXV0b21hdGljYWxseQogICAgICAgIHNlbGYucHViID0gc2VsZi5jcmVhdGVfcHVibGlzaGVyKEZsb2F0MzIsICJzZW5zb3JfZGF0YSIsIDEwKQogICAgICAgIHNlbGYuY3JlYXRlX3RpbWVyKDEuMCwgc2VsZi50aW1lcl9jYikKCiAgICBkZWYgdGltZXJfY2Ioc2VsZik6CiAgICAgICAgc2VsZi5wdWIucHVibGlzaChGbG9hdDMyKGRhdGE9NDIuMCkpCmBgYAoKLS0tCgojIyBtYW5pZmVzdC5qc29uCgpgYGBqc29uYwp7CiAgLy8gdW5pcXVlIHBsdWdpbiBJRCDigJQgcmV2ZXJzZS1kb21haW4gZm9ybWF0IHJlY29tbWVuZGVkCiAgImlkIjogImNvbS5leGFtcGxlLm15LXBsdWdpbiIsCiAgIm5hbWUiOiAiTXkgUGx1Z2luIiwKICAidmVyc2lvbiI6ICIxLjAuMCIsCgogIC8vIG1pbmltdW0gTXVzY2F0IHZlcnNpb24gcmVxdWlyZWQgdG8gbG9hZCB0aGlzIHBsdWdpbgogICJtaW5NdXNjYXRWZXJzaW9uIjogIjEuMC4wIiwKCiAgLy8gcGx1Z2luIGF1dGhvciAoZW1wdHkgc3RyaW5nIGlzIGZpbmUpCiAgImF1dGhvciI6ICJZb3VyIE5hbWUiLAoKICAvLyBkaXNwbGF5ZWQgaW4gdGhlIE11c2NhdCBwbHVnaW4gc3RvcmUgLyBpbmZvIHBhbmVsCiAgLy8gc3RyaW5nIG9yIHsgZW4gKHJlcXVpcmVkKSwga28sIGphLCB6aCwgdmksIHRoLCBlcyB9CiAgImRlc2NyaXB0aW9uIjogeyAiZW4iOiAiUGx1Z2luIGRlc2NyaXB0aW9uIiwgImtvIjogIu2UjOufrOq3uOyduCDshKTrqoUiIH0sCgogIC8vIFNWRyBzdHJpbmcgKHNpbmdsZSBmaWxlKSBvciBQTkcvSlBHIHNpemUgbWFwIChhbGwgdGhyZWUgc2l6ZXMgcmVxdWlyZWQpCiAgImljb25zIjogImFzc2V0cy9pY29uLnN2ZyIsCiAgLy8gImljb25zIjogeyAiMTYiOiAiYXNzZXRzL2ljb24tMTYucG5nIiwgIjQ4IjogImFzc2V0cy9pY29uLTQ4LnBuZyIsICIxMjgiOiAiYXNzZXRzL2ljb24tMTI4LnBuZyIgfSwKCiAgLy8gdGFyZ2V0TW9kZWxzIGFuZC9vciB0YXJnZXRDYXRlZ29yeXMg4oCUIGF0IGxlYXN0IG9uZSBpcyByZXF1aXJlZAogIC8vICJhbGwiID0gZXZlcnkgdGFyZ2V0LCBvciBsaXN0IHNwZWNpZmljIHZhbHVlcwogICJ0YXJnZXRNb2RlbHMiOiAiYWxsIiwKICAvLyAidGFyZ2V0TW9kZWxzIjogWyJDNTAwOTIwIiwgIkM1MDA5MzAiXSwKICAidGFyZ2V0Q2F0ZWdvcnlzIjogImFsbCIsCiAgLy8gInRhcmdldENhdGVnb3J5cyI6IFsibWFuaXB1bGF0ZSIsICJhbXIiLCAicmJ5MSIsICJ2aXNpb24iXSwKCiAgImNvbnRyaWJ1dGVzIjogWwogICAgLy8gcHJvZ3JhbS5jb21tYW5kIOKAlCBhZGRzIGEgc3RlcCBub2RlIHRvIHRoZSBwcm9ncmFtIHRyZWUKICAgIHsKICAgICAgInBvaW50IjogInByb2dyYW0uY29tbWFuZCIsCiAgICAgICJpY29uIjogImFzc2V0cy9pY29uLnN2ZyIsCiAgICAgICJsYWJlbCI6IHsgImtvIjogIuuCtCDtlIzrn6zqt7jsnbgiLCAiZW4iOiAiTXkgUGx1Z2luIiB9LAogICAgICAidWkiOiAicHJvZ3JhbS9jb250ZW50IiwgICAvLyBtdXN0IGJlIHVuaXF1ZSBhY3Jvc3MgYWxsIGNvbnRyaWJ1dGlvbnMKICAgICAgImNvbW1hbmRJbmZvIjogewogICAgICAgICJuYW1lIjogIk15IFBsdWdpbiIsCiAgICAgICAgImljb24iOiAiYXNzZXRzL2ljb24uc3ZnIiwKICAgICAgICAiZnVuY05hbWUiOiAicGx1Z2luLnJ1biIsCiAgICAgICAgImFyZ3MiOiB7fQogICAgICB9CiAgICB9LAogICAgLy8gdG9vbGJhciDigJQgYWRkcyBhIGJ1dHRvbiB0byB0aGUgdG9wIHRvb2xiYXIgKHJlcXVpcmVkKQogICAgewogICAgICAicG9pbnQiOiAidG9vbGJhciIsCiAgICAgICJpY29uIjogImFzc2V0cy9pY29uLnN2ZyIsCiAgICAgICJsYWJlbCI6IHsgImtvIjogIuuCtCDtlIzrn6zqt7jsnbgiLCAiZW4iOiAiTXkgUGx1Z2luIiB9LAogICAgICAidWkiOiAiY29udGVudCIsICAgICAgICAgICAvLyBtdXN0IGJlIHVuaXF1ZSBhY3Jvc3MgYWxsIGNvbnRyaWJ1dGlvbnMKICAgICAgIm9wZW5zIjogInBvcG92ZXIiCiAgICB9LAogICAgLy8gbW92ZS5ybmIg4oCUIGFkZHMgYSBwYW5lbCB0byB0aGUgTW92ZSBzY3JlZW4gcmlnaHQtc2lkZSBhcmVhCiAgICB7CiAgICAgICJwb2ludCI6ICJtb3ZlLnJuYiIsCiAgICAgICJpY29uIjogImFzc2V0cy9pY29uLnN2ZyIsCiAgICAgICJsYWJlbCI6IHsgImtvIjogIuuCtCDtlIzrn6zqt7jsnbgiLCAiZW4iOiAiTXkgUGx1Z2luIiB9LAogICAgICAidWkiOiAibW92ZS9jb250ZW50IiAgICAgICAvLyBtdXN0IGJlIHVuaXF1ZSBhY3Jvc3MgYWxsIGNvbnRyaWJ1dGlvbnMKICAgIH0KICBdLAoKICAvLyBydW50aW1lIOKAlCBvbmUgb2Y6CiAgLy8gICB7ICJ0eXBlIjogIm5vbmUiIH0KICAvLyAgIHsgInR5cGUiOiAicHl0aG9uIiwgImVudHJ5IjogIm1haW4ucHkiIH0KICAvLyAgIHsgInR5cGUiOiAiY3BwIiwgImVudHJ5IjogIjxleGVjdXRhYmxlIG5hbWU+IiB9CiAgLy8gICB7ICJ0eXBlIjogInJvczIiLCAicm9zQ2xpZW50IjogInJjbHB5IiB8ICJyY2xjcHAiLCAicGFja2FnZSI6ICIuLi4iLCAibGF1bmNoIjogIi4uLl9ub2RlIiB9CiAgInJ1bnRpbWUiOiB7ICJ0eXBlIjogInB5dGhvbiIsICJlbnRyeSI6ICJtYWluLnB5IiB9Cn0KYGBgCgoqKkJhY2tlbmQgdHlwZXM6KioKCnwgdHlwZSB8IHNvdXJjZSBkaXIgfCBidWlsZCAvIHJ1biB8IGVudHJ5IHwKfC0tLXwtLS18LS0tfC0tLXwKfCBgcHl0aG9uYCB8IGBweS9gIHwgcGlwICsgcnVuIGluIERvY2tlciB8IGBtYWluLnB5YCB8CnwgYGNwcGAgfCBgY3BwL2AgfCBDTWFrZSBpbiBEb2NrZXIgfCBidWlsdCBleGVjdXRhYmxlIG5hbWUgfAp8IGByb3MyYCAocmNscHkpIHwgYHJvczJfd3MvYCB8IGNvbGNvbiAoYW1lbnRfcHl0aG9uKSBpbiBEb2NrZXIgfCDigJQgfAp8IGByb3MyYCAocmNsY3BwKSB8IGByb3MyX3dzL2AgfCBjb2xjb24gKGFtZW50X2NtYWtlKSBpbiBEb2NrZXIgfCDigJQgfAoKPiBFdmVyeSBiYWNrIGVuZCBydW5zIGluIGEgRG9ja2VyIGNvbnRhaW5lciBtYXRjaGluZyB0aGUgcm9ib3QgKFVidW50dSAyNC4wNCArIFB5dGhvbiAzLjEyKSwgc28gZGV2IGJlaGF2ZXMgbGlrZSBwcm9kdWN0aW9uIGFuZCBjb21waWxlZCBiaW5hcmllcyBhcmUgQUJJLWNvbXBhdGlibGUuICoqRG9ja2VyIGlzIHJlcXVpcmVkKiogZm9yIGFsbCBiYWNrZW5kIHR5cGVzIChvbmx5IGEgZnJvbnRlbmQtb25seSBgbm9uZWAgcGx1Z2luIHNraXBzIGl0KS4gTm8gbG9jYWwgUHl0aG9uL3Rvb2xjaGFpbiBzZXR1cCBpcyBuZWVkZWQuCgoqKkNvbnRyaWJ1dGlvbiBwb2ludHM6KioKCnwgcG9pbnQgfCBXaGVyZSBpdCBhcHBlYXJzIHwgYGNvbW1hbmRJbmZvYCByZXF1aXJlZCB8CnwtLS18LS0tfDotLS06fAp8IGB0b29sYmFyYCB8IFRvcCB0b29sYmFyIGJ1dHRvbiAobXVzdCBoYXZlIGF0IGxlYXN0IG9uZSkgfCDigJQgfAp8IGBwcm9ncmFtLmNvbW1hbmRgIHwgUHJvZ3JhbSB0cmVlIHN0ZXAgbm9kZSB8IOKckyB8CnwgYG1vdmUucm5iYCB8IE1vdmUgc2NyZWVuIHJpZ2h0LXNpZGUgcGFuZWwgfCDigJQgfAoKKipgY29tbWFuZEluZm9gIGZpZWxkcyoqIChmb3IgYHByb2dyYW0uY29tbWFuZGApOgoKfCBmaWVsZCB8IHR5cGUgfCBkZXNjcmlwdGlvbiB8CnwtLS18LS0tfC0tLXwKfCBgbmFtZWAgfCBgc3RyaW5nYCB8IERpc3BsYXkgbmFtZSBvZiB0aGUgc3RlcCBub2RlIHwKfCBgaWNvbmAgfCBgc3RyaW5nYCB8IEljb24gcGF0aCB8CnwgYGljb25DbGFzc05hbWVgIHwgYHN0cmluZz9gIHwgT3B0aW9uYWwgQ1NTIGNsYXNzIGZvciB0aGUgaWNvbiB8CnwgYGZ1bmNOYW1lYCB8IGBzdHJpbmc/YCB8IEJhY2tlbmQgZnVuY3Rpb24gbmFtZSB0byBjYWxsIHwKfCBgYXJnc2AgfCBgUmVjb3JkPHN0cmluZywgYW55Pj9gIHwgRGVmYXVsdCBhcmd1bWVudHMgfAp8IGBkaXNhYmxlZGAgfCBgYm9vbGVhbj9gIHwgV2hldGhlciB0aGUgbm9kZSBpcyBkaXNhYmxlZCB8CnwgYGFsbG93Q2hpbGRyZW5gIHwgYHN0cmluZ1tdP2AgfCBBbGxvd2VkIGNoaWxkIGNvbW1hbmRzIHwKfCBgYWxsb3dQYXJlbnRzYCB8IGBzdHJpbmdbXT9gIHwgQWxsb3dlZCBwYXJlbnQgY29tbWFuZHMgfAp8IGBub3RBbGxvd0NoaWxkcmVuYCB8IGBzdHJpbmdbXT9gIHwgRGlzYWxsb3dlZCBjaGlsZCBjb21tYW5kcyB8CnwgYHN5bmNHcm91cElkYCB8IGBib29sZWFuP2AgfCBTeW5jIGdyb3VwIElEIGFjcm9zcyBjaGlsZHJlbiB8CnwgYGRpc2FibGVDb3B5YCB8IGBib29sZWFuP2AgfCBQcmV2ZW50IGNvcHkgfAp8IGBkaXNhYmxlRGVsZXRlYCB8IGBib29sZWFuP2AgfCBQcmV2ZW50IGRlbGV0ZSB8CnwgYHJlcXVpcmVDaGlsZHJlbmAgfCBgYm9vbGVhbj9gIHwgV2hldGhlciBhIGNoaWxkIGNvbW1hbmQgaXMgcmVxdWlyZWQgfAp8IGByZXF1aXJlUGFyZW50YCB8IGBib29sZWFuP2AgfCBXaGV0aGVyIGEgcGFyZW50IGNvbW1hbmQgaXMgcmVxdWlyZWQgfAp8IGBjaGlsZHJlbkNvbW1hbmRgIHwgYHN0cmluZz9gIHwgRGVmYXVsdCBjb21tYW5kIHR5cGUgZm9yIGNoaWxkcmVuIHwKCioqVmFsaWRhdGlvbiBydWxlczoqKgotIGBpZGAsIGBuYW1lYCwgYHZlcnNpb25gLCBgbWluTXVzY2F0VmVyc2lvbmAg4oCUIHJlcXVpcmVkIG5vbi1lbXB0eSBzdHJpbmdzCi0gYGF1dGhvcmAg4oCUIHJlcXVpcmVkIG5vbi1lbXB0eSBzdHJpbmcKLSBgZGVzY3JpcHRpb25gIOKAlCByZXF1aXJlZCAoYHN0cmluZ2Agb3IgYHsgZW4gKHJlcXVpcmVkKSwga28/LCBqYT8sIHpoPywgdmk/LCB0aD8sIGVzPyB9YCkKLSBgaWNvbnNgIOKAlCBTVkc6IHNpbmdsZSBzdHJpbmcgcGF0aCBlbmRpbmcgaW4gYC5zdmdgOyBQTkcvSlBHOiBvYmplY3Qgd2l0aCBgIjE2ImAsIGAiNDgiYCwgYCIxMjgiYCBhbGwgcHJlc2VudAotIGB0YXJnZXRNb2RlbHNgIOKAlCBgImFsbCJgIG9yIGEgbm9uLWVtcHR5IGFycmF5IG9mIHJvYm90IG1vZGVsIElEIHN0cmluZ3MgKGF0IGxlYXN0IG9uZSBvZiBgdGFyZ2V0TW9kZWxzYCAvIGB0YXJnZXRDYXRlZ29yeXNgIHJlcXVpcmVkKQotIGB0YXJnZXRDYXRlZ29yeXNgIOKAlCBgImFsbCJgIG9yIGEgbm9uLWVtcHR5IGFycmF5IG9mIGAibWFuaXB1bGF0ZSIgfCAiYW1yIiB8ICJyYnkxIiB8ICJ2aXNpb24iYCAoYXQgbGVhc3Qgb25lIG9mIGB0YXJnZXRNb2RlbHNgIC8gYHRhcmdldENhdGVnb3J5c2AgcmVxdWlyZWQpCi0gYGNvbnRyaWJ1dGVzYCDigJQgcmVxdWlyZWQgYXJyYXk7IG11c3QgY29udGFpbiBhdCBsZWFzdCBvbmUgYHRvb2xiYXJgIGVudHJ5Ci0gYHVpYCDigJQgZWFjaCBjb250cmlidXRpb24gbXVzdCBoYXZlIGEgdW5pcXVlIHZhbHVlCi0gYGNvbW1hbmRJbmZvYCDigJQgcmVxdWlyZWQgd2hlbiBgcG9pbnRgIGlzIGAicHJvZ3JhbS5jb21tYW5kImAKCmBtdXNjYXQtcGsgYnVpbGRgIHRocm93cyBvbiBhbnkgdmFsaWRhdGlvbiBlcnJvci4gYG11c2NhdC1wayBkZXZgIHByaW50cyB3YXJuaW5ncy4KCi0tLQoKIyMgTG9jYWxpemVkIGRvY3VtZW50YXRpb24KCllvdSBjYW4gc2hpcCBwZXItbGFuZ3VhZ2UgUkVBRE1FIGZpbGVzIGFsb25nc2lkZSB0aGUgbWFpbiBgUkVBRE1FLm1kYC4gUGxhY2UgdGhlbSBpbiB0aGUgcHJvamVjdCByb290OgoKYGBgClJFQURNRS5tZCAgICAgICAgIyBkZWZhdWx0IC8gRW5nbGlzaCBmYWxsYmFjawpSRUFETUUua28ubWQgICAgICMgS29yZWFuClJFQURNRS5lbi5tZCAgICAgIyBleHBsaWNpdCBFbmdsaXNoIChvcHRpb25hbCkKUkVBRE1FLmphLm1kICAgICAjIEphcGFuZXNlCmBgYAoKQWxsIGBSRUFETUUqLm1kYCBmaWxlcyBmb3VuZCBhdCB0aGUgcHJvamVjdCByb290IGFyZSBhdXRvbWF0aWNhbGx5IGNvcGllZCB0byBgZGlzdC9gIG9uCmBtdXNjYXQtcGsgYnVpbGRgLiBNdXNjYXQgZGlzcGxheXMgdGhlIGZpbGUgbWF0Y2hpbmcgdGhlIHVzZXIncyBsb2NhbGUsCmZhbGxpbmcgYmFjayB0byBgUkVBRE1FLm1kYCBpZiBub25lIG1hdGNoZXMuCgotLS0KCiMjIEluc3RhbGxpbmcgUHl0aG9uIGRlcGVuZGVuY2llcwoKQWRkIGFueSBwYWNrYWdlIHdpdGggYW4gb3B0aW9uYWwgdmVyc2lvbiBwaW4gdG8gYHJlcXVpcmVtZW50cy50eHRgOgoKYGBgCnJhaW5ib3ctcmItc2RrCnJhaW5ib3ctcmItZmxhdC1idWZmZXJzCm51bXB5PT0xLjI2LjQKb3BlbmN2LXB5dGhvbgpgYGAKClJ1biBgbXVzY2F0LXBrIGRldmAgb3IgYG11c2NhdC1wayBkZXY6YmFja2Ag4oCUIGRlcGVuZGVuY2llcyBhcmUgaW5zdGFsbGVkIGF1dG9tYXRpY2FsbHkgaW5zaWRlIHRoZSBkZXYgY29udGFpbmVyIChgcGlwIGluc3RhbGwgLXIgcmVxdWlyZW1lbnRzLnR4dGApIG9uIGV2ZXJ5IHN0YXJ0dXAuIE5vIGxvY2FsIFB5dGhvbiBvciB2ZW52IG5lZWRlZC4KCj4gYG11c2NhdC1wayBidWlsZGAgb25seSAqKnBpbnMqKiB2ZXJzaW9ucyBpbiBgcmVxdWlyZW1lbnRzLnR4dGAg4oCUIGl0IGRvZXMgbm90IHJ1biBgcGlwIGluc3RhbGxgICh0aGUgcm9ib3QgaW5zdGFsbHMgdGhlbSBhdCBsb2FkIHRpbWUpLgoKLS0tCgojIyBTd2l0Y2hpbmcgYmFja2VuZCB0eXBlCgpgYGBiYXNoCm11c2NhdC1wayB0eXBlIGNoYW5nZSAgICAgICAgICAgICAgICAgIyBpbnRlcmFjdGl2ZQptdXNjYXQtcGsgdHlwZSBjaGFuZ2UgLS10byBjcHAgICAgICAgICMg4oaSIEMrKwptdXNjYXQtcGsgdHlwZSBjaGFuZ2UgLS10byByb3MyICAgICAgICMg4oaSIFJPUzIgKHJjbHB5IC8gUHl0aG9uKQptdXNjYXQtcGsgdHlwZSBjaGFuZ2UgLS10byByb3MyLWNwcCAgICMg4oaSIFJPUzIgKHJjbGNwcCAvIEMrKykKbXVzY2F0LXBrIHR5cGUgY2hhbmdlIC0tdG8gcHl0aG9uICAgICAjIOKGkiBQeXRob24KYGBgCgo+IEV4aXN0aW5nIGJhY2tlbmQgZGlycyAoYHJvczJfd3MvYCwgYGNwcC9gKSBhcmUgbm90IGRlbGV0ZWQgd2hlbiBzd2l0Y2hpbmcgYXdheS4gUmVtb3ZlIHRoZW0gbWFudWFsbHkgaWYgbm8gbG9uZ2VyIG5lZWRlZC4KCi0tLQoKIyMgU2hpcHBpbmcKCmBgYGJhc2gKbXVzY2F0LXBrIGJ1aWxkICAgICAgICAgICAgIyBidWlsZCArIFpJUAptdXNjYXQtcGsgYnVpbGQgLS1uby16aXAgICAjIGJ1aWxkIG9ubHksIG5vIFpJUApgYGAKCi0gQnVpbGRzIGB1aS9jb250ZW50LnRzeGAg4oaSIGBkaXN0L2NvbnRlbnQuanNgIChtaW5pZmllZCkKLSBQaW5zIGByZXF1aXJlbWVudHMudHh0YCB0byBsYXRlc3QgdmVyc2lvbnMgKHN0YWJsZS1vbmx5IG9uIGBtYWluYCBicmFuY2gpCi0gQ29waWVzIGBhc3NldHMvYCwgYG1hbmlmZXN0Lmpzb25gLCBgUkVBRE1FKi5tZGAsIGBweS9gLCBhbmQgYHJlcXVpcmVtZW50cy50eHRgIGludG8gYGRpc3QvYAotIFJPUzI6IHJ1bnMgYGNvbGNvbiBidWlsZGAgaW5zaWRlIHRoZSBKYXp6eSBjb250YWluZXI7IGluY2x1ZGVzIGByb3MyX3dzL2luc3RhbGwvYAotIE91dHB1dDogYF9fUExVR0lOX0RJUl9fLXt2ZXJzaW9ufS56aXBgCgpaSVAgbGF5b3V0OgoKYGBgCl9fUExVR0lOX0RJUl9fLTEuMC4wLnppcArilJzilIAgZGlzdC8K4pSCICAg4pSc4pSAIGNvbnRlbnQuanMgICAgICAgICMgYnVpbHQgVUkK4pSCICAg4pSc4pSAIG1hbmlmZXN0Lmpzb24K4pSCICAg4pSc4pSAIGFzc2V0cy8K4pSCICAg4pSc4pSAIFJFQURNRS5tZArilIIgICDilJzilIAgUkVBRE1FLmtvLm1kICAgICAgIyBpZiBwcmVzZW50CuKUgiAgIOKUnOKUgCBtYWluLnB5ICAgICAgICAgICAjIGZyb20gcHkvCuKUgiAgIOKUlOKUgCByZXF1aXJlbWVudHMudHh0ICAjIHBpbm5lZArilJTilIAgcm9zMl93cy9pbnN0YWxsLyAgICAgICMgUk9TMiBvbmx5CmBgYAoKLS0tCgojIyBBbGwgY29tbWFuZHMKCmBgYGJhc2gKbXVzY2F0LXBrIGRldiAgICAgICAgIyBVSSArIGJhY2tlbmQKbXVzY2F0LXBrIGRldjp1aSAgICAgIyBVSSBwcmV2aWV3IG9ubHkgKHByZXZpZXctaG9zdCkKbXVzY2F0LXBrIGRldjpiYWNrICAgIyBiYWNrZW5kIG9ubHkgKFB5dGhvbjogd2F0Y2hmaWxlcyAvIFJPUzI6IERvY2tlcikKbXVzY2F0LXBrIGJ1aWxkICAgICAgIyBidWlsZCDihpIgZGlzdC8gKyBkaXN0cmlidXRhYmxlIFpJUCAoLS1uby16aXAgdG8gc2tpcCkKYGBgCgpgYGBiYXNoCm11c2NhdC1wayB0eXBlIGNoYW5nZSAgICAjIHN3aXRjaCBiYWNrZW5kIHR5cGUKbXVzY2F0LXBrIHNkayBsaXN0ICAgICAgICMgc2hvdyBjdXJyZW50IFB5dGhvbiBTREsgdmVyc2lvbgptdXNjYXQtcGsgc2RrIHVwZGF0ZSAgICAgIyB1cGRhdGUgUHl0aG9uIFNESyB0byBsYXRlc3QKbXVzY2F0LXBrIHVwZGF0ZSAgICAgICAgICMgdXBkYXRlIG11c2NhdC1wbHVnaW4ta2l0IGl0c2VsZgptdXNjYXQtcGsgdW5pbnN0YWxsICAgICAgIyByZW1vdmUgbXVzY2F0LXBrIGl0c2VsZgpgYGAK","common/assets/icon.svg":"PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiIgd2lkdGg9IjFlbSIgaGVpZ2h0PSIxZW0iIGZpbGw9Im5vbmUiPgogIDxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTE2IDNhMTMgMTMgMCAxIDAgMCAyNiAxMyAxMyAwIDAgMCAwLTI2Wm0wIDRhMyAzIDAgMCAxIDMgM3YyLjE4YTMgMyAwIDAgMSAwIDUuNjRWMjBhMyAzIDAgMSAxLTYgMHYtMi4xOGEzIDMgMCAwIDEgMC01LjY0VjEwYTMgMyAwIDAgMSAzLTNaIi8+Cjwvc3ZnPgo=","common/manifest.json":"ewogICJpZCI6ICJfX1BMVUdJTl9JRF9fIiwKICAibmFtZSI6ICJfX1BMVUdJTl9OQU1FX18iLAogICJ2ZXJzaW9uIjogIjEuMC4wIiwKICAibWluTXVzY2F0VmVyc2lvbiI6ICIxLjAuMCIsCiAgImF1dGhvciI6ICJfX1BMVUdJTl9BVVRIT1JfXyIsCiAgImVtYWlsIjogIl9fUExVR0lOX0VNQUlMX18iLAogICJkZXNjcmlwdGlvbiI6IHsKICAgICJrbyI6ICIiLAogICAgImVuIjogIiIKICB9LAogICJpY29ucyI6ICJhc3NldHMvaWNvbi5zdmciLAogICJ0YXJnZXRNb2RlbHMiOiAiYWxsIiwKICAidGFyZ2V0Q2F0ZWdvcnlzIjogImFsbCIsCiAgImNvbnRyaWJ1dGVzIjogWwogICAgewogICAgICAicG9pbnQiOiAidG9vbGJhciIsCiAgICAgICJpY29uIjogImFzc2V0cy9pY29uLnN2ZyIsCiAgICAgICJsYWJlbCI6IHsgImtvIjogIl9fUExVR0lOX05BTUVfXyIsICJlbiI6ICJfX1BMVUdJTl9OQU1FX18iIH0sCiAgICAgICJ1aSI6ICJjb250ZW50IiwKICAgICAgIm9wZW5zIjogInBvcG92ZXIiCiAgICB9CiAgXSwKICAicnVudGltZSI6IHsgInR5cGUiOiAiX19SVU5USU1FX1RZUEVfXyIsICJlbnRyeSI6ICJtYWluLnB5IiB9Cn0K","common/package.json":"ewogICJuYW1lIjogIl9fUExVR0lOX0lEX18iLAogICJ2ZXJzaW9uIjogIjEuMC4wIiwKICAicHJpdmF0ZSI6IHRydWUsCiAgInR5cGUiOiAibW9kdWxlIiwKICAiZGVwZW5kZW5jaWVzIjogewogICAgIkByYWluYm93LXJvYm90aWNzL3BsdWdpbi11aSI6ICJfX1JCX1ZFUlNJT05fXyIKICB9LAogICJkZXZEZXBlbmRlbmNpZXMiOiB7CiAgICAiQHJhaW5ib3ctcm9ib3RpY3MvbXVzY2F0LXBsdWdpbi1raXQiOiAiX19SQl9WRVJTSU9OX18iLAogICAgIkByYWluYm93LXJvYm90aWNzL3ByZXZpZXctaG9zdCI6ICJfX1JCX1ZFUlNJT05fXyIsCiAgICAiQHZpdGVqcy9wbHVnaW4tcmVhY3QiOiAiXjQuMC4wIiwKICAgICJ0eXBlc2NyaXB0IjogIl41LjguMCIsCiAgICAidml0ZSI6ICJeNi4wLjAiCiAgfQp9Cg==","common/py/main.py":"IiIiCk11c2NhdCBwbHVnaW4gYmFja2VuZCDigJQgX19QTFVHSU5fTkFNRV9fCgpSdW4gd2l0aDoKICBtdXNjYXQtcGsgZGV2OmJhY2sKICBweXRob24gbWFpbi5weQoKVGhpcyB0ZW1wbGF0ZSB1c2VzIHRoZSBoaWdoLWxldmVsIFJhaW5ib3cgUHl0aG9uIFNESyBpbnN0ZWFkIG9mIHdpcmluZyB0aGUKdHJhbnNwb3J0IGxheWVyIGRpcmVjdGx5LgoiIiIKCmltcG9ydCB0aW1lCmZyb20gdHlwaW5nIGltcG9ydCBBbnkKCmZyb20gcmFpbmJvdy5yYl9zZGsgaW1wb3J0IFJCTWFuaXB1bGF0ZVNESywgUkJQbHVnaW5DYWxsCgoKUExVR0lOX05BTUUgPSAiX19QTFVHSU5fTkFNRV9fIgoKcGx1Z2luID0gUkJQbHVnaW5DYWxsKCkKCgpAcGx1Z2luLnN1YnNjcmliZSgiZXZlbnQiKQpkZWYgb25fdWlfZXZlbnQocGF5bG9hZDogZGljdFtzdHIsIEFueV0gfCBOb25lID0gTm9uZSk6CiAgICAiIiJIYW5kbGUgbWVzc2FnZXMgcHVibGlzaGVkIGZyb20gdGhlIHBsdWdpbiBVSSB3aXRoIHJiLnB1YigiZXZlbnQiLCAuLi4pLiIiIgogICAgcHJpbnQoZiJbe1BMVUdJTl9OQU1FfV0gZXZlbnQ6IiwgcGF5bG9hZCkKCgpAcGx1Z2luLnNlcnZlKCJncmlwIikKZGVmIGdyaXAocGF5bG9hZDogZGljdFtzdHIsIEFueV0gfCBOb25lID0gTm9uZSk6CiAgICAiIiJIYW5kbGUgdGhlIGRlZmF1bHQgVUkgYnV0dG9uOiByYi5hY3Rpb24oImdyaXAiLCB7IHdpZHRoIH0pLiIiIgogICAgd2lkdGggPSAocGF5bG9hZCBvciB7fSkuZ2V0KCJ3aWR0aCIsIDIwKQogICAgcHJpbnQoZiJbe1BMVUdJTl9OQU1FfV0gZ3JpcCByZXF1ZXN0ZWQ6IHdpZHRoPXt3aWR0aH0iKQoKICAgIHBsdWdpbi5wdWJsaXNoKCJzdGF0ZSIsIHsicmVhZHkiOiBUcnVlLCAibGFzdF9ncmlwX3dpZHRoIjogd2lkdGh9KQogICAgcmV0dXJuIHsib2siOiBUcnVlLCAid2lkdGgiOiB3aWR0aH0KCgpAcGx1Z2luLnNlcnZlKCJ0b29sL2VjaG8iKQpkZWYgZWNobyhwYXlsb2FkOiBkaWN0W3N0ciwgQW55XSB8IE5vbmUgPSBOb25lKToKICAgICIiIlNtYWxsIHF1ZXJ5IGV4YW1wbGUgY2FsbGFibGUgZnJvbSB0aGUgVUkgd2l0aCByYi5hY3Rpb24oInRvb2wvZWNobyIsIC4uLikuIiIiCiAgICByZXR1cm4geyJlY2hvIjogcGF5bG9hZCwgInBsdWdpbiI6IFBMVUdJTl9OQU1FfQoKCmRlZiBwaW5nX3VpX2V4YW1wbGUoKToKICAgICIiIgogICAgRXhhbXBsZTogY2FsbCBhIHF1ZXJ5YWJsZSBzZXJ2ZWQgYnkgdGhlIFVJLgoKICAgIEluIHRoZSBmcm9udGVuZCwgcmVnaXN0ZXI6CiAgICAgIHJiLnNlcnZlKCJ0b29sL3BpbmciLCBhc3luYyAocGF5bG9hZCkgPT4gKHsgcG9uZzogcGF5bG9hZCB9KSkKICAgICIiIgogICAgcmVzID0gcGx1Z2luLmNhbGwoInRvb2wvcGluZyIsIHBheWxvYWQ9eyJoZWxsbyI6ICJwbHVnaW4ifSkKICAgIHByaW50KGYiW3tQTFVHSU5fTkFNRX1dIHBpbmcgcmVzcG9uc2U6IiwgcmVzKQogICAgcmV0dXJuIHJlcwoKCmRlZiBtb3ZlX2pfZXhhbXBsZSgpOgogICAgIiIiCiAgICBFeGFtcGxlOiB1c2Ugb3RoZXIgcm9ib3QgU0RLcyBmcm9tIHRoZSBzYW1lIGJhY2tlbmQuCgogICAgVGhpcyBmdW5jdGlvbiBpcyBub3QgY2FsbGVkIGF1dG9tYXRpY2FsbHkuIEFkZCBzYWZldHkgY2hlY2tzIGFuZCBjYWxsIGl0CiAgICBmcm9tIHlvdXIgb3duIGhhbmRsZXIgd2hlbiB0aGUgcm9ib3QgaXMgcmVhZHkuCiAgICAiIiIKICAgIG1hbmlwdWxhdGUgPSBSQk1hbmlwdWxhdGVTREsoKQoKICAgIHJldHVybiBtYW5pcHVsYXRlLm1vdmUuY2FsbF9tb3ZlX2ooCiAgICAgICAgcm9ib3RfbW9kZWw9IkM1MDA5MjAiLAogICAgICAgIHRhcmdldD17CiAgICAgICAgICAgICJ0YXJfdmFsdWVzIjogWzAsIDAsIDAsIDAsIDAsIDBdLAogICAgICAgICAgICAidGFyX2ZyYW1lIjogMCwKICAgICAgICAgICAgInRhcl91bml0IjogMCwKICAgICAgICB9LAogICAgKQoKCmRlZiBtYWluKCk6CiAgICBwbHVnaW4ucHVibGlzaCgic3RhdGUiLCB7InJlYWR5IjogVHJ1ZSwgInBsdWdpbiI6IFBMVUdJTl9OQU1FfSkKICAgIHByaW50KGYiW3tQTFVHSU5fTkFNRX1dIGJhY2tlbmQgcmVhZHkiKQoKICAgIHRyeToKICAgICAgICB3aGlsZSBUcnVlOgogICAgICAgICAgICB0aW1lLnNsZWVwKDEpCiAgICBleGNlcHQgS2V5Ym9hcmRJbnRlcnJ1cHQ6CiAgICAgICAgcHJpbnQoZiJbe1BMVUdJTl9OQU1FfV0gc3RvcHBlZCIpCgoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIG1haW4oKQo=","common/requirements.txt":"cmFpbmJvdy1yYi1zZGs9PTAuMC45LmRldjQzCnJhaW5ib3ctcmItZmxhdC1idWZmZXJzPT0wLjAuOS5kZXY0MwpyYWluYm93LXJiLXplbm9oPT0wLjAuOS5kZXY0MwpyZXF1ZXN0cz09Mi4zNC4yCg==","common/tsconfig.json":"ewogICJjb21waWxlck9wdGlvbnMiOiB7CiAgICAidGFyZ2V0IjogIkVTTmV4dCIsCiAgICAibW9kdWxlIjogIkVTTmV4dCIsCiAgICAibW9kdWxlUmVzb2x1dGlvbiI6ICJidW5kbGVyIiwKICAgICJqc3giOiAicmVhY3QtanN4IiwKICAgICJzdHJpY3QiOiB0cnVlLAogICAgInNraXBMaWJDaGVjayI6IHRydWUsCiAgICAibm9FbWl0IjogdHJ1ZSwKICAgICJsaWIiOiBbIkRPTSIsICJET00uSXRlcmFibGUiLCAiRVNOZXh0Il0KICB9LAogICJpbmNsdWRlIjogWyJ1aSJdCn0K","common/ui/content.tsx":"aW1wb3J0IHsgdXNlU3RhdGUgfSBmcm9tICJyZWFjdCI7CmltcG9ydCB7IGNvbm5lY3QgfSBmcm9tICJAcmFpbmJvdy1yb2JvdGljcy9wbHVnaW4tdWkvY29ubmVjdCI7CmltcG9ydCB7IFNsaWRlciwgQnV0dG9uLCBUZXh0LCBTd2l0Y2ggfSBmcm9tICJAcmFpbmJvdy1yb2JvdGljcy9wbHVnaW4tdWkvcmVhY3QiOwppbXBvcnQgeyB1c2VSYiB9IGZyb20gIkByYWluYm93LXJvYm90aWNzL3BsdWdpbi11aS9zZGsiOwoKZnVuY3Rpb24gQXBwKCkgewogIGNvbnN0IHJiID0gdXNlUmIoKTsKICBjb25zdCBbd2lkdGgsIHNldFdpZHRoXSA9IHVzZVN0YXRlKDIwKTsKICBjb25zdCBbb3Blbiwgc2V0T3Blbl0gPSB1c2VTdGF0ZShmYWxzZSk7CgogIHJldHVybiAoCiAgICA8PgogICAgICA8VGV4dD5fX1BMVUdJTl9OQU1FX18gd2lkdGg6IHt3aWR0aH08L1RleHQ+CiAgICAgIDxTbGlkZXIKICAgICAgICB2YWx1ZT17d2lkdGh9CiAgICAgICAgbWluPXswfQogICAgICAgIG1heD17MTAwfQogICAgICAgIG9uQ2hhbmdlPXsoZXZlbnQ6IHsgZGV0YWlsPzogbnVtYmVyIH0pID0+IHNldFdpZHRoKGV2ZW50LmRldGFpbCA/PyAwKX0KICAgICAgLz4KICAgICAgPEJ1dHRvbiBsYWJlbD0iR3JpcCIgb25QcmVzcz17KCkgPT4gcmIuYWN0aW9uKCJncmlwIiwgeyB3aWR0aCB9KX0gLz4KICAgICAgPFN3aXRjaAogICAgICAgIGNoZWNrZWQ9e29wZW59CiAgICAgICAgb25DaGFuZ2U9eyhldmVudDogeyBkZXRhaWw/OiBib29sZWFuIH0pID0+IHsKICAgICAgICAgIGNvbnN0IG5leHQgPSBldmVudC5kZXRhaWwgPz8gZmFsc2U7CiAgICAgICAgICBzZXRPcGVuKG5leHQpOwogICAgICAgICAgcmIudG9hc3QobmV4dCA/ICJvcGVuZWQiIDogImNsb3NlZCIsICJpbmZvIik7CiAgICAgICAgfX0KICAgICAgLz4KICAgIDwvPgogICk7Cn0KCmNvbm5lY3QoQXBwKTsK","common/vite.config.ts":"aW1wb3J0IHsgcmVhZGRpclN5bmMgfSBmcm9tICJmcyI7CmltcG9ydCB7IGJhc2VuYW1lLCBleHRuYW1lLCByZXNvbHZlIH0gZnJvbSAicGF0aCI7CmltcG9ydCByZWFjdCBmcm9tICJAdml0ZWpzL3BsdWdpbi1yZWFjdCI7CmltcG9ydCB7IGRlZmluZUNvbmZpZyB9IGZyb20gInZpdGUiOwoKLy8gUGx1Z2luIFVJIGFuZCBSZWFjdCBhcmUgcHJvdmlkZWQgYnkgTXVzY2F0L3ByZXZpZXctaG9zdCB2aWEgaW1wb3J0IG1hcC4KLy8gVGhleSBtdXN0IE5PVCBiZSBidW5kbGVkIGludG8gdGhlIHBsdWdpbiBaSVAuCmNvbnN0IEhPU1RfUFJPVklERURfRVhURVJOQUxTID0gWwogIC9eQHJhaW5ib3ctcm9ib3RpY3NcL3BsdWdpbi11aShcL3wkKS8sCiAgL15yZWFjdChcL3wkKS8sCiAgL15yZWFjdC1kb20oXC98JCkvLApdOwoKLy8gQWxsIHRvcC1sZXZlbCAudHN4Ly50cyBmaWxlcyBpbiB1aS8gYmVjb21lIHNlcGFyYXRlIGVudHJ5IHBvaW50cy4KLy8gU3ViZGlyZWN0b3JpZXMgKGNvbXBvbmVudHMvLCBob29rcy8sIGV0Yy4pIGFyZSBpbXBvcnRlZCBieSBlbnRyaWVzIGFuZCBidW5kbGVkIGluLgpjb25zdCB1aUVudHJpZXMgPSBPYmplY3QuZnJvbUVudHJpZXMoCiAgcmVhZGRpclN5bmMoInVpIikKICAgIC5maWx0ZXIoKGYpID0+IC9cLih0c3g/fGpzeD8pJC8udGVzdChmKSkKICAgIC5tYXAoKGYpID0+IFtiYXNlbmFtZShmLCBleHRuYW1lKGYpKSwgcmVzb2x2ZSgidWkiLCBmKV0pLAopOwoKZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHsKICBwbHVnaW5zOiBbcmVhY3QoKV0sCiAgYnVpbGQ6IHsKICAgIG91dERpcjogImRpc3QiLAogICAgZW1wdHlPdXREaXI6IGZhbHNlLAogICAgcm9sbHVwT3B0aW9uczogewogICAgICBpbnB1dDogdWlFbnRyaWVzLAogICAgICBleHRlcm5hbDogSE9TVF9QUk9WSURFRF9FWFRFUk5BTFMsCiAgICAgIG91dHB1dDogewogICAgICAgIGZvcm1hdDogImVzIiwKICAgICAgICBlbnRyeUZpbGVOYW1lczogIltuYW1lXS5qcyIsCiAgICAgICAgY2h1bmtGaWxlTmFtZXM6ICJjaHVua3MvW25hbWVdLVtoYXNoXS5qcyIsCiAgICAgICAgYXNzZXRGaWxlTmFtZXM6ICJhc3NldHMvW25hbWVdLVtoYXNoXVtleHRuYW1lXSIsCiAgICAgIH0sCiAgICB9LAogIH0sCn0pOwo=","cpp/cpp/CMakeLists.txt":"Y21ha2VfbWluaW11bV9yZXF1aXJlZChWRVJTSU9OIDMuMTYpCnByb2plY3QoX19QTFVHSU5fU0xVR19fIENYWCkKCnNldChDTUFLRV9DWFhfU1RBTkRBUkQgMTcpCnNldChDTUFLRV9DWFhfU1RBTkRBUkRfUkVRVUlSRUQgT04pCgojIFRoZSBleGVjdXRhYmxlIG5hbWUgbXVzdCBtYXRjaCBydW50aW1lLmVudHJ5IGluIG1hbmlmZXN0Lmpzb24uCmFkZF9leGVjdXRhYmxlKF9fUExVR0lOX1NMVUdfXyBtYWluLmNwcCkKCiMgVE9ETyhjcHAtc2RrKTogbGluayB0aGUgcmFpbmJvdyBjcHAgU0RLIGR5bmFtaWNhbGx5ICh0aGUgcm9ib3QgcHJvdmlkZXMgdGhlCiMgLnNvIGF0IHJ1bnRpbWUg4oCUIGRvIE5PVCBidW5kbGUvc3RhdGljLWxpbmsgaXQpLiBPbmNlIHRoZSBTREsgc2hpcHMgYW4gYXB0CiMgcGFja2FnZSB3aXRoIENNYWtlIGNvbmZpZywgZW5hYmxlOgojICAgZmluZF9wYWNrYWdlKHJhaW5ib3dfY3BwX3NkayBSRVFVSVJFRCkKIyAgIHRhcmdldF9saW5rX2xpYnJhcmllcyhfX1BMVUdJTl9TTFVHX18gUFJJVkFURSByYWluYm93OjpjcHBfc2RrKQo=","cpp/cpp/main.cpp":"Ly8gX19QTFVHSU5fTkFNRV9fIOKAlCBDKysgcGx1Z2luIGJhY2sgZW5kLgovLwovLyBUaGUgQysrIHBsdWdpbiBTREsgaXMgbm90IGF2YWlsYWJsZSB5ZXQsIHNvIHRoaXMgaXMgYSBidWlsZC9ydW4gc2tlbGV0b24uCi8vIE9uY2UgdGhlIFNESyBsYW5kcywgY29ubmVjdCB0byB0aGUgcGx1Z2luIGdhdGV3YXkgaGVyZSAodGhlIGhvc3QgZXhwb3NlcyBpdAovLyBhdCB0aGUgUkJfUExVR0lOX0dBVEVXQVkgLyBSQl9QTFVHSU5fSUQgZW52aXJvbm1lbnQgdmFyaWFibGVzKSBhbmQgd2lyZSB1cAovLyBwdWIgLyBzdWIgLyBzZXJ2ZSwgbWlycm9yaW5nIHRoZSBQeXRob24gcmFpbmJvdy5yYl9zZGsuUkJQbHVnaW5DYWxsIEFQSS4KCiNpbmNsdWRlIDxjaHJvbm8+CiNpbmNsdWRlIDxjc3RkbGliPgojaW5jbHVkZSA8aW9zdHJlYW0+CiNpbmNsdWRlIDx0aHJlYWQ+CgppbnQgbWFpbigpIHsKICBjb25zdCBjaGFyKiBwbHVnaW5faWQgPSBzdGQ6OmdldGVudigiUkJfUExVR0lOX0lEIik7CiAgc3RkOjpjb3V0IDw8ICJbX19QTFVHSU5fTkFNRV9fXSBiYWNrZW5kIHJlYWR5IgogICAgICAgICAgICA8PCAocGx1Z2luX2lkID8gc3RkOjpzdHJpbmcoIiAoaWQ9IikgKyBwbHVnaW5faWQgKyAiKSIgOiAiIikKICAgICAgICAgICAgPDwgc3RkOjplbmRsOwoKICAvLyBUT0RPOiBjb25uZWN0IHRvIHRoZSBwbHVnaW4gZ2F0ZXdheSBhbmQgcmVnaXN0ZXIgaGFuZGxlcnMgb25jZSB0aGUKICAvLyAgICAgICBDKysgU0RLIGlzIGF2YWlsYWJsZS4gRm9yIG5vdywganVzdCBrZWVwIHRoZSBwcm9jZXNzIGFsaXZlLgogIHdoaWxlICh0cnVlKSB7CiAgICBzdGQ6OnRoaXNfdGhyZWFkOjpzbGVlcF9mb3Ioc3RkOjpjaHJvbm86OnNlY29uZHMoMSkpOwogIH0KICByZXR1cm4gMDsKfQo=","ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/__init__.py":"","ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py":"aW1wb3J0IHJjbHB5CmZyb20gcmNscHkubm9kZSBpbXBvcnQgTm9kZQpmcm9tIHJhaW5ib3cucmJfc2RrIGltcG9ydCBSQk1hbmlwdWxhdGVTREsgICMgUHlQSTogcmFpbmJvdy1yYi1zZGsKCgpzZGsgPSBSQk1hbmlwdWxhdGVTREsoKQoKCmNsYXNzIF9fUExVR0lOX0NMQVNTX19Ob2RlKE5vZGUpOgogICAgZGVmIF9faW5pdF9fKHNlbGYpOgogICAgICAgIHN1cGVyKCkuX19pbml0X18oIl9fUExVR0lOX1NMVUdfX19ub2RlIikKICAgICAgICBzZWxmLmdldF9sb2dnZXIoKS5pbmZvKCJfX1BMVUdJTl9OQU1FX18gbm9kZSBzdGFydGVkIikKICAgICAgICAjIHNkayBpcyBhdmFpbGFibGUgaGVyZSDigJQgdXNlIGl0IGFsb25nc2lkZSBST1MyIHRvcGljcyBhcyBuZWVkZWQKCgpkZWYgbWFpbihhcmdzPU5vbmUpOgogICAgcmNscHkuaW5pdChhcmdzPWFyZ3MpCiAgICBub2RlID0gX19QTFVHSU5fQ0xBU1NfX05vZGUoKQogICAgcmNscHkuc3Bpbihub2RlKQogICAgbm9kZS5kZXN0cm95X25vZGUoKQogICAgcmNscHkuc2h1dGRvd24oKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBtYWluKCkK","ros2/ros2_ws/src/__PLUGIN_SLUG__/package.xml":"PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxwYWNrYWdlIGZvcm1hdD0iMyI+CiAgPG5hbWU+X19QTFVHSU5fU0xVR19fPC9uYW1lPgogIDx2ZXJzaW9uPjAuMC4xPC92ZXJzaW9uPgogIDxkZXNjcmlwdGlvbj5fX1BMVUdJTl9OQU1FX18gUk9TMiBub2RlPC9kZXNjcmlwdGlvbj4KICA8bWFpbnRhaW5lciBlbWFpbD0idG9kb0BleGFtcGxlLmNvbSI+bWFpbnRhaW5lcjwvbWFpbnRhaW5lcj4KICA8bGljZW5zZT5Qcm9wcmlldGFyeTwvbGljZW5zZT4KICA8ZGVwZW5kPnJjbHB5PC9kZXBlbmQ+CiAgPGV4cG9ydD48YnVpbGRfdHlwZT5hbWVudF9weXRob248L2J1aWxkX3R5cGU+PC9leHBvcnQ+CjwvcGFja2FnZT4K","ros2/ros2_ws/src/__PLUGIN_SLUG__/resource/__PLUGIN_SLUG__":"","ros2/ros2_ws/src/__PLUGIN_SLUG__/setup.py":"ZnJvbSBzZXR1cHRvb2xzIGltcG9ydCBzZXR1cAoKcGFja2FnZV9uYW1lID0gIl9fUExVR0lOX1NMVUdfXyIKCnNldHVwKAogICAgbmFtZT1wYWNrYWdlX25hbWUsCiAgICB2ZXJzaW9uPSIwLjAuMSIsCiAgICBwYWNrYWdlcz1bcGFja2FnZV9uYW1lXSwKICAgIGRhdGFfZmlsZXM9WwogICAgICAgICgic2hhcmUvYW1lbnRfaW5kZXgvcmVzb3VyY2VfaW5kZXgvcGFja2FnZXMiLCBbInJlc291cmNlLyIgKyBwYWNrYWdlX25hbWVdKSwKICAgICAgICAoInNoYXJlLyIgKyBwYWNrYWdlX25hbWUsIFsicGFja2FnZS54bWwiXSksCiAgICBdLAogICAgaW5zdGFsbF9yZXF1aXJlcz1bInNldHVwdG9vbHMiXSwKICAgIHppcF9zYWZlPVRydWUsCiAgICBlbnRyeV9wb2ludHM9ewogICAgICAgICJjb25zb2xlX3NjcmlwdHMiOiBbIl9fUExVR0lOX1NMVUdfX19ub2RlID0gX19QTFVHSU5fU0xVR19fLm5vZGU6bWFpbiJdLAogICAgfSwKKQo=","ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt":"Y21ha2VfbWluaW11bV9yZXF1aXJlZChWRVJTSU9OIDMuMTYpCnByb2plY3QoX19QTFVHSU5fU0xVR19fKQoKaWYoTk9UIENNQUtFX0NYWF9TVEFOREFSRCkKICBzZXQoQ01BS0VfQ1hYX1NUQU5EQVJEIDE3KQplbmRpZigpCgpmaW5kX3BhY2thZ2UoYW1lbnRfY21ha2UgUkVRVUlSRUQpCmZpbmRfcGFja2FnZShyY2xjcHAgUkVRVUlSRUQpCgphZGRfZXhlY3V0YWJsZShfX1BMVUdJTl9TTFVHX19fbm9kZSBzcmMvbm9kZS5jcHApCmFtZW50X3RhcmdldF9kZXBlbmRlbmNpZXMoX19QTFVHSU5fU0xVR19fX25vZGUgcmNsY3BwKQoKaW5zdGFsbChUQVJHRVRTIF9fUExVR0lOX1NMVUdfX19ub2RlCiAgREVTVElOQVRJT04gbGliLyR7UFJPSkVDVF9OQU1FfSkKCmFtZW50X3BhY2thZ2UoKQo=","ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml":"PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxwYWNrYWdlIGZvcm1hdD0iMyI+CiAgPG5hbWU+X19QTFVHSU5fU0xVR19fPC9uYW1lPgogIDx2ZXJzaW9uPjAuMC4xPC92ZXJzaW9uPgogIDxkZXNjcmlwdGlvbj5fX1BMVUdJTl9OQU1FX18gUk9TMiBub2RlIChyY2xjcHApPC9kZXNjcmlwdGlvbj4KICA8bWFpbnRhaW5lciBlbWFpbD0idG9kb0BleGFtcGxlLmNvbSI+bWFpbnRhaW5lcjwvbWFpbnRhaW5lcj4KICA8bGljZW5zZT5Qcm9wcmlldGFyeTwvbGljZW5zZT4KICA8YnVpbGR0b29sX2RlcGVuZD5hbWVudF9jbWFrZTwvYnVpbGR0b29sX2RlcGVuZD4KICA8ZGVwZW5kPnJjbGNwcDwvZGVwZW5kPgogIDxleHBvcnQ+PGJ1aWxkX3R5cGU+YW1lbnRfY21ha2U8L2J1aWxkX3R5cGU+PC9leHBvcnQ+CjwvcGFja2FnZT4K","ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp":"Ly8gX19QTFVHSU5fTkFNRV9fIOKAlCBST1MyIG5vZGUgKHJjbGNwcCkuCi8vCi8vIFRoZSBub2RlIGlzIGxhdW5jaGVkIHdpdGggYC0tcm9zLWFyZ3MgLXIgX19uczo9L3tzbHVnfWAgYXV0b21hdGljYWxseSBieQovLyBkZXY6YmFjay4gUk9TMiB0b3BpY3MgYXJlIGJyaWRnZWQgdG8gdGhlIHBsdWdpbiBjb21tdW5pY2F0aW9uIGxheWVyLCBzbyBhCi8vIHBsYWluIHJjbGNwcCBub2RlIGlzIGFsbCB5b3UgbmVlZCDigJQgcmVsYXRpdmUgdG9waWMgbmFtZXMgZ2V0IHRoZSBuYW1lc3BhY2UKLy8gcHJlZml4IGFwcGxpZWQgYXV0b21hdGljYWxseS4KI2luY2x1ZGUgInJjbGNwcC9yY2xjcHAuaHBwIgoKY2xhc3MgX19QTFVHSU5fQ0xBU1NfX05vZGUgOiBwdWJsaWMgcmNsY3BwOjpOb2RlIHsKcHVibGljOgogIF9fUExVR0lOX0NMQVNTX19Ob2RlKCkgOiBOb2RlKCJfX1BMVUdJTl9TTFVHX19fbm9kZSIpIHsKICAgIFJDTENQUF9JTkZPKHRoaXMtPmdldF9sb2dnZXIoKSwgIl9fUExVR0lOX05BTUVfXyBub2RlIHN0YXJ0ZWQiKTsKICAgIC8vIGNyZWF0ZSBwdWJsaXNoZXJzIC8gc3Vic2NyaWJlcnMgLyBzZXJ2aWNlcyBoZXJlCiAgfQp9OwoKaW50IG1haW4oaW50IGFyZ2MsIGNoYXIqKiBhcmd2KSB7CiAgcmNsY3BwOjppbml0KGFyZ2MsIGFyZ3YpOwogIHJjbGNwcDo6c3BpbihzdGQ6Om1ha2Vfc2hhcmVkPF9fUExVR0lOX0NMQVNTX19Ob2RlPigpKTsKICByY2xjcHA6OnNodXRkb3duKCk7CiAgcmV0dXJuIDA7Cn0K","docker/Dockerfile.bridge":"IyBTb2NrZXQuSU8gYnJpZGdlIGNvbnRhaW5lciDigJQgdGhlIGZyb250ZW5kIChwcmV2aWV3LWhvc3QpIGFuZCB0aGUgYmFja2VuZAojIGNvbnRhaW5lciByZW5kZXp2b3VzIGhlcmUgZHVyaW5nIGBtdXNjYXQtcGsgZGV2YC4KIyAgIC0gZnJvbnRlbmQgKGhvc3QpICDihpIgcHVibGlzaGVkIHBvcnQgbG9jYWxob3N0OjEwMDAwCiMgICAtIGJhY2tlbmQgY29udGFpbmVyIOKGkiBzaGFyZXMgdGhpcyBjb250YWluZXIncyBuZXRucyAoLS1uZXR3b3JrIGNvbnRhaW5lcjopLAojICAgICAgICAgICAgICAgICAgICAgICAgIHNvIGl0IHJlYWNoZXMgdGhlIGJyaWRnZSBhdCBsb2NhbGhvc3Q6MTAwMDAgdG9vCiMgQnVpbGQgY29udGV4dCBpcyB0aGUgYGNsaS9gIGRpciBzbyBpdCBjYW4gY29weSBzcmMvc29ja2V0QnJpZGdlLm1qcy4KRlJPTSBub2RlOjIwLXNsaW0KTEFCRUwgY29tLnJhaW5ib3ctcm9ib3RpY3MubXVzY2F0LXBrPSJ0cnVlIgoKV09SS0RJUiAvYnJpZGdlCkNPUFkgZG9ja2VyL2JyaWRnZS1wYWNrYWdlLmpzb24gcGFja2FnZS5qc29uClJVTiBucG0gaW5zdGFsbCAtLW9taXQ9ZGV2IC0tbm8tYXVkaXQgLS1uby1mdW5kCgpDT1BZIHNyYy9zb2NrZXRCcmlkZ2UubWpzIC4vc29ja2V0QnJpZGdlLm1qcwoKRVhQT1NFIDEwMDAwCkNNRCBbIm5vZGUiLCAic29ja2V0QnJpZGdlLm1qcyJdCg==","docker/Dockerfile.cpp":"IyBEZXYvYnVpbGQgY29udGFpbmVyIGZvciB0aGUgcHVyZSBDKysgcGx1Z2luIGJhY2sgZW5kLgojIE1hdGNoZXMgdGhlIE11c2NhdCByb2JvdCBiYWNrZW5kIE9TIChVYnVudHUgMjQuMDQpIHNvIHRoZSBjb21waWxlZCBiaW5hcnkKIyBpcyBBQkktY29tcGF0aWJsZSB3aXRoIHRoZSByb2JvdC4gVXNlZCBieSBgbXVzY2F0LXBrIGRldjpiYWNrYCBhbmQgYGJ1aWxkYAojIOKAlCBOT1Qgc2hpcHBlZCBpbiB0aGUgcGx1Z2luIFpJUC4KRlJPTSB1YnVudHU6MjQuMDQKTEFCRUwgY29tLnJhaW5ib3ctcm9ib3RpY3MubXVzY2F0LXBrPSJ0cnVlIgoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSAtLW5vLWluc3RhbGwtcmVjb21tZW5kcyBcCiAgICAgIGJ1aWxkLWVzc2VudGlhbCBcCiAgICAgIGNtYWtlIFwKICAgICYmIHJtIC1yZiAvdmFyL2xpYi9hcHQvbGlzdHMvKgoKIyBUT0RPKGNwcC1zZGspOiBpbnN0YWxsIHRoZSByYWluYm93IGNwcCBTREsgKGRldiBwYWNrYWdlKSBoZXJlLCBwaW5uZWQgdG8gdGhlCiMgcm9ib3QncyB2ZXJzaW9uLCBzbyBwbHVnaW5zIGJ1aWxkL2xpbmsgYWdhaW5zdCB0aGUgc2FtZSBTREsgYXMgcHJvZHVjdGlvbi4KIyBQbGFuOiBhcHQgcGFja2FnZSArIGR5bmFtaWMgbGluayAodGhlIHJvYm90IHByb3ZpZGVzIHRoZSAuc28gYXQgcnVudGltZTsKIyB3ZSBkbyBOT1QgYnVuZGxlIGl0IGludG8gdGhlIHBsdWdpbiBiaW5hcnkpLiBlLmcuOgojICAgUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSByYWluYm93LWNwcC1zZGstZGV2PTxyb2JvdC12ZXJzaW9uPiBcCiMgICAgICAgJiYgcm0gLXJmIC92YXIvbGliL2FwdC9saXN0cy8qCgpXT1JLRElSIC9wbHVnaW4KCkNNRCBbImJhc2giXQo=","docker/Dockerfile.dev":"IyBEZXYtb25seSBjb250YWluZXIgZm9yIHRoZSBST1MyIHBsdWdpbiBiYWNrIGVuZC4KIyBNYXRjaGVzIHRoZSBNdXNjYXQgcm9ib3QgYmFja2VuZDogUk9TMiBKYXp6eSArIFB5dGhvbiAzLjEyIChVYnVudHUgMjQuMDQpLgojIFVzZWQgYnkgYG11c2NhdC1wayBkZXY6YmFja2Ag4oCUIE5PVCBzaGlwcGVkIGluIHRoZSBwbHVnaW4gWklQLgpGUk9NIHJvczpqYXp6eS1yb3MtYmFzZQpMQUJFTCBjb20ucmFpbmJvdy1yb2JvdGljcy5tdXNjYXQtcGs9InRydWUiCgpSVU4gYXB0LWdldCB1cGRhdGUgJiYgYXB0LWdldCBpbnN0YWxsIC15IC0tbm8taW5zdGFsbC1yZWNvbW1lbmRzIFwKICAgICAgcHl0aG9uMy1waXAgXAogICAgICBweXRob24zLWNvbGNvbi1jb21tb24tZXh0ZW5zaW9ucyBcCiAgICAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCldPUktESVIgL3BsdWdpbgoKQ01EIFsiYmFzaCJdCg==","docker/Dockerfile.python":"IyBEZXYvYnVpbGQgY29udGFpbmVyIGZvciB0aGUgUHl0aG9uIHBsdWdpbiBiYWNrIGVuZC4KIyBNYXRjaGVzIHRoZSBNdXNjYXQgcm9ib3QgYmFja2VuZDogVWJ1bnR1IDI0LjA0ICsgUHl0aG9uIDMuMTIuCiMgVXNlZCBieSBgbXVzY2F0LXBrIGRldjpiYWNrYCAoaG90LXJlbG9hZCkgYW5kIGBtdXNjYXQtcGsgYnVpbGRgIChsb2NrICsgd2hlZWwgdmVuZG9yKS4KIyBOT1Qgc2hpcHBlZCBpbiB0aGUgcGx1Z2luIFpJUCDigJQgdGhlIHJvYm90IGluc3RhbGxzIGRlcHMgZnJvbSBkaXN0L3doZWVscy8gb2ZmbGluZS4KRlJPTSB1YnVudHU6MjQuMDQKTEFCRUwgY29tLnJhaW5ib3ctcm9ib3RpY3MubXVzY2F0LXBrPSJ0cnVlIgoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSAtLW5vLWluc3RhbGwtcmVjb21tZW5kcyBcCiAgICAgIHB5dGhvbjMgXAogICAgICBweXRob24zLXBpcCBcCiAgICAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyogXAogICAgJiYgcGlwMyBpbnN0YWxsIHV2PT0wLjguMTggLS1icmVhay1zeXN0ZW0tcGFja2FnZXMKCiMgVE9ETyhjcHAtc2RrKTogdGhlIFB5dGhvbiBTREsgaXMgYSBweWJpbmQgd3JhcHBlciBvdmVyIHRoZSBjcHAgU0RLLCBzbyB0aGUKIyBjcHAgU0RLIHNoYXJlZCBsaWIgbXVzdCBiZSBwcmVzZW50IGZvciB0aGUgcHl0aG9uIG1vZHVsZSB0byBsb2FkLiBJbnN0YWxsIHRoZQojIHJhaW5ib3cgY3BwIFNESyAoYXB0LCBwaW5uZWQgdG8gdGhlIHJvYm90J3MgdmVyc2lvbikgaGVyZSBvbmNlIGl0IGV4aXN0cy4KCldPUktESVIgL3BsdWdpbgoKQ01EIFsiYmFzaCJdCg==","docker/bridge-package.json":"ewogICJuYW1lIjogIm11c2NhdC1way1icmlkZ2UiLAogICJwcml2YXRlIjogdHJ1ZSwKICAidHlwZSI6ICJtb2R1bGUiLAogICJkZXBlbmRlbmNpZXMiOiB7CiAgICAic29ja2V0LmlvIjogIl40LjguMSIKICB9Cn0K"};
|
package/cli/src/type.mjs
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// muscat-pk type change — switch an existing plugin's backend type.
|
|
2
|
+
// python | cpp | ros2 (rclpy) | ros2-cpp (rclcpp).
|
|
3
|
+
// Adds the matching backend overlay; never deletes existing user code
|
|
4
|
+
// (e.g. ros2_ws/ or cpp/ are left in place when switching away).
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { basename, resolve } from "node:path";
|
|
7
|
+
import { findPluginRoot, log, prompt } from "./util.mjs";
|
|
8
|
+
import {
|
|
9
|
+
TYPE_CHOICES,
|
|
10
|
+
applyTokens,
|
|
11
|
+
deriveNames,
|
|
12
|
+
overlayCpp,
|
|
13
|
+
overlayRos2,
|
|
14
|
+
runtimeForChoice,
|
|
15
|
+
} from "./new.mjs";
|
|
16
|
+
|
|
17
|
+
const TYPES = Object.keys(TYPE_CHOICES);
|
|
18
|
+
|
|
19
|
+
function readManifest(root) {
|
|
20
|
+
const path = resolve(root, "manifest.json");
|
|
21
|
+
if (!existsSync(path)) throw new Error(`manifest.json not found in ${root}`);
|
|
22
|
+
return { path, json: JSON.parse(readFileSync(path, "utf8")) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Derive the current `--type`-style key from a manifest runtime block.
|
|
26
|
+
function currentTypeKey(runtime) {
|
|
27
|
+
if (runtime?.type === "ros2") return runtime.rosClient === "rclcpp" ? "ros2-cpp" : "ros2";
|
|
28
|
+
return runtime?.type ?? "python";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function runType(args) {
|
|
32
|
+
// accept `type change`, `type`, `type --to ros2-cpp`
|
|
33
|
+
const sub = args[0];
|
|
34
|
+
const toFlagIdx = args.indexOf("--to");
|
|
35
|
+
let to = toFlagIdx >= 0 ? args[toFlagIdx + 1] : undefined;
|
|
36
|
+
|
|
37
|
+
const root = findPluginRoot();
|
|
38
|
+
const { path, json } = readManifest(root);
|
|
39
|
+
const current = currentTypeKey(json.runtime);
|
|
40
|
+
|
|
41
|
+
if (sub && sub !== "change") {
|
|
42
|
+
log.warn(`unknown subcommand '${sub}'. Usage: muscat-pk type change [--to python|cpp|ros2|ros2-cpp]`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!to) {
|
|
46
|
+
console.log(`\n현재 타입: ${current}`);
|
|
47
|
+
console.log("변경할 타입을 선택하세요:");
|
|
48
|
+
console.log(" 1) Python");
|
|
49
|
+
console.log(" 2) C++");
|
|
50
|
+
console.log(" 3) ROS2 (rclpy / Python)");
|
|
51
|
+
console.log(" 4) ROS2 (rclcpp / C++)");
|
|
52
|
+
const choice = await prompt("\n선택 [1/2/3/4]: ", "");
|
|
53
|
+
to = { "1": "python", "2": "cpp", "3": "ros2", "4": "ros2-cpp" }[choice];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!TYPES.includes(to)) {
|
|
57
|
+
throw new Error(`invalid type. choose python | cpp | ros2 | ros2-cpp.`);
|
|
58
|
+
}
|
|
59
|
+
if (to === current) {
|
|
60
|
+
log.info(`already '${current}' — nothing to change.`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const runtimeChoice = TYPE_CHOICES[to];
|
|
65
|
+
const { slug, pluginName, pluginClass, pluginId } = deriveNames(root);
|
|
66
|
+
|
|
67
|
+
if (runtimeChoice.type === "ros2") {
|
|
68
|
+
if (existsSync(resolve(root, "ros2_ws"))) {
|
|
69
|
+
log.warn("ros2_ws/ already exists — keeping it, only flipping manifest type.");
|
|
70
|
+
} else {
|
|
71
|
+
log.step(`adding ros2_ws/ overlay (${runtimeChoice.rosClient})`);
|
|
72
|
+
await overlayRos2(root, slug, runtimeChoice.rosClient);
|
|
73
|
+
applyTokens(root, {
|
|
74
|
+
__PLUGIN_ID__: pluginId,
|
|
75
|
+
__PLUGIN_NAME__: pluginName,
|
|
76
|
+
__PLUGIN_SLUG__: slug,
|
|
77
|
+
__PLUGIN_CLASS__: pluginClass,
|
|
78
|
+
__RUNTIME_TYPE__: "ros2",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
} else if (runtimeChoice.type === "cpp") {
|
|
82
|
+
if (existsSync(resolve(root, "cpp"))) {
|
|
83
|
+
log.warn("cpp/ already exists — keeping it, only flipping manifest type.");
|
|
84
|
+
} else {
|
|
85
|
+
log.step("adding cpp/ overlay");
|
|
86
|
+
await overlayCpp(root);
|
|
87
|
+
applyTokens(root, {
|
|
88
|
+
__PLUGIN_ID__: pluginId,
|
|
89
|
+
__PLUGIN_NAME__: pluginName,
|
|
90
|
+
__PLUGIN_SLUG__: slug,
|
|
91
|
+
__PLUGIN_CLASS__: pluginClass,
|
|
92
|
+
__RUNTIME_TYPE__: "cpp",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
// → python. Leave any ros2_ws/ or cpp/ in place (never delete user code).
|
|
97
|
+
if (existsSync(resolve(root, "ros2_ws")) || existsSync(resolve(root, "cpp"))) {
|
|
98
|
+
log.warn("existing backend dir left in place (not deleted). Remove it manually if unused.");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
json.runtime = runtimeForChoice(runtimeChoice, slug);
|
|
103
|
+
writeFileSync(path, JSON.stringify(json, null, 2) + "\n");
|
|
104
|
+
log.ok(`${basename(root)} type: ${current} → ${to}`);
|
|
105
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// muscat-pk uninstall - remove the installed CLI, whether npm-global or S3 binary.
|
|
2
|
+
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync, realpathSync, rmSync } from "node:fs";
|
|
4
|
+
import { dirname, join, relative } from "node:path";
|
|
5
|
+
import { KIT_PUBLISHED_NAME, kitRoot, log, parseArgs } from "./util.mjs";
|
|
6
|
+
|
|
7
|
+
const isBinaryMode = typeof Bun !== "undefined";
|
|
8
|
+
|
|
9
|
+
function isInside(child, parent) {
|
|
10
|
+
const rel = relative(parent, child);
|
|
11
|
+
return rel === "" || (!!rel && !rel.startsWith("..") && !rel.includes(":"));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function npmGlobalRoot() {
|
|
15
|
+
return execSync("npm root -g", { encoding: "utf8" }).trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isNpmGlobalInstall() {
|
|
19
|
+
try {
|
|
20
|
+
return isInside(realpathSync(kitRoot), realpathSync(npmGlobalRoot()));
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function uninstallNpmGlobal() {
|
|
27
|
+
log.step(`npm uninstall -g ${KIT_PUBLISHED_NAME}`);
|
|
28
|
+
execSync(`npm uninstall -g ${KIT_PUBLISHED_NAME}`, { stdio: "inherit" });
|
|
29
|
+
log.ok("Global npm package removed.");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function uninstallBinary() {
|
|
33
|
+
const execPath = process.execPath;
|
|
34
|
+
log.step(`Removing binary: ${execPath}`);
|
|
35
|
+
|
|
36
|
+
if (process.platform === "win32") {
|
|
37
|
+
const ps = `Start-Sleep -Milliseconds 500; Remove-Item -Force '${execPath}'`;
|
|
38
|
+
spawn("powershell", ["-NoProfile", "-Command", ps], {
|
|
39
|
+
detached: true,
|
|
40
|
+
stdio: "ignore",
|
|
41
|
+
}).unref();
|
|
42
|
+
log.ok("Binary will be removed after this process exits.");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
rmSync(execPath, { force: true });
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err?.code !== "EACCES" && err?.code !== "EPERM") throw err;
|
|
50
|
+
log.info(" sudo is required...");
|
|
51
|
+
const result = spawnSync("sudo", ["rm", "-f", execPath], { stdio: "inherit" });
|
|
52
|
+
if (result.status !== 0) throw new Error(`sudo rm failed: ${execPath}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const parent = dirname(execPath);
|
|
56
|
+
for (const shim of ["muscat-pk.cmd", "muscat-pk.ps1"]) {
|
|
57
|
+
rmSync(join(parent, shim), { force: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
log.ok("Binary removed.");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function runUninstall(args = []) {
|
|
64
|
+
const opts = parseArgs(args, { booleans: ["help"] });
|
|
65
|
+
|
|
66
|
+
if (opts.help) {
|
|
67
|
+
log.info(
|
|
68
|
+
[
|
|
69
|
+
"Usage: muscat-pk uninstall",
|
|
70
|
+
"",
|
|
71
|
+
"Removes the installed muscat-pk CLI.",
|
|
72
|
+
" npm global install -> npm uninstall -g @rainbow-robotics/muscat-plugin-kit",
|
|
73
|
+
" S3 binary install -> remove the muscat-pk executable",
|
|
74
|
+
].join("\n"),
|
|
75
|
+
);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (isBinaryMode) {
|
|
80
|
+
uninstallBinary();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (isNpmGlobalInstall()) {
|
|
85
|
+
uninstallNpmGlobal();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const localBin = process.argv[1] || "";
|
|
90
|
+
if (localBin.includes(`${join("node_modules", ".bin")}`) || existsSync(join(process.cwd(), "package.json"))) {
|
|
91
|
+
log.warn("This muscat-pk appears to be running from a project/local install.");
|
|
92
|
+
log.warn("Remove it from the project package manager instead, for example:");
|
|
93
|
+
log.warn(` npm uninstall ${KIT_PUBLISHED_NAME}`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
throw new Error("Could not determine install mode. Remove muscat-pk manually from your PATH.");
|
|
98
|
+
}
|