autoscholar-cli 1.0.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.
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getIMFData = exports.searchWorldBank = exports.searchExa = exports.searchTavily = exports.searchSerper = exports.getOpenAlexReferences = exports.multiSourceSearch = exports.searchGoogleScholar = exports.searchCrossRef = exports.searchArxiv = exports.searchOpenAlex = exports.FirecrawlClient = exports.FREDClient = exports.EODHDClient = exports.FMPClient = void 0;
4
+ var fmp_1 = require("./fmp");
5
+ Object.defineProperty(exports, "FMPClient", { enumerable: true, get: function () { return fmp_1.FMPClient; } });
6
+ var eodhd_1 = require("./eodhd");
7
+ Object.defineProperty(exports, "EODHDClient", { enumerable: true, get: function () { return eodhd_1.EODHDClient; } });
8
+ var fred_1 = require("./fred");
9
+ Object.defineProperty(exports, "FREDClient", { enumerable: true, get: function () { return fred_1.FREDClient; } });
10
+ var firecrawl_1 = require("./firecrawl");
11
+ Object.defineProperty(exports, "FirecrawlClient", { enumerable: true, get: function () { return firecrawl_1.FirecrawlClient; } });
12
+ var academic_1 = require("./academic");
13
+ Object.defineProperty(exports, "searchOpenAlex", { enumerable: true, get: function () { return academic_1.searchOpenAlex; } });
14
+ Object.defineProperty(exports, "searchArxiv", { enumerable: true, get: function () { return academic_1.searchArxiv; } });
15
+ Object.defineProperty(exports, "searchCrossRef", { enumerable: true, get: function () { return academic_1.searchCrossRef; } });
16
+ Object.defineProperty(exports, "searchGoogleScholar", { enumerable: true, get: function () { return academic_1.searchGoogleScholar; } });
17
+ Object.defineProperty(exports, "multiSourceSearch", { enumerable: true, get: function () { return academic_1.multiSourceSearch; } });
18
+ Object.defineProperty(exports, "getOpenAlexReferences", { enumerable: true, get: function () { return academic_1.getOpenAlexReferences; } });
19
+ var websearch_1 = require("./websearch");
20
+ Object.defineProperty(exports, "searchSerper", { enumerable: true, get: function () { return websearch_1.searchSerper; } });
21
+ Object.defineProperty(exports, "searchTavily", { enumerable: true, get: function () { return websearch_1.searchTavily; } });
22
+ Object.defineProperty(exports, "searchExa", { enumerable: true, get: function () { return websearch_1.searchExa; } });
23
+ Object.defineProperty(exports, "searchWorldBank", { enumerable: true, get: function () { return websearch_1.searchWorldBank; } });
24
+ Object.defineProperty(exports, "getIMFData", { enumerable: true, get: function () { return websearch_1.getIMFData; } });
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.searchSerper = searchSerper;
7
+ exports.searchTavily = searchTavily;
8
+ exports.searchExa = searchExa;
9
+ exports.searchWorldBank = searchWorldBank;
10
+ exports.getIMFData = getIMFData;
11
+ const axios_1 = __importDefault(require("axios"));
12
+ async function searchSerper(query, apiKey, options) {
13
+ try {
14
+ const response = await axios_1.default.post('https://google.serper.dev/search', {
15
+ q: query,
16
+ num: options?.num || 10,
17
+ type: options?.type || 'search',
18
+ }, {
19
+ headers: {
20
+ 'X-API-KEY': apiKey,
21
+ 'Content-Type': 'application/json',
22
+ },
23
+ timeout: 15000,
24
+ });
25
+ return (response.data.organic || []).map((r) => ({
26
+ title: r.title || '',
27
+ url: r.link || '',
28
+ snippet: r.snippet || '',
29
+ }));
30
+ }
31
+ catch {
32
+ return [];
33
+ }
34
+ }
35
+ async function searchTavily(query, apiKey, options) {
36
+ try {
37
+ const response = await axios_1.default.post('https://api.tavily.com/search', {
38
+ query,
39
+ max_results: options?.maxResults || 10,
40
+ search_depth: options?.searchDepth || 'basic',
41
+ include_answer: options?.includeAnswer ?? true,
42
+ api_key: apiKey,
43
+ }, { timeout: 20000 });
44
+ return {
45
+ answer: response.data.answer,
46
+ results: (response.data.results || []).map((r) => ({
47
+ title: r.title || '',
48
+ url: r.url || '',
49
+ content: r.content || '',
50
+ score: r.score || 0,
51
+ })),
52
+ };
53
+ }
54
+ catch {
55
+ return { results: [] };
56
+ }
57
+ }
58
+ async function searchExa(query, apiKey, options) {
59
+ try {
60
+ const response = await axios_1.default.post('https://api.exa.ai/search', {
61
+ query,
62
+ numResults: options?.numResults || 10,
63
+ useAutoprompt: options?.useAutoprompt ?? true,
64
+ type: options?.type || 'neural',
65
+ contents: { text: true },
66
+ }, {
67
+ headers: {
68
+ 'x-api-key': apiKey,
69
+ 'Content-Type': 'application/json',
70
+ },
71
+ timeout: 20000,
72
+ });
73
+ return (response.data.results || []).map((r) => ({
74
+ title: r.title || '',
75
+ url: r.url || '',
76
+ text: r.text || '',
77
+ publishedDate: r.publishedDate,
78
+ }));
79
+ }
80
+ catch {
81
+ return [];
82
+ }
83
+ }
84
+ async function searchWorldBank(indicator, country = 'all', options) {
85
+ try {
86
+ const response = await axios_1.default.get(`https://api.worldbank.org/v2/country/${country}/indicator/${indicator}`, {
87
+ params: {
88
+ format: 'json',
89
+ date: options?.dateRange || '2000:2025',
90
+ per_page: options?.perPage || 500,
91
+ },
92
+ timeout: 15000,
93
+ });
94
+ if (Array.isArray(response.data) && response.data.length > 1) {
95
+ return response.data[1] || [];
96
+ }
97
+ return [];
98
+ }
99
+ catch {
100
+ return [];
101
+ }
102
+ }
103
+ async function getIMFData(datasetId, frequency, country, indicator, startPeriod, endPeriod) {
104
+ try {
105
+ const url = `http://dataservices.imf.org/REST/SDMX_JSON.svc/CompactData/${datasetId}/${frequency}.${country}.${indicator}`;
106
+ const params = {};
107
+ if (startPeriod)
108
+ params.startPeriod = startPeriod;
109
+ if (endPeriod)
110
+ params.endPeriod = endPeriod;
111
+ const response = await axios_1.default.get(url, { params, timeout: 20000 });
112
+ return response.data;
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ }
package/dist/index.js ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const banner_1 = require("./cli/banner");
6
+ const interactive_1 = require("./cli/interactive");
7
+ const runCommand_1 = require("./cli/runCommand");
8
+ const configCommand_1 = require("./cli/configCommand");
9
+ const outputsCommand_1 = require("./cli/outputsCommand");
10
+ const resumeCommand_1 = require("./cli/resumeCommand");
11
+ const loader_1 = require("./config/loader");
12
+ const program = new commander_1.Command();
13
+ program
14
+ .name('autoscholar')
15
+ .description('Autonomous scientific research agent — generate publication-ready papers from your terminal')
16
+ .version('1.0.0');
17
+ program
18
+ .command('run')
19
+ .description('Run full research pipeline')
20
+ .option('--topic <topic>', 'Research topic')
21
+ .option('--assets <assets>', 'Comma-separated asset list (e.g. BTC,ETH,WTI)')
22
+ .option('--method <method>', 'Econometric method (e.g. DCC-GARCH, OLS, VAR)')
23
+ .option('--hf', 'Use high-frequency data')
24
+ .option('--dry', 'Dry run — show plan without executing')
25
+ .option('--background', 'Run in background mode')
26
+ .action(async (opts) => {
27
+ (0, banner_1.showBanner)();
28
+ const config = await (0, loader_1.ensureConfig)();
29
+ if (!config)
30
+ return;
31
+ await (0, runCommand_1.runPipeline)(opts, config);
32
+ });
33
+ program
34
+ .command('config')
35
+ .description('Manage API keys and configuration')
36
+ .action(async () => {
37
+ (0, banner_1.showBanner)();
38
+ await (0, configCommand_1.manageConfig)();
39
+ });
40
+ program
41
+ .command('outputs')
42
+ .description('View and manage generated research outputs')
43
+ .action(async () => {
44
+ (0, banner_1.showBanner)();
45
+ const config = await (0, loader_1.ensureConfig)();
46
+ if (!config)
47
+ return;
48
+ await (0, outputsCommand_1.listOutputs)();
49
+ });
50
+ program
51
+ .command('resume <projectId>')
52
+ .description('Resume an existing research project')
53
+ .action(async (projectId) => {
54
+ (0, banner_1.showBanner)();
55
+ const config = await (0, loader_1.ensureConfig)();
56
+ if (!config)
57
+ return;
58
+ await (0, resumeCommand_1.resumeProject)(projectId, config);
59
+ });
60
+ if (process.argv.length <= 2) {
61
+ (async () => {
62
+ (0, banner_1.showBanner)();
63
+ const config = await (0, loader_1.ensureConfig)();
64
+ if (!config)
65
+ return;
66
+ (0, banner_1.showWelcome)();
67
+ await (0, interactive_1.runInteractiveMode)(config);
68
+ })();
69
+ }
70
+ else {
71
+ program.parse();
72
+ }
@@ -0,0 +1,413 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.generatePaper = generatePaper;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const child_process_1 = require("child_process");
43
+ const ora_1 = __importDefault(require("ora"));
44
+ const llm_1 = require("../utils/llm");
45
+ const banner_1 = require("../cli/banner");
46
+ const project_1 = require("../utils/project");
47
+ const SECTIONS = [
48
+ 'introduction',
49
+ 'literature_review',
50
+ 'methodology',
51
+ 'data',
52
+ 'results',
53
+ 'discussion',
54
+ 'conclusion',
55
+ ];
56
+ async function generatePaper(input, projectId, config, logger) {
57
+ const spinner = (0, ora_1.default)({ text: 'Writing paper sections...', indent: 2 }).start();
58
+ const projectDir = (0, project_1.getProjectDir)(projectId);
59
+ const outputDir = path.join(projectDir, 'output');
60
+ if (!fs.existsSync(outputDir))
61
+ fs.mkdirSync(outputDir, { recursive: true });
62
+ logger.info(`[LATEX] Starting paper generation: ${input.title}`);
63
+ const sectionContents = {};
64
+ for (let i = 0; i < SECTIONS.length; i++) {
65
+ const section = SECTIONS[i];
66
+ spinner.text = `Writing section ${i + 1}/${SECTIONS.length}: ${section}...`;
67
+ logger.info(`[LATEX] Generating section: ${section}`);
68
+ try {
69
+ const content = await generateSection(section, input, config);
70
+ sectionContents[section] = content;
71
+ logger.info(`[LATEX] Section ${section}: ${content.length} chars`);
72
+ }
73
+ catch (err) {
74
+ logger.warn(`[LATEX] Section ${section} failed: ${err.message}`);
75
+ sectionContents[section] = `% Section ${section} generation failed\n\\textit{This section could not be generated.}`;
76
+ }
77
+ }
78
+ spinner.text = 'Assembling LaTeX document...';
79
+ const latex = buildLatexDocument(input, sectionContents);
80
+ const texPath = path.join(outputDir, 'paper.tex');
81
+ fs.writeFileSync(texPath, latex, 'utf-8');
82
+ if (input.bibtexBlock) {
83
+ const bibPath = path.join(outputDir, 'references.bib');
84
+ fs.writeFileSync(bibPath, input.bibtexBlock, 'utf-8');
85
+ }
86
+ logger.info(`[LATEX] Document assembled: ${texPath}`);
87
+ spinner.text = 'Compiling LaTeX to PDF...';
88
+ const compileResult = compilePdf(texPath, outputDir, logger);
89
+ spinner.stop();
90
+ if (compileResult.success) {
91
+ (0, banner_1.printSuccess)(`Paper compiled: ${compileResult.pdfPath}`);
92
+ (0, banner_1.printInfo)(`Pages: ~${compileResult.pageCount}`);
93
+ }
94
+ else {
95
+ (0, banner_1.printError)('PDF compilation failed — LaTeX source saved');
96
+ (0, banner_1.printInfo)(`LaTeX: ${texPath}`);
97
+ }
98
+ return compileResult;
99
+ }
100
+ async function generateSection(section, input, config) {
101
+ const sectionPrompts = {
102
+ introduction: `Write the Introduction section for an academic paper.
103
+ Include: motivation, research question, contribution statement, paper outline.
104
+ Topic: ${input.topic}
105
+ Title: ${input.title}
106
+ Contributions: ${input.contributions.join('; ')}
107
+ Method: ${input.methodology}`,
108
+ literature_review: `Write the Literature Review section.
109
+ Synthesize the academic literature, organize by themes, identify the gap being addressed.
110
+ Topic: ${input.topic}
111
+ Literature synthesis: ${input.literatureSynthesis.substring(0, 3000)}
112
+ Gap: ${input.gapAnalysis?.recommendedGap?.description || 'N/A'}`,
113
+ methodology: `Write the Methodology section.
114
+ Explain the econometric/statistical framework, model specification, estimation procedure, and identification strategy.
115
+ Method: ${input.methodology}
116
+ Method type: ${input.methodType}
117
+ Topic: ${input.topic}`,
118
+ data: `Write the Data Description section.
119
+ Describe data sources, sample construction, variable definitions, and descriptive statistics.
120
+ Sources: ${input.dataDescription.sources.join(', ')}
121
+ Total observations: ${input.dataDescription.totalRows}
122
+ Datasets: ${input.dataDescription.datasets.map((d) => `${d.source}: ${d.description} (${d.rows} rows)`).join('; ')}
123
+ Transformations: ${input.dataDescription.transformations.join(', ')}`,
124
+ results: `Write the Empirical Results section.
125
+ Present main findings, robustness checks, and statistical significance.
126
+ Models estimated: ${input.analysisResults.models.map((m) => `${m.name} (R²=${m.rSquared?.toFixed(3) || 'N/A'})`).join('; ')}
127
+ Key diagnostics: ${input.analysisResults.diagnostics.join('; ')}
128
+ Summary: ${input.analysisResults.summary}`,
129
+ discussion: `Write the Discussion section.
130
+ Interpret findings, compare with prior literature, discuss implications, acknowledge limitations.
131
+ Topic: ${input.topic}
132
+ Key findings: ${input.analysisResults.summary}
133
+ Literature context: ${input.literatureSynthesis.substring(0, 1000)}`,
134
+ conclusion: `Write the Conclusion section.
135
+ Summarize contributions, main findings, policy implications, and future research directions.
136
+ Title: ${input.title}
137
+ Contributions: ${input.contributions.join('; ')}
138
+ Key findings: ${input.analysisResults.summary}`,
139
+ };
140
+ const response = await (0, llm_1.callLLM)(config, `You are an expert academic writer producing content for a top-tier journal.
141
+
142
+ RULES:
143
+ - Write in LaTeX format (no \\section{} header — it will be added automatically)
144
+ - Use proper academic language — formal, precise, citation-rich
145
+ - Reference cite keys using \\cite{key} or \\citet{key}, \\citep{key}
146
+ - Use math mode for equations: $...$ or \\begin{equation}...\\end{equation}
147
+ - Use \\textbf{} for emphasis, not bold
148
+ - Do NOT include \\begin{document} or \\end{document}
149
+ - Write 2-5 pages of content per section
150
+ - Use professional academic English suitable for Journal of Finance / Econometrica`, sectionPrompts[section] || `Write the ${section} section for: ${input.topic}`, { maxTokens: 8192 });
151
+ let content = response;
152
+ const latexMatch = response.match(/```(?:latex)?\s*\n?([\s\S]*?)```/);
153
+ if (latexMatch) {
154
+ content = latexMatch[1];
155
+ }
156
+ return content;
157
+ }
158
+ function buildLatexDocument(input, sections) {
159
+ const figureIncludes = input.analysisResults.figures
160
+ .slice(0, 20)
161
+ .map((fig, i) => {
162
+ const relPath = `figures/${fig.filename}`;
163
+ return `
164
+ \\begin{figure}[htbp]
165
+ \\centering
166
+ \\includegraphics[width=0.85\\textwidth]{${relPath}}
167
+ \\caption{${escapeLatex(fig.caption)}}
168
+ \\label{fig:fig${i + 1}}
169
+ \\end{figure}`;
170
+ })
171
+ .join('\n');
172
+ const tableIncludes = input.analysisResults.tables
173
+ .slice(0, 10)
174
+ .map((tbl) => tbl.latex)
175
+ .join('\n\n');
176
+ return `\\documentclass[12pt,a4paper]{article}
177
+
178
+ % ── Encoding & Fonts ──
179
+ \\usepackage[utf8]{inputenc}
180
+ \\usepackage[T1]{fontenc}
181
+ \\usepackage{libertine}
182
+ \\usepackage[libertine]{newtxmath}
183
+ \\usepackage{microtype}
184
+
185
+ % ── Layout ──
186
+ \\usepackage[margin=1in]{geometry}
187
+ \\usepackage{setspace}
188
+ \\onehalfspacing
189
+
190
+ % ── Packages ──
191
+ \\usepackage{amsmath,amssymb,amsthm}
192
+ \\usepackage{graphicx}
193
+ \\usepackage{booktabs}
194
+ \\usepackage{longtable}
195
+ \\usepackage{hyperref}
196
+ \\usepackage{natbib}
197
+ \\usepackage{xcolor}
198
+ \\usepackage{float}
199
+ \\usepackage{caption}
200
+ \\usepackage{subcaption}
201
+ \\usepackage{enumitem}
202
+ \\usepackage{tabularx}
203
+ \\usepackage{adjustbox}
204
+
205
+ % ── Colors ──
206
+ \\definecolor{linkblue}{HTML}{0077B6}
207
+ \\hypersetup{
208
+ colorlinks=true,
209
+ linkcolor=linkblue,
210
+ citecolor=linkblue,
211
+ urlcolor=linkblue
212
+ }
213
+
214
+ % ── Theorem Environments ──
215
+ \\newtheorem{theorem}{Theorem}
216
+ \\newtheorem{proposition}{Proposition}
217
+ \\newtheorem{lemma}{Lemma}
218
+ \\newtheorem{corollary}{Corollary}
219
+ \\newtheorem{hypothesis}{Hypothesis}
220
+
221
+ % ── Custom Commands ──
222
+ \\newcommand{\\E}{\\mathbb{E}}
223
+ \\newcommand{\\Var}{\\text{Var}}
224
+ \\newcommand{\\Cov}{\\text{Cov}}
225
+
226
+ \\title{\\textbf{${escapeLatex(input.title)}}}
227
+ \\author{Euler --- The AutoScholar Research Agent\\\\\\textit{AutoScholar.ai}}
228
+ \\date{\\today}
229
+
230
+ \\begin{document}
231
+
232
+ \\maketitle
233
+
234
+ \\begin{abstract}
235
+ ${input.abstract || 'This paper investigates ' + escapeLatex(input.topic) + '.'}
236
+ \\end{abstract}
237
+
238
+ \\textbf{Keywords:} ${escapeLatex(input.topic)}
239
+
240
+ \\textbf{JEL Classification:} G10, G12, C58
241
+
242
+ \\newpage
243
+ \\tableofcontents
244
+ \\newpage
245
+
246
+ % ═══════════════════════════════════════════
247
+ \\section{Introduction}
248
+ % ═══════════════════════════════════════════
249
+
250
+ ${sections.introduction || ''}
251
+
252
+ % ═══════════════════════════════════════════
253
+ \\section{Literature Review}
254
+ % ═══════════════════════════════════════════
255
+
256
+ ${sections.literature_review || ''}
257
+
258
+ % ═══════════════════════════════════════════
259
+ \\section{Methodology}
260
+ % ═══════════════════════════════════════════
261
+
262
+ ${sections.methodology || ''}
263
+
264
+ % ═══════════════════════════════════════════
265
+ \\section{Data}
266
+ % ═══════════════════════════════════════════
267
+
268
+ ${sections.data || ''}
269
+
270
+ % ═══════════════════════════════════════════
271
+ \\section{Empirical Results}
272
+ % ═══════════════════════════════════════════
273
+
274
+ ${sections.results || ''}
275
+
276
+ ${tableIncludes}
277
+
278
+ ${figureIncludes}
279
+
280
+ % ═══════════════════════════════════════════
281
+ \\section{Discussion}
282
+ % ═══════════════════════════════════════════
283
+
284
+ ${sections.discussion || ''}
285
+
286
+ % ═══════════════════════════════════════════
287
+ \\section{Conclusion}
288
+ % ═══════════════════════════════════════════
289
+
290
+ ${sections.conclusion || ''}
291
+
292
+ % ═══════════════════════════════════════════
293
+ % References
294
+ % ═══════════════════════════════════════════
295
+
296
+ \\bibliographystyle{apalike}
297
+ \\bibliography{references}
298
+
299
+ % ═══════════════════════════════════════════
300
+ \\appendix
301
+ \\section{AI Disclosure}
302
+ % ═══════════════════════════════════════════
303
+
304
+ This paper was generated by \\textbf{AutoScholar}, an autonomous scientific research agent.
305
+ The entire research pipeline — from literature review to data collection, econometric analysis,
306
+ and paper writing — was executed autonomously by the Euler orchestration system.
307
+
308
+ \\begin{itemize}
309
+ \\item \\textbf{Gauss} (Literature Gap Finder): Searched and analyzed academic literature
310
+ \\item \\textbf{Turing} (Data Collector): Gathered data from financial and macroeconomic APIs
311
+ \\item \\textbf{Newton} (Dataset Builder): Constructed and cleaned the analysis dataset
312
+ \\item \\textbf{Fisher} (Analysis Engine): Estimated econometric models and generated figures
313
+ \\item \\textbf{Euler} (Orchestrator): Coordinated the pipeline and wrote this paper
314
+ \\end{itemize}
315
+
316
+ \\end{document}
317
+ `;
318
+ }
319
+ function compilePdf(texPath, outputDir, logger) {
320
+ const pdfPath = texPath.replace('.tex', '.pdf');
321
+ const pdflatexPaths = [
322
+ '/Library/TeX/texbin/pdflatex',
323
+ '/usr/local/texlive/2025/bin/universal-darwin/pdflatex',
324
+ '/usr/local/texlive/2024/bin/universal-darwin/pdflatex',
325
+ '/opt/homebrew/bin/pdflatex',
326
+ 'pdflatex',
327
+ ];
328
+ let pdflatex = 'pdflatex';
329
+ for (const p of pdflatexPaths) {
330
+ try {
331
+ (0, child_process_1.execSync)(`${p} --version`, { stdio: 'ignore' });
332
+ pdflatex = p;
333
+ break;
334
+ }
335
+ catch { }
336
+ }
337
+ try {
338
+ const opts = {
339
+ cwd: outputDir,
340
+ timeout: 60000,
341
+ stdio: 'pipe',
342
+ maxBuffer: 10 * 1024 * 1024,
343
+ };
344
+ const texFile = path.basename(texPath);
345
+ logger.info('[LATEX] Compilation pass 1...');
346
+ (0, child_process_1.execSync)(`${pdflatex} -interaction=nonstopmode -halt-on-error "${texFile}"`, opts);
347
+ const bibFile = path.join(outputDir, 'references.bib');
348
+ if (fs.existsSync(bibFile)) {
349
+ try {
350
+ const auxFile = texFile.replace('.tex', '');
351
+ const bibtexPaths = [
352
+ '/Library/TeX/texbin/bibtex',
353
+ '/usr/local/texlive/2025/bin/universal-darwin/bibtex',
354
+ '/usr/local/texlive/2024/bin/universal-darwin/bibtex',
355
+ 'bibtex',
356
+ ];
357
+ let bibtex = 'bibtex';
358
+ for (const p of bibtexPaths) {
359
+ try {
360
+ (0, child_process_1.execSync)(`${p} --version 2>&1`, { stdio: 'ignore' });
361
+ bibtex = p;
362
+ break;
363
+ }
364
+ catch { }
365
+ }
366
+ logger.info('[LATEX] Running BibTeX...');
367
+ (0, child_process_1.execSync)(`${bibtex} "${auxFile}"`, { ...opts, timeout: 30000 });
368
+ }
369
+ catch {
370
+ logger.warn('[LATEX] BibTeX failed — continuing without bibliography');
371
+ }
372
+ }
373
+ logger.info('[LATEX] Compilation pass 2...');
374
+ (0, child_process_1.execSync)(`${pdflatex} -interaction=nonstopmode "${texFile}"`, opts);
375
+ logger.info('[LATEX] Compilation pass 3...');
376
+ (0, child_process_1.execSync)(`${pdflatex} -interaction=nonstopmode "${texFile}"`, opts);
377
+ if (fs.existsSync(pdfPath)) {
378
+ const stats = fs.statSync(pdfPath);
379
+ const pageCount = Math.max(1, Math.round(stats.size / 50000));
380
+ logger.info(`[LATEX] PDF generated: ${pdfPath} (${stats.size} bytes, ~${pageCount} pages)`);
381
+ return {
382
+ success: true,
383
+ pdfPath,
384
+ pageCount,
385
+ compilationLog: 'Compilation successful',
386
+ };
387
+ }
388
+ }
389
+ catch (err) {
390
+ logger.error(`[LATEX] Compilation error: ${err.message}`);
391
+ const logPath = texPath.replace('.tex', '.log');
392
+ if (fs.existsSync(logPath)) {
393
+ const log = fs.readFileSync(logPath, 'utf-8');
394
+ const errors = log.split('\n').filter((l) => l.startsWith('!')).slice(0, 5);
395
+ logger.error(`[LATEX] Errors: ${errors.join('; ')}`);
396
+ }
397
+ }
398
+ return {
399
+ success: false,
400
+ pdfPath: texPath,
401
+ pageCount: 0,
402
+ compilationLog: 'Compilation failed',
403
+ };
404
+ }
405
+ function escapeLatex(text) {
406
+ if (!text)
407
+ return '';
408
+ return text
409
+ .replace(/\\/g, '\\textbackslash{}')
410
+ .replace(/[&%$#_{}]/g, (match) => `\\${match}`)
411
+ .replace(/~/g, '\\textasciitilde{}')
412
+ .replace(/\^/g, '\\textasciicircum{}');
413
+ }