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.
Files changed (93) hide show
  1. package/dist/src/cli/mac-mcp-gateway.js +4 -3
  2. package/dist/src/cli/mac-mcp-gateway.js.map +1 -1
  3. package/dist/src/cli/starmux.js +1 -1
  4. package/dist/src/cli/starmux.js.map +1 -1
  5. package/dist/src/runtime-paths.js +3 -3
  6. package/dist/src/runtime-paths.js.map +1 -1
  7. package/dist/vendor/desktop-commander/LICENSE +21 -0
  8. package/dist/vendor/desktop-commander/dist/bootstrap.js +22 -0
  9. package/dist/vendor/desktop-commander/dist/command-manager.js +246 -0
  10. package/dist/vendor/desktop-commander/dist/config-field-definitions.js +37 -0
  11. package/dist/vendor/desktop-commander/dist/config-manager.js +634 -0
  12. package/dist/vendor/desktop-commander/dist/config-recovery.js +81 -0
  13. package/dist/vendor/desktop-commander/dist/config.js +19 -0
  14. package/dist/vendor/desktop-commander/dist/custom-stdio.js +358 -0
  15. package/dist/vendor/desktop-commander/dist/error-handlers.js +15 -0
  16. package/dist/vendor/desktop-commander/dist/handlers/edit-search-handlers.js +6 -0
  17. package/dist/vendor/desktop-commander/dist/handlers/filesystem-handlers.js +450 -0
  18. package/dist/vendor/desktop-commander/dist/handlers/history-handlers.js +67 -0
  19. package/dist/vendor/desktop-commander/dist/handlers/index.js +7 -0
  20. package/dist/vendor/desktop-commander/dist/handlers/process-handlers.js +15 -0
  21. package/dist/vendor/desktop-commander/dist/handlers/search-answers.js +172 -0
  22. package/dist/vendor/desktop-commander/dist/handlers/search-handlers.js +136 -0
  23. package/dist/vendor/desktop-commander/dist/handlers/terminal-handlers.js +35 -0
  24. package/dist/vendor/desktop-commander/dist/index.js +88 -0
  25. package/dist/vendor/desktop-commander/dist/search-manager.js +1262 -0
  26. package/dist/vendor/desktop-commander/dist/server.js +1414 -0
  27. package/dist/vendor/desktop-commander/dist/terminal-manager.js +757 -0
  28. package/dist/vendor/desktop-commander/dist/tools/config.js +211 -0
  29. package/dist/vendor/desktop-commander/dist/tools/edit.js +455 -0
  30. package/dist/vendor/desktop-commander/dist/tools/feedback.js +10 -0
  31. package/dist/vendor/desktop-commander/dist/tools/filesystem.js +1047 -0
  32. package/dist/vendor/desktop-commander/dist/tools/fuzzySearch.js +74 -0
  33. package/dist/vendor/desktop-commander/dist/tools/fuzzySearchCore.js +125 -0
  34. package/dist/vendor/desktop-commander/dist/tools/improved-process-tools.js +726 -0
  35. package/dist/vendor/desktop-commander/dist/tools/mime-types.js +28 -0
  36. package/dist/vendor/desktop-commander/dist/tools/pdf/extract-images.js +131 -0
  37. package/dist/vendor/desktop-commander/dist/tools/pdf/index.js +4 -0
  38. package/dist/vendor/desktop-commander/dist/tools/pdf/lib/pdf2md.js +78 -0
  39. package/dist/vendor/desktop-commander/dist/tools/pdf/manipulations.js +112 -0
  40. package/dist/vendor/desktop-commander/dist/tools/pdf/markdown.js +30 -0
  41. package/dist/vendor/desktop-commander/dist/tools/pdf/utils.js +45 -0
  42. package/dist/vendor/desktop-commander/dist/tools/process.js +90 -0
  43. package/dist/vendor/desktop-commander/dist/tools/prompts.js +259 -0
  44. package/dist/vendor/desktop-commander/dist/tools/schemas.js +242 -0
  45. package/dist/vendor/desktop-commander/dist/tools/usage.js +24 -0
  46. package/dist/vendor/desktop-commander/dist/types.js +1 -0
  47. package/dist/vendor/desktop-commander/dist/ui/config-editor/config-editor-runtime.js +150 -0
  48. package/dist/vendor/desktop-commander/dist/ui/config-editor/index.html +13 -0
  49. package/dist/vendor/desktop-commander/dist/ui/config-editor/styles.css +587 -0
  50. package/dist/vendor/desktop-commander/dist/ui/contracts.js +57 -0
  51. package/dist/vendor/desktop-commander/dist/ui/file-preview/index.html +16 -0
  52. package/dist/vendor/desktop-commander/dist/ui/file-preview/preview-runtime.js +471 -0
  53. package/dist/vendor/desktop-commander/dist/ui/file-preview/shared/preview-file-types.js +59 -0
  54. package/dist/vendor/desktop-commander/dist/ui/file-preview/styles.css +1631 -0
  55. package/dist/vendor/desktop-commander/dist/ui/resources.js +93 -0
  56. package/dist/vendor/desktop-commander/dist/utils/ab-test.js +122 -0
  57. package/dist/vendor/desktop-commander/dist/utils/atomic-write.js +80 -0
  58. package/dist/vendor/desktop-commander/dist/utils/capture.js +40 -0
  59. package/dist/vendor/desktop-commander/dist/utils/dockerPrompt.js +9 -0
  60. package/dist/vendor/desktop-commander/dist/utils/exit-process.js +31 -0
  61. package/dist/vendor/desktop-commander/dist/utils/feature-flags.js +18 -0
  62. package/dist/vendor/desktop-commander/dist/utils/files/base.js +5 -0
  63. package/dist/vendor/desktop-commander/dist/utils/files/binary.js +65 -0
  64. package/dist/vendor/desktop-commander/dist/utils/files/docx.js +694 -0
  65. package/dist/vendor/desktop-commander/dist/utils/files/excel.js +497 -0
  66. package/dist/vendor/desktop-commander/dist/utils/files/factory.js +168 -0
  67. package/dist/vendor/desktop-commander/dist/utils/files/image.js +89 -0
  68. package/dist/vendor/desktop-commander/dist/utils/files/index.js +14 -0
  69. package/dist/vendor/desktop-commander/dist/utils/files/pdf.js +124 -0
  70. package/dist/vendor/desktop-commander/dist/utils/files/text.js +423 -0
  71. package/dist/vendor/desktop-commander/dist/utils/fuzzySearchLogger.js +126 -0
  72. package/dist/vendor/desktop-commander/dist/utils/internal-facts.js +23 -0
  73. package/dist/vendor/desktop-commander/dist/utils/lazy-package.js +44 -0
  74. package/dist/vendor/desktop-commander/dist/utils/lineEndingHandler.js +77 -0
  75. package/dist/vendor/desktop-commander/dist/utils/logger.js +72 -0
  76. package/dist/vendor/desktop-commander/dist/utils/mcp-ui-ab-test.js +62 -0
  77. package/dist/vendor/desktop-commander/dist/utils/open-browser.js +2 -0
  78. package/dist/vendor/desktop-commander/dist/utils/process-detection.js +260 -0
  79. package/dist/vendor/desktop-commander/dist/utils/process-tree.js +223 -0
  80. package/dist/vendor/desktop-commander/dist/utils/rename.js +74 -0
  81. package/dist/vendor/desktop-commander/dist/utils/ripgrep-resolver.js +69 -0
  82. package/dist/vendor/desktop-commander/dist/utils/shell.js +187 -0
  83. package/dist/vendor/desktop-commander/dist/utils/system-info.js +727 -0
  84. package/dist/vendor/desktop-commander/dist/utils/terminal-timestamps.js +18 -0
  85. package/dist/vendor/desktop-commander/dist/utils/toolHistory.js +309 -0
  86. package/dist/vendor/desktop-commander/dist/utils/trackTools.js +55 -0
  87. package/dist/vendor/desktop-commander/dist/utils/unsupportedParams.js +66 -0
  88. package/dist/vendor/desktop-commander/dist/utils/usageTracker.js +225 -0
  89. package/dist/vendor/desktop-commander/dist/utils/welcome-onboarding.js +2 -0
  90. package/dist/vendor/desktop-commander/dist/utils/withTimeout.js +85 -0
  91. package/dist/vendor/desktop-commander/dist/version.js +1 -0
  92. package/dist/vendor/desktop-commander/package.json +15 -0
  93. 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
+ }