ai-pdf-builder 0.2.0 → 0.3.1
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/chunk-6CO4SNHC.mjs +594 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +751 -0
- package/dist/cli.mjs +230 -0
- package/dist/index.mjs +28 -565
- package/package.json +6 -3
package/dist/cli.js
ADDED
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var fs4 = __toESM(require("fs"));
|
|
28
|
+
var path4 = __toESM(require("path"));
|
|
29
|
+
|
|
30
|
+
// src/generator.ts
|
|
31
|
+
var import_child_process2 = require("child_process");
|
|
32
|
+
var fs3 = __toESM(require("fs"));
|
|
33
|
+
var path3 = __toESM(require("path"));
|
|
34
|
+
|
|
35
|
+
// src/templates.ts
|
|
36
|
+
var fs = __toESM(require("fs"));
|
|
37
|
+
var path = __toESM(require("path"));
|
|
38
|
+
var TEMPLATES_DIR = path.join(__dirname, "..", "templates");
|
|
39
|
+
var BUILT_IN_TEMPLATES = {
|
|
40
|
+
default: {
|
|
41
|
+
name: "default",
|
|
42
|
+
path: path.join(TEMPLATES_DIR, "default.latex"),
|
|
43
|
+
description: "Clean, professional default template with modern styling",
|
|
44
|
+
supportedDocTypes: ["memo", "whitepaper", "report", "proposal", "generic"]
|
|
45
|
+
},
|
|
46
|
+
memo: {
|
|
47
|
+
name: "memo",
|
|
48
|
+
path: path.join(TEMPLATES_DIR, "memo.latex"),
|
|
49
|
+
description: "Business memo template with executive summary styling",
|
|
50
|
+
supportedDocTypes: ["memo", "report", "proposal"]
|
|
51
|
+
},
|
|
52
|
+
agreement: {
|
|
53
|
+
name: "agreement",
|
|
54
|
+
path: path.join(TEMPLATES_DIR, "agreement.latex"),
|
|
55
|
+
description: "Legal agreement template with formal structure",
|
|
56
|
+
supportedDocTypes: ["agreement", "generic"]
|
|
57
|
+
},
|
|
58
|
+
termsheet: {
|
|
59
|
+
name: "termsheet",
|
|
60
|
+
path: path.join(TEMPLATES_DIR, "termsheet.latex"),
|
|
61
|
+
description: "Term sheet template for investment documents",
|
|
62
|
+
supportedDocTypes: ["termsheet", "agreement"]
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var customTemplates = /* @__PURE__ */ new Map();
|
|
66
|
+
var templateCache = /* @__PURE__ */ new Map();
|
|
67
|
+
function getTemplate(name) {
|
|
68
|
+
if (templateCache.has(name)) {
|
|
69
|
+
return templateCache.get(name) || null;
|
|
70
|
+
}
|
|
71
|
+
if (customTemplates.has(name)) {
|
|
72
|
+
const config = customTemplates.get(name);
|
|
73
|
+
return loadTemplateFile(config.path, name);
|
|
74
|
+
}
|
|
75
|
+
if (BUILT_IN_TEMPLATES[name]) {
|
|
76
|
+
return loadTemplateFile(BUILT_IN_TEMPLATES[name].path, name);
|
|
77
|
+
}
|
|
78
|
+
if (fs.existsSync(name)) {
|
|
79
|
+
return loadTemplateFile(name, name);
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function getTemplatePath(name) {
|
|
84
|
+
if (customTemplates.has(name)) {
|
|
85
|
+
return customTemplates.get(name).path;
|
|
86
|
+
}
|
|
87
|
+
if (BUILT_IN_TEMPLATES[name]) {
|
|
88
|
+
return BUILT_IN_TEMPLATES[name].path;
|
|
89
|
+
}
|
|
90
|
+
if (fs.existsSync(name)) {
|
|
91
|
+
return name;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
function loadTemplateFile(filePath, cacheKey) {
|
|
96
|
+
try {
|
|
97
|
+
if (!fs.existsSync(filePath)) {
|
|
98
|
+
console.warn(`Template file not found: ${filePath}`);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
102
|
+
templateCache.set(cacheKey, content);
|
|
103
|
+
return content;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(`Error loading template ${filePath}:`, error);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function listTemplates() {
|
|
110
|
+
const builtIn = Object.values(BUILT_IN_TEMPLATES);
|
|
111
|
+
const custom = Array.from(customTemplates.values());
|
|
112
|
+
return [...builtIn, ...custom];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/utils.ts
|
|
116
|
+
var import_child_process = require("child_process");
|
|
117
|
+
var fs2 = __toESM(require("fs"));
|
|
118
|
+
var path2 = __toESM(require("path"));
|
|
119
|
+
var os = __toESM(require("os"));
|
|
120
|
+
function checkPandoc() {
|
|
121
|
+
try {
|
|
122
|
+
const version = (0, import_child_process.execSync)("pandoc --version", { encoding: "utf-8" });
|
|
123
|
+
const versionMatch = version.match(/pandoc\s+([\d.]+)/);
|
|
124
|
+
const pathResult = (0, import_child_process.execSync)("which pandoc", { encoding: "utf-8" }).trim();
|
|
125
|
+
return {
|
|
126
|
+
available: true,
|
|
127
|
+
version: versionMatch ? versionMatch[1] : "unknown",
|
|
128
|
+
path: pathResult
|
|
129
|
+
};
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return {
|
|
132
|
+
available: false,
|
|
133
|
+
error: "Pandoc not found. Install with: brew install pandoc (macOS) or apt-get install pandoc (Linux)"
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function checkLaTeX() {
|
|
138
|
+
try {
|
|
139
|
+
const version = (0, import_child_process.execSync)("pdflatex --version", { encoding: "utf-8" });
|
|
140
|
+
const versionMatch = version.match(/pdfTeX\s+([\d.-]+)/);
|
|
141
|
+
const pathResult = (0, import_child_process.execSync)("which pdflatex", { encoding: "utf-8" }).trim();
|
|
142
|
+
return {
|
|
143
|
+
available: true,
|
|
144
|
+
engine: "pdflatex",
|
|
145
|
+
version: versionMatch ? versionMatch[1] : "unknown",
|
|
146
|
+
path: pathResult
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
return {
|
|
150
|
+
available: false,
|
|
151
|
+
error: "pdflatex not found. Install BasicTeX (brew install --cask basictex) or TeX Live"
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function checkSystem() {
|
|
156
|
+
const pandoc = checkPandoc();
|
|
157
|
+
const latex = checkLaTeX();
|
|
158
|
+
const ready = pandoc.available && latex.available;
|
|
159
|
+
let message = "";
|
|
160
|
+
if (ready) {
|
|
161
|
+
message = `Ready: Pandoc ${pandoc.version}, pdfTeX ${latex.version}`;
|
|
162
|
+
} else {
|
|
163
|
+
const missing = [];
|
|
164
|
+
if (!pandoc.available) missing.push("Pandoc");
|
|
165
|
+
if (!latex.available) missing.push("LaTeX/pdflatex");
|
|
166
|
+
message = `Missing dependencies: ${missing.join(", ")}`;
|
|
167
|
+
}
|
|
168
|
+
return { pandoc, latex, ready, message };
|
|
169
|
+
}
|
|
170
|
+
function createTempDir(prefix = "pdf-builder") {
|
|
171
|
+
const tempDir = path2.join(os.tmpdir(), `${prefix}-${Date.now()}`);
|
|
172
|
+
fs2.mkdirSync(tempDir, { recursive: true });
|
|
173
|
+
return tempDir;
|
|
174
|
+
}
|
|
175
|
+
function cleanupTempDir(dirPath) {
|
|
176
|
+
try {
|
|
177
|
+
if (fs2.existsSync(dirPath)) {
|
|
178
|
+
fs2.rmSync(dirPath, { recursive: true, force: true });
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.warn(`Warning: Could not clean up temp directory: ${dirPath}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function generateYAMLFrontMatter(metadata) {
|
|
185
|
+
const lines = ["---"];
|
|
186
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
187
|
+
if (value !== void 0 && value !== "") {
|
|
188
|
+
const escapedValue = String(value).replace(/"/g, '\\"');
|
|
189
|
+
lines.push(`${key}: "${escapedValue}"`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
lines.push("---");
|
|
193
|
+
return lines.join("\n");
|
|
194
|
+
}
|
|
195
|
+
function sanitizeContent(content) {
|
|
196
|
+
const dangerousCommands = [
|
|
197
|
+
"\\input",
|
|
198
|
+
"\\include",
|
|
199
|
+
"\\write18",
|
|
200
|
+
"\\immediate",
|
|
201
|
+
"\\openin",
|
|
202
|
+
"\\openout",
|
|
203
|
+
"\\read",
|
|
204
|
+
"\\write",
|
|
205
|
+
"\\newwrite",
|
|
206
|
+
"\\closeout",
|
|
207
|
+
"\\closein"
|
|
208
|
+
];
|
|
209
|
+
let sanitized = content;
|
|
210
|
+
for (const cmd of dangerousCommands) {
|
|
211
|
+
const regex = new RegExp(cmd.replace("\\", "\\\\") + "[^\\s{]*({[^}]*})?", "gi");
|
|
212
|
+
sanitized = sanitized.replace(regex, "");
|
|
213
|
+
}
|
|
214
|
+
return sanitized;
|
|
215
|
+
}
|
|
216
|
+
function parseColor(color) {
|
|
217
|
+
if (/^\d+,\s*\d+,\s*\d+$/.test(color)) {
|
|
218
|
+
return color.replace(/\s/g, "");
|
|
219
|
+
}
|
|
220
|
+
const rgbMatch = color.match(/RGB\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i);
|
|
221
|
+
if (rgbMatch) {
|
|
222
|
+
return `${rgbMatch[1]},${rgbMatch[2]},${rgbMatch[3]}`;
|
|
223
|
+
}
|
|
224
|
+
const hexMatch = color.match(/^#?([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})$/);
|
|
225
|
+
if (hexMatch) {
|
|
226
|
+
const r = parseInt(hexMatch[1], 16);
|
|
227
|
+
const g = parseInt(hexMatch[2], 16);
|
|
228
|
+
const b = parseInt(hexMatch[3], 16);
|
|
229
|
+
return `${r},${g},${b}`;
|
|
230
|
+
}
|
|
231
|
+
return "59,130,246";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/generator.ts
|
|
235
|
+
var DEFAULT_OPTIONS = {
|
|
236
|
+
toc: true,
|
|
237
|
+
tocDepth: 2,
|
|
238
|
+
numberSections: true,
|
|
239
|
+
fontSize: 11,
|
|
240
|
+
margin: "1in",
|
|
241
|
+
paperSize: "letter",
|
|
242
|
+
timeout: 6e4
|
|
243
|
+
};
|
|
244
|
+
async function generatePDF(options) {
|
|
245
|
+
const startTime = Date.now();
|
|
246
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
247
|
+
const sysCheck = checkSystem();
|
|
248
|
+
if (!sysCheck.ready) {
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
error: `System requirements not met: ${sysCheck.message}`
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (!opts.content || opts.content.trim().length === 0) {
|
|
255
|
+
return {
|
|
256
|
+
success: false,
|
|
257
|
+
error: "Content is required"
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
let tempDir = null;
|
|
261
|
+
try {
|
|
262
|
+
tempDir = opts.workDir || createTempDir("pdf-builder");
|
|
263
|
+
const sanitizedContent = sanitizeContent(opts.content);
|
|
264
|
+
const fullMarkdown = buildMarkdownDocument(sanitizedContent, opts);
|
|
265
|
+
const inputPath = path3.join(tempDir, "input.md");
|
|
266
|
+
fs3.writeFileSync(inputPath, fullMarkdown, "utf-8");
|
|
267
|
+
const templatePath = await prepareTemplate(tempDir, opts);
|
|
268
|
+
const outputPath = opts.outputPath || path3.join(tempDir, "output.pdf");
|
|
269
|
+
const pandocOpts = {
|
|
270
|
+
inputPath,
|
|
271
|
+
outputPath,
|
|
272
|
+
templatePath,
|
|
273
|
+
toc: opts.toc ?? true,
|
|
274
|
+
tocDepth: opts.tocDepth ?? 2,
|
|
275
|
+
numberSections: opts.numberSections ?? true,
|
|
276
|
+
fontSize: opts.fontSize ?? 11,
|
|
277
|
+
margin: opts.margin ?? "1in",
|
|
278
|
+
paperSize: opts.paperSize ?? "letter",
|
|
279
|
+
timeout: opts.timeout ?? 6e4
|
|
280
|
+
};
|
|
281
|
+
await executePandoc(pandocOpts);
|
|
282
|
+
if (!fs3.existsSync(outputPath)) {
|
|
283
|
+
return {
|
|
284
|
+
success: false,
|
|
285
|
+
error: "PDF generation failed - output file not created"
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
const buffer = fs3.readFileSync(outputPath);
|
|
289
|
+
const stats = fs3.statSync(outputPath);
|
|
290
|
+
const generationTime = Date.now() - startTime;
|
|
291
|
+
const result = {
|
|
292
|
+
success: true,
|
|
293
|
+
buffer: opts.outputPath ? void 0 : buffer,
|
|
294
|
+
path: opts.outputPath || void 0,
|
|
295
|
+
fileSize: stats.size,
|
|
296
|
+
generationTime
|
|
297
|
+
};
|
|
298
|
+
result.pageCount = estimatePageCount(buffer);
|
|
299
|
+
return result;
|
|
300
|
+
} catch (error) {
|
|
301
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
302
|
+
return {
|
|
303
|
+
success: false,
|
|
304
|
+
error: `PDF generation failed: ${errorMessage}`,
|
|
305
|
+
generationTime: Date.now() - startTime
|
|
306
|
+
};
|
|
307
|
+
} finally {
|
|
308
|
+
if (tempDir && !opts.workDir && !opts.outputPath) {
|
|
309
|
+
} else if (tempDir && !opts.workDir) {
|
|
310
|
+
cleanupTempDir(tempDir);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function buildMarkdownDocument(content, opts) {
|
|
315
|
+
const parts = [];
|
|
316
|
+
if (opts.metadata) {
|
|
317
|
+
const frontMatter = generateYAMLFrontMatter({
|
|
318
|
+
...opts.metadata,
|
|
319
|
+
geometry: `margin=${opts.margin || "1in"}`,
|
|
320
|
+
fontsize: `${opts.fontSize || 11}pt`,
|
|
321
|
+
papersize: opts.paperSize || "letter"
|
|
322
|
+
});
|
|
323
|
+
parts.push(frontMatter);
|
|
324
|
+
parts.push("");
|
|
325
|
+
}
|
|
326
|
+
parts.push(content);
|
|
327
|
+
return parts.join("\n");
|
|
328
|
+
}
|
|
329
|
+
async function prepareTemplate(tempDir, opts) {
|
|
330
|
+
const templateName = opts.template || "default";
|
|
331
|
+
let templateContent = getTemplate(templateName);
|
|
332
|
+
if (!templateContent) {
|
|
333
|
+
const templatePath = getTemplatePath(templateName);
|
|
334
|
+
if (templatePath && fs3.existsSync(templatePath)) {
|
|
335
|
+
templateContent = fs3.readFileSync(templatePath, "utf-8");
|
|
336
|
+
} else {
|
|
337
|
+
templateContent = getTemplate("default");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (!templateContent) {
|
|
341
|
+
throw new Error(`Template not found: ${templateName}`);
|
|
342
|
+
}
|
|
343
|
+
if (opts.customColors) {
|
|
344
|
+
templateContent = applyColorCustomizations(templateContent, opts.customColors);
|
|
345
|
+
}
|
|
346
|
+
const customTemplatePath = path3.join(tempDir, "template.latex");
|
|
347
|
+
fs3.writeFileSync(customTemplatePath, templateContent, "utf-8");
|
|
348
|
+
return customTemplatePath;
|
|
349
|
+
}
|
|
350
|
+
function applyColorCustomizations(template, colors) {
|
|
351
|
+
let result = template;
|
|
352
|
+
if (colors.primary) {
|
|
353
|
+
const rgb = parseColor(colors.primary);
|
|
354
|
+
result = result.replace(
|
|
355
|
+
/\\definecolor{(?:zeroBlue|primaryColor)}{RGB}{[^}]+}/g,
|
|
356
|
+
`\\definecolor{primaryColor}{RGB}{${rgb}}`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (colors.secondary) {
|
|
360
|
+
const rgb = parseColor(colors.secondary);
|
|
361
|
+
result = result.replace(
|
|
362
|
+
/\\definecolor{(?:zeroGray|secondaryColor)}{RGB}{[^}]+}/g,
|
|
363
|
+
`\\definecolor{secondaryColor}{RGB}{${rgb}}`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
if (colors.accent) {
|
|
367
|
+
const rgb = parseColor(colors.accent);
|
|
368
|
+
result = result.replace(
|
|
369
|
+
/\\definecolor{(?:zeroDark|accentColor)}{RGB}{[^}]+}/g,
|
|
370
|
+
`\\definecolor{accentColor}{RGB}{${rgb}}`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
function executePandoc(opts) {
|
|
376
|
+
return new Promise((resolve3, reject) => {
|
|
377
|
+
const args = [
|
|
378
|
+
opts.inputPath,
|
|
379
|
+
"-o",
|
|
380
|
+
opts.outputPath,
|
|
381
|
+
"--pdf-engine=pdflatex",
|
|
382
|
+
`-V`,
|
|
383
|
+
`geometry:margin=${opts.margin}`,
|
|
384
|
+
`-V`,
|
|
385
|
+
`fontsize=${opts.fontSize}pt`,
|
|
386
|
+
`-V`,
|
|
387
|
+
`papersize=${opts.paperSize}`,
|
|
388
|
+
`-V`,
|
|
389
|
+
`documentclass=article`,
|
|
390
|
+
`-V`,
|
|
391
|
+
`colorlinks=true`,
|
|
392
|
+
`-V`,
|
|
393
|
+
`linkcolor=blue`,
|
|
394
|
+
`-V`,
|
|
395
|
+
`urlcolor=blue`
|
|
396
|
+
];
|
|
397
|
+
if (opts.templatePath) {
|
|
398
|
+
args.push(`--template=${opts.templatePath}`);
|
|
399
|
+
}
|
|
400
|
+
if (opts.toc) {
|
|
401
|
+
args.push("--toc");
|
|
402
|
+
args.push(`--toc-depth=${opts.tocDepth}`);
|
|
403
|
+
}
|
|
404
|
+
if (opts.numberSections) {
|
|
405
|
+
args.push("--number-sections");
|
|
406
|
+
}
|
|
407
|
+
const pandoc = (0, import_child_process2.spawn)("pandoc", args, {
|
|
408
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
409
|
+
});
|
|
410
|
+
let stderr = "";
|
|
411
|
+
pandoc.stderr.on("data", (data) => {
|
|
412
|
+
stderr += data.toString();
|
|
413
|
+
});
|
|
414
|
+
const timeout = setTimeout(() => {
|
|
415
|
+
pandoc.kill("SIGKILL");
|
|
416
|
+
reject(new Error(`Pandoc timed out after ${opts.timeout}ms`));
|
|
417
|
+
}, opts.timeout);
|
|
418
|
+
pandoc.on("close", (code) => {
|
|
419
|
+
clearTimeout(timeout);
|
|
420
|
+
if (code === 0) {
|
|
421
|
+
resolve3();
|
|
422
|
+
} else {
|
|
423
|
+
reject(new Error(`Pandoc failed with code ${code}: ${stderr}`));
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
pandoc.on("error", (error) => {
|
|
427
|
+
clearTimeout(timeout);
|
|
428
|
+
reject(new Error(`Failed to execute Pandoc: ${error.message}`));
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
function estimatePageCount(buffer) {
|
|
433
|
+
const content = buffer.toString("binary");
|
|
434
|
+
const matches = content.match(/\/Type\s*\/Page[^s]/g);
|
|
435
|
+
return matches ? matches.length : 1;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/presets.ts
|
|
439
|
+
async function generateMemo(content, metadata, options) {
|
|
440
|
+
return generatePDF({
|
|
441
|
+
content,
|
|
442
|
+
metadata,
|
|
443
|
+
template: "memo",
|
|
444
|
+
toc: false,
|
|
445
|
+
numberSections: true,
|
|
446
|
+
fontSize: 11,
|
|
447
|
+
margin: "1in",
|
|
448
|
+
...options
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
async function generateAgreement(content, metadata, options) {
|
|
452
|
+
return generatePDF({
|
|
453
|
+
content,
|
|
454
|
+
metadata,
|
|
455
|
+
template: "agreement",
|
|
456
|
+
toc: false,
|
|
457
|
+
numberSections: true,
|
|
458
|
+
fontSize: 10,
|
|
459
|
+
margin: "1in",
|
|
460
|
+
...options
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
async function generateTermsheet(content, metadata, options) {
|
|
464
|
+
return generatePDF({
|
|
465
|
+
content,
|
|
466
|
+
metadata,
|
|
467
|
+
template: "termsheet",
|
|
468
|
+
toc: false,
|
|
469
|
+
numberSections: true,
|
|
470
|
+
fontSize: 10,
|
|
471
|
+
margin: "1in",
|
|
472
|
+
...options
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
async function generateWhitepaper(content, metadata, options) {
|
|
476
|
+
return generatePDF({
|
|
477
|
+
content,
|
|
478
|
+
metadata,
|
|
479
|
+
template: "default",
|
|
480
|
+
toc: true,
|
|
481
|
+
tocDepth: 2,
|
|
482
|
+
numberSections: true,
|
|
483
|
+
fontSize: 11,
|
|
484
|
+
margin: "1in",
|
|
485
|
+
...options
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
async function generateReport(content, metadata, options) {
|
|
489
|
+
return generatePDF({
|
|
490
|
+
content,
|
|
491
|
+
metadata,
|
|
492
|
+
template: "default",
|
|
493
|
+
toc: true,
|
|
494
|
+
tocDepth: 2,
|
|
495
|
+
numberSections: true,
|
|
496
|
+
fontSize: 11,
|
|
497
|
+
margin: "1in",
|
|
498
|
+
...options
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
async function generateProposal(content, metadata, options) {
|
|
502
|
+
return generatePDF({
|
|
503
|
+
content,
|
|
504
|
+
metadata,
|
|
505
|
+
template: "default",
|
|
506
|
+
toc: false,
|
|
507
|
+
numberSections: true,
|
|
508
|
+
fontSize: 11,
|
|
509
|
+
margin: "1in",
|
|
510
|
+
...options
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
async function generateSAFE(content, metadata, options) {
|
|
514
|
+
return generatePDF({
|
|
515
|
+
content,
|
|
516
|
+
metadata,
|
|
517
|
+
template: "agreement",
|
|
518
|
+
toc: false,
|
|
519
|
+
numberSections: true,
|
|
520
|
+
fontSize: 10,
|
|
521
|
+
margin: "1in",
|
|
522
|
+
...options
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
async function generateNDA(content, metadata, options) {
|
|
526
|
+
return generatePDF({
|
|
527
|
+
content,
|
|
528
|
+
metadata,
|
|
529
|
+
template: "agreement",
|
|
530
|
+
toc: false,
|
|
531
|
+
numberSections: true,
|
|
532
|
+
fontSize: 10,
|
|
533
|
+
margin: "1in",
|
|
534
|
+
...options
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/cli.ts
|
|
539
|
+
var VERSION = "0.3.1";
|
|
540
|
+
var HELP = `
|
|
541
|
+
ai-pdf-builder v${VERSION}
|
|
542
|
+
Generate professional PDFs from Markdown
|
|
543
|
+
|
|
544
|
+
USAGE:
|
|
545
|
+
ai-pdf-builder <command> [options]
|
|
546
|
+
|
|
547
|
+
COMMANDS:
|
|
548
|
+
generate <type> <input> Generate a PDF from markdown file
|
|
549
|
+
check Check system requirements
|
|
550
|
+
templates List available templates
|
|
551
|
+
help Show this help message
|
|
552
|
+
|
|
553
|
+
DOCUMENT TYPES:
|
|
554
|
+
whitepaper Technical documentation, litepapers
|
|
555
|
+
memo Executive summaries, internal memos
|
|
556
|
+
agreement Legal contracts, general agreements
|
|
557
|
+
termsheet Investment term sheets
|
|
558
|
+
safe SAFE agreements
|
|
559
|
+
nda Non-disclosure agreements
|
|
560
|
+
report Business reports, analysis
|
|
561
|
+
proposal Business proposals, pitches
|
|
562
|
+
|
|
563
|
+
OPTIONS:
|
|
564
|
+
-o, --output <file> Output file path (default: output.pdf)
|
|
565
|
+
-t, --title <title> Document title
|
|
566
|
+
-s, --subtitle <text> Document subtitle
|
|
567
|
+
-a, --author <name> Author name
|
|
568
|
+
-d, --date <date> Document date
|
|
569
|
+
-v, --version <ver> Document version
|
|
570
|
+
--no-toc Disable table of contents
|
|
571
|
+
--color-primary <hex> Primary brand color
|
|
572
|
+
--color-secondary <hex> Secondary brand color
|
|
573
|
+
|
|
574
|
+
EXAMPLES:
|
|
575
|
+
# Generate a whitepaper
|
|
576
|
+
ai-pdf-builder generate whitepaper ./content.md -o whitepaper.pdf -t "My Project"
|
|
577
|
+
|
|
578
|
+
# Generate a term sheet
|
|
579
|
+
ai-pdf-builder generate termsheet ./terms.md -t "Series Seed" -s "Acme Inc."
|
|
580
|
+
|
|
581
|
+
# Generate a SAFE
|
|
582
|
+
ai-pdf-builder generate safe ./safe.md -o safe-agreement.pdf
|
|
583
|
+
|
|
584
|
+
# Check if Pandoc/LaTeX are installed
|
|
585
|
+
ai-pdf-builder check
|
|
586
|
+
|
|
587
|
+
# List available templates
|
|
588
|
+
ai-pdf-builder templates
|
|
589
|
+
`;
|
|
590
|
+
function parseArgs(args) {
|
|
591
|
+
const options = {
|
|
592
|
+
output: "output.pdf",
|
|
593
|
+
toc: true
|
|
594
|
+
};
|
|
595
|
+
let command = "help";
|
|
596
|
+
let type;
|
|
597
|
+
let input;
|
|
598
|
+
let i = 0;
|
|
599
|
+
while (i < args.length) {
|
|
600
|
+
const arg = args[i];
|
|
601
|
+
if (arg === "generate" || arg === "check" || arg === "templates" || arg === "help") {
|
|
602
|
+
command = arg;
|
|
603
|
+
if (arg === "generate") {
|
|
604
|
+
type = args[++i];
|
|
605
|
+
input = args[++i];
|
|
606
|
+
}
|
|
607
|
+
} else if (arg === "-o" || arg === "--output") {
|
|
608
|
+
options.output = args[++i];
|
|
609
|
+
} else if (arg === "-t" || arg === "--title") {
|
|
610
|
+
options.title = args[++i];
|
|
611
|
+
} else if (arg === "-s" || arg === "--subtitle") {
|
|
612
|
+
options.subtitle = args[++i];
|
|
613
|
+
} else if (arg === "-a" || arg === "--author") {
|
|
614
|
+
options.author = args[++i];
|
|
615
|
+
} else if (arg === "-d" || arg === "--date") {
|
|
616
|
+
options.date = args[++i];
|
|
617
|
+
} else if (arg === "-v" || arg === "--version") {
|
|
618
|
+
options.version = args[++i];
|
|
619
|
+
} else if (arg === "--no-toc") {
|
|
620
|
+
options.toc = false;
|
|
621
|
+
} else if (arg === "--color-primary") {
|
|
622
|
+
options.colorPrimary = args[++i];
|
|
623
|
+
} else if (arg === "--color-secondary") {
|
|
624
|
+
options.colorSecondary = args[++i];
|
|
625
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
626
|
+
command = "help";
|
|
627
|
+
}
|
|
628
|
+
i++;
|
|
629
|
+
}
|
|
630
|
+
return { command, type, input, options };
|
|
631
|
+
}
|
|
632
|
+
async function runGenerate(type, input, options) {
|
|
633
|
+
if (!fs4.existsSync(input)) {
|
|
634
|
+
console.error(`Error: Input file not found: ${input}`);
|
|
635
|
+
process.exit(1);
|
|
636
|
+
}
|
|
637
|
+
const content = fs4.readFileSync(input, "utf-8");
|
|
638
|
+
const metadata = {
|
|
639
|
+
title: options.title || path4.basename(input, path4.extname(input)),
|
|
640
|
+
subtitle: options.subtitle || "",
|
|
641
|
+
author: options.author || "",
|
|
642
|
+
date: options.date || (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", { month: "long", year: "numeric" }),
|
|
643
|
+
version: options.version || ""
|
|
644
|
+
};
|
|
645
|
+
const pdfOptions = {
|
|
646
|
+
outputPath: path4.resolve(options.output),
|
|
647
|
+
toc: options.toc,
|
|
648
|
+
customColors: options.colorPrimary || options.colorSecondary ? {
|
|
649
|
+
primary: options.colorPrimary,
|
|
650
|
+
secondary: options.colorSecondary
|
|
651
|
+
} : void 0
|
|
652
|
+
};
|
|
653
|
+
console.log(`Generating ${type}...`);
|
|
654
|
+
let result;
|
|
655
|
+
switch (type) {
|
|
656
|
+
case "whitepaper":
|
|
657
|
+
result = await generateWhitepaper(content, metadata, pdfOptions);
|
|
658
|
+
break;
|
|
659
|
+
case "memo":
|
|
660
|
+
result = await generateMemo(content, metadata, pdfOptions);
|
|
661
|
+
break;
|
|
662
|
+
case "agreement":
|
|
663
|
+
result = await generateAgreement(content, metadata, pdfOptions);
|
|
664
|
+
break;
|
|
665
|
+
case "termsheet":
|
|
666
|
+
result = await generateTermsheet(content, metadata, pdfOptions);
|
|
667
|
+
break;
|
|
668
|
+
case "safe":
|
|
669
|
+
result = await generateSAFE(content, metadata, pdfOptions);
|
|
670
|
+
break;
|
|
671
|
+
case "nda":
|
|
672
|
+
result = await generateNDA(content, metadata, pdfOptions);
|
|
673
|
+
break;
|
|
674
|
+
case "report":
|
|
675
|
+
result = await generateReport(content, metadata, pdfOptions);
|
|
676
|
+
break;
|
|
677
|
+
case "proposal":
|
|
678
|
+
result = await generateProposal(content, metadata, pdfOptions);
|
|
679
|
+
break;
|
|
680
|
+
default:
|
|
681
|
+
console.error(`Unknown document type: ${type}`);
|
|
682
|
+
console.log("Valid types: whitepaper, memo, agreement, termsheet, safe, nda, report, proposal");
|
|
683
|
+
process.exit(1);
|
|
684
|
+
}
|
|
685
|
+
if (result.success) {
|
|
686
|
+
console.log(`\u2713 Generated: ${options.output}`);
|
|
687
|
+
console.log(` Size: ${(result.fileSize / 1024).toFixed(1)} KB`);
|
|
688
|
+
console.log(` Time: ${result.generationTime}ms`);
|
|
689
|
+
if (result.pageCount) {
|
|
690
|
+
console.log(` Pages: ${result.pageCount}`);
|
|
691
|
+
}
|
|
692
|
+
} else {
|
|
693
|
+
console.error(`\u2717 Error: ${result.error}`);
|
|
694
|
+
process.exit(1);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
function runCheck() {
|
|
698
|
+
console.log("Checking system requirements...\n");
|
|
699
|
+
const check = checkSystem();
|
|
700
|
+
if (check.ready) {
|
|
701
|
+
console.log("\u2713 System is ready for PDF generation\n");
|
|
702
|
+
console.log(` Pandoc: ${check.pandoc?.version || "installed"}`);
|
|
703
|
+
console.log(` LaTeX: ${check.latex?.version || "installed"}`);
|
|
704
|
+
} else {
|
|
705
|
+
console.log("\u2717 System requirements not met\n");
|
|
706
|
+
console.log(` ${check.message}
|
|
707
|
+
`);
|
|
708
|
+
console.log("Install instructions:");
|
|
709
|
+
console.log(" macOS: brew install pandoc && brew install --cask basictex");
|
|
710
|
+
console.log(" Linux: sudo apt-get install pandoc texlive-full");
|
|
711
|
+
process.exit(1);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function runTemplates() {
|
|
715
|
+
console.log("Available templates:\n");
|
|
716
|
+
const templates = listTemplates();
|
|
717
|
+
templates.forEach((t) => {
|
|
718
|
+
console.log(` ${t.name.padEnd(15)} - ${t.description || "No description"}`);
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
async function main() {
|
|
722
|
+
const args = process.argv.slice(2);
|
|
723
|
+
if (args.length === 0) {
|
|
724
|
+
console.log(HELP);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const { command, type, input, options } = parseArgs(args);
|
|
728
|
+
switch (command) {
|
|
729
|
+
case "generate":
|
|
730
|
+
if (!type || !input) {
|
|
731
|
+
console.error("Error: generate requires <type> and <input> arguments");
|
|
732
|
+
console.log("Usage: ai-pdf-builder generate <type> <input> [options]");
|
|
733
|
+
process.exit(1);
|
|
734
|
+
}
|
|
735
|
+
await runGenerate(type, input, options);
|
|
736
|
+
break;
|
|
737
|
+
case "check":
|
|
738
|
+
runCheck();
|
|
739
|
+
break;
|
|
740
|
+
case "templates":
|
|
741
|
+
runTemplates();
|
|
742
|
+
break;
|
|
743
|
+
case "help":
|
|
744
|
+
default:
|
|
745
|
+
console.log(HELP);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
main().catch((err) => {
|
|
749
|
+
console.error("Error:", err.message);
|
|
750
|
+
process.exit(1);
|
|
751
|
+
});
|