buddy-workbench 0.1.0 → 0.1.2
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 +32 -0
- package/package.json +1 -1
- package/server/routes/clipboard.js +25 -2
- package/server/services/clipboard-history.js +28 -1
- package/server.js +1 -0
- package/ui/dist/assets/{index-CCCOP2nr.js → index-DZw6LzmX.js} +101 -101
- package/ui/dist/assets/index-d11DtBlK.css +1 -0
- package/ui/dist/devbuddy-180.png +0 -0
- package/ui/dist/devbuddy-192.png +0 -0
- package/ui/dist/devbuddy-512.png +0 -0
- package/ui/dist/devbuddy.icns +0 -0
- package/ui/dist/devbuddy.svg +1 -1
- package/ui/dist/favicon.png +0 -0
- package/ui/dist/index.html +4 -2
- package/ui/dist/manifest.webmanifest +12 -0
- package/ui/dist/assets/index-VPzoCQox.css +0 -1
package/README.md
CHANGED
|
@@ -12,6 +12,38 @@ 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
|
+
## Running on macOS
|
|
16
|
+
|
|
17
|
+
### Double-click Launcher (Terminal)
|
|
18
|
+
A pre-configured macOS launcher `Start Buddy.command` is available in the root directory.
|
|
19
|
+
1. Simply double-click `Start Buddy.command` in Finder to start the workbench.
|
|
20
|
+
2. A terminal window will open showing the server logs.
|
|
21
|
+
3. Close the terminal window to stop the workbench and all of its running service processes.
|
|
22
|
+
|
|
23
|
+
### Background App (Automator)
|
|
24
|
+
To run the workbench in the background without leaving a terminal window open:
|
|
25
|
+
1. Open the **Automator** app on your Mac.
|
|
26
|
+
2. Create a new document and choose **Application**.
|
|
27
|
+
3. Search for the **Run Shell Script** action and drag it into the workflow.
|
|
28
|
+
4. Set the **Shell** option to `/bin/bash` (or `/bin/zsh`).
|
|
29
|
+
5. Paste the following script:
|
|
30
|
+
```bash
|
|
31
|
+
# Load user shell profile to inherit full terminal PATH (like nvm, fnm, npm-global, etc.)
|
|
32
|
+
if [ -f "$HOME/.zprofile" ]; then source "$HOME/.zprofile"; fi
|
|
33
|
+
if [ -f "$HOME/.zshrc" ]; then source "$HOME/.zshrc"; fi
|
|
34
|
+
|
|
35
|
+
# Fallbacks for standard paths and npm global binaries
|
|
36
|
+
export PATH="$HOME/.npm-global/bin:/usr/local/bin:/opt/homebrew/bin:$PATH"
|
|
37
|
+
|
|
38
|
+
cd "/Users/lynan/Documents/GitHub/buddy"
|
|
39
|
+
npm start
|
|
40
|
+
```
|
|
41
|
+
*(Note: Adjust the path if the directory moves.)*
|
|
42
|
+
6. Save (`Cmd + S`) the document as `Buddy.app` in your Applications folder or Desktop.
|
|
43
|
+
7. **Custom Icon**:
|
|
44
|
+
- Copy the icon file `ui/public/devbuddy.icns` (`Cmd + C`).
|
|
45
|
+
- Select `Buddy.app`, press `Cmd + I` (Get Info), click the small icon at the top-left of the info panel, and press `Cmd + V`.
|
|
46
|
+
|
|
15
47
|
## UI development
|
|
16
48
|
|
|
17
49
|
The UI is an independent React + Vite project in `ui/`, built with Ant Design. Run `npm run build` to build it, then run `npm start` to start Express. For UI development, use `npm run dev` in one terminal and `npm run dev:ui` in another; Vite proxies API and plugin requests to Express.
|
package/package.json
CHANGED
|
@@ -1,8 +1,31 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import { clipboardDates, clipboardItems, clipboardOriginal, deleteClipboardItem } from '../services/clipboard-history.js';
|
|
1
|
+
import express, { Router } from 'express';
|
|
2
|
+
import { clipboardDates, clipboardItems, clipboardOriginal, deleteClipboardItem, saveClipboardImage, clipboardImagePath } 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) }));
|
|
6
|
+
|
|
7
|
+
router.post('/image', express.raw({ type: 'image/*', limit: '10mb' }), (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const buffer = req.body;
|
|
10
|
+
if (!buffer || buffer.length === 0) {
|
|
11
|
+
return res.status(400).json({ error: 'Image body is empty.' });
|
|
12
|
+
}
|
|
13
|
+
const item = saveClipboardImage(buffer);
|
|
14
|
+
res.json(item);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
res.status(500).json({ error: error.message });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
router.get('/image/:filename', (req, res) => {
|
|
21
|
+
try {
|
|
22
|
+
const filePath = clipboardImagePath(req.params.filename);
|
|
23
|
+
res.sendFile(filePath);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
res.status(404).json({ error: 'Image not found.' });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
6
29
|
router.get('/:date/:id', (req, res) => { const text = clipboardOriginal(req.params.date, req.params.id); if (text === null) return res.status(404).json({ error: 'Clipboard entry not found.' }); res.type('text/plain').send(text); });
|
|
7
30
|
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(); });
|
|
8
31
|
export default router;
|
|
@@ -11,7 +11,7 @@ let lastValue = '';
|
|
|
11
11
|
function today() { return new Intl.DateTimeFormat('en-CA').format(new Date()); }
|
|
12
12
|
function dayFile(date) { const [year, month, day] = date.split('-'); return join(paths.clipboardDir, year, month, `${day}.json`); }
|
|
13
13
|
function readJson(file, fallback) { try { return existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : fallback; } catch { return fallback; } }
|
|
14
|
-
function writeJson(file, data) { mkdirSync(dirname(file), { recursive: true }); writeFileSync(file, JSON.stringify(data, null, 2)); }
|
|
14
|
+
function writeJson(file, data) { mkdirSync(dirname(file), { recursive: true }); writeFileSync(file, JSON.stringify(data, null, 2), 'utf8'); }
|
|
15
15
|
|
|
16
16
|
export function clipboardDates() {
|
|
17
17
|
if (!existsSync(paths.clipboardDir)) return [];
|
|
@@ -29,6 +29,10 @@ export function deleteClipboardItem(date, id) {
|
|
|
29
29
|
const contentPath = join(paths.clipboardDir, 'content', item.contentFile);
|
|
30
30
|
if (existsSync(contentPath)) unlinkSync(contentPath);
|
|
31
31
|
}
|
|
32
|
+
if (item.imageFile) {
|
|
33
|
+
const imagePath = join(paths.clipboardDir, 'content', item.imageFile);
|
|
34
|
+
if (existsSync(imagePath)) unlinkSync(imagePath);
|
|
35
|
+
}
|
|
32
36
|
return true;
|
|
33
37
|
}
|
|
34
38
|
|
|
@@ -83,3 +87,26 @@ export async function captureClipboard() {
|
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
export function startClipboardCapture() { captureClipboard(); const timer = setInterval(captureClipboard, 1000); timer.unref(); }
|
|
90
|
+
|
|
91
|
+
export function saveClipboardImage(buffer) {
|
|
92
|
+
const date = today();
|
|
93
|
+
const id = crypto.randomUUID();
|
|
94
|
+
const filename = `${id}.png`;
|
|
95
|
+
|
|
96
|
+
mkdirSync(join(paths.clipboardDir, 'content'), { recursive: true });
|
|
97
|
+
writeFileSync(join(paths.clipboardDir, 'content', filename), buffer);
|
|
98
|
+
|
|
99
|
+
const item = {
|
|
100
|
+
id,
|
|
101
|
+
imageFile: filename,
|
|
102
|
+
createdAt: new Date().toISOString()
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const items = dayItems(date);
|
|
106
|
+
writeJson(dayFile(date), [item, ...items].slice(0, 200));
|
|
107
|
+
return item;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function clipboardImagePath(filename) {
|
|
111
|
+
return join(paths.clipboardDir, 'content', filename);
|
|
112
|
+
}
|