buddy-workbench 0.1.30 → 0.1.32

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
@@ -12,6 +12,20 @@ npm start
12
12
 
13
13
  Open `http://localhost:3100`. To use another port, run `PORT=4000 npm start`. Startup attempts to stop any process currently using the selected port.
14
14
 
15
+ When using the `devbuddy` CLI, pass the port directly with `--port`:
16
+
17
+ ```bash
18
+ devbuddy start --port 4000
19
+ devbuddy restart --port 4000
20
+ ```
21
+
22
+ Update the globally installed CLI from npm:
23
+
24
+ ```bash
25
+ devbuddy self-update
26
+ devbuddy self-update 0.1.32
27
+ ```
28
+
15
29
  ## Running on macOS
16
30
 
17
31
  ### Double-click Launcher (Terminal)
package/bin/devbuddy.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { execSync, spawn } from 'node:child_process';
3
+ import { execFileSync, execSync, spawn } from 'node:child_process';
4
4
  import { existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
5
5
  import { dirname, join } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
@@ -13,6 +13,85 @@ const dataDir = join(root, 'data');
13
13
  const pidFile = join(dataDir, 'devbuddy.pid');
14
14
  const logFile = join(dataDir, 'devbuddy.log');
15
15
  const serverJs = join(root, 'server.js');
16
+ const packageJsonFile = join(root, 'package.json');
17
+ const defaultPort = 3100;
18
+
19
+ function parsePort(args) {
20
+ let port;
21
+
22
+ for (let index = 0; index < args.length; index += 1) {
23
+ const arg = args[index];
24
+ if (arg === '--port') {
25
+ if (port !== undefined || args[index + 1] === undefined) {
26
+ throw new Error('Usage: --port <number>');
27
+ }
28
+ port = args[++index];
29
+ } else if (arg.startsWith('--port=')) {
30
+ if (port !== undefined) throw new Error('Port was specified more than once.');
31
+ port = arg.slice('--port='.length);
32
+ } else {
33
+ throw new Error(`Unknown option: ${arg}`);
34
+ }
35
+ }
36
+
37
+ const value = port ?? process.env.PORT ?? defaultPort;
38
+ if (!/^\d+$/.test(String(value)) || Number(value) < 1 || Number(value) > 65535) {
39
+ throw new Error('Port must be a number between 1 and 65535.');
40
+ }
41
+ return Number(value);
42
+ }
43
+
44
+ function packageDetails() {
45
+ return JSON.parse(readFileSync(packageJsonFile, 'utf8'));
46
+ }
47
+
48
+ function npmCommand() {
49
+ return process.platform === 'win32' ? 'npm.cmd' : 'npm';
50
+ }
51
+
52
+ function parseUpdateVersion(args) {
53
+ if (args.length > 1) throw new Error('Usage: devbuddy self-update [version]');
54
+ if (!args[0]) return 'latest';
55
+ if (args[0].startsWith('-') || /\s/.test(args[0])) {
56
+ throw new Error('Version must be a package version, tag, or dist-tag.');
57
+ }
58
+ return args[0];
59
+ }
60
+
61
+ function latestPublishedVersion(packageName) {
62
+ try {
63
+ return execFileSync(npmCommand(), ['view', packageName, 'version'], {
64
+ cwd: root,
65
+ encoding: 'utf8',
66
+ stdio: ['ignore', 'pipe', 'pipe']
67
+ }).trim();
68
+ } catch {
69
+ throw new Error(`Unable to query the latest ${packageName} version from npm.`);
70
+ }
71
+ }
72
+
73
+ function selfUpdate(args) {
74
+ const details = packageDetails();
75
+ const version = parseUpdateVersion(args);
76
+ const currentVersion = details.version;
77
+ const targetVersion = version === 'latest' ? latestPublishedVersion(details.name) : version;
78
+
79
+ if (version === 'latest' && targetVersion === currentVersion) {
80
+ console.log(`${details.name} is already up to date (${currentVersion}).`);
81
+ return;
82
+ }
83
+
84
+ console.log(`Updating ${details.name} from ${currentVersion} to ${version}…`);
85
+ try {
86
+ execFileSync(npmCommand(), ['install', '--global', `${details.name}@${version}`], {
87
+ cwd: root,
88
+ stdio: 'inherit'
89
+ });
90
+ } catch {
91
+ throw new Error(`Unable to install ${details.name}@${version}. Check npm permissions and network access.`);
92
+ }
93
+ console.log(`Updated ${details.name} to ${targetVersion}.`);
94
+ }
16
95
 
17
96
  function isProcessRunning(pid) {
18
97
  try {
@@ -58,11 +137,11 @@ function sendSignal(pid, signal) {
58
137
  }
59
138
  }
60
139
 
61
- async function start() {
140
+ async function start(port) {
62
141
  const existingPid = getRunningPid();
63
142
  if (existingPid) {
64
143
  console.log(`DevBuddy is already running (PID ${existingPid}).`);
65
- console.log(`URL: http://localhost:${process.env.PORT || 3100}`);
144
+ console.log(`URL: http://localhost:${port}`);
66
145
  return;
67
146
  }
68
147
 
@@ -75,7 +154,7 @@ async function start() {
75
154
  detached: true,
76
155
  stdio: ['ignore', out, err],
77
156
  cwd: root,
78
- env: { ...process.env }
157
+ env: { ...process.env, PORT: String(port) }
79
158
  });
80
159
 
81
160
  if (!child.pid) {
@@ -90,7 +169,6 @@ async function start() {
90
169
  await new Promise((resolve) => setTimeout(resolve, 800));
91
170
 
92
171
  if (isProcessRunning(child.pid)) {
93
- const port = process.env.PORT || 3100;
94
172
  console.log(`DevBuddy started successfully (PID ${child.pid}).`);
95
173
  console.log(`URL: http://localhost:${port}`);
96
174
  console.log(`Logs: ${logFile}`);
@@ -133,10 +211,9 @@ async function stop() {
133
211
  console.log('DevBuddy stopped.');
134
212
  }
135
213
 
136
- function status() {
214
+ function status(port) {
137
215
  const pid = getRunningPid();
138
216
  if (pid) {
139
- const port = process.env.PORT || 3100;
140
217
  console.log(`DevBuddy is running (PID ${pid}).`);
141
218
  console.log(`URL: http://localhost:${port}`);
142
219
  } else {
@@ -149,33 +226,47 @@ function help() {
149
226
  DevBuddy CLI
150
227
 
151
228
  Usage:
152
- devbuddy <command>
229
+ devbuddy <command> [--port <number>]
153
230
 
154
231
  Commands:
155
232
  start Start DevBuddy in the background
156
233
  stop Stop the running DevBuddy instance
157
234
  status Check the status of DevBuddy
158
235
  restart Restart DevBuddy
236
+ self-update [version]
237
+ Update the globally installed DevBuddy CLI
159
238
  help Display this help message
239
+
240
+ Options:
241
+ --port <number> Port to listen on (default: 3100)
242
+
243
+ Examples:
244
+ devbuddy start --port 4000
160
245
  `);
161
246
  }
162
247
 
163
248
  async function main() {
164
249
  const command = process.argv[2] || 'help';
250
+ const args = process.argv.slice(3);
165
251
 
166
252
  switch (command.toLowerCase()) {
167
253
  case 'start':
168
- await start();
254
+ await start(parsePort(args));
169
255
  break;
170
256
  case 'stop':
171
257
  await stop();
172
258
  break;
173
259
  case 'status':
174
- status();
260
+ status(parsePort(args));
175
261
  break;
176
262
  case 'restart':
177
263
  await stop();
178
- await start();
264
+ await start(parsePort(args));
265
+ break;
266
+ case 'self-update':
267
+ case 'selfupdate':
268
+ case 'selfupgrade':
269
+ selfUpdate(args);
179
270
  break;
180
271
  case 'help':
181
272
  case '-h':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-workbench",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
package/server/config.js CHANGED
@@ -14,6 +14,7 @@ export const paths = {
14
14
  errors: join(root, 'data', 'errors.json'),
15
15
  postman: join(root, 'data', 'postman.json'),
16
16
  branchSync: join(root, 'data', 'branch-sync.json'),
17
+ presentations: join(root, 'data', 'presentations.json'),
17
18
  shutdownLog: join(root, 'data', 'shutdown.log'),
18
19
  clipboardDir: join(root, 'data', 'clipboard'),
19
20
  plugins: join(root, 'plugins'),
@@ -0,0 +1,31 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { paths } from '../config.js';
4
+
5
+ const EMPTY_DATA = { plans: [], activePlan: null };
6
+
7
+ export function getPresentations() {
8
+ try {
9
+ if (!existsSync(paths.presentations)) return EMPTY_DATA;
10
+ const parsed = JSON.parse(readFileSync(paths.presentations, 'utf8'));
11
+ return {
12
+ plans: Array.isArray(parsed.plans) ? parsed.plans : [],
13
+ activePlan: parsed.activePlan || null
14
+ };
15
+ } catch {
16
+ return EMPTY_DATA;
17
+ }
18
+ }
19
+
20
+ export function savePresentations(data) {
21
+ mkdirSync(dirname(paths.presentations), { recursive: true });
22
+ const current = getPresentations();
23
+ const next = {
24
+ plans: Array.isArray(data.plans) ? data.plans : [],
25
+ activePlan: Object.prototype.hasOwnProperty.call(data, 'activePlan')
26
+ ? data.activePlan || null
27
+ : current.activePlan
28
+ };
29
+ writeFileSync(paths.presentations, JSON.stringify(next, null, 2), 'utf8');
30
+ return next;
31
+ }
@@ -0,0 +1,19 @@
1
+ import { Router } from 'express';
2
+ import { getPresentations, savePresentations } from '../repositories/presentations.js';
3
+
4
+ const router = Router();
5
+
6
+ router.get('/', (_req, res) => {
7
+ res.json(getPresentations());
8
+ });
9
+
10
+ router.put('/', (req, res) => {
11
+ res.json(savePresentations(req.body || {}));
12
+ });
13
+
14
+ router.put('/active', (req, res) => {
15
+ const current = getPresentations();
16
+ res.json(savePresentations({ ...current, activePlan: req.body?.plan || null }));
17
+ });
18
+
19
+ export default router;
package/server.js CHANGED
@@ -23,6 +23,7 @@ import fileOrganizerRoutes from './server/routes/file-organizer.js';
23
23
  import branchSyncRoutes from './server/routes/branch-sync.js';
24
24
  import updateRoutes from './server/routes/updates.js';
25
25
  import dataBackupRoutes from './server/routes/data-backup.js';
26
+ import presentationsRoutes from './server/routes/presentations.js';
26
27
  import { addErrorRecord } from './server/repositories/errors.js';
27
28
  import { startClipboardCapture } from './server/services/clipboard-history.js';
28
29
 
@@ -81,6 +82,7 @@ app.use('/api/file-organizer', fileOrganizerRoutes);
81
82
  app.use('/api/branch-sync', branchSyncRoutes);
82
83
  app.use('/api/updates', updateRoutes);
83
84
  app.use('/api/data-backup', dataBackupRoutes);
85
+ app.use('/api/presentations', presentationsRoutes);
84
86
 
85
87
  app.use((err, req, res, _next) => {
86
88
  addErrorRecord({