From 6fa44c63672a9503e0548afdbcac55548949d9c6 Mon Sep 17 00:00:00 2001 From: Doug Brown <60854716+sonoxo@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:14:29 -0400 Subject: [PATCH 1/9] feat(agent-bridge): add SoundCloudOpen desktop bridge --- integrations/soundcloudopenBridge.js | 198 +++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 integrations/soundcloudopenBridge.js diff --git a/integrations/soundcloudopenBridge.js b/integrations/soundcloudopenBridge.js new file mode 100644 index 00000000..fc63038d --- /dev/null +++ b/integrations/soundcloudopenBridge.js @@ -0,0 +1,198 @@ +'use strict'; + +const { spawn } = require('child_process'); + +const AUDIO_FORMATS = ['mp3', 'm4a', 'opus', 'flac', 'wav', 'aac']; +const BROWSERS = ['auto', 'chrome', 'chromium', 'edge', 'firefox', 'brave', 'opera', 'safari', 'none']; + +function clean(value) { + if (value === undefined || value === null) return null; + const text = String(value).trim(); + return text || null; +} + +function validateSoundCloudUrl(value) { + const url = clean(value); + if (!url) throw new Error('A SoundCloud URL is required.'); + let parsed; + try { + parsed = new URL(url); + } catch (_error) { + throw new Error('Enter a valid SoundCloud URL.'); + } + const host = parsed.hostname.toLowerCase(); + if (host !== 'soundcloud.com' && !host.endsWith('.soundcloud.com')) { + throw new Error('Only soundcloud.com URLs are accepted.'); + } + return parsed.toString(); +} + +function normalizeFormat(value) { + const format = clean(value) || 'mp3'; + if (!AUDIO_FORMATS.includes(format)) { + throw new Error(`Unsupported audio format: ${format}`); + } + return format; +} + +function normalizeBrowser(value) { + const browser = clean(value) || 'auto'; + if (!BROWSERS.includes(browser)) { + throw new Error(`Unsupported browser option: ${browser}`); + } + return browser; +} + +function buildMissionArgs(input) { + const options = input || {}; + const prompt = clean(options.prompt); + if (!prompt) throw new Error('Describe the sound you want.'); + + const args = [prompt]; + const soundcloudUrl = clean(options.soundcloudUrl); + if (soundcloudUrl) args.push('--soundcloud', validateSoundCloudUrl(soundcloudUrl)); + + const format = normalizeFormat(options.format); + if (format !== 'mp3') args.push('--format', format); + + if (options.bpm !== undefined && options.bpm !== null && String(options.bpm).trim() !== '') { + const bpm = Number(options.bpm); + if (!Number.isInteger(bpm) || bpm < 1 || bpm > 400) { + throw new Error('BPM must be a whole number between 1 and 400.'); + } + args.push('--bpm', String(bpm)); + } + + const genre = clean(options.genre); + const mood = clean(options.mood); + const useCase = clean(options.useCase); + if (genre) args.push('--genre', genre); + if (mood) args.push('--mood', mood); + if (useCase) args.push('--use-case', useCase); + + if (options.count !== undefined && options.count !== null && String(options.count).trim() !== '') { + const count = Number(options.count); + if (!Number.isInteger(count) || count < 1 || count > 10) { + throw new Error('Candidate count must be a whole number between 1 and 10.'); + } + args.push('--count', String(count)); + } + + if (options.promptOnly) args.push('--prompt-only'); + return args; +} + +function buildDownloadArgs(input) { + const options = input || {}; + const url = validateSoundCloudUrl(options.url); + const args = [url, '--format', normalizeFormat(options.format), '--browser', normalizeBrowser(options.browser)]; + + const output = clean(options.output); + if (output) args.push('--output', output); + if (options.saveJson) args.push('--save-json'); + if (options.listOnly) args.push('--list'); + if (options.printCommand) args.push('--print-command'); + return args; +} + +function runProcess(command, args, options) { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + cwd: options && options.cwd ? options.cwd : process.cwd(), + env: Object.assign({}, process.env, options && options.env ? options.env : {}), + shell: false, + windowsHide: true + }); + + let stdout = ''; + let stderr = ''; + + child.stdout.on('data', chunk => { stdout += chunk.toString(); }); + child.stderr.on('data', chunk => { stderr += chunk.toString(); }); + child.on('error', reject); + child.on('close', code => { + resolve({ code, stdout: stdout.trim(), stderr: stderr.trim(), command, args: args.slice() }); + }); + }); +} + +function xuniaCandidates() { + return [ + { command: 'xunia-sounds', prefix: [] }, + { command: 'xuniasounds', prefix: [] }, + { command: 'python3', prefix: ['-m', 'soundcloudopen.xunia_sounds'] }, + { command: 'python', prefix: ['-m', 'soundcloudopen.xunia_sounds'] } + ]; +} + +function soundCloudOpenCandidates() { + return [ + { command: 'soundcloudopen', prefix: [] }, + { command: 'sco', prefix: [] }, + { command: 'python3', prefix: ['-m', 'soundcloudopen.cli'] }, + { command: 'python', prefix: ['-m', 'soundcloudopen.cli'] } + ]; +} + +async function runCandidates(candidates, args) { + const failures = []; + for (const candidate of candidates) { + try { + const result = await runProcess(candidate.command, candidate.prefix.concat(args)); + if (result.code === 0) return result; + failures.push(`${candidate.command}: ${result.stderr || `exit ${result.code}`}`); + } catch (error) { + failures.push(`${candidate.command}: ${error.message}`); + } + } + const error = new Error('SoundCloudOpen is not ready on this computer. Install SoundCloudOpen 1.2+ and try again.'); + error.details = failures; + throw error; +} + +async function checkAvailability() { + const [xunia, downloader] = await Promise.all([ + runCandidates(xuniaCandidates(), ['--version']).catch(error => ({ error })), + runCandidates(soundCloudOpenCandidates(), ['--version']).catch(error => ({ error })) + ]); + + return { + ready: !xunia.error && !downloader.error, + xunia: xunia.error ? { ready: false, error: xunia.error.message } : { ready: true, version: xunia.stdout, command: xunia.command }, + downloader: downloader.error ? { ready: false, error: downloader.error.message } : { ready: true, version: downloader.stdout, command: downloader.command } + }; +} + +async function buildMission(input) { + const result = await runCandidates(xuniaCandidates(), buildMissionArgs(input)); + if (input && input.promptOnly) return { type: 'prompt', text: result.stdout, command: result.command }; + + let mission; + try { + mission = JSON.parse(result.stdout); + } catch (_error) { + throw new Error(`XUNIA SOUNDS returned non-JSON output: ${result.stdout || result.stderr}`); + } + return { type: 'mission', mission, command: result.command }; +} + +async function saveAuthorizedMedia(input) { + const result = await runCandidates(soundCloudOpenCandidates(), buildDownloadArgs(input)); + return { + ok: result.code === 0, + stdout: result.stdout, + stderr: result.stderr, + command: result.command + }; +} + +module.exports = { + AUDIO_FORMATS, + BROWSERS, + validateSoundCloudUrl, + buildMissionArgs, + buildDownloadArgs, + checkAvailability, + buildMission, + saveAuthorizedMedia +}; From 147d4e05c5707aa7113cfea06124b5a9e263be54 Mon Sep 17 00:00:00 2001 From: Doug Brown <60854716+sonoxo@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:14:53 -0400 Subject: [PATCH 2/9] feat(agent-ui): add XUNIA Sounds desktop panel --- app/xunia.html | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 app/xunia.html diff --git a/app/xunia.html b/app/xunia.html new file mode 100644 index 00000000..4ee5f572 --- /dev/null +++ b/app/xunia.html @@ -0,0 +1,104 @@ + + + + + + XUNIA SOUNDS // SoundCloudOpen + + + +
+

XUNIA SOUNDS + SoundCloudOpen

+
Desktop bridge for Soundnode. Describe a sound, build a VIRGINIA/BeatStars mission with XUNIA SOUNDS, or save SoundCloud media you own or are allowed to save.
+ +
+ BEGINNER FLOW + YOU DESCRIBE → XUNIA SOUNDS BUILDS THE PLAN → CLAUDE/BEATSTARS DISCOVERY → REVIEW LICENSE → OPTIONAL SOUNDCLOUDOPEN SAVE +
+ +
Checking SoundCloudOpen…
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + + + +
+ +
The save button does not bypass SoundCloud permissions. Use it only for audio you own, downloads SoundCloud permits, or material you otherwise have permission to save.
+
Ready.
+
+ + + From 3090c240ef2d4399d3a8e75ac4f8ccda8ec129c8 Mon Sep 17 00:00:00 2001 From: Doug Brown <60854716+sonoxo@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:15:06 -0400 Subject: [PATCH 3/9] feat(agent-ui): wire XUNIA desktop controls to Electron IPC --- app/public/js/xuniaSoundCloudOpen.js | 97 ++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 app/public/js/xuniaSoundCloudOpen.js diff --git a/app/public/js/xuniaSoundCloudOpen.js b/app/public/js/xuniaSoundCloudOpen.js new file mode 100644 index 00000000..c79cda50 --- /dev/null +++ b/app/public/js/xuniaSoundCloudOpen.js @@ -0,0 +1,97 @@ +'use strict'; + +const { ipcRenderer } = require('electron'); + +function byId(id) { + return document.getElementById(id); +} + +function value(id) { + return byId(id).value.trim(); +} + +function missionPayload(promptOnly) { + return { + prompt: value('prompt'), + genre: value('genre'), + mood: value('mood'), + bpm: value('bpm'), + count: value('count'), + useCase: value('useCase'), + format: value('format'), + soundcloudUrl: value('soundcloudUrl'), + promptOnly: !!promptOnly + }; +} + +function downloadPayload() { + return { + url: value('soundcloudUrl'), + format: value('format'), + browser: value('browser'), + output: value('output') + }; +} + +function showOutput(payload) { + const node = byId('outputView'); + node.textContent = typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2); +} + +function setStatus(message, good) { + const status = byId('status'); + status.textContent = message; + status.className = `status ${good === true ? 'good' : good === false ? 'bad' : ''}`; +} + +async function check() { + setStatus('Checking SoundCloudOpen…'); + try { + const result = await ipcRenderer.invoke('xunia:check'); + if (result.ready) { + setStatus(`READY — ${result.xunia.version} / ${result.downloader.version}`, true); + } else { + setStatus('NOT READY — install SoundCloudOpen 1.2+ on this computer.', false); + } + showOutput(result); + } catch (error) { + setStatus(error.message, false); + showOutput({ error: error.message }); + } +} + +async function buildMission(promptOnly) { + showOutput(promptOnly ? 'Building Claude discovery prompt…' : 'Building XUNIA mission…'); + try { + const result = await ipcRenderer.invoke('xunia:mission', missionPayload(promptOnly)); + showOutput(result); + } catch (error) { + showOutput({ error: error.message }); + } +} + +async function saveAuthorizedMedia() { + const url = value('soundcloudUrl'); + if (!url) { + showOutput({ error: 'Add an authorized SoundCloud track or playlist URL first.' }); + return; + } + + const confirmed = window.confirm('Save this SoundCloud media only if you own it, SoundCloud permits the download, or you otherwise have permission. Continue?'); + if (!confirmed) return; + + showOutput('Starting SoundCloudOpen…'); + try { + const result = await ipcRenderer.invoke('xunia:download', downloadPayload()); + showOutput(result); + } catch (error) { + showOutput({ error: error.message }); + } +} + +byId('check').addEventListener('click', check); +byId('mission').addEventListener('click', () => buildMission(false)); +byId('promptOnly').addEventListener('click', () => buildMission(true)); +byId('save').addEventListener('click', saveAuthorizedMedia); + +check(); From 983d8a900404358fb03c628439965b869292a72a Mon Sep 17 00:00:00 2001 From: Doug Brown <60854716+sonoxo@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:15:31 -0400 Subject: [PATCH 4/9] feat(agent-bridge): expose SoundCloudOpen through Electron IPC --- main.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/main.js b/main.js index dfc195ac..0b6e2257 100644 --- a/main.js +++ b/main.js @@ -10,6 +10,7 @@ const { } = require('electron'); const windowStateKeeper = require('electron-window-state'); const configuration = require('./app/public/js/common/configLocation'); +const soundCloudOpenBridge = require('./integrations/soundcloudopenBridge'); // custom constants const clientId = '342b8a7af638944906dcdb46f9d56d98'; @@ -18,6 +19,7 @@ const SCconnect = `https://soundcloud.com/connect?&client_id=${clientId}&redirec let mainWindow; let authenticationWindow; +let xuniaWindow; app.on('ready', () => { checkUserConfig(); @@ -124,6 +126,42 @@ function initMainWindow() { menuBar(); } +function openXuniaWindow() { + if (xuniaWindow && !xuniaWindow.isDestroyed()) { + xuniaWindow.show(); + xuniaWindow.focus(); + return; + } + + xuniaWindow = new BrowserWindow({ + width: 980, + height: 780, + minWidth: 720, + minHeight: 600, + title: 'XUNIA SOUNDS + SoundCloudOpen', + webPreferences: { + nodeIntegration: true + } + }); + + xuniaWindow.loadURL(`file://${__dirname}/app/xunia.html`); + xuniaWindow.on('closed', () => { + xuniaWindow = null; + }); +} + +ipcMain.handle('xunia:check', async () => { + return soundCloudOpenBridge.checkAvailability(); +}); + +ipcMain.handle('xunia:mission', async (_event, payload) => { + return soundCloudOpenBridge.buildMission(payload || {}); +}); + +ipcMain.handle('xunia:download', async (_event, payload) => { + return soundCloudOpenBridge.saveAuthorizedMedia(payload || {}); +}); + app.on('will-quit', () => { // Unregister all shortcuts. globalShortcut.unregisterAll() @@ -207,6 +245,24 @@ function menuBar() { role: 'editMenu', label: 'Soundnode' }, + { + label: 'XUNIA SOUNDS', + submenu: [ + { + label: 'Open XUNIA SOUNDS + SoundCloudOpen', + accelerator: 'CmdOrCtrl+Shift+X', + click() { + openXuniaWindow(); + } + }, + { + label: 'SoundCloudOpen on GitHub', + click() { + require('electron').shell.openExternal('https://github.com/sonoxo/soundcloudopen') + } + } + ] + }, { role: 'view', label: 'View', From cf384d4896360f2a19a31ab512e89101b56336a4 Mon Sep 17 00:00:00 2001 From: Doug Brown <60854716+sonoxo@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:15:44 -0400 Subject: [PATCH 5/9] test(agent-verify): cover SoundCloudOpen desktop bridge --- tests/xunia-soundcloudopen.test.js | 67 ++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/xunia-soundcloudopen.test.js diff --git a/tests/xunia-soundcloudopen.test.js b/tests/xunia-soundcloudopen.test.js new file mode 100644 index 00000000..f0e917af --- /dev/null +++ b/tests/xunia-soundcloudopen.test.js @@ -0,0 +1,67 @@ +'use strict'; + +const assert = require('assert'); +const bridge = require('../integrations/soundcloudopenBridge'); + +function testMissionArgs() { + const args = bridge.buildMissionArgs({ + prompt: 'dark melodic trap', + genre: 'trap', + mood: 'cold', + bpm: 90, + count: 4, + useCase: 'album', + format: 'wav', + soundcloudUrl: 'https://soundcloud.com/example/owned-track' + }); + + assert.strictEqual(args[0], 'dark melodic trap'); + assert.ok(args.includes('--genre')); + assert.ok(args.includes('trap')); + assert.ok(args.includes('--mood')); + assert.ok(args.includes('cold')); + assert.ok(args.includes('--bpm')); + assert.ok(args.includes('90')); + assert.ok(args.includes('--count')); + assert.ok(args.includes('4')); + assert.ok(args.includes('--use-case')); + assert.ok(args.includes('album')); + assert.ok(args.includes('--format')); + assert.ok(args.includes('wav')); + assert.ok(args.includes('--soundcloud')); +} + +function testPromptOnly() { + const args = bridge.buildMissionArgs({ prompt: 'warm soul', promptOnly: true }); + assert.ok(args.includes('--prompt-only')); +} + +function testDownloadArgs() { + const args = bridge.buildDownloadArgs({ + url: 'https://soundcloud.com/example/sets/owned-playlist', + format: 'flac', + browser: 'none', + output: '/tmp/xunia', + saveJson: true + }); + + assert.strictEqual(args[0], 'https://soundcloud.com/example/sets/owned-playlist'); + assert.deepStrictEqual(args.slice(1, 5), ['--format', 'flac', '--browser', 'none']); + assert.ok(args.includes('--output')); + assert.ok(args.includes('/tmp/xunia')); + assert.ok(args.includes('--save-json')); +} + +function testValidation() { + assert.throws(() => bridge.buildMissionArgs({ prompt: '' }), /Describe the sound/); + assert.throws(() => bridge.buildMissionArgs({ prompt: 'x', bpm: 0 }), /BPM/); + assert.throws(() => bridge.buildMissionArgs({ prompt: 'x', count: 11 }), /Candidate count/); + assert.throws(() => bridge.buildDownloadArgs({ url: 'https://example.com/file.mp3' }), /soundcloud.com/); + assert.throws(() => bridge.buildDownloadArgs({ url: 'https://soundcloud.com/example/x', format: 'exe' }), /Unsupported audio format/); +} + +testMissionArgs(); +testPromptOnly(); +testDownloadArgs(); +testValidation(); +console.log('XUNIA SoundCloudOpen bridge tests: PASS'); From 08c7a2e4dbd04ab87d7e7b632a6c0fa59b099577 Mon Sep 17 00:00:00 2001 From: Doug Brown <60854716+sonoxo@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:16:03 -0400 Subject: [PATCH 6/9] test(agent-verify): add XUNIA bridge verification scripts --- package.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 09d077a0..bf718460 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "Soundnode", - "version": "7.0.0", + "version": "7.1.0", "main": "main.js", - "description": "Soundnode App is the Soundcloud for desktop", + "description": "Soundnode desktop with XUNIA SOUNDS and SoundCloudOpen integration", "repository": { "type": "git", - "url": "git://github.com/Soundnode/soundnode-app.git" + "url": "git://github.com/sonoxo/soundnode-app-xunia.git" }, "scripts": { "prestart": "npm run watch", @@ -20,9 +20,11 @@ "package:osx": "electron-packager ./ Soundnode --platform=darwin --out ./dist/Soundnode --electron-version 8.0.1 --overwrite --icon ./app/soundnode.ico", "package:linux": "electron-packager ./ Soundnode --platform=linux --out ./dist/Soundnode --electron-version 8.0.1 --overwrite --icon ./app/soundnode.icns", "package:win32": "electron-packager ./ Soundnode --platform=win32 --out ./dist/Soundnode --electron-version 8.0.1 --overwrite --icon ./app/soundnode.icns", - "package:all": "npm run package:osx && npm run package:linux && npm run package:win32" + "package:all": "npm run package:osx && npm run package:linux && npm run package:win32", + "test:xunia": "node tests/xunia-soundcloudopen.test.js", + "check:xunia": "node --check integrations/soundcloudopenBridge.js && node --check app/public/js/xuniaSoundCloudOpen.js && node tests/xunia-soundcloudopen.test.js" }, - "author": "Michael Lancaster", + "author": "Michael Lancaster / XUNIA integration by sonoxo", "license": "GPL-3.0", "devDependencies": { "babel-core": "6.26.3", From 7e2f78cfa51740bb8040953961866fd837135d3b Mon Sep 17 00:00:00 2001 From: Doug Brown <60854716+sonoxo@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:16:10 -0400 Subject: [PATCH 7/9] ci(agent-verify): verify XUNIA bridge across desktop platforms --- .github/workflows/xunia-soundcloudopen.yml | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/xunia-soundcloudopen.yml diff --git a/.github/workflows/xunia-soundcloudopen.yml b/.github/workflows/xunia-soundcloudopen.yml new file mode 100644 index 00000000..bab7d796 --- /dev/null +++ b/.github/workflows/xunia-soundcloudopen.yml @@ -0,0 +1,26 @@ +name: XUNIA SoundCloudOpen Bridge + +on: + push: + branches: + - master + - feature/xunia-soundcloudopen-desktop-bridge + pull_request: + branches: + - master + +jobs: + verify: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + node: [18, 20] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - name: Verify XUNIA + SoundCloudOpen bridge + run: npm run check:xunia From 672abb73429f482b838eafbf13e064cdfa15926d Mon Sep 17 00:00:00 2001 From: Doug Brown <60854716+sonoxo@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:16:28 -0400 Subject: [PATCH 8/9] docs(agent-verify): document Soundnode + SoundCloudOpen integration --- doc/XUNIA_SOUNDCLOUDOPEN.md | 79 +++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 doc/XUNIA_SOUNDCLOUDOPEN.md diff --git a/doc/XUNIA_SOUNDCLOUDOPEN.md b/doc/XUNIA_SOUNDCLOUDOPEN.md new file mode 100644 index 00000000..403a1c47 --- /dev/null +++ b/doc/XUNIA_SOUNDCLOUDOPEN.md @@ -0,0 +1,79 @@ +# XUNIA SOUNDS + SoundCloudOpen Desktop Bridge + +## Beginner version + +This fork turns Soundnode into a desktop front end for the creator-owned SoundCloudOpen workflow. + +**PLAY / BROWSE IN SOUNDNODE → OPEN XUNIA SOUNDS → DESCRIBE A SOUND → BUILD A VIRGINIA + BEATSTARS DISCOVERY MISSION → REVIEW THE BEAT/LiCENSE → OPTIONALLY SAVE SOUNDCLOUD MEDIA YOU OWN OR ARE ALLOWED TO SAVE** + +The original Soundnode player remains intact. The new integration lives beside it. + +## What each part does + +| Part | Job | +|---|---| +| Soundnode | Desktop SoundCloud listening/browsing interface | +| XUNIA SOUNDS panel | Beginner desktop control surface | +| `integrations/soundcloudopenBridge.js` | Shell-safe bridge from Electron to the SoundCloudOpen CLI | +| `xunia-sounds` | Builds the VIRGINIA / 3LM CLAUDE / BeatStars discovery mission | +| `soundcloudopen` | Saves authorized SoundCloud tracks/playlists with the existing SoundCloudOpen rules | +| BeatStars MCP | Discovery tool provider used through Claude | + +## Install the companion CLI + +Soundnode does not embed Python. Install SoundCloudOpen 1.2+ on the same computer: + +```bash +python3 -m pip install git+https://github.com/sonoxo/soundcloudopen.git +``` + +Then verify: + +```bash +soundcloudopen --version +xunia-sounds --version +``` + +## Open the desktop panel + +Start Soundnode, then use: + +**XUNIA SOUNDS → Open XUNIA SOUNDS + SoundCloudOpen** + +Keyboard shortcut: + +```text +Cmd/Ctrl + Shift + X +``` + +The panel can: + +1. check whether both SoundCloudOpen commands are available; +2. build a structured XUNIA SOUNDS mission; +3. build only the Claude/BeatStars natural-language prompt; +4. pass an optional authorized SoundCloud URL into the mission; and +5. run the existing SoundCloudOpen save command for authorized media. + +## Security boundary + +The Electron bridge never constructs a shell command string. It calls child processes with an executable plus an argument array and `shell: false`. + +SoundCloudOpen remains responsible for its existing URL validation, dependency checks, browser-cookie selection, metadata handling, and yt-dlp/FFmpeg execution. + +The desktop panel also validates SoundCloud hostnames before execution and asks for confirmation before starting a save. + +## Verification + +The integration has a dependency-free Node test lane: + +```bash +npm run check:xunia +``` + +CI runs that verification on: + +- Windows / Node 18 and 20 +- macOS / Node 18 and 20 +- Ubuntu / Node 18 and 20 + +This focused lane intentionally does not install the legacy Electron 8 / node-sass dependency tree just to verify the new bridge. From d345333afca8efc4a681e6063d776396743d5fc5 Mon Sep 17 00:00:00 2001 From: Doug Brown <60854716+sonoxo@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:16:45 -0400 Subject: [PATCH 9/9] docs(agent-verify): surface SoundCloudOpen desktop bridge --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index 24a13b7c..921fc0b8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,44 @@ +# SOUNDNODE APP — XUNIA DESKTOP + +## XUNIA SOUNDS + SoundCloudOpen + +This fork now includes a focused desktop bridge to [SoundCloudOpen](https://github.com/sonoxo/soundcloudopen). + +**Soundnode plays/browses → XUNIA SOUNDS builds the creator mission → 3LM CLAUDE can use BeatStars discovery → SoundCloudOpen handles authorized SoundCloud saving.** + +Open it from the desktop menu: + +**XUNIA SOUNDS → Open XUNIA SOUNDS + SoundCloudOpen** + +Shortcut: `Cmd/Ctrl + Shift + X` + +The new panel supports: + +- SoundCloudOpen/XUNIA CLI readiness checks; +- VIRGINIA + BeatStars mission creation; +- BPM, genre, mood, use-case, candidate-count, and optional SoundCloud source controls; +- Claude prompt-only output; +- authorized SoundCloud track/playlist saving through the existing SoundCloudOpen CLI; and +- shell-safe process execution with argument arrays instead of command-string interpolation. + +Install the companion CLI on the same computer: + +```bash +python3 -m pip install git+https://github.com/sonoxo/soundcloudopen.git +soundcloudopen --version +xunia-sounds --version +``` + +Focused verification: + +```bash +npm run check:xunia +``` + +Full integration guide: [doc/XUNIA_SOUNDCLOUDOPEN.md](doc/XUNIA_SOUNDCLOUDOPEN.md) + +--- + [![Join the chat at https://gitter.im/Soundnode/soundnode-app](https://badges.gitter.im/Soundnode/soundnode-app.svg)](https://gitter.im/Soundnode/soundnode-app?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Soundnode App