lcluster 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  <p>A powerful Lavalink cluster manager for your terminal.</p>
5
5
 
6
6
  <p>
7
- <img src="https://img.shields.io/badge/version-1.0.0-blue" />
7
+ <img src="https://img.shields.io/badge/version-1.0.1-blue" />
8
8
  <img src="https://img.shields.io/badge/node-%3E%3D18.0.0-green" />
9
9
  <img src="https://img.shields.io/badge/license-GPLv3-purple" />
10
10
  <img src="https://img.shields.io/badge/built%20with-Claude%20AI-orange" />
@@ -111,7 +111,7 @@ Lavalink v4 compatible client.
111
111
 
112
112
  ```
113
113
  ╔═════════════════════════════════════════════════════════════════════╗
114
- ║ ⬡ lcluster v1.0.0 ● 2 online ⚠ 1 warn gateway :2333 ● ║
114
+ ║ ⬡ lcluster v1.0.1 ● 2 online ⚠ 1 warn gateway :2333 ● ║
115
115
  ╚═════════════════════════════════════════════════════════════════════╝
116
116
 
117
117
  ┌─ nodes (3/5) ──────────────────────────────────── [↑↓ scroll] ─┐
@@ -154,13 +154,6 @@ Full architectural guides, TUI maps, setup instructions, and deployment strategi
154
154
 
155
155
  ---
156
156
 
157
- ## Roadmap
158
-
159
- - [x] v1.0.0 — Core cluster manager, TUI dashboard, gateway, alerts
160
- - [ ] v1.0.1 — Custom Discord bot integration with token support
161
-
162
- ---
163
-
164
157
  ## Credits
165
158
 
166
159
  lcluster was designed and built by **Ram Krishna** with architecture,
@@ -0,0 +1,42 @@
1
+ server:
2
+ port: 2333
3
+ address: 0.0.0.0
4
+
5
+ lavalink:
6
+ server:
7
+ password: "youshallnotpass"
8
+ sources:
9
+ youtube: true
10
+ bandcamp: true
11
+ soundcloud: true
12
+ twitch: true
13
+ vimeo: true
14
+ mixer: true
15
+ http: true
16
+ local: false
17
+ filters:
18
+ volume: true
19
+ equalizer: true
20
+ karaoke: true
21
+ timescale: true
22
+ tremolo: true
23
+ vibrato: true
24
+ distortion: true
25
+ rotation: true
26
+ channelMix: true
27
+ lowPass: true
28
+ bufferDurationMs: 400
29
+ youtubePlaylistLoadLimit: 6
30
+ playerUpdateInterval: 5
31
+ youtubeSearchEnabled: true
32
+ soundcloudSearchEnabled: true
33
+ gc-warnings: true
34
+
35
+ metrics:
36
+ prometheus:
37
+ enabled: false
38
+ endpoint: /metrics
39
+
40
+ sentry:
41
+ dsn: ""
42
+ environment: ""
@@ -0,0 +1,31 @@
1
+ server:
2
+ port: 2333
3
+ address: 0.0.0.0
4
+
5
+ lavalink:
6
+ server:
7
+ password: "youshallnotpass"
8
+ sources:
9
+ youtube: true
10
+ bandcamp: true
11
+ soundcloud: true
12
+ twitch: true
13
+ vimeo: true
14
+ mixer: true
15
+ http: true
16
+ local: false
17
+ bufferDurationMs: 400
18
+ youtubePlaylistLoadLimit: 6
19
+ playerUpdateInterval: 5
20
+ youtubeSearchEnabled: true
21
+ soundcloudSearchEnabled: true
22
+ gc-warnings: true
23
+
24
+ metrics:
25
+ prometheus:
26
+ enabled: false
27
+ endpoint: /metrics
28
+
29
+ sentry:
30
+ dsn: ""
31
+ environment: ""
@@ -0,0 +1,31 @@
1
+ server:
2
+ port: 2333
3
+ address: 0.0.0.0
4
+
5
+ lavalink:
6
+ server:
7
+ password: "youshallnotpass"
8
+ sources:
9
+ youtube: true
10
+ bandcamp: false
11
+ soundcloud: false
12
+ twitch: false
13
+ vimeo: false
14
+ mixer: false
15
+ http: false
16
+ local: false
17
+ bufferDurationMs: 400
18
+ youtubePlaylistLoadLimit: 6
19
+ playerUpdateInterval: 5
20
+ youtubeSearchEnabled: true
21
+ soundcloudSearchEnabled: true
22
+ gc-warnings: true
23
+
24
+ metrics:
25
+ prometheus:
26
+ enabled: false
27
+ endpoint: /metrics
28
+
29
+ sentry:
30
+ dsn: ""
31
+ environment: ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lcluster",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A powerful Lavalink cluster manager for your terminal",
5
5
  "main": "src/cli.js",
6
6
  "type": "module",
package/src/cli.js CHANGED
@@ -10,7 +10,7 @@ if (!process.env.LCLUSTER_TSX_BOOTSTRAPPED) {
10
10
  const result = spawnSync(process.execPath, [
11
11
  '--no-warnings',
12
12
  '--import',
13
- 'tsx',
13
+ import.meta.resolve('tsx'),
14
14
  __filename,
15
15
  ...process.argv.slice(2)
16
16
  ], {
package/src/main.js CHANGED
@@ -16,7 +16,7 @@ export async function runCLI() {
16
16
  .name('lcluster')
17
17
  .description('⬡ lcluster — Lavalink Cluster Manager')
18
18
  .version(`
19
- ⬡ lcluster v1.0.0
19
+ ⬡ lcluster v1.0.1
20
20
 
21
21
  Built by Ram Krishna & Claude (Anthropic AI)
22
22
  Lavalink Cluster Manager for Node.js
@@ -17,7 +17,7 @@ export default function Dashboard({ nodes, gatewayPort, uptimeStart, onAction })
17
17
  <Box flexDirection="column" paddingX={2} paddingY={1} width="100%" height="100%" backgroundColor={theme.background}>
18
18
  <Border borderColor={theme.border}>
19
19
  <Box flexDirection="row" justifyContent="space-between" width="100%">
20
- <Text color={theme.accent}> ⬡ lcluster v1.0.0</Text>
20
+ <Text color={theme.accent}> ⬡ lcluster v1.0.1</Text>
21
21
  <Box flexDirection="row" gap={2}>
22
22
  <Text color={theme.online}>● {nodes.filter(n => n.status === 'online').length} online</Text>
23
23
  <Text color={theme.degraded}>⚠ {nodes.filter(n => n.status === 'degraded' || n.status === 'reconnecting').length} warn</Text>
@@ -86,8 +86,7 @@ export default function Settings({ onBack }) {
86
86
  <Box flexDirection="column" width="100%" height="100%" backgroundColor={theme.background}>
87
87
  <Border title="settings" borderColor={theme.border} flexGrow={1}>
88
88
  <Box flexDirection="column" paddingX={1} marginY={1}>
89
- <Text color={theme.textDim}>APPEARANCE</Text>
90
- </Box>
89
+ <Text color={theme.textDim}>APPEARANCE</Text>
91
90
  <Box marginBottom={1}>
92
91
  <Text color={theme.textDim}>Theme </Text>
93
92
  <Text color={field === 0 ? theme.text : theme.textDim}>
@@ -140,15 +139,15 @@ export default function Settings({ onBack }) {
140
139
  <Box flexDirection="column" paddingX={1} marginTop={1}>
141
140
  <Text color={theme.borderDim}>{'─'.repeat(45)}</Text>
142
141
  <Box marginTop={1} flexDirection="column">
143
- <Text color={theme.text} bold>lcluster v1.0.0</Text>
142
+ <Text color={theme.text} bold>lcluster v1.0.1</Text>
144
143
  <Text color={theme.textDim}>Built by <Text color={theme.text}>Ram Krishna</Text> & <Text color={theme.text}>Claude (Anthropic AI)</Text></Text>
145
144
  <Text color={theme.textDim}>This project was designed and built with the help of AI.</Text>
146
145
  </Box>
147
146
  </Box>
148
147
  </Border >
149
- <Box marginTop={1}>
150
- <Text color={theme.textDim}>[↑↓] fields [←→] change theme [enter] save [q/esc] cancel</Text>
151
- </Box>
148
+ <Box marginTop={1}>
149
+ <Text color={theme.textDim}>[↑↓] fields [←→] change theme [enter] save [q/esc] cancel</Text>
150
+ </Box>
152
151
  </Box >
153
152
  );
154
153
  }
@@ -2,18 +2,70 @@ import React, { useState } from 'react';
2
2
  import { Box, Text, useInput } from 'ink';
3
3
  import { getTheme } from '../theme/index.js';
4
4
  import Border from '../components/Border.jsx';
5
- import { listTemplates } from '../../templates/manager.js';
5
+ import { listTemplates, deleteTemplate, saveTemplate } from '../../templates/manager.js';
6
+ import TextInput from 'ink-text-input';
6
7
 
7
8
  export default function Templates({ onBack }) {
8
9
  const theme = getTheme();
9
- const [templates] = useState(() => listTemplates());
10
+ const [templates, setTemplates] = useState(() => listTemplates());
10
11
  const [selectedIndex, setSelectedIndex] = useState(0);
12
+ const [showCreate, setShowCreate] = useState(false);
13
+ const [newName, setNewName] = useState('');
14
+ const [error, setError] = useState('');
15
+
16
+ const refresh = () => setTemplates(listTemplates());
11
17
 
12
18
  useInput((input, key) => {
19
+ if (showCreate) {
20
+ if (key.escape) {
21
+ setShowCreate(false);
22
+ setNewName('');
23
+ setError('');
24
+ }
25
+ if (key.return) {
26
+ if (!newName || newName.length < 2) {
27
+ setError('Name too short');
28
+ return;
29
+ }
30
+ const finalName = newName.endsWith('.yml') ? newName : newName + '.yml';
31
+ if (templates.find(t => t.name === finalName)) {
32
+ setError('Template already exists');
33
+ return;
34
+ }
35
+
36
+ // Create default baseline
37
+ const baseContent = `server:\n port: 2333\n address: 0.0.0.0\n\nlavalink:\n server:\n password: "youshallnotpass"\n sources:\n youtube: true\n`;
38
+ saveTemplate(finalName, baseContent);
39
+ setShowCreate(false);
40
+ setNewName('');
41
+ setError('');
42
+ refresh();
43
+ setSelectedIndex(templates.length); // point back to add new button usually
44
+ }
45
+ return;
46
+ }
47
+
13
48
  if (key.upArrow) setSelectedIndex(Math.max(0, selectedIndex - 1));
14
49
  if (key.downArrow) setSelectedIndex(Math.min(templates.length, selectedIndex + 1));
15
50
  if (input === 'q' || key.escape) onBack();
16
- // In full impl [enter] would open editor or delete
51
+
52
+ // Delete template
53
+ if (input === 'd' && selectedIndex < templates.length) {
54
+ const t = templates[selectedIndex];
55
+ if (t.builtin) {
56
+ setError('Cannot delete built-in template');
57
+ setTimeout(() => setError(''), 2000);
58
+ } else {
59
+ deleteTemplate(t.name);
60
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
61
+ refresh();
62
+ }
63
+ }
64
+
65
+ // Add new
66
+ if (key.return && selectedIndex === templates.length) {
67
+ setShowCreate(true);
68
+ }
17
69
  });
18
70
 
19
71
  return (
@@ -21,21 +73,45 @@ export default function Templates({ onBack }) {
21
73
  <Border title="templates" borderColor={theme.border} flexGrow={1}>
22
74
  <Box flexDirection="column" paddingX={1} flexGrow={1}>
23
75
  {templates.map((t, i) => (
24
- <Text key={t.name} color={selectedIndex === i ? theme.text : theme.textDim} bold={selectedIndex === i}>
25
- {selectedIndex === i ? <Text color={theme.accent}>▶ </Text> : ' '}
76
+ <Text key={t.name} color={selectedIndex === i && !showCreate ? theme.text : theme.textDim} bold={selectedIndex === i && !showCreate}>
77
+ {selectedIndex === i && !showCreate ? <Text color={theme.accent}>▶ </Text> : ' '}
26
78
  {t.name.padEnd(20)} {t.builtin && <Text color={theme.textDim}>(built-in)</Text>}
27
79
  </Text>
28
80
  ))}
29
- <Box marginTop={1}>
30
- <Text color={selectedIndex === templates.length ? theme.text : theme.textDim} bold={selectedIndex === templates.length}>
31
- {selectedIndex === templates.length ? <Text color={theme.accent}>▶ </Text> : ' '}
32
- + Add new template
33
- </Text>
34
- </Box>
81
+
82
+ {!showCreate && (
83
+ <Box marginTop={1}>
84
+ <Text color={selectedIndex === templates.length ? theme.text : theme.textDim} bold={selectedIndex === templates.length}>
85
+ {selectedIndex === templates.length ? <Text color={theme.accent}>▶ </Text> : ' '}
86
+ + Add new template
87
+ </Text>
88
+ </Box>
89
+ )}
90
+
91
+ {showCreate && (
92
+ <Box marginTop={1} flexDirection="column">
93
+ <Box>
94
+ <Text color={theme.accent}>▶ </Text>
95
+ <Text color={theme.text}>Name: </Text>
96
+ <TextInput value={newName} onChange={setNewName} focus={showCreate} />
97
+ </Box>
98
+ <Text color={theme.textDim}>Press [Enter] to create, [Esc] to cancel. Edit manually in ~/.lcluster/templates</Text>
99
+ </Box>
100
+ )}
101
+
102
+ {error && (
103
+ <Box marginTop={1}>
104
+ <Text color={theme.error}>⚠ {error}</Text>
105
+ </Box>
106
+ )}
35
107
  </Box>
36
108
  </Border>
37
109
  <Box marginTop={1}>
38
- <Text color={theme.textDim}>[↑↓] navigate [enter] select / edit [d] delete (user only) [q] back</Text>
110
+ {showCreate ? (
111
+ <Text color={theme.textDim}>[enter] create [esc] cancel</Text>
112
+ ) : (
113
+ <Text color={theme.textDim}>[↑↓] navigate [enter] create new [d] delete (user only) [q] back</Text>
114
+ )}
39
115
  </Box>
40
116
  </Box>
41
117
  );