starmux 0.12.0 → 0.13.0
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/dist/src/cli/mac-mcp-gateway.js +4 -3
- package/dist/src/cli/mac-mcp-gateway.js.map +1 -1
- package/dist/src/cli/starmux.js +1 -1
- package/dist/src/cli/starmux.js.map +1 -1
- package/dist/src/runtime-paths.js +3 -3
- package/dist/src/runtime-paths.js.map +1 -1
- package/dist/vendor/desktop-commander/LICENSE +21 -0
- package/dist/vendor/desktop-commander/dist/bootstrap.js +22 -0
- package/dist/vendor/desktop-commander/dist/command-manager.js +246 -0
- package/dist/vendor/desktop-commander/dist/config-field-definitions.js +37 -0
- package/dist/vendor/desktop-commander/dist/config-manager.js +634 -0
- package/dist/vendor/desktop-commander/dist/config-recovery.js +81 -0
- package/dist/vendor/desktop-commander/dist/config.js +19 -0
- package/dist/vendor/desktop-commander/dist/custom-stdio.js +358 -0
- package/dist/vendor/desktop-commander/dist/error-handlers.js +15 -0
- package/dist/vendor/desktop-commander/dist/handlers/edit-search-handlers.js +6 -0
- package/dist/vendor/desktop-commander/dist/handlers/filesystem-handlers.js +450 -0
- package/dist/vendor/desktop-commander/dist/handlers/history-handlers.js +67 -0
- package/dist/vendor/desktop-commander/dist/handlers/index.js +7 -0
- package/dist/vendor/desktop-commander/dist/handlers/process-handlers.js +15 -0
- package/dist/vendor/desktop-commander/dist/handlers/search-answers.js +172 -0
- package/dist/vendor/desktop-commander/dist/handlers/search-handlers.js +136 -0
- package/dist/vendor/desktop-commander/dist/handlers/terminal-handlers.js +35 -0
- package/dist/vendor/desktop-commander/dist/index.js +88 -0
- package/dist/vendor/desktop-commander/dist/search-manager.js +1262 -0
- package/dist/vendor/desktop-commander/dist/server.js +1414 -0
- package/dist/vendor/desktop-commander/dist/terminal-manager.js +757 -0
- package/dist/vendor/desktop-commander/dist/tools/config.js +211 -0
- package/dist/vendor/desktop-commander/dist/tools/edit.js +455 -0
- package/dist/vendor/desktop-commander/dist/tools/feedback.js +10 -0
- package/dist/vendor/desktop-commander/dist/tools/filesystem.js +1047 -0
- package/dist/vendor/desktop-commander/dist/tools/fuzzySearch.js +74 -0
- package/dist/vendor/desktop-commander/dist/tools/fuzzySearchCore.js +125 -0
- package/dist/vendor/desktop-commander/dist/tools/improved-process-tools.js +726 -0
- package/dist/vendor/desktop-commander/dist/tools/mime-types.js +28 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/extract-images.js +131 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/index.js +4 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/lib/pdf2md.js +78 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/manipulations.js +112 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/markdown.js +30 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/utils.js +45 -0
- package/dist/vendor/desktop-commander/dist/tools/process.js +90 -0
- package/dist/vendor/desktop-commander/dist/tools/prompts.js +259 -0
- package/dist/vendor/desktop-commander/dist/tools/schemas.js +242 -0
- package/dist/vendor/desktop-commander/dist/tools/usage.js +24 -0
- package/dist/vendor/desktop-commander/dist/types.js +1 -0
- package/dist/vendor/desktop-commander/dist/ui/config-editor/config-editor-runtime.js +150 -0
- package/dist/vendor/desktop-commander/dist/ui/config-editor/index.html +13 -0
- package/dist/vendor/desktop-commander/dist/ui/config-editor/styles.css +587 -0
- package/dist/vendor/desktop-commander/dist/ui/contracts.js +57 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/index.html +16 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/preview-runtime.js +471 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/shared/preview-file-types.js +59 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/styles.css +1631 -0
- package/dist/vendor/desktop-commander/dist/ui/resources.js +93 -0
- package/dist/vendor/desktop-commander/dist/utils/ab-test.js +122 -0
- package/dist/vendor/desktop-commander/dist/utils/atomic-write.js +80 -0
- package/dist/vendor/desktop-commander/dist/utils/capture.js +40 -0
- package/dist/vendor/desktop-commander/dist/utils/dockerPrompt.js +9 -0
- package/dist/vendor/desktop-commander/dist/utils/exit-process.js +31 -0
- package/dist/vendor/desktop-commander/dist/utils/feature-flags.js +18 -0
- package/dist/vendor/desktop-commander/dist/utils/files/base.js +5 -0
- package/dist/vendor/desktop-commander/dist/utils/files/binary.js +65 -0
- package/dist/vendor/desktop-commander/dist/utils/files/docx.js +694 -0
- package/dist/vendor/desktop-commander/dist/utils/files/excel.js +497 -0
- package/dist/vendor/desktop-commander/dist/utils/files/factory.js +168 -0
- package/dist/vendor/desktop-commander/dist/utils/files/image.js +89 -0
- package/dist/vendor/desktop-commander/dist/utils/files/index.js +14 -0
- package/dist/vendor/desktop-commander/dist/utils/files/pdf.js +124 -0
- package/dist/vendor/desktop-commander/dist/utils/files/text.js +423 -0
- package/dist/vendor/desktop-commander/dist/utils/fuzzySearchLogger.js +126 -0
- package/dist/vendor/desktop-commander/dist/utils/internal-facts.js +23 -0
- package/dist/vendor/desktop-commander/dist/utils/lazy-package.js +44 -0
- package/dist/vendor/desktop-commander/dist/utils/lineEndingHandler.js +77 -0
- package/dist/vendor/desktop-commander/dist/utils/logger.js +72 -0
- package/dist/vendor/desktop-commander/dist/utils/mcp-ui-ab-test.js +62 -0
- package/dist/vendor/desktop-commander/dist/utils/open-browser.js +2 -0
- package/dist/vendor/desktop-commander/dist/utils/process-detection.js +260 -0
- package/dist/vendor/desktop-commander/dist/utils/process-tree.js +223 -0
- package/dist/vendor/desktop-commander/dist/utils/rename.js +74 -0
- package/dist/vendor/desktop-commander/dist/utils/ripgrep-resolver.js +69 -0
- package/dist/vendor/desktop-commander/dist/utils/shell.js +187 -0
- package/dist/vendor/desktop-commander/dist/utils/system-info.js +727 -0
- package/dist/vendor/desktop-commander/dist/utils/terminal-timestamps.js +18 -0
- package/dist/vendor/desktop-commander/dist/utils/toolHistory.js +309 -0
- package/dist/vendor/desktop-commander/dist/utils/trackTools.js +55 -0
- package/dist/vendor/desktop-commander/dist/utils/unsupportedParams.js +66 -0
- package/dist/vendor/desktop-commander/dist/utils/usageTracker.js +225 -0
- package/dist/vendor/desktop-commander/dist/utils/welcome-onboarding.js +2 -0
- package/dist/vendor/desktop-commander/dist/utils/withTimeout.js +85 -0
- package/dist/vendor/desktop-commander/dist/version.js +1 -0
- package/dist/vendor/desktop-commander/package.json +15 -0
- package/package.json +14 -2
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Simple MIME type detection based on file extension
|
|
2
|
+
export function getMimeType(filePath) {
|
|
3
|
+
const extension = filePath.toLowerCase().split('.').pop() || '';
|
|
4
|
+
if (extension === "pdf") {
|
|
5
|
+
return "application/pdf";
|
|
6
|
+
}
|
|
7
|
+
// Image types - only the formats we can display
|
|
8
|
+
const imageTypes = {
|
|
9
|
+
'png': 'image/png',
|
|
10
|
+
'jpg': 'image/jpeg',
|
|
11
|
+
'jpeg': 'image/jpeg',
|
|
12
|
+
'gif': 'image/gif',
|
|
13
|
+
'webp': 'image/webp'
|
|
14
|
+
};
|
|
15
|
+
// Check if the file is an image
|
|
16
|
+
if (extension in imageTypes) {
|
|
17
|
+
return imageTypes[extension];
|
|
18
|
+
}
|
|
19
|
+
// Default to text/plain for all other files
|
|
20
|
+
return 'text/plain';
|
|
21
|
+
}
|
|
22
|
+
export function isPdfFile(mimeType) {
|
|
23
|
+
const [baseType] = mimeType.toLowerCase().split(';');
|
|
24
|
+
return baseType.trim() === 'application/pdf';
|
|
25
|
+
}
|
|
26
|
+
export function isImageFile(mimeType) {
|
|
27
|
+
return mimeType.startsWith('image/');
|
|
28
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { LazyPackage } from '../../utils/lazy-package.js';
|
|
2
|
+
import { closePdfDocument } from './utils.js';
|
|
3
|
+
// import(): unpdf is an ES module only. If it fails, Node keeps it failed until a restart.
|
|
4
|
+
export const unpdfPackage = new LazyPackage('unpdf', 'PDF reading support', () => import('unpdf'), true);
|
|
5
|
+
/**
|
|
6
|
+
* Optimized image extraction from PDF using unpdf's built-in extractImages method
|
|
7
|
+
* @param pdfBuffer PDF file as Uint8Array
|
|
8
|
+
* @param pageNumbers Optional array of specific page numbers to process
|
|
9
|
+
* @param compressionOptions Image compression settings
|
|
10
|
+
* @returns Record of page numbers to extracted images
|
|
11
|
+
*/
|
|
12
|
+
export async function extractImagesFromPdf(pdfBuffer, pageNumbers, compressionOptions = {}) {
|
|
13
|
+
const { getDocumentProxy, extractImages } = await unpdfPackage.load();
|
|
14
|
+
const pdfDocument = await getDocumentProxy(pdfBuffer);
|
|
15
|
+
const pagesToProcess = pageNumbers || Array.from({ length: pdfDocument.numPages }, (_, i) => i + 1);
|
|
16
|
+
const pageResults = {};
|
|
17
|
+
try {
|
|
18
|
+
// Process pages in parallel batches for better performance
|
|
19
|
+
const batchSize = 5; // Process 5 pages at a time
|
|
20
|
+
const batches = [];
|
|
21
|
+
for (let i = 0; i < pagesToProcess.length; i += batchSize) {
|
|
22
|
+
batches.push(pagesToProcess.slice(i, i + batchSize));
|
|
23
|
+
}
|
|
24
|
+
for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
|
|
25
|
+
const batch = batches[batchIndex];
|
|
26
|
+
const batchPromises = batch.map(async (pageNum) => {
|
|
27
|
+
if (pageNum < 1 || pageNum > pdfDocument.numPages) {
|
|
28
|
+
return { pageNum, images: [] };
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
// Use unpdf's built-in extractImages
|
|
32
|
+
const extractedImages = await extractImages(pdfDocument, pageNum);
|
|
33
|
+
const pageImages = [];
|
|
34
|
+
for (let index = 0; index < extractedImages.length; index++) {
|
|
35
|
+
const img = extractedImages[index];
|
|
36
|
+
const originalSize = img.data.length;
|
|
37
|
+
try {
|
|
38
|
+
const compressionResult = await convertRawImageToBase64(img.data, img.width, img.height, img.channels, compressionOptions);
|
|
39
|
+
if (compressionResult) {
|
|
40
|
+
pageImages.push({
|
|
41
|
+
objId: index, // Use index as objId since unpdf doesn't provide original objId
|
|
42
|
+
width: img.width,
|
|
43
|
+
height: img.height,
|
|
44
|
+
data: compressionResult.data,
|
|
45
|
+
mimeType: compressionResult.mimeType,
|
|
46
|
+
originalSize,
|
|
47
|
+
compressedSize: Math.round(compressionResult.data.length * 0.75) // Approximate base64 overhead
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
// Ignore conversion errors as requested
|
|
53
|
+
console.warn(`Failed to convert image ${index} on page ${pageNum}:`, err instanceof Error ? err.message : String(err));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { pageNum, images: pageImages };
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.warn(`Failed to extract images from page ${pageNum}:`, error instanceof Error ? error.message : String(error));
|
|
60
|
+
return { pageNum, images: [] };
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
// Wait for the current batch to complete
|
|
64
|
+
const batchResults = await Promise.all(batchPromises);
|
|
65
|
+
// Store results
|
|
66
|
+
for (const { pageNum, images } of batchResults) {
|
|
67
|
+
pageResults[pageNum] = images;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
// Clean up document
|
|
73
|
+
try {
|
|
74
|
+
if (typeof pdfDocument.cleanup === 'function') {
|
|
75
|
+
await pdfDocument.cleanup(false);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (e) { /* Ignore cleanup errors */ }
|
|
79
|
+
await closePdfDocument(pdfDocument);
|
|
80
|
+
}
|
|
81
|
+
return pageResults;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Convert raw image data to compressed base64 using sharp
|
|
85
|
+
*/
|
|
86
|
+
async function convertRawImageToBase64(data, width, height, channels, options = {}) {
|
|
87
|
+
const { format = 'webp', quality = 85, maxDimension = 1200 } = options;
|
|
88
|
+
// Smart resizing - only resize large images
|
|
89
|
+
let targetWidth = width;
|
|
90
|
+
let targetHeight = height;
|
|
91
|
+
if (width > maxDimension || height > maxDimension) {
|
|
92
|
+
const scale = maxDimension / Math.max(width, height);
|
|
93
|
+
targetWidth = Math.round(width * scale);
|
|
94
|
+
targetHeight = Math.round(height * scale);
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
// Try to dynamically import sharp
|
|
98
|
+
const sharp = (await import('sharp')).default;
|
|
99
|
+
// sharp takes Buffer, Uint8Array, etc.
|
|
100
|
+
// unpdf returns Uint8ClampedArray, which works with Buffer.from()
|
|
101
|
+
let pipeline = sharp(Buffer.from(data), {
|
|
102
|
+
raw: {
|
|
103
|
+
width,
|
|
104
|
+
height,
|
|
105
|
+
channels: channels
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
if (targetWidth !== width || targetHeight !== height) {
|
|
109
|
+
pipeline = pipeline.resize(targetWidth, targetHeight);
|
|
110
|
+
}
|
|
111
|
+
let outputBuffer;
|
|
112
|
+
let mimeType;
|
|
113
|
+
if (format === 'jpeg') {
|
|
114
|
+
outputBuffer = await pipeline.jpeg({ quality }).toBuffer();
|
|
115
|
+
mimeType = 'image/jpeg';
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Default to webp
|
|
119
|
+
outputBuffer = await pipeline.webp({ quality }).toBuffer();
|
|
120
|
+
mimeType = 'image/webp';
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
data: outputBuffer.toString('base64'),
|
|
124
|
+
mimeType
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
console.warn('Image conversion failed (likely missing sharp or invalid data):', error instanceof Error ? error.message : String(error));
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { editPdf, insertRenderOptions, pdfLibPackage } from './manipulations.js';
|
|
2
|
+
export { parsePdfToMarkdown, parseMarkdownToPdf, resolveRender, mdToPdfPackage } from './markdown.js';
|
|
3
|
+
export { pdf2mdPackage } from './lib/pdf2md.js';
|
|
4
|
+
export { extractImagesFromPdf, unpdfPackage } from './extract-images.js';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import { LazyPackage } from '../../../utils/lazy-package.js';
|
|
3
|
+
import { closePdfDocument, generatePageNumbers } from '../utils.js';
|
|
4
|
+
import { extractImagesFromPdf } from '../extract-images.js';
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
export const pdf2mdPackage = new LazyPackage('@opendocsg/pdf2md', 'PDF reading support', () => {
|
|
7
|
+
const { parse } = require('@opendocsg/pdf2md/lib/util/pdf');
|
|
8
|
+
const { makeTransformations, transform } = require('@opendocsg/pdf2md/lib/util/transformations');
|
|
9
|
+
return { parse, makeTransformations, transform };
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Extracts metadata from a PDF document.
|
|
13
|
+
* @param pdfDocument The PDF document to extract metadata from.
|
|
14
|
+
* @returns A PdfMetadata object containing the extracted metadata.
|
|
15
|
+
*/
|
|
16
|
+
const extractMetadata = ({ pdfDocument, metadata }) => ({
|
|
17
|
+
totalPages: pdfDocument.numPages,
|
|
18
|
+
title: metadata.Title,
|
|
19
|
+
author: metadata.Author,
|
|
20
|
+
creator: metadata.Creator,
|
|
21
|
+
producer: metadata.Producer,
|
|
22
|
+
version: metadata.PDFFormatVersion,
|
|
23
|
+
creationDate: metadata.CreationDate,
|
|
24
|
+
modificationDate: metadata.ModDate,
|
|
25
|
+
isEncrypted: metadata.IsEncrypted,
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* Reads a PDF and converts it to Markdown, returning structured data.
|
|
29
|
+
* @param pdfBuffer The PDF buffer to convert.
|
|
30
|
+
* @param pageNumbers The page numbers to extract. If an empty array, all pages are extracted;
|
|
31
|
+
* a range that selects no pages (offset past the last page, length 0) extracts none.
|
|
32
|
+
* @returns A Promise that resolves to a PdfParseResult object containing the parsed data.
|
|
33
|
+
*/
|
|
34
|
+
export async function pdf2md(pdfBuffer, pageNumbers = []) {
|
|
35
|
+
const { parse, makeTransformations, transform } = pdf2mdPackage.load();
|
|
36
|
+
const result = await parse(pdfBuffer);
|
|
37
|
+
const { fonts, pages, pdfDocument } = result;
|
|
38
|
+
try {
|
|
39
|
+
// Calculate which pages to process
|
|
40
|
+
const allPages = Array.isArray(pageNumbers) && pageNumbers.length === 0;
|
|
41
|
+
const filterPageNumbers = Array.isArray(pageNumbers) ?
|
|
42
|
+
pageNumbers :
|
|
43
|
+
generatePageNumbers(pageNumbers.offset, pageNumbers.length, pages.length);
|
|
44
|
+
// Filter and transform pages
|
|
45
|
+
const pagesToProcess = allPages ?
|
|
46
|
+
pages :
|
|
47
|
+
pages.filter((_, index) => filterPageNumbers.includes(index + 1));
|
|
48
|
+
const pageNumberMap = allPages ?
|
|
49
|
+
pages.map((_, index) => index + 1) :
|
|
50
|
+
filterPageNumbers.filter(pageNum => pageNum >= 1 && pageNum <= pages.length);
|
|
51
|
+
const transformations = makeTransformations(fonts.map);
|
|
52
|
+
const parseResult = transform(pagesToProcess, transformations);
|
|
53
|
+
// Extract images
|
|
54
|
+
const imagesByPage = await extractImagesFromPdf(pdfBuffer, pageNumberMap, { format: 'webp', quality: 85 });
|
|
55
|
+
// Create pages without images for now
|
|
56
|
+
const processedPages = parseResult.pages.map((page, index) => {
|
|
57
|
+
const pageNumber = pageNumberMap[index];
|
|
58
|
+
return {
|
|
59
|
+
pageNumber,
|
|
60
|
+
text: page.items.join('\n') + '\n',
|
|
61
|
+
images: imagesByPage[pageNumber] || [],
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
const metadata = extractMetadata(result);
|
|
65
|
+
return { pages: processedPages, metadata };
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
if (pdfDocument) {
|
|
69
|
+
try {
|
|
70
|
+
if (typeof pdfDocument.cleanup === 'function') {
|
|
71
|
+
await pdfDocument.cleanup(false);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (e) { }
|
|
75
|
+
await closePdfDocument(pdfDocument);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import { LazyPackage } from '../../utils/lazy-package.js';
|
|
4
|
+
import { normalizePageIndexes } from './utils.js';
|
|
5
|
+
import { parseMarkdownToPdf } from './markdown.js';
|
|
6
|
+
// Use createRequire to load pdf-lib as CJS (works around Node 25 ESM resolution issues)
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
export const pdfLibPackage = new LazyPackage('pdf-lib', 'PDF editing support', () => require('pdf-lib'));
|
|
9
|
+
async function loadPdfDocumentFromBuffer(filePathOrBuffer) {
|
|
10
|
+
const { PDFDocument } = pdfLibPackage.load();
|
|
11
|
+
const buffer = typeof filePathOrBuffer === 'string' ? await fs.readFile(filePathOrBuffer) : filePathOrBuffer;
|
|
12
|
+
const pdfBytes = new Uint8Array(buffer);
|
|
13
|
+
return await PDFDocument.load(pdfBytes);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The render options for an inserted page's markdown: the call's options, with
|
|
17
|
+
* the page size and margins of the original first page (pageLayout), then the
|
|
18
|
+
* call's pdf_options, then the insert's own pdfOptions, each over the one before.
|
|
19
|
+
*/
|
|
20
|
+
export function insertRenderOptions(op, options = {}, pageLayout) {
|
|
21
|
+
return { ...options, pdf_options: { ...pageLayout, ...options.pdf_options, ...op.pdfOptions } };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Delete pages from a PDF document
|
|
25
|
+
* @param pdfDoc PDF document to delete pages from
|
|
26
|
+
* @param pageIndexes Page indices to delete, negative indices are from end
|
|
27
|
+
*/
|
|
28
|
+
function deletePages(pdfDoc, pageIndexes) {
|
|
29
|
+
const pageCount = pdfDoc.getPageCount();
|
|
30
|
+
// A page that doesn't exist is an error, as for an insert: nothing is deleted
|
|
31
|
+
if (pageIndexes.some(idx => !Number.isInteger(idx) || idx >= pageCount || idx < -pageCount)) {
|
|
32
|
+
throw new Error('Invalid page index');
|
|
33
|
+
}
|
|
34
|
+
// Transform negative indices to absolute and filter valid ones
|
|
35
|
+
const normalizedIndexes = normalizePageIndexes(pageIndexes, pageCount).sort((a, b) => b - a);
|
|
36
|
+
for (const pageIndex of normalizedIndexes) {
|
|
37
|
+
pdfDoc.removePage(pageIndex);
|
|
38
|
+
}
|
|
39
|
+
return pdfDoc;
|
|
40
|
+
}
|
|
41
|
+
function getPageLayout(page) {
|
|
42
|
+
const { width, height } = page.getSize();
|
|
43
|
+
const mediaBox = page.getMediaBox(); // Full page area
|
|
44
|
+
const cropBox = page.getCropBox(); // Visible area (may indicate margins)
|
|
45
|
+
// Calculate margins (if CropBox differs from MediaBox)
|
|
46
|
+
let marginLeft = cropBox.x - mediaBox.x;
|
|
47
|
+
let marginBottom = cropBox.y - mediaBox.y;
|
|
48
|
+
let marginRight = (mediaBox.x + mediaBox.width) - (cropBox.x + cropBox.width);
|
|
49
|
+
let marginTop = (mediaBox.y + mediaBox.height) - (cropBox.y + cropBox.height);
|
|
50
|
+
if (marginLeft === 0 && marginRight === 0 && marginTop === 0 && marginBottom === 0) {
|
|
51
|
+
marginLeft = 72;
|
|
52
|
+
marginBottom = 72;
|
|
53
|
+
marginRight = 72;
|
|
54
|
+
marginTop = 72;
|
|
55
|
+
}
|
|
56
|
+
// Convert points to inches (1 inch = 72 points)
|
|
57
|
+
// Puppeteer requires standard units and doesn't accept decimal points
|
|
58
|
+
const pointsToInches = (pts) => (pts / 72).toFixed(4);
|
|
59
|
+
return {
|
|
60
|
+
format: undefined, // Explicitly disable format to use custom dimensions
|
|
61
|
+
width: `${pointsToInches(width)}in`,
|
|
62
|
+
height: `${pointsToInches(height)}in`,
|
|
63
|
+
margin: {
|
|
64
|
+
top: `${pointsToInches(marginTop)}in`,
|
|
65
|
+
right: `${pointsToInches(marginRight)}in`,
|
|
66
|
+
bottom: `${pointsToInches(marginBottom)}in`,
|
|
67
|
+
left: `${pointsToInches(marginLeft)}in`
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async function insertPages(destPdfDocument, pageIndex, sourcePdfDocument) {
|
|
72
|
+
let insertPosition = pageIndex < 0 ? destPdfDocument.getPageCount() + pageIndex : pageIndex;
|
|
73
|
+
if (insertPosition < 0 || insertPosition > destPdfDocument.getPageCount()) {
|
|
74
|
+
throw new Error('Invalid page index');
|
|
75
|
+
}
|
|
76
|
+
const copiedPages = await destPdfDocument.copyPages(sourcePdfDocument, sourcePdfDocument.getPageIndices());
|
|
77
|
+
for (let i = 0; i < copiedPages.length; i++) {
|
|
78
|
+
destPdfDocument.insertPage(insertPosition + i, copiedPages[i]);
|
|
79
|
+
}
|
|
80
|
+
return destPdfDocument;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Edit an existing PDF by deleting or inserting pages
|
|
84
|
+
* @param pdfPath Path to the PDF file to edit
|
|
85
|
+
* @param operations List of operations to perform
|
|
86
|
+
* @returns The modified PDF as a Uint8Array
|
|
87
|
+
*/
|
|
88
|
+
export async function editPdf(pdfPath, operations, options = {}) {
|
|
89
|
+
const pdfDoc = await loadPdfDocumentFromBuffer(pdfPath);
|
|
90
|
+
// Get page layout from the ORIGINAL first page
|
|
91
|
+
const pageLayout = pdfDoc.getPageCount() > 0 ? getPageLayout(pdfDoc.getPage(0)) : undefined;
|
|
92
|
+
for (const op of operations) {
|
|
93
|
+
if (op.type === 'delete') {
|
|
94
|
+
deletePages(pdfDoc, op.pageIndexes);
|
|
95
|
+
}
|
|
96
|
+
else if (op.type == 'insert') {
|
|
97
|
+
let sourcePdfDocument;
|
|
98
|
+
if (op.markdown !== undefined) {
|
|
99
|
+
const pdfBuffer = await parseMarkdownToPdf(op.markdown, insertRenderOptions(op, options, pageLayout));
|
|
100
|
+
sourcePdfDocument = await loadPdfDocumentFromBuffer(pdfBuffer);
|
|
101
|
+
}
|
|
102
|
+
else if (op.sourcePdfPath) {
|
|
103
|
+
sourcePdfDocument = await loadPdfDocumentFromBuffer(op.sourcePdfPath);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
throw new Error('No source provided for insert operation');
|
|
107
|
+
}
|
|
108
|
+
await insertPages(pdfDoc, op.pageIndex, sourcePdfDocument);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return await pdfDoc.save();
|
|
112
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { pdf2md } from './lib/pdf2md.js';
|
|
3
|
+
import { LazyPackage } from '../../utils/lazy-package.js';
|
|
4
|
+
|
|
5
|
+
const isUrl = (source) => source.startsWith('http://') || source.startsWith('https://');
|
|
6
|
+
|
|
7
|
+
export const mdToPdfPackage = new LazyPackage('md-to-pdf', 'PDF writing support', () => ({}));
|
|
8
|
+
|
|
9
|
+
export function ensureChromeAvailable() {}
|
|
10
|
+
|
|
11
|
+
export function resolveRender(markdown, options = {}) {
|
|
12
|
+
return { body: markdown, options: { ...options }, ignoredOptions: [] };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function loadPdfToBuffer(source) {
|
|
16
|
+
if (isUrl(source)) {
|
|
17
|
+
const response = await fetch(source);
|
|
18
|
+
return await response.arrayBuffer();
|
|
19
|
+
}
|
|
20
|
+
return await fs.readFile(source);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function parsePdfToMarkdown(source, pageNumbers = []) {
|
|
24
|
+
const data = await loadPdfToBuffer(source);
|
|
25
|
+
return await pdf2md(new Uint8Array(data), pageNumbers);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function parseMarkdownToPdf() {
|
|
29
|
+
throw new Error('Headless browser PDF rendering (Puppeteer/Chrome) is stripped in Starmux for security and privacy.');
|
|
30
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize page indexes, handling negative indices and removing duplicates
|
|
3
|
+
*/
|
|
4
|
+
export const normalizePageIndexes = (pageIndexes, pageCount) => {
|
|
5
|
+
const normalizedIndexes = pageIndexes
|
|
6
|
+
.map(idx => idx < 0 ? pageCount + idx : idx)
|
|
7
|
+
.filter(idx => idx >= 0 && idx < pageCount);
|
|
8
|
+
// Use Set to remove duplicates
|
|
9
|
+
return [...new Set(normalizedIndexes)];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Generate page numbers based on offset and length
|
|
13
|
+
* @param offset Zero-based offset or negative for the last pages (length is then ignored, as for lines)
|
|
14
|
+
* @param length Number of pages to generate
|
|
15
|
+
* @param totalPages Total number of pages in the document
|
|
16
|
+
* @returns Array of page numbers (empty when the offset is past the last page or length is 0)
|
|
17
|
+
*/
|
|
18
|
+
export function generatePageNumbers(offset, length, totalPages) {
|
|
19
|
+
// Compute 1-based start page
|
|
20
|
+
const startPage = offset < 0
|
|
21
|
+
? totalPages + offset + 1
|
|
22
|
+
: offset + 1;
|
|
23
|
+
// Clamp start page
|
|
24
|
+
if (startPage > totalPages)
|
|
25
|
+
return [];
|
|
26
|
+
const safeStart = Math.max(1, startPage);
|
|
27
|
+
// Compute final page (inclusive), truncated by totalPages; a negative offset reads to the last page
|
|
28
|
+
const endPage = offset < 0 ? totalPages : Math.min(safeStart + length - 1, totalPages);
|
|
29
|
+
const count = endPage - safeStart + 1;
|
|
30
|
+
if (count <= 0)
|
|
31
|
+
return [];
|
|
32
|
+
// Preallocate array for speed
|
|
33
|
+
return Array.from({ length: count }, (_, i) => safeStart + i);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Close a PDF document opened with unpdf, so pdf.js frees what it holds for it
|
|
37
|
+
* @param pdfDocument The document, closed through its loading task: unpdf 1.8's
|
|
38
|
+
* document has no destroy() of its own (1.4's only calls loadingTask.destroy())
|
|
39
|
+
*/
|
|
40
|
+
export async function closePdfDocument(pdfDocument) {
|
|
41
|
+
try {
|
|
42
|
+
await pdfDocument.loadingTask.destroy();
|
|
43
|
+
}
|
|
44
|
+
catch (e) { /* Ignore cleanup errors */ }
|
|
45
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { execFile } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { KillProcessArgsSchema } from './schemas.js';
|
|
5
|
+
import { resolveProgramPath } from '../utils/shell.js';
|
|
6
|
+
import { terminatePid } from '../utils/process-tree.js';
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
/**
|
|
9
|
+
* Parse `tasklist /FO CSV /NH` output (#662). Fields per row, all quoted:
|
|
10
|
+
* [Image Name, PID, Session Name, Session#, Mem Usage]. CSV instead of the
|
|
11
|
+
* default table: image names can contain spaces and Mem Usage contains
|
|
12
|
+
* thousands separators, so positional whitespace splitting can't work.
|
|
13
|
+
* tasklist has no live per-process CPU%, so cpu is reported as n/a.
|
|
14
|
+
*/
|
|
15
|
+
export function parseWindowsTasklistCsv(stdout) {
|
|
16
|
+
return stdout.split(/\r?\n/)
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.map(line => {
|
|
19
|
+
const fields = (line.match(/"([^"]*)"/g) || []).map(f => f.slice(1, -1));
|
|
20
|
+
return {
|
|
21
|
+
pid: parseInt(fields[1], 10),
|
|
22
|
+
command: fields[0] ?? '',
|
|
23
|
+
cpu: 'n/a',
|
|
24
|
+
memory: fields[4] ?? '',
|
|
25
|
+
};
|
|
26
|
+
})
|
|
27
|
+
.filter(p => !Number.isNaN(p.pid));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse `ps aux` output: 11 whitespace-separated columns, COMMAND from
|
|
31
|
+
* column 10 onward (it can contain spaces, so it must be rejoined, not
|
|
32
|
+
* taken as the last token).
|
|
33
|
+
*/
|
|
34
|
+
export function parsePsAux(stdout) {
|
|
35
|
+
return stdout.split(/\r?\n/)
|
|
36
|
+
.slice(1)
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.map(line => {
|
|
39
|
+
const parts = line.trim().split(/\s+/);
|
|
40
|
+
return {
|
|
41
|
+
pid: parseInt(parts[1], 10),
|
|
42
|
+
command: parts.slice(10).join(' ') || parts[parts.length - 1],
|
|
43
|
+
cpu: parts[2],
|
|
44
|
+
memory: parts[3],
|
|
45
|
+
};
|
|
46
|
+
})
|
|
47
|
+
.filter(p => !Number.isNaN(p.pid));
|
|
48
|
+
}
|
|
49
|
+
export async function listProcesses() {
|
|
50
|
+
const isWindows = os.platform() === 'win32';
|
|
51
|
+
const [program, args] = isWindows ? ['tasklist', ['/FO', 'CSV', '/NH']] : ['ps', ['aux']];
|
|
52
|
+
try {
|
|
53
|
+
const { stdout } = await execFileAsync(resolveProgramPath(program), args);
|
|
54
|
+
const processes = isWindows ? parseWindowsTasklistCsv(stdout) : parsePsAux(stdout);
|
|
55
|
+
return {
|
|
56
|
+
content: [{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: processes.map(p => `PID: ${p.pid}, Command: ${p.command}, CPU: ${p.cpu}, Memory: ${p.memory}`).join('\n')
|
|
59
|
+
}],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: "text", text: `Error: Failed to list processes: ${error instanceof Error ? error.message : String(error)}` }],
|
|
65
|
+
isError: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export async function killProcess(args) {
|
|
70
|
+
const parsed = KillProcessArgsSchema.safeParse(args);
|
|
71
|
+
if (!parsed.success) {
|
|
72
|
+
return {
|
|
73
|
+
content: [{ type: "text", text: `Error: Invalid arguments for kill_process: ${parsed.error}` }],
|
|
74
|
+
isError: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
// Returns once the process is gone, so a success means it no longer runs
|
|
79
|
+
await terminatePid(parsed.data.pid);
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text", text: `Successfully terminated process ${parsed.data.pid}` }],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: "text", text: `Error: Failed to kill process: ${error instanceof Error ? error.message : String(error)}` }],
|
|
87
|
+
isError: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|