buddy-workbench 0.1.15 → 0.1.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-workbench",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,7 +53,7 @@ export function saveDefaultEditor(defaultEditor) {
53
53
 
54
54
  export function saveDefaultBrowser(defaultBrowser) {
55
55
  const settings = readSettings();
56
- const valid = ['chrome', 'edge', 'safari'];
56
+ const valid = process.platform === 'win32' ? ['chrome', 'edge'] : ['chrome', 'edge', 'safari'];
57
57
  settings.defaultBrowser = valid.includes(defaultBrowser) ? defaultBrowser : 'chrome';
58
58
  mkdirSync(dirname(paths.settings), { recursive: true });
59
59
  writeFileSync(paths.settings, JSON.stringify(settings, null, 2), { mode: 0o600 });
@@ -1,5 +1,5 @@
1
1
  import express, { Router } from 'express';
2
- import { clipboardDates, clipboardItems, clipboardOriginal, deleteClipboardItem, saveClipboardImage, clipboardImagePath, allTaggedClipboardItems, updateClipboardItemTags } from '../services/clipboard-history.js';
2
+ import { clipboardDates, clipboardItems, clipboardOriginal, deleteClipboardItem, saveClipboardImage, clipboardImagePath, allTaggedClipboardItems, updateClipboardItemTags, markClipboardCopied } from '../services/clipboard-history.js';
3
3
 
4
4
  const router = Router();
5
5
  router.get('/', (req, res) => res.json({ dates: clipboardDates(), items: clipboardItems(req.query.date) }));
@@ -36,5 +36,14 @@ router.put('/:date/:id/tags', (req, res) => {
36
36
  if (!updated) return res.status(404).json({ error: 'Clipboard entry not found.' });
37
37
  res.json(updated);
38
38
  });
39
+ router.post('/:date/:id/copied', async (req, res) => {
40
+ try {
41
+ const updated = await markClipboardCopied(req.params.date, req.params.id);
42
+ res.json(updated || { ok: true });
43
+ } catch (error) {
44
+ res.status(500).json({ error: error.message });
45
+ }
46
+ });
39
47
  router.delete('/:date/:id', (req, res) => { if (!deleteClipboardItem(req.params.date, req.params.id)) return res.status(404).json({ error: 'Clipboard entry not found.' }); res.status(204).end(); });
40
48
  export default router;
49
+
@@ -158,4 +158,47 @@ router.get('/:id/issues', async (req, res) => {
158
158
  }
159
159
  });
160
160
 
161
+ // Clone an issue
162
+ router.post('/issues/clone', async (req, res) => {
163
+ const { issueKey, summary } = req.body || {};
164
+ if (!summary || typeof summary !== 'string' || !summary.trim()) {
165
+ return res.status(400).json({ error: 'Summary is required for clone.' });
166
+ }
167
+
168
+ const jiraHost = getJiraHost();
169
+ if (!jiraHost) {
170
+ return res.status(400).json({ error: 'Jira domain is not configured. Please set Domain in Settings.' });
171
+ }
172
+
173
+ const settings = readSettings();
174
+ const token = settings.jiraAccessToken;
175
+ const headers = { 'Content-Type': 'application/json' };
176
+ if (token) {
177
+ headers.Authorization = token.startsWith('Bearer ') ? token : `Bearer ${token}`;
178
+ }
179
+
180
+ try {
181
+ const projectKey = issueKey ? issueKey.split('-')[0] : '';
182
+ const url = `https://${jiraHost}/rest/api/2/issue`;
183
+ const payload = {
184
+ fields: {
185
+ summary: summary.trim(),
186
+ ...(projectKey ? { project: { key: projectKey } } : {}),
187
+ issuetype: { name: 'Task' }
188
+ }
189
+ };
190
+
191
+ const response = await httpClient.post(url, payload, { headers });
192
+ if (response.status !== 201 && response.status !== 200) {
193
+ const errMsg = response.data?.errorMessages?.[0] || response.data?.message || `Jira API returned status ${response.status}`;
194
+ return res.status(response.status).json({ error: errMsg });
195
+ }
196
+
197
+ const created = response.data || {};
198
+ res.status(201).json({ success: true, issue: { key: created.key || issueKey, id: created.id } });
199
+ } catch (error) {
200
+ res.status(500).json({ error: error.message || 'Failed to clone Jira issue.' });
201
+ }
202
+ });
203
+
161
204
  export default router;
@@ -33,10 +33,17 @@ export function getBrowserBookmarkPaths() {
33
33
  * Checks if a browser process is currently running.
34
34
  */
35
35
  export function checkBrowserRunning(browserName) {
36
+ const platform = os.platform();
36
37
  try {
37
- const processPattern = browserName === 'chrome' ? 'Google Chrome' : 'Microsoft Edge';
38
- const output = execSync(`pgrep -f "${processPattern}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
39
- return output.trim().length > 0;
38
+ if (platform === 'win32') {
39
+ const exeName = browserName === 'chrome' ? 'chrome.exe' : 'msedge.exe';
40
+ const output = execSync(`tasklist /FI "IMAGENAME eq ${exeName}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
41
+ return output.toLowerCase().includes(exeName.toLowerCase());
42
+ } else {
43
+ const processPattern = browserName === 'chrome' ? 'Google Chrome' : 'Microsoft Edge';
44
+ const output = execSync(`pgrep -f "${processPattern}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
45
+ return output.trim().length > 0;
46
+ }
40
47
  } catch {
41
48
  return false;
42
49
  }
@@ -287,7 +287,11 @@ export async function captureClipboard() {
287
287
  const dates = clipboardDates();
288
288
  for (const d of dates) {
289
289
  const dayList = dayItems(d);
290
- if (dayList.some(item => item.imageHash === hash)) {
290
+ if (dayList.some(item => {
291
+ if (item.imageHash === hash || item.fileHash === hash) return true;
292
+ if (Array.isArray(item.imageHashes) && item.imageHashes.includes(hash)) return true;
293
+ return false;
294
+ })) {
291
295
  isDuplicate = true;
292
296
  break;
293
297
  }
@@ -313,7 +317,11 @@ export async function captureClipboard() {
313
317
  const dates = clipboardDates();
314
318
  for (const d of dates) {
315
319
  const dayList = dayItems(d);
316
- if (dayList.some(item => item.imageHash === hash)) {
320
+ if (dayList.some(item => {
321
+ if (item.imageHash === hash || item.fileHash === hash) return true;
322
+ if (Array.isArray(item.imageHashes) && item.imageHashes.includes(hash)) return true;
323
+ return false;
324
+ })) {
317
325
  isDuplicate = true;
318
326
  break;
319
327
  }
@@ -332,11 +340,61 @@ export async function captureClipboard() {
332
340
 
333
341
  export function startClipboardCapture() { captureClipboard(); const timer = setInterval(captureClipboard, 2000); timer.unref(); }
334
342
 
343
+ export async function markClipboardCopied(date, id) {
344
+ let targetDate = date;
345
+ let items = dayItems(targetDate);
346
+ let item = items.find((entry) => entry.id === id);
347
+
348
+ if (!item) {
349
+ const dates = clipboardDates();
350
+ for (const d of dates) {
351
+ const dayList = dayItems(d);
352
+ const found = dayList.find((entry) => entry.id === id);
353
+ if (found) {
354
+ targetDate = d;
355
+ items = dayList;
356
+ item = found;
357
+ break;
358
+ }
359
+ }
360
+ }
361
+
362
+ if (!item) return null;
363
+
364
+ if (item.imageFile) {
365
+ try {
366
+ const imageData = await getMacClipboardImageData();
367
+ if (imageData && imageData.rgbaBuf.length > 0) {
368
+ const currentHash = createHash('md5').update(imageData.rgbaBuf).digest('hex');
369
+ lastImageHash = currentHash;
370
+
371
+ const existingHashes = Array.isArray(item.imageHashes)
372
+ ? item.imageHashes
373
+ : [item.imageHash, item.fileHash].filter(Boolean);
374
+
375
+ if (!existingHashes.includes(currentHash)) {
376
+ const updatedItem = { ...item, imageHashes: [...existingHashes, currentHash] };
377
+ const nextItems = items.map((entry) => (entry.id === id ? updatedItem : entry));
378
+ writeJson(dayFile(targetDate), nextItems);
379
+ return updatedItem;
380
+ }
381
+ }
382
+ } catch {}
383
+ } else {
384
+ const text = clipboardOriginal(targetDate, id) || item.text || item.preview || '';
385
+ if (text) {
386
+ lastValue = text.trim();
387
+ }
388
+ }
389
+ return item;
390
+ }
391
+
335
392
  export function saveClipboardImage(buffer, ext = 'png', imageHash = '') {
336
393
  const date = today();
337
394
  const id = randomUUID();
338
395
  const filename = `${id}.${ext}`;
339
- const hash = imageHash || createHash('md5').update(buffer).digest('hex');
396
+ const fileHash = createHash('md5').update(buffer).digest('hex');
397
+ const hash = imageHash || fileHash;
340
398
 
341
399
  mkdirSync(join(paths.clipboardDir, 'content'), { recursive: true });
342
400
  writeFileSync(join(paths.clipboardDir, 'content', filename), buffer);
@@ -345,6 +403,8 @@ export function saveClipboardImage(buffer, ext = 'png', imageHash = '') {
345
403
  id,
346
404
  imageFile: filename,
347
405
  imageHash: hash,
406
+ fileHash,
407
+ imageHashes: Array.from(new Set([hash, fileHash].filter(Boolean))),
348
408
  createdAt: new Date().toISOString()
349
409
  };
350
410
 
@@ -103,7 +103,23 @@ export function scriptErrorLogs(launcherId, scriptId) { return logs.get(keyFor(l
103
103
  export function clearScriptLogs(launcherId, scriptId) { logs.set(keyFor(launcherId, scriptId), { output: '', error: '', errorCount: 0 }); }
104
104
 
105
105
  async function listeningProcesses() {
106
- if (process.platform === 'win32') return [];
106
+ if (process.platform === 'win32') {
107
+ try {
108
+ const { stdout } = await execFileAsync('netstat', ['-ano', '-p', 'tcp']);
109
+ const rows = [];
110
+ const matches = stdout.matchAll(/TCP\s+(?:\[::\]|[\d.]+):(\d+)\s+.*?LISTENING\s+(\d+)/gi);
111
+ for (const m of matches) {
112
+ const port = Number(m[1]);
113
+ const pid = Number(m[2]);
114
+ if (port && pid) {
115
+ rows.push({ pid, port, command: 'node' });
116
+ }
117
+ }
118
+ return rows;
119
+ } catch {
120
+ return [];
121
+ }
122
+ }
107
123
  try {
108
124
  const { stdout } = await execFileAsync('lsof', ['-nP', '-iTCP', '-sTCP:LISTEN', '-F', 'pcn'], { maxBuffer: 1024 * 1024 });
109
125
  const rows = [];