buddy-workbench 0.1.64 → 0.1.66
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 +1 -1
- package/server/config.js +1 -0
- package/server/repositories/memos.js +83 -0
- package/server/routes/memos.js +55 -0
- package/server/routes/settings.js +4 -1
- package/server/services/dev-configurations.js +8 -1
- package/server.js +2 -0
- package/ui/dist/assets/index-Cj8iuRTr.js +569 -0
- package/ui/dist/assets/index-PD86RxyZ.css +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/sw.js +2 -2
- package/ui/dist/assets/index-BJPNYKME.js +0 -548
- package/ui/dist/assets/index-BLZTK59v.css +0 -1
package/package.json
CHANGED
package/server/config.js
CHANGED
|
@@ -29,6 +29,7 @@ export const paths = {
|
|
|
29
29
|
jiraTemplates: join(userDataDir, 'jira-templates.json'),
|
|
30
30
|
jiraRecentlyCreated: join(userDataDir, 'jira-recently-created.json'),
|
|
31
31
|
todos: join(userDataDir, 'todos.json'),
|
|
32
|
+
memos: join(userDataDir, 'memos.json'),
|
|
32
33
|
staticPages: join(userDataDir, 'static-pages.json'),
|
|
33
34
|
errors: join(userDataDir, 'errors.json'),
|
|
34
35
|
postman: join(userDataDir, 'postman.json'),
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { paths } from '../config.js';
|
|
4
|
+
|
|
5
|
+
export const starterMemos = [
|
|
6
|
+
{
|
|
7
|
+
id: 'starter-weekly-reset',
|
|
8
|
+
title: 'Weekly reset',
|
|
9
|
+
content: '# Weekly reset\n\nA small review to start the week with less noise.\n\n- Pick one meaningful outcome\n- Clear the two oldest loose ends\n- Leave room for unexpected work\n\n> Progress feels better when the next step is obvious.',
|
|
10
|
+
tags: ['planning', 'weekly'],
|
|
11
|
+
createdAt: '2026-08-10T08:00:00.000Z',
|
|
12
|
+
updatedAt: '2026-08-10T08:00:00.000Z'
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'starter-product-ideas',
|
|
16
|
+
title: 'Product ideas',
|
|
17
|
+
content: '## Product ideas\n\nA lightweight inbox for small observations could become a useful source of product direction.\n\nThe important part is making capture effortless and review intentional.',
|
|
18
|
+
tags: ['ideas', 'product'],
|
|
19
|
+
createdAt: '2026-08-09T10:30:00.000Z',
|
|
20
|
+
updatedAt: '2026-08-09T10:30:00.000Z'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'starter-reading-notes',
|
|
24
|
+
title: 'Reading notes: The Creative Act',
|
|
25
|
+
content: '## The Creative Act\n\nThe best work often begins with attention rather than ambition.\n\nA useful prompt:\n\n> What is asking to be noticed here?',
|
|
26
|
+
tags: ['reading', 'reflection'],
|
|
27
|
+
createdAt: '2026-08-08T15:15:00.000Z',
|
|
28
|
+
updatedAt: '2026-08-08T15:15:00.000Z'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'starter-code-pattern',
|
|
32
|
+
title: 'Small code pattern',
|
|
33
|
+
content: '## Keep async flows boring\n\nPrefer a short, visible sequence over clever abstractions when a request has only a few steps.\n\n```js\nconst data = await load();\nconst result = transform(data);\nawait save(result);\n```',
|
|
34
|
+
tags: ['code', 'notes'],
|
|
35
|
+
createdAt: '2026-08-07T09:45:00.000Z',
|
|
36
|
+
updatedAt: '2026-08-07T09:45:00.000Z'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'starter-design-checklist',
|
|
40
|
+
title: 'Design checklist',
|
|
41
|
+
content: '## Before shipping a screen\n\n- Is the primary action obvious?\n- Does the empty state help someone begin?\n- What happens on a narrow window?\n- Can the interface explain itself without a tour?',
|
|
42
|
+
tags: ['design', 'checklist'],
|
|
43
|
+
createdAt: '2026-08-06T13:20:00.000Z',
|
|
44
|
+
updatedAt: '2026-08-06T13:20:00.000Z'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'starter-quick-capture',
|
|
48
|
+
title: 'Quick capture',
|
|
49
|
+
content: 'A good note does not need to be complete. Leave enough context for your future self to pick it up again.',
|
|
50
|
+
tags: ['inbox'],
|
|
51
|
+
createdAt: '2026-08-05T17:10:00.000Z',
|
|
52
|
+
updatedAt: '2026-08-05T17:10:00.000Z'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'starter-weekend-list',
|
|
56
|
+
title: 'Weekend list',
|
|
57
|
+
content: '- Walk somewhere unfamiliar\n- Cook one new dish\n- Read without taking notes\n- Leave Sunday evening unplanned',
|
|
58
|
+
tags: ['personal', 'weekend'],
|
|
59
|
+
createdAt: '2026-08-04T11:00:00.000Z',
|
|
60
|
+
updatedAt: '2026-08-04T11:00:00.000Z'
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'starter-rich-markdown',
|
|
64
|
+
title: 'Markdown field guide',
|
|
65
|
+
content: '## Useful Markdown\n\nLinks, images, and tables make notes easier to revisit.\n\n| Syntax | Use case |\n| --- | --- |\n| `[label](url)` | Link to a reference |\n| `` | Add an image |\n| `| cell | cell |` | Compare details |\n\nRead the [Markdown Guide](https://www.markdownguide.org/basic-syntax/) for more patterns.\n\n',
|
|
66
|
+
tags: ['markdown', 'reference'],
|
|
67
|
+
createdAt: '2026-08-03T09:00:00.000Z',
|
|
68
|
+
updatedAt: '2026-08-03T09:00:00.000Z'
|
|
69
|
+
}
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
export function listMemos() {
|
|
73
|
+
try {
|
|
74
|
+
return existsSync(paths.memos) ? JSON.parse(readFileSync(paths.memos, 'utf8')) : [];
|
|
75
|
+
} catch {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function saveMemos(memos) {
|
|
81
|
+
mkdirSync(dirname(paths.memos), { recursive: true });
|
|
82
|
+
writeFileSync(paths.memos, JSON.stringify(memos, null, 2));
|
|
83
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { listMemos, saveMemos, starterMemos } from '../repositories/memos.js';
|
|
3
|
+
|
|
4
|
+
const router = Router();
|
|
5
|
+
|
|
6
|
+
function validMemo(body) {
|
|
7
|
+
const title = String(body?.title || '').trim();
|
|
8
|
+
const content = String(body?.content || '');
|
|
9
|
+
const tags = Array.isArray(body?.tags)
|
|
10
|
+
? [...new Set(body.tags.map((tag) => String(tag).trim().toLowerCase()).filter(Boolean))].slice(0, 12)
|
|
11
|
+
: String(body?.tags || '').split(',').map((tag) => tag.trim().toLowerCase()).filter(Boolean).slice(0, 12);
|
|
12
|
+
if (!title && !content.trim()) return null;
|
|
13
|
+
return { title: title || 'Untitled memo', content, tags };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
router.get('/', (_req, res) => {
|
|
17
|
+
const starterIds = new Set(starterMemos.map((memo) => memo.id));
|
|
18
|
+
const memos = listMemos();
|
|
19
|
+
const cleanedMemos = memos.filter((memo) => !starterIds.has(memo.id));
|
|
20
|
+
if (cleanedMemos.length !== memos.length) saveMemos(cleanedMemos);
|
|
21
|
+
res.json(cleanedMemos);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
router.post('/', (req, res) => {
|
|
25
|
+
const data = validMemo(req.body);
|
|
26
|
+
if (!data) return res.status(400).json({ error: 'Memo content is required.' });
|
|
27
|
+
const now = new Date().toISOString();
|
|
28
|
+
const memo = { id: crypto.randomUUID(), ...data, createdAt: now, updatedAt: now };
|
|
29
|
+
const memos = listMemos();
|
|
30
|
+
memos.unshift(memo);
|
|
31
|
+
saveMemos(memos);
|
|
32
|
+
res.status(201).json(memo);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
router.put('/:id', (req, res) => {
|
|
36
|
+
const memos = listMemos();
|
|
37
|
+
const index = memos.findIndex((memo) => memo.id === req.params.id);
|
|
38
|
+
if (index < 0) return res.status(404).json({ error: 'Memo not found.' });
|
|
39
|
+
const data = validMemo(req.body);
|
|
40
|
+
if (!data) return res.status(400).json({ error: 'Memo content is required.' });
|
|
41
|
+
const updated = { ...memos[index], ...data, updatedAt: new Date().toISOString() };
|
|
42
|
+
memos[index] = updated;
|
|
43
|
+
saveMemos(memos);
|
|
44
|
+
res.json(updated);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
router.delete('/:id', (req, res) => {
|
|
48
|
+
const memos = listMemos();
|
|
49
|
+
const next = memos.filter((memo) => memo.id !== req.params.id);
|
|
50
|
+
if (next.length === memos.length) return res.status(404).json({ error: 'Memo not found.' });
|
|
51
|
+
saveMemos(next);
|
|
52
|
+
res.status(204).end();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export default router;
|
|
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync } from 'node:fs';
|
|
|
2
2
|
import { Router } from 'express';
|
|
3
3
|
import { paths } from '../config.js';
|
|
4
4
|
import { saveAccessToken, saveClipboardDeduplicateMinutes, saveClipboardEnabled, saveClipboardImageEnabled, saveDefaultBrowser, saveDefaultEditor, saveDomain, saveJiraIssuePrefix, saveTheme, settingsStatus } from '../repositories/settings.js';
|
|
5
|
-
import { getNpmPackageVersions, installNvmVersion, manageGlobalNpmPackage, readDevConfigurations, readNvmConfiguration, saveDevConfigurations, searchNpmPackages } from '../services/dev-configurations.js';
|
|
5
|
+
import { getNpmPackageVersions, installNvmVersion, manageGlobalNpmPackage, readDevConfigurations, readNvmConfiguration, saveDevConfigurations, searchNpmPackages, useSuggestedNpmPrefix } from '../services/dev-configurations.js';
|
|
6
6
|
import { openInSystemBrowser, openInSystemEditor, openInSystemFolder } from '../services/browser.js';
|
|
7
7
|
import { selectDirectory, selectFile } from '../services/dialog.js';
|
|
8
8
|
|
|
@@ -25,6 +25,9 @@ router.put('/dev-configurations', async (req, res) => {
|
|
|
25
25
|
router.post('/dev-configurations/nvm/install', async (req, res) => {
|
|
26
26
|
try { res.json(await installNvmVersion(req.body?.version)); } catch (error) { res.status(400).json({ error: error.message }); }
|
|
27
27
|
});
|
|
28
|
+
router.post('/dev-configurations/npm/use-prefix-suggestion', async (_req, res) => {
|
|
29
|
+
try { res.json(await useSuggestedNpmPrefix()); } catch (error) { res.status(400).json({ error: error.message }); }
|
|
30
|
+
});
|
|
28
31
|
router.post('/dev-configurations/npm/search', async (req, res) => {
|
|
29
32
|
try { res.json(await searchNpmPackages(req.body?.query)); } catch (error) { res.status(400).json({ error: error.message }); }
|
|
30
33
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
-
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { basename, dirname, join } from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
@@ -224,6 +224,13 @@ async function setNpmConfig(key, value) {
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
export async function useSuggestedNpmPrefix() {
|
|
228
|
+
const prefix = join(homedir(), '.npm-global');
|
|
229
|
+
mkdirSync(prefix, { recursive: true });
|
|
230
|
+
await setNpmConfig('prefix', prefix);
|
|
231
|
+
return { prefix };
|
|
232
|
+
}
|
|
233
|
+
|
|
227
234
|
export async function saveDevConfigurations(configurations = {}, options = {}) {
|
|
228
235
|
const section = options.section || '';
|
|
229
236
|
const npm = configurations.npm || {};
|
package/server.js
CHANGED
|
@@ -16,6 +16,7 @@ import prReviewRoutes from './server/routes/pr-review.js';
|
|
|
16
16
|
import prConflictRoutes from './server/routes/pr-conflicts.js';
|
|
17
17
|
import jiraFiltersRoutes from './server/routes/jira-filters.js';
|
|
18
18
|
import todoRoutes from './server/routes/todos.js';
|
|
19
|
+
import memoRoutes from './server/routes/memos.js';
|
|
19
20
|
import staticPagesRoutes from './server/routes/static-pages.js';
|
|
20
21
|
import errorRoutes from './server/routes/errors.js';
|
|
21
22
|
import postmanRoutes from './server/routes/postman.js';
|
|
@@ -92,6 +93,7 @@ app.use('/api/pr-review', prReviewRoutes);
|
|
|
92
93
|
app.use('/api/pr-conflicts', prConflictRoutes);
|
|
93
94
|
app.use('/api/jira-filters', jiraFiltersRoutes);
|
|
94
95
|
app.use('/api/todos', todoRoutes);
|
|
96
|
+
app.use('/api/memos', memoRoutes);
|
|
95
97
|
app.use('/api/static-pages', staticPagesRoutes);
|
|
96
98
|
app.use('/api/errors', errorRoutes);
|
|
97
99
|
app.use('/api/postman', postmanRoutes);
|