@citadel-labs/beads-ui 1.0.1 → 2.0.0
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 +5 -3
- package/bin/beads-board.js +131 -20
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -27,12 +27,14 @@ Then from any project that uses [Beads](https://github.com/steveyegge/beads):
|
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
cd /path/to/your/project
|
|
30
|
-
bdui # Start dashboard
|
|
31
|
-
bdui /path/to/project #
|
|
30
|
+
bdui # Start dashboard in background, prints URL
|
|
31
|
+
bdui /path/to/project # Specify a project directory
|
|
32
32
|
bdui --port 9000 # Custom port
|
|
33
|
+
bdui status # Check if dashboard is running
|
|
34
|
+
bdui stop # Stop the dashboard
|
|
33
35
|
```
|
|
34
36
|
|
|
35
|
-
Open **http://localhost:8377** in your browser.
|
|
37
|
+
The server starts in the background and returns control to your terminal. Open the printed URL (default **http://localhost:8377**) in your browser. Port auto-increments if taken.
|
|
36
38
|
|
|
37
39
|
### As a Claude Code Plugin
|
|
38
40
|
|
package/bin/beads-board.js
CHANGED
|
@@ -2,25 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const fs = require('node:fs');
|
|
5
|
+
const { spawn } = require('node:child_process');
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const SERVER_SCRIPT = path.join(__dirname, '..', 'server', 'index.js');
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Argument parsing
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
const rawArgs = process.argv.slice(2);
|
|
14
|
+
|
|
15
|
+
// Extract subcommand (start, stop, status) — default to "start"
|
|
16
|
+
const SUBCOMMANDS = ['start', 'stop', 'status'];
|
|
17
|
+
let subcommand = 'start';
|
|
18
|
+
const args = [];
|
|
19
|
+
for (const arg of rawArgs) {
|
|
20
|
+
if (SUBCOMMANDS.includes(arg) && args.length === 0 && subcommand === 'start') {
|
|
21
|
+
subcommand = arg;
|
|
22
|
+
} else {
|
|
23
|
+
args.push(arg);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
7
26
|
|
|
8
27
|
// --help
|
|
9
28
|
if (args.includes('--help') || args.includes('-h')) {
|
|
10
29
|
console.log(`bdui — Kanban dashboard and git log viewer for Beads
|
|
11
30
|
|
|
12
31
|
Usage:
|
|
13
|
-
bdui [project-dir] [options]
|
|
32
|
+
bdui [project-dir] [options] Start the dashboard (default)
|
|
33
|
+
bdui start [project-dir] [options] Start the dashboard
|
|
34
|
+
bdui stop [project-dir] Stop a running dashboard
|
|
35
|
+
bdui status [project-dir] Show running dashboard info
|
|
14
36
|
|
|
15
37
|
Options:
|
|
16
|
-
--port <port>
|
|
17
|
-
--
|
|
18
|
-
--
|
|
38
|
+
--port <port> Port to listen on (default: 8377)
|
|
39
|
+
--foreground Run in foreground (don't daemonize)
|
|
40
|
+
--help, -h Show this help message
|
|
41
|
+
--version, -v Show version number
|
|
19
42
|
|
|
20
43
|
Examples:
|
|
21
|
-
bdui #
|
|
44
|
+
bdui # Start dashboard for current directory
|
|
22
45
|
bdui /path/to/project # Specify project directory
|
|
23
|
-
bdui --port 9000 # Custom port
|
|
46
|
+
bdui --port 9000 # Custom port
|
|
47
|
+
bdui stop # Stop the running dashboard
|
|
48
|
+
bdui status # Check if dashboard is running`);
|
|
24
49
|
process.exit(0);
|
|
25
50
|
}
|
|
26
51
|
|
|
@@ -31,28 +56,34 @@ if (args.includes('--version') || args.includes('-v')) {
|
|
|
31
56
|
process.exit(0);
|
|
32
57
|
}
|
|
33
58
|
|
|
34
|
-
//
|
|
59
|
+
// --foreground
|
|
60
|
+
const foreground = args.includes('--foreground');
|
|
61
|
+
const filteredArgs = args.filter(a => a !== '--foreground');
|
|
62
|
+
|
|
63
|
+
// --port
|
|
35
64
|
let customPort = null;
|
|
36
|
-
const portIdx =
|
|
65
|
+
const portIdx = filteredArgs.indexOf('--port');
|
|
37
66
|
if (portIdx !== -1) {
|
|
38
|
-
customPort =
|
|
67
|
+
customPort = filteredArgs[portIdx + 1];
|
|
39
68
|
if (!customPort || isNaN(parseInt(customPort, 10))) {
|
|
40
69
|
console.error('Error: --port requires a numeric value');
|
|
41
70
|
process.exit(1);
|
|
42
71
|
}
|
|
43
|
-
|
|
72
|
+
filteredArgs.splice(portIdx, 2);
|
|
44
73
|
}
|
|
45
74
|
|
|
46
75
|
// Remaining arg is the project directory
|
|
47
|
-
const projectDir =
|
|
76
|
+
const projectDir = filteredArgs[0] ? path.resolve(filteredArgs[0]) : process.cwd();
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Validation
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
48
81
|
|
|
49
|
-
// Validate project directory exists
|
|
50
82
|
if (!fs.existsSync(projectDir)) {
|
|
51
83
|
console.error(`Error: directory not found: ${projectDir}`);
|
|
52
84
|
process.exit(1);
|
|
53
85
|
}
|
|
54
86
|
|
|
55
|
-
// Check for .beads/ directory
|
|
56
87
|
if (!fs.existsSync(path.join(projectDir, '.beads'))) {
|
|
57
88
|
console.error(`Error: no .beads/ directory found in ${projectDir}`);
|
|
58
89
|
console.error('This project does not appear to use Beads issue tracking.');
|
|
@@ -60,12 +91,92 @@ if (!fs.existsSync(path.join(projectDir, '.beads'))) {
|
|
|
60
91
|
process.exit(1);
|
|
61
92
|
}
|
|
62
93
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Pidfile helpers
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
const PIDFILE = path.join(projectDir, '.beads-board.pid');
|
|
99
|
+
|
|
100
|
+
function getRunningInstance() {
|
|
101
|
+
try {
|
|
102
|
+
const data = JSON.parse(fs.readFileSync(PIDFILE, 'utf8'));
|
|
103
|
+
process.kill(data.pid, 0); // throws if not running
|
|
104
|
+
return data;
|
|
105
|
+
} catch {
|
|
106
|
+
// Clean up stale pidfile
|
|
107
|
+
try { fs.unlinkSync(PIDFILE); } catch {}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
66
110
|
}
|
|
67
111
|
|
|
68
|
-
//
|
|
69
|
-
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Subcommands
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
70
115
|
|
|
71
|
-
|
|
116
|
+
if (subcommand === 'status') {
|
|
117
|
+
const instance = getRunningInstance();
|
|
118
|
+
if (instance) {
|
|
119
|
+
console.log(`beads-board running at http://localhost:${instance.port} (pid ${instance.pid})`);
|
|
120
|
+
} else {
|
|
121
|
+
console.log('beads-board is not running');
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (subcommand === 'stop') {
|
|
128
|
+
const instance = getRunningInstance();
|
|
129
|
+
if (!instance) {
|
|
130
|
+
console.log('beads-board is not running');
|
|
131
|
+
process.exit(0);
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
process.kill(instance.pid, 'SIGTERM');
|
|
135
|
+
console.log(`beads-board stopped (was http://localhost:${instance.port}, pid ${instance.pid})`);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error(`Failed to stop beads-board (pid ${instance.pid}): ${err.message}`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// subcommand === 'start'
|
|
144
|
+
const existing = getRunningInstance();
|
|
145
|
+
if (existing) {
|
|
146
|
+
console.log(`beads-board already running at http://localhost:${existing.port} (pid ${existing.pid})`);
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (foreground) {
|
|
151
|
+
// Run server in foreground (debugging)
|
|
152
|
+
if (customPort) process.env.PORT = customPort;
|
|
153
|
+
process.argv = [process.argv[0], __filename, projectDir];
|
|
154
|
+
require(SERVER_SCRIPT);
|
|
155
|
+
} else {
|
|
156
|
+
// Spawn detached server process
|
|
157
|
+
const env = { ...process.env };
|
|
158
|
+
if (customPort) env.PORT = customPort;
|
|
159
|
+
|
|
160
|
+
const child = spawn(process.execPath, [SERVER_SCRIPT, projectDir], {
|
|
161
|
+
detached: true,
|
|
162
|
+
stdio: 'ignore',
|
|
163
|
+
env,
|
|
164
|
+
});
|
|
165
|
+
child.unref();
|
|
166
|
+
|
|
167
|
+
// Wait for pidfile to confirm startup (poll up to 3s)
|
|
168
|
+
const start = Date.now();
|
|
169
|
+
const poll = setInterval(() => {
|
|
170
|
+
const instance = getRunningInstance();
|
|
171
|
+
if (instance) {
|
|
172
|
+
clearInterval(poll);
|
|
173
|
+
console.log(`beads-board running at http://localhost:${instance.port}`);
|
|
174
|
+
process.exit(0);
|
|
175
|
+
}
|
|
176
|
+
if (Date.now() - start > 3000) {
|
|
177
|
+
clearInterval(poll);
|
|
178
|
+
console.error('Error: server failed to start within 3 seconds');
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
}, 100);
|
|
182
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@citadel-labs/beads-ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Kanban dashboard and git log viewer for Beads",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bdui": "bin/beads-board.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "cd ui && npm run build",
|
|
10
|
-
"start": "node server/index.js"
|
|
10
|
+
"start": "node server/index.js",
|
|
11
|
+
"publish:patch": "npm version patch && git push && git push --tags && npm publish --access public",
|
|
12
|
+
"publish:minor": "npm version minor && git push && git push --tags && npm publish --access public",
|
|
13
|
+
"publish:major": "npm version major && git push && git push --tags && npm publish --access public"
|
|
11
14
|
},
|
|
12
15
|
"license": "MIT"
|
|
13
16
|
}
|