buddy-workbench 0.1.12 → 0.1.13

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.
@@ -1,13 +1,125 @@
1
1
  import { execFile } from 'node:child_process';
2
+ import { createHash, randomUUID } from 'node:crypto';
2
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';
3
4
  import { dirname, join } from 'node:path';
4
5
  import { promisify } from 'node:util';
6
+ import { fileURLToPath } from 'node:url';
7
+ import encodeMozjpeg, { init as initMozjpeg } from '../../ui/node_modules/@jsquash/jpeg/encode.js';
5
8
  import { paths } from '../config.js';
6
9
  import { readSettings } from '../repositories/settings.js';
7
10
 
8
11
  const execFileAsync = promisify(execFile);
9
12
  const previewLimit = 2000;
10
13
  let lastValue = '';
14
+ let lastImageHash = '';
15
+ let mozjpegWasmModule = null;
16
+
17
+ async function compressMozjpeg(rgbaBuf, width, height, quality = 75) {
18
+ if (!mozjpegWasmModule) {
19
+ const wasmPath = fileURLToPath(import.meta.resolve('../../ui/node_modules/@jsquash/jpeg/codec/enc/mozjpeg_enc.wasm'));
20
+ mozjpegWasmModule = await WebAssembly.compile(readFileSync(wasmPath));
21
+ await initMozjpeg(mozjpegWasmModule);
22
+ }
23
+ const data = new Uint8ClampedArray(rgbaBuf.buffer, rgbaBuf.byteOffset, rgbaBuf.byteLength);
24
+ const arrayBuf = await encodeMozjpeg({ data, width, height }, { quality });
25
+ return Buffer.from(arrayBuf);
26
+ }
27
+
28
+ async function getMacClipboardImageData() {
29
+ const jsaScript = `
30
+ ObjC.import("AppKit");
31
+ var pb = $.NSPasteboard.generalPasteboard;
32
+ var data = pb.dataForType($.NSPasteboardTypePNG);
33
+ if (data.isNil()) {
34
+ data = pb.dataForType($.NSPasteboardTypeTIFF);
35
+ }
36
+ if (!data.isNil()) {
37
+ var image = $.NSImage.alloc.initWithData(data);
38
+ if (!image.isNil()) {
39
+ var tiff = image.TIFFRepresentation;
40
+ var rep = $.NSBitmapImageRep.imageRepWithData(tiff);
41
+ var w = rep.pixelsWide;
42
+ var h = rep.pixelsHigh;
43
+ var rgbaRep = $.NSBitmapImageRep.alloc.initWithBitmapDataPlanesPixelsWidePixelsHighBitsPerSampleSamplesPerPixelHasAlphaIsPlanarColorSpaceNameBytesPerRowBitsPerPixel(
44
+ null, w, h, 8, 4, true, false, $.NSDeviceRGBColorSpace, w * 4, 32
45
+ );
46
+ $.NSGraphicsContext.saveGraphicsState;
47
+ var ctx = $.NSGraphicsContext.graphicsContextWithBitmapImageRep(rgbaRep);
48
+ $.NSGraphicsContext.currentContext = ctx;
49
+ rep.drawInRect($.NSMakeRect(0, 0, w, h));
50
+ $.NSGraphicsContext.restoreGraphicsState;
51
+ var rawBytes = rgbaRep.bitmapData;
52
+ var nsData = $.NSData.dataWithBytesLength(rawBytes, w * h * 4);
53
+ w + ":" + h + ":" + nsData.base64EncodedStringWithOptions(0).js;
54
+ } else {
55
+ "";
56
+ }
57
+ } else {
58
+ "";
59
+ }
60
+ `;
61
+ try {
62
+ const { stdout } = await execFileAsync('osascript', ['-l', 'JavaScript', '-e', jsaScript], { timeout: 3000 });
63
+ const str = stdout.trim();
64
+ if (!str) return null;
65
+ const firstColon = str.indexOf(':');
66
+ const secondColon = str.indexOf(':', firstColon + 1);
67
+ if (firstColon === -1 || secondColon === -1) return null;
68
+ const width = parseInt(str.slice(0, firstColon), 10);
69
+ const height = parseInt(str.slice(firstColon + 1, secondColon), 10);
70
+ const base64 = str.slice(secondColon + 1);
71
+ if (!width || !height || !base64) return null;
72
+ return { width, height, rgbaBuf: Buffer.from(base64, 'base64') };
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ async function getMacClipboardImageBuffer() {
79
+ const swiftCode = `
80
+ import Cocoa
81
+ let pb = NSPasteboard.general
82
+ if let data = pb.data(forType: .png) {
83
+ FileHandle.standardOutput.write(data)
84
+ } else if let data = pb.data(forType: .tiff) {
85
+ if let img = NSImage(data: data),
86
+ let tiff = img.tiffRepresentation,
87
+ let rep = NSBitmapImageRep(data: tiff),
88
+ let png = rep.representation(using: .png, properties: [:]) {
89
+ FileHandle.standardOutput.write(png)
90
+ }
91
+ }
92
+ `;
93
+ try {
94
+ const { stdout } = await execFileAsync('swift', ['-e', swiftCode], { encoding: 'buffer', timeout: 3000 });
95
+ if (stdout && stdout.length > 0) return stdout;
96
+ } catch {
97
+ try {
98
+ const jsaScript = `
99
+ ObjC.import("AppKit");
100
+ var pb = $.NSPasteboard.generalPasteboard;
101
+ var data = pb.dataForType($.NSPasteboardTypePNG);
102
+ if (data.isNil()) {
103
+ var tiffData = pb.dataForType($.NSPasteboardTypeTIFF);
104
+ if (!tiffData.isNil()) {
105
+ var img = $.NSImage.alloc.initWithData(tiffData);
106
+ var rep = $.NSBitmapImageRep.imageRepWithData(img.TIFFRepresentation);
107
+ data = rep.representationUsingTypeProperties($.NSPNGFileType, $());
108
+ }
109
+ }
110
+ if (!data.isNil()) {
111
+ data.base64EncodedStringWithOptions(0).js;
112
+ } else {
113
+ "";
114
+ }
115
+ `;
116
+ const { stdout } = await execFileAsync('osascript', ['-l', 'JavaScript', '-e', jsaScript], { timeout: 3000 });
117
+ const str = stdout.trim();
118
+ if (str) return Buffer.from(str, 'base64');
119
+ } catch {}
120
+ }
121
+ return null;
122
+ }
11
123
 
12
124
  function today() { return new Intl.DateTimeFormat('en-CA').format(new Date()); }
13
125
  function dayFile(date) { const [year, month, day] = date.split('-'); return join(paths.clipboardDir, year, month, `${day}.json`); }
@@ -105,61 +217,126 @@ export function deleteClipboardItem(date, id) {
105
217
 
106
218
  export async function captureClipboard() {
107
219
  if (process.platform !== 'darwin') return;
108
- if (readSettings().clipboardEnabled === false) return;
220
+ const settings = readSettings();
221
+ if (settings.clipboardEnabled === false) return;
222
+
223
+ // 1. Text capture
109
224
  try {
110
- const { stdout } = await execFileAsync('pbpaste'); const text = stdout.trim();
111
- if (!text || text === lastValue) return;
112
- lastValue = text;
113
-
114
- const now = Date.now();
115
- const limit = now - 60 * 60 * 1000;
116
- const date = today();
117
- const items = dayItems(date);
118
-
119
- let recentItems = [...items];
120
- const yesterdayDate = new Intl.DateTimeFormat('en-CA').format(new Date(now - 24 * 60 * 60 * 1000));
121
- if (date !== yesterdayDate) {
122
- recentItems = [...recentItems, ...dayItems(yesterdayDate)];
123
- }
124
-
125
- const oneHourItems = recentItems.filter(item => {
126
- const itemTime = new Date(item.createdAt).getTime();
127
- return itemTime >= limit;
128
- });
129
-
130
- const alreadyExists = oneHourItems.some(item => {
131
- if (item.text !== undefined) {
132
- return item.text === text;
133
- }
134
- if (item.contentFile) {
135
- const prefix = item.preview.slice(0, -1);
136
- if (text.startsWith(prefix)) {
137
- try {
138
- const fullText = readFileSync(join(paths.clipboardDir, 'content', item.contentFile), 'utf8');
139
- return fullText === text;
140
- } catch {
141
- return false;
225
+ const { stdout } = await execFileAsync('pbpaste');
226
+ const text = stdout.trim();
227
+ if (text && text !== lastValue) {
228
+ lastValue = text;
229
+ const deduplicateMinutes = typeof settings.clipboardDeduplicateMinutes === 'number' && !isNaN(settings.clipboardDeduplicateMinutes) ? settings.clipboardDeduplicateMinutes : 60;
230
+ const date = today();
231
+ const items = dayItems(date);
232
+
233
+ let alreadyExists = false;
234
+ if (deduplicateMinutes > 0) {
235
+ const now = Date.now();
236
+ const limit = now - deduplicateMinutes * 60 * 1000;
237
+ const minDate = new Intl.DateTimeFormat('en-CA').format(new Date(limit));
238
+
239
+ let recentItems = [];
240
+ const dates = clipboardDates();
241
+ for (const d of dates) {
242
+ if (d >= minDate) {
243
+ recentItems.push(...dayItems(d));
244
+ } else {
245
+ break;
142
246
  }
143
247
  }
248
+
249
+ const dedupeWindowItems = recentItems.filter(item => new Date(item.createdAt).getTime() >= limit);
250
+
251
+ alreadyExists = dedupeWindowItems.some(item => {
252
+ if (item.text !== undefined) {
253
+ return item.text === text;
254
+ }
255
+ if (item.contentFile) {
256
+ const prefix = item.preview.slice(0, -1);
257
+ if (text.startsWith(prefix)) {
258
+ try {
259
+ const fullText = readFileSync(join(paths.clipboardDir, 'content', item.contentFile), 'utf8');
260
+ return fullText === text;
261
+ } catch {
262
+ return false;
263
+ }
264
+ }
265
+ }
266
+ return false;
267
+ });
268
+ }
269
+
270
+ if (!alreadyExists) {
271
+ const id = randomUUID(); const isLong = text.length > previewLimit;
272
+ if (isLong) { mkdirSync(join(paths.clipboardDir, 'content'), { recursive: true }); writeFileSync(join(paths.clipboardDir, 'content', id), text, 'utf8'); }
273
+ const item = isLong ? { id, preview: `${text.slice(0, previewLimit)}…`, contentFile: id, createdAt: new Date().toISOString() } : { id, text, createdAt: new Date().toISOString() };
274
+ writeJson(dayFile(date), [item, ...items].slice(0, 200));
144
275
  }
145
- return false;
146
- });
147
-
148
- if (alreadyExists) return;
149
-
150
- const id = crypto.randomUUID(); const isLong = text.length > previewLimit;
151
- if (isLong) { mkdirSync(join(paths.clipboardDir, 'content'), { recursive: true }); writeFileSync(join(paths.clipboardDir, 'content', id), text, 'utf8'); }
152
- const item = isLong ? { id, preview: `${text.slice(0, previewLimit)}…`, contentFile: id, createdAt: new Date().toISOString() } : { id, text, createdAt: new Date().toISOString() };
153
- writeJson(dayFile(date), [item, ...items].slice(0, 200));
276
+ }
154
277
  } catch {}
278
+
279
+ // 2. Image capture (Mac only & clipboardImageEnabled !== false)
280
+ if (settings.clipboardImageEnabled !== false) {
281
+ try {
282
+ const imageData = await getMacClipboardImageData();
283
+ if (imageData && imageData.rgbaBuf.length > 0) {
284
+ const hash = createHash('md5').update(imageData.rgbaBuf).digest('hex');
285
+ if (hash !== lastImageHash) {
286
+ let isDuplicate = false;
287
+ const dates = clipboardDates();
288
+ for (const d of dates) {
289
+ const dayList = dayItems(d);
290
+ if (dayList.some(item => item.imageHash === hash)) {
291
+ isDuplicate = true;
292
+ break;
293
+ }
294
+ }
295
+
296
+ lastImageHash = hash;
297
+ if (!isDuplicate) {
298
+ try {
299
+ const mozjpegBuf = await compressMozjpeg(imageData.rgbaBuf, imageData.width, imageData.height, 75);
300
+ saveClipboardImage(mozjpegBuf, 'jpg', hash);
301
+ } catch {
302
+ const rawBuf = await getMacClipboardImageBuffer();
303
+ if (rawBuf) saveClipboardImage(rawBuf, 'png', hash);
304
+ }
305
+ }
306
+ }
307
+ } else {
308
+ const imageBuffer = await getMacClipboardImageBuffer();
309
+ if (imageBuffer && imageBuffer.length > 0) {
310
+ const hash = createHash('md5').update(imageBuffer).digest('hex');
311
+ if (hash !== lastImageHash) {
312
+ let isDuplicate = false;
313
+ const dates = clipboardDates();
314
+ for (const d of dates) {
315
+ const dayList = dayItems(d);
316
+ if (dayList.some(item => item.imageHash === hash)) {
317
+ isDuplicate = true;
318
+ break;
319
+ }
320
+ }
321
+
322
+ lastImageHash = hash;
323
+ if (!isDuplicate) {
324
+ saveClipboardImage(imageBuffer, 'png', hash);
325
+ }
326
+ }
327
+ }
328
+ }
329
+ } catch {}
330
+ }
155
331
  }
156
332
 
157
333
  export function startClipboardCapture() { captureClipboard(); const timer = setInterval(captureClipboard, 2000); timer.unref(); }
158
334
 
159
- export function saveClipboardImage(buffer, ext = 'png') {
335
+ export function saveClipboardImage(buffer, ext = 'png', imageHash = '') {
160
336
  const date = today();
161
- const id = crypto.randomUUID();
337
+ const id = randomUUID();
162
338
  const filename = `${id}.${ext}`;
339
+ const hash = imageHash || createHash('md5').update(buffer).digest('hex');
163
340
 
164
341
  mkdirSync(join(paths.clipboardDir, 'content'), { recursive: true });
165
342
  writeFileSync(join(paths.clipboardDir, 'content', filename), buffer);
@@ -167,6 +344,7 @@ export function saveClipboardImage(buffer, ext = 'png') {
167
344
  const item = {
168
345
  id,
169
346
  imageFile: filename,
347
+ imageHash: hash,
170
348
  createdAt: new Date().toISOString()
171
349
  };
172
350
 
@@ -0,0 +1,43 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+
4
+ const execFileAsync = promisify(execFile);
5
+
6
+ export async function selectDirectory() {
7
+ if (process.platform === 'darwin') {
8
+ try {
9
+ const { stdout } = await execFileAsync('osascript', [
10
+ '-e',
11
+ 'POSIX path of (choose folder with prompt "Select Project Folder")'
12
+ ]);
13
+ const dirPath = stdout.trim();
14
+ return dirPath ? { path: dirPath, canceled: false } : { path: null, canceled: true };
15
+ } catch {
16
+ return { path: null, canceled: true };
17
+ }
18
+ } else if (process.platform === 'win32') {
19
+ try {
20
+ const psScript = `
21
+ Add-Type -AssemblyName System.Windows.Forms
22
+ $dialog = New-Object System.Windows.Forms.FolderBrowserDialog
23
+ $dialog.Description = "Select Project Folder"
24
+ if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
25
+ Write-Output $dialog.SelectedPath
26
+ }
27
+ `;
28
+ const { stdout } = await execFileAsync('powershell', ['-NoProfile', '-Command', psScript]);
29
+ const dirPath = stdout.trim();
30
+ return dirPath ? { path: dirPath, canceled: false } : { path: null, canceled: true };
31
+ } catch {
32
+ return { path: null, canceled: true };
33
+ }
34
+ } else {
35
+ try {
36
+ const { stdout } = await execFileAsync('zenity', ['--file-selection', '--directory', '--title=Select Project Folder']);
37
+ const dirPath = stdout.trim();
38
+ return dirPath ? { path: dirPath, canceled: false } : { path: null, canceled: true };
39
+ } catch {
40
+ return { path: null, canceled: true };
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,300 @@
1
+ import { randomUUID } from 'node:crypto';
2
+
3
+ function generateId() {
4
+ return typeof randomUUID === 'function'
5
+ ? randomUUID()
6
+ : `pm_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
7
+ }
8
+
9
+ function parseUrl(urlObj) {
10
+ if (!urlObj) return '';
11
+ if (typeof urlObj === 'string') return urlObj;
12
+ if (urlObj.raw) return urlObj.raw;
13
+
14
+ const protocol = urlObj.protocol ? `${urlObj.protocol}://` : '';
15
+ const host = Array.isArray(urlObj.host) ? urlObj.host.join('.') : (urlObj.host || '');
16
+ const path = Array.isArray(urlObj.path) ? `/${urlObj.path.join('/')}` : (urlObj.path || '');
17
+ let query = '';
18
+ if (Array.isArray(urlObj.query) && urlObj.query.length > 0) {
19
+ const activeParams = urlObj.query
20
+ .filter((q) => !q.disabled && q.key)
21
+ .map((q) => `${encodeURIComponent(q.key)}=${encodeURIComponent(q.value || '')}`);
22
+ if (activeParams.length > 0) {
23
+ query = `?${activeParams.join('&')}`;
24
+ }
25
+ }
26
+ return `${protocol}${host}${path}${query}`;
27
+ }
28
+
29
+ function parseHeaders(headers) {
30
+ if (!Array.isArray(headers)) return [];
31
+ return headers.map((h) => ({
32
+ key: h.key || '',
33
+ value: h.value || '',
34
+ enabled: !h.disabled,
35
+ description: h.description || ''
36
+ }));
37
+ }
38
+
39
+ function parseParamsFromUrl(urlStr) {
40
+ if (!urlStr || typeof urlStr !== 'string') return [];
41
+ const queryIndex = urlStr.indexOf('?');
42
+ if (queryIndex === -1) return [];
43
+ const queryString = urlStr.slice(queryIndex + 1);
44
+ const searchParams = new URLSearchParams(queryString);
45
+ const params = [];
46
+ searchParams.forEach((value, key) => {
47
+ params.push({ key, value, enabled: true });
48
+ });
49
+ return params;
50
+ }
51
+
52
+ function parseBody(bodyObj) {
53
+ if (!bodyObj) return { mode: 'none', raw: '', json: '', formdata: [], urlencoded: [] };
54
+ const mode = bodyObj.mode || 'none';
55
+ let raw = bodyObj.raw || '';
56
+ let json = '';
57
+
58
+ if (mode === 'raw') {
59
+ if (bodyObj.options?.raw?.language === 'json') {
60
+ json = raw;
61
+ } else {
62
+ try {
63
+ JSON.parse(raw);
64
+ json = raw;
65
+ } catch {
66
+ json = '';
67
+ }
68
+ }
69
+ }
70
+
71
+ const formdata = Array.isArray(bodyObj.formdata)
72
+ ? bodyObj.formdata.map((item) => ({
73
+ key: item.key || '',
74
+ value: item.value || '',
75
+ type: item.type || 'text',
76
+ enabled: !item.disabled
77
+ }))
78
+ : [];
79
+
80
+ const urlencoded = Array.isArray(bodyObj.urlencoded)
81
+ ? bodyObj.urlencoded.map((item) => ({
82
+ key: item.key || '',
83
+ value: item.value || '',
84
+ enabled: !item.disabled
85
+ }))
86
+ : [];
87
+
88
+ return { mode, raw, json, formdata, urlencoded };
89
+ }
90
+
91
+ function parseAuth(authObj) {
92
+ if (!authObj || !authObj.type) return { type: 'none', token: '', username: '', password: '' };
93
+ const type = authObj.type;
94
+ if (type === 'bearer') {
95
+ const tokenObj = Array.isArray(authObj.bearer) ? authObj.bearer.find((b) => b.key === 'token') : null;
96
+ return { type: 'bearer', token: tokenObj?.value || '', username: '', password: '' };
97
+ }
98
+ if (type === 'basic') {
99
+ const userObj = Array.isArray(authObj.basic) ? authObj.basic.find((b) => b.key === 'username') : null;
100
+ const passObj = Array.isArray(authObj.basic) ? authObj.basic.find((b) => b.key === 'password') : null;
101
+ return { type: 'basic', token: '', username: userObj?.value || '', password: passObj?.value || '' };
102
+ }
103
+ return { type: 'none', token: '', username: '', password: '' };
104
+ }
105
+
106
+ function parseItem(item, folderId = null) {
107
+ const isFolder = Array.isArray(item.item);
108
+ if (isFolder) {
109
+ const currentFolderId = generateId();
110
+ const folderNode = {
111
+ id: currentFolderId,
112
+ name: item.name || 'Folder',
113
+ description: item.description || '',
114
+ parentId: folderId,
115
+ isFolder: true
116
+ };
117
+
118
+ let children = [];
119
+ item.item.forEach((subItem) => {
120
+ const res = parseItem(subItem, currentFolderId);
121
+ if (Array.isArray(res)) {
122
+ children.push(...res);
123
+ } else {
124
+ children.push(res);
125
+ }
126
+ });
127
+
128
+ return [folderNode, ...children];
129
+ }
130
+
131
+ // Request item
132
+ const reqObj = item.request || {};
133
+ const rawUrl = parseUrl(reqObj.url);
134
+ const parsedParams = parseParamsFromUrl(rawUrl);
135
+
136
+ const requestNode = {
137
+ id: generateId(),
138
+ name: item.name || 'Request',
139
+ description: typeof reqObj.description === 'string' ? reqObj.description : (item.description || ''),
140
+ parentId: folderId,
141
+ isFolder: false,
142
+ method: (reqObj.method || 'GET').toUpperCase(),
143
+ url: rawUrl,
144
+ params: parsedParams,
145
+ headers: parseHeaders(reqObj.header),
146
+ body: parseBody(reqObj.body),
147
+ auth: parseAuth(reqObj.auth)
148
+ };
149
+
150
+ return requestNode;
151
+ }
152
+
153
+ export function parseCurlCommand(curlStr) {
154
+ if (!curlStr || typeof curlStr !== 'string') throw new Error('Empty cURL command');
155
+
156
+ const cleaned = curlStr.replace(/\\\r?\n/g, ' ').trim();
157
+ const args = [];
158
+ const regex = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|(\S+)/g;
159
+ let match;
160
+ while ((match = regex.exec(cleaned)) !== null) {
161
+ if (match[1] !== undefined) {
162
+ args.push(match[1].replace(/\\"/g, '"'));
163
+ } else if (match[2] !== undefined) {
164
+ args.push(match[2].replace(/\\'/g, "'"));
165
+ } else {
166
+ args.push(match[3]);
167
+ }
168
+ }
169
+
170
+ if (args.length === 0) throw new Error('Invalid cURL command');
171
+
172
+ let method = null;
173
+ let url = '';
174
+ const headers = [];
175
+ let bodyRaw = '';
176
+ let authInfo = { type: 'none' };
177
+
178
+ for (let i = 0; i < args.length; i++) {
179
+ const arg = args[i];
180
+
181
+ if (arg === '-X' || arg === '--request') {
182
+ if (args[i + 1]) {
183
+ method = args[i + 1].toUpperCase();
184
+ i++;
185
+ }
186
+ } else if (arg === '-H' || arg === '--header') {
187
+ if (args[i + 1]) {
188
+ const headerStr = args[i + 1];
189
+ const colonIdx = headerStr.indexOf(':');
190
+ if (colonIdx > 0) {
191
+ const key = headerStr.slice(0, colonIdx).trim();
192
+ const value = headerStr.slice(colonIdx + 1).trim();
193
+ headers.push({ key, value, disabled: false });
194
+ }
195
+ i++;
196
+ }
197
+ } else if (['-d', '--data', '--data-raw', '--data-binary'].includes(arg)) {
198
+ if (args[i + 1]) {
199
+ bodyRaw = args[i + 1];
200
+ i++;
201
+ }
202
+ } else if (arg === '-u' || arg === '--user') {
203
+ if (args[i + 1]) {
204
+ const userPass = args[i + 1];
205
+ const [u, p] = userPass.split(':');
206
+ authInfo = { type: 'basic', username: u || '', password: p || '' };
207
+ i++;
208
+ }
209
+ } else if (arg === '--url') {
210
+ if (args[i + 1]) {
211
+ url = args[i + 1];
212
+ i++;
213
+ }
214
+ } else if (!arg.startsWith('-') && arg.toLowerCase() !== 'curl' && !url) {
215
+ url = arg;
216
+ }
217
+ }
218
+
219
+ if (!url) {
220
+ throw new Error('Could not find target URL in cURL command');
221
+ }
222
+
223
+ if (!method) {
224
+ method = bodyRaw ? 'POST' : 'GET';
225
+ }
226
+
227
+ let reqName = `${method} Request`;
228
+ try {
229
+ const parsedUrl = new URL(url.startsWith('http') ? url : `http://${url}`);
230
+ const pathname = parsedUrl.pathname;
231
+ if (pathname && pathname !== '/') {
232
+ const parts = pathname.split('/').filter(Boolean);
233
+ if (parts.length > 0) reqName = `${method} /${parts[parts.length - 1]}`;
234
+ } else {
235
+ reqName = `${method} ${parsedUrl.hostname}`;
236
+ }
237
+ } catch {}
238
+
239
+ return {
240
+ info: { name: `cURL: ${reqName}` },
241
+ item: [{
242
+ name: reqName,
243
+ request: {
244
+ method,
245
+ url,
246
+ header: headers,
247
+ body: bodyRaw ? { mode: 'raw', raw: bodyRaw } : undefined,
248
+ auth: authInfo.type !== 'none' ? authInfo : undefined
249
+ }
250
+ }]
251
+ };
252
+ }
253
+
254
+ export function parsePostmanCollection(jsonInput) {
255
+ let collectionData;
256
+ if (typeof jsonInput === 'string') {
257
+ const trimmed = jsonInput.trim();
258
+ if (trimmed.toLowerCase().startsWith('curl') || (trimmed.includes('-H') && trimmed.includes('http'))) {
259
+ collectionData = parseCurlCommand(trimmed);
260
+ } else {
261
+ collectionData = JSON.parse(trimmed);
262
+ }
263
+ } else {
264
+ collectionData = jsonInput;
265
+ }
266
+
267
+ const info = collectionData.info || {};
268
+ const items = Array.isArray(collectionData.item) ? collectionData.item : [];
269
+
270
+ const collectionId = generateId();
271
+ const collectionName = info.name || 'Imported Collection';
272
+ const description = info.description || '';
273
+
274
+ const variables = Array.isArray(collectionData.variable)
275
+ ? collectionData.variable.map((v) => ({
276
+ key: v.key || '',
277
+ value: v.value || '',
278
+ enabled: !v.disabled
279
+ }))
280
+ : [];
281
+
282
+ const parsedItems = [];
283
+ items.forEach((it) => {
284
+ const res = parseItem(it, null);
285
+ if (Array.isArray(res)) {
286
+ parsedItems.push(...res);
287
+ } else {
288
+ parsedItems.push(res);
289
+ }
290
+ });
291
+
292
+ return {
293
+ id: collectionId,
294
+ name: collectionName,
295
+ description: typeof description === 'string' ? description : '',
296
+ variables,
297
+ items: parsedItems,
298
+ createdAt: new Date().toISOString()
299
+ };
300
+ }
package/server.js CHANGED
@@ -17,6 +17,7 @@ import jiraFiltersRoutes from './server/routes/jira-filters.js';
17
17
  import todoRoutes from './server/routes/todos.js';
18
18
  import staticPagesRoutes from './server/routes/static-pages.js';
19
19
  import errorRoutes from './server/routes/errors.js';
20
+ import postmanRoutes from './server/routes/postman.js';
20
21
  import { addErrorRecord } from './server/repositories/errors.js';
21
22
  import { startClipboardCapture } from './server/services/clipboard-history.js';
22
23
 
@@ -69,6 +70,7 @@ app.use('/api/jira-filters', jiraFiltersRoutes);
69
70
  app.use('/api/todos', todoRoutes);
70
71
  app.use('/api/static-pages', staticPagesRoutes);
71
72
  app.use('/api/errors', errorRoutes);
73
+ app.use('/api/postman', postmanRoutes);
72
74
 
73
75
  app.use((err, req, res, _next) => {
74
76
  addErrorRecord({