@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 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 for current directory
31
- bdui /path/to/project # Or specify a project directory
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. The server auto-detects an available port and reuses an existing instance if one is already running.
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
 
@@ -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 args = process.argv.slice(2);
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> Port to listen on (default: 8377)
17
- --help, -h Show this help message
18
- --version, -v Show version number
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 # Use current directory
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
- // Parse --port
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 = args.indexOf('--port');
65
+ const portIdx = filteredArgs.indexOf('--port');
37
66
  if (portIdx !== -1) {
38
- customPort = args[portIdx + 1];
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
- args.splice(portIdx, 2);
72
+ filteredArgs.splice(portIdx, 2);
44
73
  }
45
74
 
46
75
  // Remaining arg is the project directory
47
- const projectDir = args[0] ? path.resolve(args[0]) : process.cwd();
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
- // Set PORT env var if custom port specified, then delegate to server
64
- if (customPort) {
65
- process.env.PORT = customPort;
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
- // Rewrite process.argv so server/index.js sees the project dir
69
- process.argv = [process.argv[0], __filename, projectDir];
112
+ // ---------------------------------------------------------------------------
113
+ // Subcommands
114
+ // ---------------------------------------------------------------------------
70
115
 
71
- require(path.join(__dirname, '..', 'server', 'index.js'));
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": "1.0.1",
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
  }