@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.
Files changed (51) hide show
  1. package/README.md +116 -16
  2. package/cli/README.md +5 -5
  3. package/cli/bin/create-muscat-plugin.mjs +1 -1
  4. package/cli/bin/muscat-pk.mjs +20 -10
  5. package/cli/bundle-templates.mjs +28 -0
  6. package/cli/docker/Dockerfile.bridge +17 -0
  7. package/cli/docker/Dockerfile.cpp +22 -0
  8. package/cli/docker/Dockerfile.dev +14 -0
  9. package/cli/docker/Dockerfile.python +20 -0
  10. package/cli/docker/bridge-package.json +8 -0
  11. package/cli/src/bridge.mjs +92 -0
  12. package/cli/src/build.mjs +398 -36
  13. package/cli/src/check-package.mjs +8 -23
  14. package/cli/src/cpp.mjs +61 -0
  15. package/cli/src/dev.mjs +16 -3
  16. package/cli/src/devBack.mjs +239 -53
  17. package/cli/src/devUi.mjs +118 -15
  18. package/cli/src/esbuild.mjs +51 -10
  19. package/cli/src/manifest.mjs +193 -0
  20. package/cli/src/new.mjs +168 -37
  21. package/cli/src/preview.mjs +91 -0
  22. package/cli/src/python.mjs +122 -0
  23. package/cli/src/ros2.mjs +90 -0
  24. package/cli/src/socketBridge.mjs +129 -0
  25. package/cli/src/templates-embedded.mjs +3 -0
  26. package/cli/src/type.mjs +105 -0
  27. package/cli/src/uninstall.mjs +98 -0
  28. package/cli/src/update.mjs +259 -30
  29. package/cli/src/util.mjs +97 -7
  30. package/cli/templates/common/README.md +383 -9
  31. package/cli/templates/common/manifest.json +13 -13
  32. package/cli/templates/common/package.json +5 -11
  33. package/cli/templates/common/py/main.py +88 -0
  34. package/cli/templates/common/requirements.txt +4 -0
  35. package/cli/templates/common/tsconfig.json +1 -1
  36. package/cli/templates/common/{src → ui}/content.tsx +3 -2
  37. package/cli/templates/common/vite.config.ts +22 -13
  38. package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
  39. package/cli/templates/cpp/cpp/main.cpp +25 -0
  40. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/__init__.py +0 -0
  41. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +25 -0
  42. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/package.xml +10 -0
  43. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/resource/__PLUGIN_SLUG__ +0 -0
  44. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/setup.py +18 -0
  45. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +17 -0
  46. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
  47. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +22 -0
  48. package/package.json +4 -4
  49. package/cli/src/pack.mjs +0 -58
  50. package/cli/templates/sdk/main.py +0 -21
  51. package/cli/templates/sdk/requirements.txt +0 -1
@@ -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"};
@@ -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
+ }