commit 75520a358bc924b22eb7b06edf759818bcb21a66 Author: onyx-and-iris Date: Mon Jun 30 11:59:08 2025 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b45bf04 --- /dev/null +++ b/.gitignore @@ -0,0 +1,144 @@ +# Generated by ignr: github.com/onyx-and-iris/ignr + +## Node ## +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.* +!.env.example + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Sveltekit cache directory +.svelte-kit/ + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Firebase cache directory +.firebase/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v3 +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Vite logs files +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + +# End of ignr diff --git a/index.js b/index.js new file mode 100755 index 0000000..b004807 --- /dev/null +++ b/index.js @@ -0,0 +1,102 @@ +#!/usr/bin/env node + +import cli from "./utils/cli.js"; +import { sceneHelp, sceneList, sceneSwitch, sceneCurrent } from "./utils/scene.js"; +import { QWebChannel } from "qwebchannel"; + + +const address = process.env.MELD_CLI_HOST || "localhost"; +const port = process.env.MELD_CLI_PORT || 13376; + +const input = cli.input; +const flags = cli.flags; + +var socket = new WebSocket(`ws://${address}:${port}`); + + +socket.onopen = function() { + let channel; + (() => { + try { + switch (input[0]) { + case "scene": + if (flags.help) { + console.log(sceneHelp); + socket.close(); + process.exit(0); + } + + const subcommand = input[1]; + switch (subcommand) { + case "list": + channel = new QWebChannel(socket, function (channel) { + sceneList(channel) + .then((scenes) => { + if (scenes.length === 0) { + console.log("No scenes found."); + } else { + console.log("Available scenes:"); + scenes.forEach(scene => { + console.log(`- ${scene.name} (ID: ${scene.id})`); + }); + } + socket.close(); + process.exit(0); + }) + .catch((err) => { + console.error(`${err}`); + socket.close(); + process.exit(1); + }); + }); + break; + case "switch": + const sceneName = input[2]; + if (!sceneName) { + console.error("Error: Scene name is required for the switch command."); + process.exit(1); + } + + channel = new QWebChannel(socket, function (channel) { + sceneSwitch(channel, sceneName) + .then(() => { + socket.close(); + process.exit(0); + }) + .catch((err) => { + console.error(`${err}`); + socket.close(); + process.exit(1); + }); + }); + break; + case "current": + channel = new QWebChannel(socket, function (channel) { + sceneCurrent(channel) + .then((currentScene) => { + if (currentScene) { + console.log(`Current scene: ${currentScene.name} (ID: ${currentScene.id})`); + } else { + console.log("No current scene found."); + } + socket.close(); + process.exit(0); + }) + .catch((err) => { + console.error(`${err}`); + socket.close(); + process.exit(1); + }); + }); + break; + default: + console.log(sceneHelp); + socket.close(); + process.exit(0); + } + } + } catch (error) { + console.error("Error handling CLI flags:", error); + } + })(); +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9192b55 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,149 @@ +{ + "name": "meld-cli", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "meld-cli", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "cli-meow-help": "^4.0.0", + "meow": "^13.2.0", + "qwebchannel": "^6.2.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.0.tgz", + "integrity": "sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==", + "license": "MIT", + "dependencies": { + "chalk": "^5.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/cli-meow-help": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-meow-help/-/cli-meow-help-4.0.0.tgz", + "integrity": "sha512-kQybi0SBFQU2+P+GolMG2/BTLjStQzv7zWnJUAmT4AUxyWfv8weCLlUJT7Po0pPSl7+N3y8UieQz/Pq87c9uWA==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "chalk-template": "^1.1.0", + "cli-table3": "^0.6.0" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/qwebchannel": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/qwebchannel/-/qwebchannel-6.2.0.tgz", + "integrity": "sha512-DfJMFZutES2w/v2L3q6id+RoqG92PxnWIkW6dBQh1Be3mo6y2BEckvNfymXlIyCxks5dz0UbmzKZ2jT1qjOCzw==", + "license": "LGPL-3.0" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9c49631 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "meld-cli", + "version": "0.1.0", + "description": "A command line interface for Meld Studio WebChannel API", + "keywords": [ + "meld", + "meld-studio", + "meld-cli" + ], + "homepage": "https://github.com/onyx-and-iris/meld-api#readme", + "bugs": { + "url": "https://github.com/onyx-and-iris/meld-api/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/onyx-and-iris/meld-cli.git" + }, + "license": "MIT", + "author": "onyx-and-iris", + "type": "module", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "cli-meow-help": "^4.0.0", + "meow": "^13.2.0", + "qwebchannel": "^6.2.0" + } +} diff --git a/utils/cli.js b/utils/cli.js new file mode 100644 index 0000000..15242cb --- /dev/null +++ b/utils/cli.js @@ -0,0 +1,34 @@ +import meow from "meow"; +import meowHelp from "cli-meow-help"; + +const commands = { + scene: { + desc: "Manage scenes", + }, +}; + +const flags = { + help: { + type: "boolean", + shortFlag: "h", + description: "Display help information" + }, + version: { + type: "boolean", + shortFlag: "v", + description: "Display the version number" + } +} + +const helpText = meowHelp({ + name: "meld-cli", + flags, + commands, + description: "A command-line interface for managing scenes in Meld", + defaults: false, +}); + +export default meow(helpText, { + importMeta: import.meta, + flags, +}) \ No newline at end of file diff --git a/utils/scene.js b/utils/scene.js new file mode 100644 index 0000000..443813b --- /dev/null +++ b/utils/scene.js @@ -0,0 +1,90 @@ +import meowHelp from "cli-meow-help"; + +const commands = { + list: { + desc: "List all scenes", + }, + switch: { + desc: "Switch to a scene by name", + }, + current: { + desc: "Show the current scene", + }, +} + +const flags = { + help: { + type: "boolean", + shortFlag: "h", + description: "Display help information" + }, +}; + +const sceneHelp = meowHelp({ + name: "meld-cli", + flags, + commands, + description: "Manage scenes in Meld", + defaults: false, +}); + +function sceneList(channel) { + if (!channel.objects || !channel.objects.meld) { + return Promise.reject(new Error("Meld object not found in channel.")); + } + + const meld = channel.objects.meld; + const scenes = []; + for (const [key, value] of Object.entries(meld.session.items)) { + if (value.type === "scene") { + scenes.push({ name: value.name, id: key }); + } + } + if (scenes.length === 0) { + return Promise.reject(new Error("No scenes found.")); + } + return Promise.resolve(scenes); +} + +function sceneSwitch(channel, sceneName) { + if (!channel.objects || !channel.objects.meld) { + return Promise.reject(new Error("Meld object not found in channel.")); + } + + const meld = channel.objects.meld; + let itemId; + for (const [key, value] of Object.entries(meld.session.items)) { + if (value.type === "scene" && value.name === sceneName) { + itemId = key; + break; + } + } + if (!itemId) { + return Promise.reject(new Error(`Scene "${sceneName}" not found.`)); + } + + return new Promise((resolve, reject) => { + meld.showScene(itemId).then(() => { + console.log(`Switched to scene: ${sceneName}`); + resolve(); + }).catch(err => { + reject(err); + }); + }); +} + +function sceneCurrent(channel) { + if (!channel.objects || !channel.objects.meld) { + throw new Error("Meld object not found in channel."); + } + + const meld = channel.objects.meld; + for (const [key, value] of Object.entries(meld.session.items)) { + if (value.type === "scene" && value.current) { + return Promise.resolve({ name: value.name, id: key }); + } + } + return Promise.reject(new Error("No current scene found.")); +} + +export { sceneHelp, sceneList, sceneSwitch, sceneCurrent }; \ No newline at end of file