@involvex/youtube-music-cli 0.0.39 → 0.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/package.json +120 -0
- package/dist/scripts/build-cli.d.ts +1 -0
- package/dist/scripts/build-cli.js +46 -0
- package/dist/source/cli.js +26 -1
- package/dist/source/services/completions/completions.service.d.ts +2 -0
- package/dist/source/services/completions/completions.service.js +313 -0
- package/dist/source/utils/constants.d.ts +1 -1
- package/dist/source/utils/constants.js +28 -1
- package/dist/{youtube-music-cli.exe → youtube-music-cli} +0 -0
- package/package.json +9 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
## [0.0.44](https://github.com/involvex/youtube-music-cli/compare/v0.0.43...v0.0.44) (2026-02-23)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- add standalone executable support with build-time versioning ([18f730c](https://github.com/involvex/youtube-music-cli/commit/18f730cef46dfe862cfbce0065af4b4989296ef0))
|
|
6
|
+
|
|
7
|
+
## [0.0.43](https://github.com/involvex/youtube-music-cli/compare/v0.0.42...v0.0.43) (2026-02-23)
|
|
8
|
+
|
|
9
|
+
## [0.0.42](https://github.com/involvex/youtube-music-cli/compare/v0.0.41...v0.0.42) (2026-02-23)
|
|
10
|
+
|
|
11
|
+
## [0.0.41](https://github.com/involvex/youtube-music-cli/compare/v0.0.40...v0.0.41) (2026-02-23)
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- add shell completions for bash, zsh, powershell, and fish ([5adecc3](https://github.com/involvex/youtube-music-cli/commit/5adecc3af1cf6dae5a188c24c598e67c8260c844))
|
|
16
|
+
|
|
17
|
+
## [0.0.40](https://github.com/involvex/youtube-music-cli/compare/v0.0.39...v0.0.40) (2026-02-23)
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
- enable MSIX packaging for Windows distribution ([14eafbf](https://github.com/involvex/youtube-music-cli/commit/14eafbf522f1d2fbacf7ed1243df035cd254891b))
|
|
22
|
+
|
|
1
23
|
## [0.0.39](https://github.com/involvex/youtube-music-cli/compare/v0.0.38...v0.0.39) (2026-02-23)
|
|
2
24
|
|
|
3
25
|
### Features
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@involvex/youtube-music-cli",
|
|
3
|
+
"version": "0.0.44",
|
|
4
|
+
"description": "- A Commandline music player for youtube-music",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/involvex/youtube-music-cli.git"
|
|
8
|
+
},
|
|
9
|
+
"funding": "https://github.com/sponsors/involvex",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"author": "involvex",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"bin": {
|
|
14
|
+
"youtube-music-cli": "dist/source/cli.js",
|
|
15
|
+
"ymc": "dist/source/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"CHANGELOG.md",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
".github/FUNDING.yml"
|
|
23
|
+
],
|
|
24
|
+
"icon": "assets/icon2.PNG",
|
|
25
|
+
"sponsor": {
|
|
26
|
+
"url": "https://github.com/sponsors/involvex"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://involvex.github.io/youtube-music-cli/",
|
|
29
|
+
"keywords": [
|
|
30
|
+
"cli",
|
|
31
|
+
"youtube",
|
|
32
|
+
"youtube-music",
|
|
33
|
+
"youtube-cli",
|
|
34
|
+
"youtube-music-cli",
|
|
35
|
+
"ink",
|
|
36
|
+
"cli-music"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"prebuild": "bun run format && bun run lint:fix && bun run typecheck",
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"bun:build": "bun build source/cli.tsx --outfile dist/index.js --target node --footer \"//Copyright (c) 2026 involvex\"",
|
|
42
|
+
"compile": "bun scripts/build-cli.ts",
|
|
43
|
+
"dev": "bun run --bun source/cli.tsx",
|
|
44
|
+
"dev:watch": "bun run --bun --watch source/cli.tsx",
|
|
45
|
+
"format": "prettier --write .",
|
|
46
|
+
"format:check": "prettier --check .",
|
|
47
|
+
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern dist",
|
|
48
|
+
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix --ignore-pattern dist",
|
|
49
|
+
"start": "bun run dist/source/cli.js",
|
|
50
|
+
"test": "bun run build && ava",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
|
|
53
|
+
"clean": "rimraf dist",
|
|
54
|
+
"release": "powershell -File scripts/release.ps1",
|
|
55
|
+
"build:web": "cd web && bun run build",
|
|
56
|
+
"dev:web": "cd web && bun run dev",
|
|
57
|
+
"build:all": "bun run build && bun run build:web",
|
|
58
|
+
"msix": "bun run compile && msix-packager-cli package --config ./msix-config.json --skip-build"
|
|
59
|
+
},
|
|
60
|
+
"prettier": "@vdemedes/prettier-config",
|
|
61
|
+
"ava": {
|
|
62
|
+
"files": [
|
|
63
|
+
"tests/**/*.js"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"@distube/ytdl-core": "^4.16.12",
|
|
68
|
+
"@types/bun": "^1.3.9",
|
|
69
|
+
"ansi-escapes": "^7.3.0",
|
|
70
|
+
"discord-rpc": "^4.0.1",
|
|
71
|
+
"ink": "^6.8.0",
|
|
72
|
+
"ink-table": "^3.1.0",
|
|
73
|
+
"ink-text-input": "^6.0.0",
|
|
74
|
+
"jiti": "^2.6.1",
|
|
75
|
+
"meow": "^14.1.0",
|
|
76
|
+
"node-notifier": "^10.0.1",
|
|
77
|
+
"node-youtube-music": "^0.10.3",
|
|
78
|
+
"play-sound": "^1.1.6",
|
|
79
|
+
"react": "^19.2.4",
|
|
80
|
+
"ws": "^8.19.0",
|
|
81
|
+
"youtube-ext": "^1.1.25",
|
|
82
|
+
"youtubei.js": "^16.0.1"
|
|
83
|
+
},
|
|
84
|
+
"devDependencies": {
|
|
85
|
+
"@eslint/js": "^10.0.1",
|
|
86
|
+
"@sindresorhus/tsconfig": "^8.1.0",
|
|
87
|
+
"@types/node": "^25.3.0",
|
|
88
|
+
"@types/node-notifier": "^8.0.5",
|
|
89
|
+
"@types/react": "^19.2.14",
|
|
90
|
+
"@types/ws": "^8.18.1",
|
|
91
|
+
"@vdemedes/prettier-config": "^2.0.1",
|
|
92
|
+
"ava": "^6.4.1",
|
|
93
|
+
"chalk": "^5.6.2",
|
|
94
|
+
"conventional-changelog-cli": "^5.0.0",
|
|
95
|
+
"eslint": "^10.0.1",
|
|
96
|
+
"eslint-plugin-react": "^7.37.5",
|
|
97
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
98
|
+
"globals": "^17.3.0",
|
|
99
|
+
"ink-testing-library": "^4.0.0",
|
|
100
|
+
"prettier": "^3.8.1",
|
|
101
|
+
"prettier-plugin-organize-imports": "^4.3.0",
|
|
102
|
+
"prettier-plugin-packagejson": "^3.0.0",
|
|
103
|
+
"prettier-plugin-sort-imports": "^1.8.11",
|
|
104
|
+
"react-devtools-core": "^7.0.1",
|
|
105
|
+
"rimraf": "^6.1.3",
|
|
106
|
+
"ts-node": "^10.9.2",
|
|
107
|
+
"typescript": "^5.9.3",
|
|
108
|
+
"typescript-eslint": "^8.56.0"
|
|
109
|
+
},
|
|
110
|
+
"engines": {
|
|
111
|
+
"node": ">=16"
|
|
112
|
+
},
|
|
113
|
+
"sponsors": "https://github.com/sponsors/involvex",
|
|
114
|
+
"overrides": {
|
|
115
|
+
"minimatch": "^10.2.1"
|
|
116
|
+
},
|
|
117
|
+
"trustedDependencies": [
|
|
118
|
+
"unrs-resolver"
|
|
119
|
+
]
|
|
120
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
3
|
+
const rootDir = process.cwd();
|
|
4
|
+
const isWindows = process.platform === 'win32';
|
|
5
|
+
const outputName = isWindows ? 'youtube-music-cli.exe' : 'youtube-music-cli';
|
|
6
|
+
const outfile = path.join(rootDir, 'dist', outputName);
|
|
7
|
+
const banner = '//Copyright (c) 2026 involvex';
|
|
8
|
+
const iconPath = path.join(rootDir, 'assets', 'icon.ico');
|
|
9
|
+
const platformTarget = isWindows
|
|
10
|
+
? 'bun-windows-x64'
|
|
11
|
+
: process.platform === 'darwin'
|
|
12
|
+
? 'bun-darwin-x64'
|
|
13
|
+
: 'bun-linux-x64';
|
|
14
|
+
const compileOptions = isWindows
|
|
15
|
+
? {
|
|
16
|
+
target: platformTarget,
|
|
17
|
+
outfile,
|
|
18
|
+
footer: banner,
|
|
19
|
+
windowsTitle: 'YouTube Music CLI',
|
|
20
|
+
windowsPublisher: 'involvex',
|
|
21
|
+
windowsIcon: iconPath,
|
|
22
|
+
windowsCopyright: 'Copyright (c) 2026 involvex',
|
|
23
|
+
windowsDescription: 'A Commandline music player for youtube-music',
|
|
24
|
+
}
|
|
25
|
+
: {
|
|
26
|
+
target: platformTarget,
|
|
27
|
+
outfile,
|
|
28
|
+
};
|
|
29
|
+
const buildOptions = {
|
|
30
|
+
entrypoints: [path.join(rootDir, 'source', 'cli.tsx')],
|
|
31
|
+
compile: compileOptions,
|
|
32
|
+
footer: banner,
|
|
33
|
+
minify: true,
|
|
34
|
+
sourcemap: 'linked',
|
|
35
|
+
bytecode: false,
|
|
36
|
+
define: {
|
|
37
|
+
'process.env.NODE_ENV': JSON.stringify('production'),
|
|
38
|
+
VERSION: JSON.stringify(pkg.version ?? '0.0.0'),
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
const result = await Bun.build(buildOptions);
|
|
42
|
+
if (!result.success) {
|
|
43
|
+
console.error('Build failed', result.logs ?? result);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.log('Build succeeded:', result.outputs.map(output => output.path));
|
package/dist/source/cli.js
CHANGED
|
@@ -10,12 +10,19 @@ import { getImportService } from "./services/import/import.service.js";
|
|
|
10
10
|
import { getWebServerManager } from "./services/web/web-server-manager.js";
|
|
11
11
|
import { getWebStreamingService } from "./services/web/web-streaming.service.js";
|
|
12
12
|
import { getVersionCheckService } from "./services/version-check/version-check.service.js";
|
|
13
|
+
import { generateCompletion, } from "./services/completions/completions.service.js";
|
|
13
14
|
import { getConfigService } from "./services/config/config.service.js";
|
|
14
15
|
import { getPlayerService } from "./services/player/player.service.js";
|
|
15
16
|
import { APP_VERSION } from "./utils/constants.js";
|
|
16
17
|
import { ensurePlaybackDependencies } from "./services/player/dependency-check.service.js";
|
|
17
18
|
import { getMusicService } from "./services/youtube-music/api.js";
|
|
19
|
+
const isStandalone = process
|
|
20
|
+
.isStandaloneExecutable ||
|
|
21
|
+
globalThis.Bun?.isStandalone;
|
|
22
|
+
const argv = isStandalone ? process.argv.slice(1) : process.argv.slice(2);
|
|
18
23
|
const cli = meow(`
|
|
24
|
+
youtube-music-cli@${APP_VERSION}
|
|
25
|
+
|
|
19
26
|
Usage
|
|
20
27
|
$ youtube-music-cli
|
|
21
28
|
$ youtube-music-cli play <track-id|youtube-url>
|
|
@@ -53,6 +60,12 @@ const cli = meow(`
|
|
|
53
60
|
--name Custom name for imported playlist
|
|
54
61
|
--help, -h Show this help
|
|
55
62
|
|
|
63
|
+
Shell Completions
|
|
64
|
+
$ youtube-music-cli completions bash
|
|
65
|
+
$ youtube-music-cli completions zsh
|
|
66
|
+
$ youtube-music-cli completions powershell
|
|
67
|
+
$ youtube-music-cli completions fish
|
|
68
|
+
|
|
56
69
|
Examples
|
|
57
70
|
$ youtube-music-cli
|
|
58
71
|
$ youtube-music-cli play dQw4w9WgXcQ
|
|
@@ -61,8 +74,10 @@ const cli = meow(`
|
|
|
61
74
|
$ youtube-music-cli plugins install adblock
|
|
62
75
|
$ youtube-music-cli import spotify "https://open.spotify.com/playlist/..."
|
|
63
76
|
$ youtube-music-cli --web --web-port 3000
|
|
77
|
+
$ youtube-music-cli completions powershell | Out-File $PROFILE
|
|
64
78
|
`, {
|
|
65
79
|
importMeta: import.meta,
|
|
80
|
+
argv,
|
|
66
81
|
flags: {
|
|
67
82
|
theme: {
|
|
68
83
|
type: 'string',
|
|
@@ -307,7 +322,17 @@ if (command === 'plugins') {
|
|
|
307
322
|
}
|
|
308
323
|
else {
|
|
309
324
|
// Handle other direct commands
|
|
310
|
-
if (command === '
|
|
325
|
+
if (command === 'completions') {
|
|
326
|
+
const shell = args[0];
|
|
327
|
+
const validShells = ['bash', 'zsh', 'powershell', 'fish'];
|
|
328
|
+
if (!shell || !validShells.includes(shell)) {
|
|
329
|
+
console.error('Usage: youtube-music-cli completions <bash|zsh|powershell|fish>');
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
console.log(generateCompletion(shell));
|
|
333
|
+
process.exit(0);
|
|
334
|
+
}
|
|
335
|
+
else if (command === 'play' && args[0]) {
|
|
311
336
|
// Play specific track
|
|
312
337
|
cli.flags.playTrack = args[0];
|
|
313
338
|
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
const COMMANDS = [
|
|
2
|
+
'play',
|
|
3
|
+
'search',
|
|
4
|
+
'playlist',
|
|
5
|
+
'suggestions',
|
|
6
|
+
'pause',
|
|
7
|
+
'resume',
|
|
8
|
+
'skip',
|
|
9
|
+
'back',
|
|
10
|
+
'plugins',
|
|
11
|
+
'import',
|
|
12
|
+
'completions',
|
|
13
|
+
];
|
|
14
|
+
const PLUGINS_SUBCOMMANDS = [
|
|
15
|
+
'list',
|
|
16
|
+
'install',
|
|
17
|
+
'remove',
|
|
18
|
+
'uninstall',
|
|
19
|
+
'update',
|
|
20
|
+
'enable',
|
|
21
|
+
'disable',
|
|
22
|
+
];
|
|
23
|
+
const IMPORT_SUBCOMMANDS = ['spotify', 'youtube'];
|
|
24
|
+
const COMPLETIONS_SUBCOMMANDS = [
|
|
25
|
+
'bash',
|
|
26
|
+
'zsh',
|
|
27
|
+
'powershell',
|
|
28
|
+
'fish',
|
|
29
|
+
];
|
|
30
|
+
const FLAGS = [
|
|
31
|
+
'--theme',
|
|
32
|
+
'--volume',
|
|
33
|
+
'--shuffle',
|
|
34
|
+
'--repeat',
|
|
35
|
+
'--headless',
|
|
36
|
+
'--web',
|
|
37
|
+
'--web-host',
|
|
38
|
+
'--web-port',
|
|
39
|
+
'--web-only',
|
|
40
|
+
'--web-auth',
|
|
41
|
+
'--name',
|
|
42
|
+
'--help',
|
|
43
|
+
'--version',
|
|
44
|
+
];
|
|
45
|
+
export function generateCompletion(shell) {
|
|
46
|
+
switch (shell) {
|
|
47
|
+
case 'bash':
|
|
48
|
+
return generateBashCompletion();
|
|
49
|
+
case 'zsh':
|
|
50
|
+
return generateZshCompletion();
|
|
51
|
+
case 'powershell':
|
|
52
|
+
return generatePowerShellCompletion();
|
|
53
|
+
case 'fish':
|
|
54
|
+
return generateFishCompletion();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function generateBashCompletion() {
|
|
58
|
+
const cmds = COMMANDS.join(' ');
|
|
59
|
+
const pluginsSubs = PLUGINS_SUBCOMMANDS.join(' ');
|
|
60
|
+
const importSubs = IMPORT_SUBCOMMANDS.join(' ');
|
|
61
|
+
const completionsSubs = COMPLETIONS_SUBCOMMANDS.join(' ');
|
|
62
|
+
const flags = FLAGS.join(' ');
|
|
63
|
+
return `# youtube-music-cli bash completion
|
|
64
|
+
# Add to ~/.bashrc or ~/.bash_profile:
|
|
65
|
+
# source <(ymc completions bash)
|
|
66
|
+
# # or:
|
|
67
|
+
# ymc completions bash >> ~/.bash_completion
|
|
68
|
+
|
|
69
|
+
_ymc_completions() {
|
|
70
|
+
local cur prev words cword
|
|
71
|
+
_init_completion || return
|
|
72
|
+
|
|
73
|
+
local commands="${cmds}"
|
|
74
|
+
local flags="${flags}"
|
|
75
|
+
|
|
76
|
+
case "$prev" in
|
|
77
|
+
plugins)
|
|
78
|
+
COMPREPLY=($(compgen -W "${pluginsSubs}" -- "$cur"))
|
|
79
|
+
return ;;
|
|
80
|
+
import)
|
|
81
|
+
COMPREPLY=($(compgen -W "${importSubs}" -- "$cur"))
|
|
82
|
+
return ;;
|
|
83
|
+
completions)
|
|
84
|
+
COMPREPLY=($(compgen -W "${completionsSubs}" -- "$cur"))
|
|
85
|
+
return ;;
|
|
86
|
+
--theme|-t)
|
|
87
|
+
COMPREPLY=($(compgen -W "dark light midnight matrix" -- "$cur"))
|
|
88
|
+
return ;;
|
|
89
|
+
--repeat|-r)
|
|
90
|
+
COMPREPLY=($(compgen -W "off all one" -- "$cur"))
|
|
91
|
+
return ;;
|
|
92
|
+
esac
|
|
93
|
+
|
|
94
|
+
if [[ "$cur" == -* ]]; then
|
|
95
|
+
COMPREPLY=($(compgen -W "$flags" -- "$cur"))
|
|
96
|
+
else
|
|
97
|
+
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
|
98
|
+
fi
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
complete -F _ymc_completions ymc youtube-music-cli
|
|
102
|
+
`;
|
|
103
|
+
}
|
|
104
|
+
function generateZshCompletion() {
|
|
105
|
+
return `#compdef ymc youtube-music-cli
|
|
106
|
+
# youtube-music-cli zsh completion
|
|
107
|
+
# Add to your zsh config:
|
|
108
|
+
# source <(ymc completions zsh)
|
|
109
|
+
# # or copy to a directory in $fpath:
|
|
110
|
+
# ymc completions zsh > ~/.zsh/completions/_ymc
|
|
111
|
+
|
|
112
|
+
_ymc() {
|
|
113
|
+
local -a commands subcommands flags
|
|
114
|
+
|
|
115
|
+
commands=(
|
|
116
|
+
'play:Play a track by ID or YouTube URL'
|
|
117
|
+
'search:Search for tracks'
|
|
118
|
+
'playlist:Play a playlist by ID'
|
|
119
|
+
'suggestions:Show music suggestions'
|
|
120
|
+
'pause:Pause playback'
|
|
121
|
+
'resume:Resume playback'
|
|
122
|
+
'skip:Skip to next track'
|
|
123
|
+
'back:Go to previous track'
|
|
124
|
+
'plugins:Manage plugins'
|
|
125
|
+
'import:Import playlists from Spotify or YouTube'
|
|
126
|
+
'completions:Generate shell completion scripts'
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
flags=(
|
|
130
|
+
'--theme[Theme to use]:theme:(dark light midnight matrix)'
|
|
131
|
+
'--volume[Initial volume (0-100)]:volume:'
|
|
132
|
+
'--shuffle[Enable shuffle mode]'
|
|
133
|
+
'--repeat[Repeat mode]:mode:(off all one)'
|
|
134
|
+
'--headless[Run without TUI]'
|
|
135
|
+
'--web[Enable web UI server]'
|
|
136
|
+
'--web-host[Web server host]:host:'
|
|
137
|
+
'--web-port[Web server port]:port:'
|
|
138
|
+
'--web-only[Run web server without CLI UI]'
|
|
139
|
+
'--web-auth[Authentication token for web server]:token:'
|
|
140
|
+
'--name[Custom name for imported playlist]:name:'
|
|
141
|
+
'--help[Show help]'
|
|
142
|
+
'--version[Show version]'
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
case $words[2] in
|
|
146
|
+
plugins)
|
|
147
|
+
local -a plugin_cmds
|
|
148
|
+
plugin_cmds=(
|
|
149
|
+
'list:List installed plugins'
|
|
150
|
+
'install:Install a plugin'
|
|
151
|
+
'remove:Remove a plugin'
|
|
152
|
+
'uninstall:Alias for remove'
|
|
153
|
+
'update:Update a plugin'
|
|
154
|
+
'enable:Enable a plugin'
|
|
155
|
+
'disable:Disable a plugin'
|
|
156
|
+
)
|
|
157
|
+
_describe 'plugin commands' plugin_cmds
|
|
158
|
+
return ;;
|
|
159
|
+
import)
|
|
160
|
+
local -a import_sources
|
|
161
|
+
import_sources=('spotify:Import from Spotify' 'youtube:Import from YouTube')
|
|
162
|
+
_describe 'import sources' import_sources
|
|
163
|
+
return ;;
|
|
164
|
+
completions)
|
|
165
|
+
local -a shells
|
|
166
|
+
shells=('bash:Bash completion' 'zsh:Zsh completion' 'powershell:PowerShell completion' 'fish:Fish completion')
|
|
167
|
+
_describe 'shells' shells
|
|
168
|
+
return ;;
|
|
169
|
+
esac
|
|
170
|
+
|
|
171
|
+
_arguments -s \\
|
|
172
|
+
$flags \\
|
|
173
|
+
'1:command:->cmd' \\
|
|
174
|
+
'*::args:->args'
|
|
175
|
+
|
|
176
|
+
case $state in
|
|
177
|
+
cmd)
|
|
178
|
+
_describe 'commands' commands ;;
|
|
179
|
+
args)
|
|
180
|
+
_message 'arguments' ;;
|
|
181
|
+
esac
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
_ymc
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
function generatePowerShellCompletion() {
|
|
188
|
+
const cmds = COMMANDS.map(c => `'${c}'`).join(', ');
|
|
189
|
+
const pluginsSubs = PLUGINS_SUBCOMMANDS.map(c => `'${c}'`).join(', ');
|
|
190
|
+
const importSubs = IMPORT_SUBCOMMANDS.map(c => `'${c}'`).join(', ');
|
|
191
|
+
const completionsSubs = COMPLETIONS_SUBCOMMANDS.map(c => `'${c}'`).join(', ');
|
|
192
|
+
const flags = FLAGS.map(f => `'${f}'`).join(', ');
|
|
193
|
+
return `# youtube-music-cli PowerShell completion
|
|
194
|
+
# Add to your PowerShell profile ($PROFILE):
|
|
195
|
+
# ymc completions powershell | Out-File -Append $PROFILE
|
|
196
|
+
# # or:
|
|
197
|
+
# Invoke-Expression (ymc completions powershell | Out-String)
|
|
198
|
+
|
|
199
|
+
$ymcCompleterBlock = {
|
|
200
|
+
param($wordToComplete, $commandAst, $cursorPosition)
|
|
201
|
+
|
|
202
|
+
$commands = @(${cmds})
|
|
203
|
+
$pluginsSubCommands = @(${pluginsSubs})
|
|
204
|
+
$importSubCommands = @(${importSubs})
|
|
205
|
+
$completionsSubCommands = @(${completionsSubs})
|
|
206
|
+
$flags = @(${flags})
|
|
207
|
+
$themes = @('dark', 'light', 'midnight', 'matrix')
|
|
208
|
+
$repeatModes = @('off', 'all', 'one')
|
|
209
|
+
|
|
210
|
+
$tokens = $commandAst.CommandElements
|
|
211
|
+
$prevToken = if ($tokens.Count -ge 2) { $tokens[$tokens.Count - 2].ToString() } else { '' }
|
|
212
|
+
$firstArg = if ($tokens.Count -ge 2) { $tokens[1].ToString() } else { '' }
|
|
213
|
+
|
|
214
|
+
# Context-aware completions
|
|
215
|
+
switch ($prevToken) {
|
|
216
|
+
'plugins' {
|
|
217
|
+
$pluginsSubCommands | Where-Object { $_ -like "$wordToComplete*" } |
|
|
218
|
+
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
'import' {
|
|
222
|
+
$importSubCommands | Where-Object { $_ -like "$wordToComplete*" } |
|
|
223
|
+
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
'completions' {
|
|
227
|
+
$completionsSubCommands | Where-Object { $_ -like "$wordToComplete*" } |
|
|
228
|
+
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
{ $_ -in '--theme', '-t' } {
|
|
232
|
+
$themes | Where-Object { $_ -like "$wordToComplete*" } |
|
|
233
|
+
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
{ $_ -in '--repeat', '-r' } {
|
|
237
|
+
$repeatModes | Where-Object { $_ -like "$wordToComplete*" } |
|
|
238
|
+
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if ($wordToComplete.StartsWith('-')) {
|
|
244
|
+
$flags | Where-Object { $_ -like "$wordToComplete*" } |
|
|
245
|
+
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
|
|
246
|
+
} elseif ($firstArg -eq $wordToComplete -or $tokens.Count -le 1) {
|
|
247
|
+
$commands | Where-Object { $_ -like "$wordToComplete*" } |
|
|
248
|
+
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
Register-ArgumentCompleter -Native -CommandName @('ymc', 'youtube-music-cli') -ScriptBlock $ymcCompleterBlock
|
|
253
|
+
`;
|
|
254
|
+
}
|
|
255
|
+
function generateFishCompletion() {
|
|
256
|
+
const commandCompletions = COMMANDS.map(cmd => `complete -c ymc -n '__fish_use_subcommand' -f -a '${cmd}' -d '${getFishDescription(cmd)}'`).join('\n');
|
|
257
|
+
const pluginsCompletions = PLUGINS_SUBCOMMANDS.map(sub => `complete -c ymc -n '__fish_seen_subcommand_from plugins' -f -a '${sub}'`).join('\n');
|
|
258
|
+
const importCompletions = IMPORT_SUBCOMMANDS.map(sub => `complete -c ymc -n '__fish_seen_subcommand_from import' -f -a '${sub}'`).join('\n');
|
|
259
|
+
const completionsCompletions = COMPLETIONS_SUBCOMMANDS.map(sub => `complete -c ymc -n '__fish_seen_subcommand_from completions' -f -a '${sub}'`).join('\n');
|
|
260
|
+
return `# youtube-music-cli fish completion
|
|
261
|
+
# Save to: ~/.config/fish/completions/ymc.fish
|
|
262
|
+
# ymc completions fish > ~/.config/fish/completions/ymc.fish
|
|
263
|
+
|
|
264
|
+
# Disable file completions by default
|
|
265
|
+
complete -c ymc -f
|
|
266
|
+
|
|
267
|
+
# Main commands
|
|
268
|
+
${commandCompletions}
|
|
269
|
+
|
|
270
|
+
# Plugins subcommands
|
|
271
|
+
${pluginsCompletions}
|
|
272
|
+
|
|
273
|
+
# Import subcommands
|
|
274
|
+
${importCompletions}
|
|
275
|
+
|
|
276
|
+
# Completions subcommands
|
|
277
|
+
${completionsCompletions}
|
|
278
|
+
|
|
279
|
+
# Flags
|
|
280
|
+
complete -c ymc -l theme -s t -d 'Theme to use' -r -a 'dark light midnight matrix'
|
|
281
|
+
complete -c ymc -l volume -s v -d 'Initial volume (0-100)' -r
|
|
282
|
+
complete -c ymc -l shuffle -s s -d 'Enable shuffle mode'
|
|
283
|
+
complete -c ymc -l repeat -s r -d 'Repeat mode' -r -a 'off all one'
|
|
284
|
+
complete -c ymc -l headless -d 'Run without TUI'
|
|
285
|
+
complete -c ymc -l web -d 'Enable web UI server'
|
|
286
|
+
complete -c ymc -l web-host -d 'Web server host' -r
|
|
287
|
+
complete -c ymc -l web-port -d 'Web server port' -r
|
|
288
|
+
complete -c ymc -l web-only -d 'Run web server without CLI UI'
|
|
289
|
+
complete -c ymc -l web-auth -d 'Authentication token for web server' -r
|
|
290
|
+
complete -c ymc -l name -d 'Custom name for imported playlist' -r
|
|
291
|
+
complete -c ymc -l help -s h -d 'Show help'
|
|
292
|
+
complete -c ymc -l version -d 'Show version'
|
|
293
|
+
|
|
294
|
+
# Also register for youtube-music-cli
|
|
295
|
+
complete -c youtube-music-cli -w ymc
|
|
296
|
+
`;
|
|
297
|
+
}
|
|
298
|
+
function getFishDescription(cmd) {
|
|
299
|
+
const descriptions = {
|
|
300
|
+
play: 'Play a track by ID or YouTube URL',
|
|
301
|
+
search: 'Search for tracks',
|
|
302
|
+
playlist: 'Play a playlist by ID',
|
|
303
|
+
suggestions: 'Show music suggestions',
|
|
304
|
+
pause: 'Pause playback',
|
|
305
|
+
resume: 'Resume playback',
|
|
306
|
+
skip: 'Skip to next track',
|
|
307
|
+
back: 'Go to previous track',
|
|
308
|
+
plugins: 'Manage plugins',
|
|
309
|
+
import: 'Import playlists from Spotify or YouTube',
|
|
310
|
+
completions: 'Generate shell completion scripts',
|
|
311
|
+
};
|
|
312
|
+
return descriptions[cmd] ?? cmd;
|
|
313
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const APP_NAME = "@involvex/youtube-music-cli";
|
|
2
|
-
export declare const APP_VERSION
|
|
2
|
+
export declare const APP_VERSION: string;
|
|
3
3
|
export declare const CONFIG_DIR: string;
|
|
4
4
|
export declare const CONFIG_FILE: string;
|
|
5
5
|
export declare const VIEW: {
|
|
@@ -1,6 +1,33 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
function loadAppVersion() {
|
|
5
|
+
if (typeof VERSION !== 'undefined') {
|
|
6
|
+
return VERSION;
|
|
7
|
+
}
|
|
8
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
for (let i = 0; i < 5; i++) {
|
|
10
|
+
try {
|
|
11
|
+
const content = readFileSync(resolve(dir, 'package.json'), 'utf8');
|
|
12
|
+
const pkg = JSON.parse(content);
|
|
13
|
+
if (typeof pkg.version === 'string' &&
|
|
14
|
+
pkg.name?.includes('youtube-music-cli')) {
|
|
15
|
+
return pkg.version;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
/* ignore */
|
|
20
|
+
}
|
|
21
|
+
const parent = dirname(dir);
|
|
22
|
+
if (parent === dir)
|
|
23
|
+
break;
|
|
24
|
+
dir = parent;
|
|
25
|
+
}
|
|
26
|
+
return '0.0.0';
|
|
27
|
+
}
|
|
1
28
|
// Application constants
|
|
2
29
|
export const APP_NAME = '@involvex/youtube-music-cli';
|
|
3
|
-
export const APP_VERSION =
|
|
30
|
+
export const APP_VERSION = loadAppVersion();
|
|
4
31
|
// Config directory
|
|
5
32
|
export const CONFIG_DIR = process.platform === 'win32'
|
|
6
33
|
? `${process.env['USERPROFILE']}\\.youtube-music-cli`
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@involvex/youtube-music-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.44",
|
|
4
4
|
"description": "- A Commandline music player for youtube-music",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"prebuild": "bun run format && bun run lint:fix && bun run typecheck",
|
|
40
40
|
"build": "tsc",
|
|
41
|
-
"bun:build": "bun build source/cli.tsx --outfile dist/index.js --target node",
|
|
42
|
-
"compile": "bun build
|
|
41
|
+
"bun:build": "bun build source/cli.tsx --outfile dist/index.js --target node --footer \"//Copyright (c) 2026 involvex\"",
|
|
42
|
+
"compile": "bun scripts/build-cli.ts",
|
|
43
43
|
"dev": "bun run --bun source/cli.tsx",
|
|
44
44
|
"dev:watch": "bun run --bun --watch source/cli.tsx",
|
|
45
45
|
"format": "prettier --write .",
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"release": "powershell -File scripts/release.ps1",
|
|
55
55
|
"build:web": "cd web && bun run build",
|
|
56
56
|
"dev:web": "cd web && bun run dev",
|
|
57
|
-
"build:all": "bun run build && bun run build:web"
|
|
57
|
+
"build:all": "bun run build && bun run build:web",
|
|
58
|
+
"msix": "bun run compile && msix-packager-cli package --config ./msix-config.json --skip-build"
|
|
58
59
|
},
|
|
59
60
|
"prettier": "@vdemedes/prettier-config",
|
|
60
61
|
"ava": {
|
|
@@ -64,20 +65,21 @@
|
|
|
64
65
|
},
|
|
65
66
|
"dependencies": {
|
|
66
67
|
"@distube/ytdl-core": "^4.16.12",
|
|
68
|
+
"@types/bun": "^1.3.9",
|
|
67
69
|
"ansi-escapes": "^7.3.0",
|
|
70
|
+
"discord-rpc": "^4.0.1",
|
|
68
71
|
"ink": "^6.8.0",
|
|
69
72
|
"ink-table": "^3.1.0",
|
|
70
73
|
"ink-text-input": "^6.0.0",
|
|
71
74
|
"jiti": "^2.6.1",
|
|
72
75
|
"meow": "^14.1.0",
|
|
73
76
|
"node-notifier": "^10.0.1",
|
|
74
|
-
"discord-rpc": "^4.0.1",
|
|
75
77
|
"node-youtube-music": "^0.10.3",
|
|
76
78
|
"play-sound": "^1.1.6",
|
|
77
79
|
"react": "^19.2.4",
|
|
80
|
+
"ws": "^8.19.0",
|
|
78
81
|
"youtube-ext": "^1.1.25",
|
|
79
|
-
"youtubei.js": "^16.0.1"
|
|
80
|
-
"ws": "^8.19.0"
|
|
82
|
+
"youtubei.js": "^16.0.1"
|
|
81
83
|
},
|
|
82
84
|
"devDependencies": {
|
|
83
85
|
"@eslint/js": "^10.0.1",
|