@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.3 → 0.1.2-dev.31

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 (48) hide show
  1. package/README.md +95 -16
  2. package/cli/README.md +4 -5
  3. package/cli/bin/create-muscat-plugin.mjs +1 -1
  4. package/cli/bin/muscat-pk.mjs +16 -10
  5. package/cli/docker/Dockerfile.bridge +17 -0
  6. package/cli/docker/Dockerfile.cpp +22 -0
  7. package/cli/docker/Dockerfile.dev +14 -0
  8. package/cli/docker/Dockerfile.python +20 -0
  9. package/cli/docker/bridge-package.json +8 -0
  10. package/cli/src/bridge.mjs +92 -0
  11. package/cli/src/build.mjs +406 -36
  12. package/cli/src/check-package.mjs +2 -1
  13. package/cli/src/cpp.mjs +65 -0
  14. package/cli/src/dev.mjs +16 -3
  15. package/cli/src/devBack.mjs +246 -49
  16. package/cli/src/devUi.mjs +118 -15
  17. package/cli/src/esbuild.mjs +51 -10
  18. package/cli/src/manifest.mjs +189 -0
  19. package/cli/src/new.mjs +152 -27
  20. package/cli/src/preview.mjs +91 -0
  21. package/cli/src/python.mjs +112 -0
  22. package/cli/src/ros2.mjs +66 -0
  23. package/cli/src/socketBridge.mjs +129 -0
  24. package/cli/src/type.mjs +114 -0
  25. package/cli/src/update.mjs +267 -30
  26. package/cli/src/util.mjs +65 -4
  27. package/cli/templates/common/README.md +382 -9
  28. package/cli/templates/common/manifest.json +13 -13
  29. package/cli/templates/common/package.json +5 -11
  30. package/cli/templates/common/py/main.py +88 -0
  31. package/cli/templates/common/requirements.txt +4 -0
  32. package/cli/templates/common/tsconfig.json +1 -1
  33. package/cli/templates/common/{src → ui}/content.tsx +3 -2
  34. package/cli/templates/common/vite.config.ts +22 -13
  35. package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
  36. package/cli/templates/cpp/cpp/main.cpp +25 -0
  37. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/__init__.py +0 -0
  38. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +25 -0
  39. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/package.xml +10 -0
  40. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/resource/__PLUGIN_SLUG__ +0 -0
  41. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/setup.py +18 -0
  42. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +17 -0
  43. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
  44. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +22 -0
  45. package/package.json +4 -4
  46. package/cli/src/pack.mjs +0 -58
  47. package/cli/templates/sdk/main.py +0 -21
  48. package/cli/templates/sdk/requirements.txt +0 -1
package/README.md CHANGED
@@ -1,27 +1,106 @@
1
1
  # @rainbow-robotics/muscat-plugin-kit
2
2
 
3
- Muscat plugin developer kit: `muscat-pk`, `create-muscat-plugin`, build/pack,
4
- and preview-host.
3
+ Muscat 플러그인 개발 CLI. 스캐폴딩, 빌드/팩, 개발 서버(UI + 백엔드)를 제공합니다.
5
4
 
6
- This package does **not** own the UI contract. Plugin source imports
7
- `@rainbow-robotics/plugin-ui/*`, and `muscat-pk build` leaves that package external so the
8
- robot/preview-host can provide its own runtime.
5
+ ## 설치
6
+
7
+ ### 방법 1 — npm (Node.js 20+ 필요)
8
+
9
+ ```bash
10
+ # 최신 stable
11
+ npm install -g @rainbow-robotics/muscat-plugin-kit
12
+
13
+ # dev 채널 (최신 개발 빌드)
14
+ npm install -g @rainbow-robotics/muscat-plugin-kit@dev
15
+
16
+ # 특정 버전
17
+ npm install -g @rainbow-robotics/muscat-plugin-kit@1.2.3
18
+ ```
19
+
20
+ ### 방법 2 — 설치 스크립트 (Node.js 없어도 됨)
21
+
22
+ macOS / Linux / WSL:
23
+
24
+ ```bash
25
+ # 최신 stable
26
+ curl -fsSL https://raw.githubusercontent.com/rainbow-robotics/web-rainbow-ui/main/scripts/install-muscat-pk.sh | sh
27
+
28
+ # dev 채널
29
+ MUSCAT_PK_TAG=dev curl -fsSL https://raw.githubusercontent.com/rainbow-robotics/web-rainbow-ui/main/scripts/install-muscat-pk.sh | sh
30
+
31
+ # 특정 버전
32
+ MUSCAT_PK_TAG=1.2.3 curl -fsSL https://raw.githubusercontent.com/rainbow-robotics/web-rainbow-ui/main/scripts/install-muscat-pk.sh | sh
33
+ ```
34
+
35
+ Windows PowerShell:
36
+
37
+ ```powershell
38
+ # 최신 stable
39
+ irm https://raw.githubusercontent.com/rainbow-robotics/web-rainbow-ui/main/scripts/install-muscat-pk.ps1 | iex
40
+
41
+ # dev 채널
42
+ $env:MUSCAT_PK_TAG="dev"; irm https://raw.githubusercontent.com/rainbow-robotics/web-rainbow-ui/main/scripts/install-muscat-pk.ps1 | iex
43
+ ```
44
+
45
+ > 설치 스크립트는 Node.js 20+가 있으면 npm을 우선 사용하고, 없으면 S3에서 self-contained 바이너리를 받습니다.
46
+
47
+ ### 방법 3 — 바이너리 직접 다운로드
48
+
49
+ ```
50
+ https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/v{버전}/{바이너리}
51
+ ```
52
+
53
+ | OS | 파일 |
54
+ |---|---|
55
+ | Linux x64 | `muscat-pk-linux-x64` |
56
+ | macOS ARM64 | `muscat-pk-darwin-arm64` |
57
+ | Windows x64 | `muscat-pk-win-x64.exe` |
58
+
59
+ 채널 포인터:
60
+
61
+ ```
62
+ # 최신 stable 버전 문자열
63
+ https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/latest-version.txt
64
+
65
+ # 최신 dev 버전 문자열
66
+ https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/dev-version.txt
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 빠른 시작
9
72
 
10
73
  ```bash
11
- npm i -g @rainbow-robotics/muscat-plugin-kit
12
- muscat-pk new my-plugin --type sdk
74
+ muscat-pk new my-plugin # 플러그인 프로젝트 생성 (python 또는 ros2 선택)
13
75
  cd my-plugin
14
- muscat-pk build
15
- muscat-pk dev:ui
16
- muscat-pk pack
76
+
77
+ muscat-pk dev # UI + 백엔드 동시 실행
78
+ muscat-pk dev:ui # UI 미리보기만
79
+ muscat-pk dev:back # 백엔드만 (Python watchfiles / ROS2 Docker)
80
+
81
+ muscat-pk build # dist/ 빌드
82
+ muscat-pk pack # 배포용 ZIP 생성
17
83
  ```
18
84
 
19
- Published package responsibilities:
85
+ ## 명령어 목록
86
+
87
+ | 명령 | 설명 |
88
+ |---|---|
89
+ | `muscat-pk new [dir]` | 플러그인 프로젝트 스캐폴딩 |
90
+ | `muscat-pk type change` | 백엔드 타입 전환 (python ↔ ros2) |
91
+ | `muscat-pk build` | `src/content.tsx` → `dist/content.js` 빌드 |
92
+ | `muscat-pk pack` | 빌드 후 배포용 ZIP 생성 |
93
+ | `muscat-pk dev` | UI + 백엔드 동시 개발 서버 |
94
+ | `muscat-pk dev:ui` | 미니 Muscat 미리보기 서버 |
95
+ | `muscat-pk dev:back` | 백엔드 개발 서버 (hot-reload) |
96
+ | `muscat-pk update` | kit + plugin-ui 최신 버전으로 업데이트 |
97
+ | `muscat-pk sdk <list\|update>` | Python rainbow-rb-sdk 버전 관리 |
98
+
99
+ ## 패키지 역할
20
100
 
21
- | Package | Role |
101
+ | 패키지 | 역할 |
22
102
  |---|---|
23
- | `@rainbow-robotics/plugin-ui` | Contract/runtime: rb-* elements, author React bindings, sandbox connect, host renderer |
24
- | `@rainbow-robotics/muscat-plugin-kit` | CLI, scaffold templates, build/pack, preview-host |
103
+ | `@rainbow-robotics/plugin-ui` | UI 런타임 계약: rb-* 엘리먼트, React 바인딩, sandbox connect |
104
+ | `@rainbow-robotics/muscat-plugin-kit` | CLI, 템플릿, 빌드/팩, 미리보기 호스트 |
25
105
 
26
- The plugin ZIP contains plugin code and assets only. It relies on the host's
27
- import map for `@rainbow-robotics/plugin-ui`, `react`, and `react-dom`.
106
+ 플러그인 ZIP에는 플러그인 코드와 에셋만 포함됩니다. `@rainbow-robotics/plugin-ui`, `react`, `react-dom`은 호스트(Muscat/로봇)의 import map에서 제공됩니다.
package/cli/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @rainbow-robotics/muscat-plugin-kit (`muscat-pk`)
2
2
 
3
- CLI to scaffold, build, preview and pack Muscat plugins.
3
+ CLI to scaffold, build, and preview Muscat plugins.
4
4
 
5
5
  Install/use it the same way an external plugin author will:
6
6
 
@@ -10,11 +10,10 @@ muscat-pk new my-plugin
10
10
  ```
11
11
 
12
12
  ```bash
13
- muscat-pk new my-plugin [--type sdk|ros2|ui] [--pm npm|yarn|pnpm|bun] [--yes]
14
- muscat-pk build # src/content.tsx -> dist/content.js (+ manifest, sandbox, assets)
15
- muscat-pk pack # build (minified) + distributable ZIP with a size report
13
+ muscat-pk new my-plugin [--type python|cpp|ros2|ros2-cpp] [--pm npm|yarn|pnpm|bun] [--yes]
14
+ muscat-pk build # ui/content.tsx -> dist/ (minified) + distributable ZIP (--no-zip to skip)
16
15
  muscat-pk dev:ui # preview the plugin in a mini-Muscat (preview-host)
17
- muscat-pk dev:back # run the back end (.venv + python main.py)
16
+ muscat-pk dev:back # run the back end in a Docker container (python / cpp / ros2)
18
17
  muscat-pk dev # dev:ui + dev:back
19
18
  muscat-pk update # update @rainbow-robotics/muscat-plugin-kit and @rainbow-robotics/plugin-ui
20
19
  muscat-pk sdk list | sdk update [version] # manage rainbow-rb-sdk (python)
@@ -3,6 +3,6 @@
3
3
  import { runNew } from "../src/new.mjs";
4
4
 
5
5
  runNew(process.argv.slice(2)).catch((err) => {
6
- console.error(`\n✖ ${err?.message || err}`);
6
+ console.error(`\n ✖ ${err?.message || err}`);
7
7
  process.exit(1);
8
8
  });
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  // muscat-pk — Muscat plugin developer CLI.
3
- // Subcommands: new, build, pack, dev, dev:ui, dev:back, update, sdk.
3
+ // Subcommands: new, build, dev, dev:ui, dev:back, update, sdk.
4
+ import { log } from "../src/util.mjs";
4
5
  import { runNew } from "../src/new.mjs";
5
6
  import { runBuild } from "../src/build.mjs";
6
- import { runPack } from "../src/pack.mjs";
7
7
  import { runDevUi } from "../src/devUi.mjs";
8
8
  import { runDevBack } from "../src/devBack.mjs";
9
9
  import { runDev } from "../src/dev.mjs";
10
10
  import { runUpdate } from "../src/update.mjs";
11
11
  import { runSdk } from "../src/sdk.mjs";
12
+ import { runType } from "../src/type.mjs";
13
+ import { runPreview } from "../src/preview.mjs";
12
14
 
13
15
  const [, , cmd, ...args] = process.argv;
14
16
 
@@ -17,12 +19,13 @@ const HELP = `muscat-pk — Muscat plugin kit
17
19
  Usage: muscat-pk <command> [options]
18
20
 
19
21
  Commands:
20
- new [dir] Scaffold a new plugin project
21
- build Build src/content.tsx -> dist/content.js (+ manifest, assets)
22
- pack Build then create a distributable ZIP (with size report)
23
- dev Run dev:ui and dev:back together
22
+ new [dir] Scaffold a new plugin project (prompts: author, email, description, type, front-package-manager)
23
+ type change Switch backend type (python | cpp | ros2-python | ros2-cpp)
24
+ build Build + lock deps + vendor wheels + zip (--no-zip: skip zip, --no-lock: skip python lock/vendor)
25
+ dev Run Socket.IO bridge, dev:ui, and dev:back together
24
26
  dev:ui Preview the plugin in a mini-Muscat (preview-host)
25
- dev:back Run the plugin back end (python main.py)
27
+ dev:back Run the plugin back end (python: watchfiles reload / ros2: Docker)
28
+ preview Preview against a real Muscat server (prompts for robot IP)
26
29
  update Update @rainbow-robotics/muscat-plugin-kit and @rainbow-robotics/plugin-ui
27
30
  sdk <list|update [version]> Manage the python rainbow-rb-sdk version
28
31
 
@@ -33,16 +36,18 @@ async function main() {
33
36
  switch (cmd) {
34
37
  case "new":
35
38
  return await runNew(args);
39
+ case "type":
40
+ return await runType(args);
36
41
  case "build":
37
42
  return await runBuild(args);
38
- case "pack":
39
- return await runPack(args);
40
43
  case "dev":
41
44
  return await runDev(args);
42
45
  case "dev:ui":
43
46
  return await runDevUi(args);
44
47
  case "dev:back":
45
48
  return await runDevBack(args);
49
+ case "preview":
50
+ return await runPreview(args);
46
51
  case "update":
47
52
  return await runUpdate(args);
48
53
  case "sdk":
@@ -59,7 +64,8 @@ async function main() {
59
64
  process.exitCode = 1;
60
65
  }
61
66
  } catch (err) {
62
- console.error(`\n✖ ${err?.message || err}`);
67
+ console.error();
68
+ log.error(err?.message || String(err));
63
69
  process.exitCode = 1;
64
70
  }
65
71
  }
@@ -0,0 +1,17 @@
1
+ # Socket.IO bridge container — the frontend (preview-host) and the backend
2
+ # container rendezvous here during `muscat-pk dev`.
3
+ # - frontend (host) → published port localhost:10000
4
+ # - backend container → shares this container's netns (--network container:),
5
+ # so it reaches the bridge at localhost:10000 too
6
+ # Build context is the `cli/` dir so it can copy src/socketBridge.mjs.
7
+ FROM node:20-slim
8
+ LABEL com.rainbow-robotics.muscat-pk="true"
9
+
10
+ WORKDIR /bridge
11
+ COPY docker/bridge-package.json package.json
12
+ RUN npm install --omit=dev --no-audit --no-fund
13
+
14
+ COPY src/socketBridge.mjs ./socketBridge.mjs
15
+
16
+ EXPOSE 10000
17
+ CMD ["node", "socketBridge.mjs"]
@@ -0,0 +1,22 @@
1
+ # Dev/build container for the pure C++ plugin back end.
2
+ # Matches the Muscat robot backend OS (Ubuntu 24.04) so the compiled binary
3
+ # is ABI-compatible with the robot. Used by `muscat-pk dev:back` and `build`
4
+ # — NOT shipped in the plugin ZIP.
5
+ FROM ubuntu:24.04
6
+ LABEL com.rainbow-robotics.muscat-pk="true"
7
+
8
+ RUN apt-get update && apt-get install -y --no-install-recommends \
9
+ build-essential \
10
+ cmake \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # TODO(cpp-sdk): install the rainbow cpp SDK (dev package) here, pinned to the
14
+ # robot's version, so plugins build/link against the same SDK as production.
15
+ # Plan: apt package + dynamic link (the robot provides the .so at runtime;
16
+ # we do NOT bundle it into the plugin binary). e.g.:
17
+ # RUN apt-get update && apt-get install -y rainbow-cpp-sdk-dev=<robot-version> \
18
+ # && rm -rf /var/lib/apt/lists/*
19
+
20
+ WORKDIR /plugin
21
+
22
+ CMD ["bash"]
@@ -0,0 +1,14 @@
1
+ # Dev-only container for the ROS2 plugin back end.
2
+ # Matches the Muscat robot backend: ROS2 Jazzy + Python 3.12 (Ubuntu 24.04).
3
+ # Used by `muscat-pk dev:back` — NOT shipped in the plugin ZIP.
4
+ FROM ros:jazzy-ros-base
5
+ LABEL com.rainbow-robotics.muscat-pk="true"
6
+
7
+ RUN apt-get update && apt-get install -y --no-install-recommends \
8
+ python3-pip \
9
+ python3-colcon-common-extensions \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ WORKDIR /plugin
13
+
14
+ CMD ["bash"]
@@ -0,0 +1,20 @@
1
+ # Dev/build container for the Python plugin back end.
2
+ # Matches the Muscat robot backend: Ubuntu 24.04 + Python 3.12.
3
+ # Used by `muscat-pk dev:back` (hot-reload) and `muscat-pk build` (lock + wheel vendor).
4
+ # NOT shipped in the plugin ZIP — the robot installs deps from dist/wheels/ offline.
5
+ FROM ubuntu:24.04
6
+ LABEL com.rainbow-robotics.muscat-pk="true"
7
+
8
+ RUN apt-get update && apt-get install -y --no-install-recommends \
9
+ python3 \
10
+ python3-pip \
11
+ && rm -rf /var/lib/apt/lists/* \
12
+ && pip3 install uv --break-system-packages
13
+
14
+ # TODO(cpp-sdk): the Python SDK is a pybind wrapper over the cpp SDK, so the
15
+ # cpp SDK shared lib must be present for the python module to load. Install the
16
+ # rainbow cpp SDK (apt, pinned to the robot's version) here once it exists.
17
+
18
+ WORKDIR /plugin
19
+
20
+ CMD ["bash"]
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "muscat-pk-bridge",
3
+ "private": true,
4
+ "type": "module",
5
+ "dependencies": {
6
+ "socket.io": "^4.8.1"
7
+ }
8
+ }
@@ -0,0 +1,92 @@
1
+ // Socket.IO bridge container lifecycle for `muscat-pk dev`.
2
+ // The bridge runs in Docker and publishes port 10000 to the host:
3
+ // - frontend (preview-host, on the host) connects to localhost:10000
4
+ // - the backend container joins via `--network container:<bridge>` and reaches
5
+ // it at localhost:10000 too — no host.docker.internal needed (robust cross-OS).
6
+ import { spawnSync } from "node:child_process";
7
+ import { createServer, connect } from "node:net";
8
+ import { existsSync } from "node:fs";
9
+ import { resolve } from "node:path";
10
+ import { hasBin, kitRoot, log, pruneMuskatImages } from "./util.mjs";
11
+
12
+ export const BRIDGE_IMAGE = "muscat-pk-bridge";
13
+ export const BRIDGE_CONTAINER = "muscat-pk-bridge-run";
14
+ export const BRIDGE_PORT = 10000;
15
+
16
+ const DOCKERFILE = resolve(kitRoot, "cli/docker/Dockerfile.bridge");
17
+ const BUILD_CONTEXT = resolve(kitRoot, "cli");
18
+
19
+ function assertDockerReady() {
20
+ if (!hasBin("docker")) {
21
+ throw new Error("Docker not found. `muscat-pk dev` runs the Socket.IO bridge in a container.");
22
+ }
23
+ if (!existsSync(DOCKERFILE)) {
24
+ throw new Error(`kit Dockerfile missing at ${DOCKERFILE} (reinstall muscat-plugin-kit).`);
25
+ }
26
+ }
27
+
28
+ // Is a host TCP port already bound? (Catches another bridge or a stray process.)
29
+ function isPortInUse(port) {
30
+ return new Promise((res) => {
31
+ const srv = createServer();
32
+ srv.once("error", (err) => res(err.code === "EADDRINUSE"));
33
+ srv.once("listening", () => srv.close(() => res(false)));
34
+ srv.listen(port, "0.0.0.0");
35
+ });
36
+ }
37
+
38
+ // Wait until the published port accepts a TCP connection (bridge is ready).
39
+ function waitForPort(port, timeoutMs = 15_000) {
40
+ const start = Date.now();
41
+ return new Promise((res, rej) => {
42
+ const tryOnce = () => {
43
+ const sock = connect(port, "127.0.0.1");
44
+ sock.once("connect", () => { sock.destroy(); res(); });
45
+ sock.once("error", () => {
46
+ sock.destroy();
47
+ if (Date.now() - start > timeoutMs) rej(new Error("bridge container did not become ready in time"));
48
+ else setTimeout(tryOnce, 300);
49
+ });
50
+ };
51
+ tryOnce();
52
+ });
53
+ }
54
+
55
+ function ensureImage() {
56
+ log.step(`docker build -t ${BRIDGE_IMAGE} (first run is slow, then cached)…`);
57
+ const r = spawnSync("docker", ["build", "-t", BRIDGE_IMAGE, "-f", DOCKERFILE, BUILD_CONTEXT], { stdio: "inherit" });
58
+ if (r.status !== 0) throw new Error("docker build failed (bridge image).");
59
+ pruneMuskatImages();
60
+ }
61
+
62
+ /** Build the image, preflight the port, and start the bridge container. */
63
+ export async function startBridge() {
64
+ assertDockerReady();
65
+
66
+ // remove our own stale container first, then check the host port is free
67
+ spawnSync("docker", ["rm", "-f", BRIDGE_CONTAINER], { stdio: "ignore" });
68
+ if (await isPortInUse(BRIDGE_PORT)) {
69
+ throw new Error(
70
+ `port ${BRIDGE_PORT} is already in use.\n` +
71
+ ` Another 'muscat-pk dev' may be running, or a process is holding the port.\n` +
72
+ ` Stop it, or free the port, then retry.`,
73
+ );
74
+ }
75
+
76
+ ensureImage();
77
+
78
+ log.step("starting Socket.IO bridge container…");
79
+ const up = spawnSync(
80
+ "docker",
81
+ ["run", "-d", "--name", BRIDGE_CONTAINER, "-p", `${BRIDGE_PORT}:${BRIDGE_PORT}`, BRIDGE_IMAGE],
82
+ { stdio: "inherit" },
83
+ );
84
+ if (up.status !== 0) throw new Error("failed to start bridge container.");
85
+
86
+ await waitForPort(BRIDGE_PORT);
87
+ log.step(`Socket.IO bridge listening on ws://127.0.0.1:${BRIDGE_PORT}`);
88
+ }
89
+
90
+ export function stopBridge() {
91
+ spawnSync("docker", ["rm", "-f", BRIDGE_CONTAINER], { stdio: "ignore" });
92
+ }