overleaf-forge 2.7.3 → 2.9.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/README.md CHANGED
@@ -148,7 +148,7 @@ For more than one project, per-project contexts, or the SSA bootstrap, use a `pr
148
148
  npx overleaf-forge init
149
149
  ```
150
150
 
151
- That creates `~/.overleaf-mcp/projects.json` from the example and copies the editable templates into `~/.overleaf-mcp/templates/`. Then fill in the config:
151
+ That creates `~/.overleaf-mcp/projects.json` from the example and copies the editable templates into `~/.overleaf-mcp/templates/`. You can fill in the settings conversationally instead of by hand: ask the assistant to set up overleaf-forge and it runs the `configure` tool (which asks where your templates, voice linter, and course folders live, then writes them). Or edit the file directly:
152
152
 
153
153
  ```json
154
154
  {
@@ -171,7 +171,7 @@ That creates `~/.overleaf-mcp/projects.json` from the example and copies the edi
171
171
  | `settings.gitToken` | Overleaf git token, used by every project (or set `OVERLEAF_GIT_TOKEN`). A per-project `gitToken` overrides it. |
172
172
  | `settings.repoDir` | Where project clones land by default (`repoDir/<name>`). A per-project `localPath` overrides it. |
173
173
  | `settings.templatesDir` | Optional. Directory of scaffold templates, overriding the bundled defaults. |
174
- | `settings.voiceLinter` | Optional. A prose-linter command (or set `OVERLEAF_VOICE_LINTER`) that `voice_lint` runs on a file. No default. |
174
+ | `settings.voiceLinter` | Optional. A prose-linter command (or set `OVERLEAF_VOICE_LINTER`) that `voice_lint` runs on a file. Defaults to the bundled `examples/voice-lint.mjs`; override with your own house style. |
175
175
  | `projects.<key>.projectId` | The id from `https://www.overleaf.com/project/<ID>`. |
176
176
  | `projects.<key>.cwd` | Directory you launch the client from for this project; used to auto-detect the active project. |
177
177
  | `projects.<key>.localPath` | Explicit clone location (optional). |
@@ -251,6 +251,7 @@ By the built-in convention this parses the name, locates the parent course folde
251
251
 
252
252
  | Tool | Purpose |
253
253
  | --- | --- |
254
+ | `configure` | Set up the global settings conversationally: call with no arguments to see what's unset and the questions to ask, then call again with the answers. Writes `projects.json`. |
254
255
  | `get_context` | Read the writing guidelines + the active project's context. Intended first call of a session. |
255
256
  | `list_projects` | List configured projects; mark which one auto-detects from the current directory. |
256
257
  | `register_project` | Add or overwrite a project entry without hand-editing JSON. |
@@ -293,7 +294,7 @@ By the built-in convention this parses the name, locates the parent course folde
293
294
  | `cite_lint` | Report undefined (`\cite` with no entry) and unused (entry never cited) citations. Read-only. |
294
295
  | `checkpoint` | Mark a local rollback point (a `mcp-snap/<label>` tag) before a risky edit. |
295
296
  | `restore` | Roll back to a checkpoint via a forward commit + push (no force, no history rewrite). |
296
- | `voice_lint` | Run a configured prose linter (`settings.voiceLinter`) on a `.tex`. Read-only, advisory. |
297
+ | `voice_lint` | Run a prose linter on a `.tex` (the bundled `examples/voice-lint.mjs` by default; override via `settings.voiceLinter`). Lints the local working copy as-is, never pulls. Read-only, advisory. |
297
298
 
298
299
  ## How it works
299
300
 
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ // Example prose linter for overleaf-forge's `voice_lint` tool.
3
+ //
4
+ // Contract: take a file path as the last argument, print findings to stderr,
5
+ // exit 2 if anything was found and 0 if clean. `voice_lint` runs whatever
6
+ // command settings.voiceLinter (or $OVERLEAF_VOICE_LINTER) points at; this
7
+ // script is the bundled default so the tool works with no configuration.
8
+ //
9
+ // The checks are deliberately generic writing hygiene, chosen to rarely
10
+ // false-positive on LaTeX. Replace them with your own house style: copy this
11
+ // file, edit CHECKS, and point settings.voiceLinter at your copy
12
+ // (e.g. "node /path/to/your-linter.mjs").
13
+ import { readFileSync } from 'node:fs';
14
+
15
+ const file = process.argv[2];
16
+ if (!file) process.exit(0);
17
+ let text;
18
+ try { text = readFileSync(file, 'utf8'); } catch { process.exit(0); }
19
+
20
+ // [regex, label]. Each is a documented, broadly-defensible prose check.
21
+ const CHECKS = [
22
+ [/\b(TODO|FIXME|XXX)\b/, 'leftover marker (TODO/FIXME/XXX)'],
23
+ [/\b(\w+)\s+\1\b/i, 'repeated word'],
24
+ [/\bin order to\b/i, "wordy: 'in order to' -> 'to'"],
25
+ [/\b(very|really|quite|basically|actually) \w/i, 'filler intensifier (often cuttable)'],
26
+ ];
27
+
28
+ // Skip code/verbatim environments and comment lines so source does not false-positive.
29
+ const SKIP_BEGIN = /\\begin\{(lstlisting|verbatim|minted|Verbatim)\}/;
30
+ const SKIP_END = /\\end\{(lstlisting|verbatim|minted|Verbatim)\}/;
31
+
32
+ const findings = [];
33
+ let skipping = false;
34
+ text.split(/\r?\n/).forEach((line, i) => {
35
+ if (SKIP_BEGIN.test(line)) { skipping = true; return; }
36
+ if (SKIP_END.test(line)) { skipping = false; return; }
37
+ if (skipping || line.trimStart().startsWith('%')) return;
38
+ for (const [rx, label] of CHECKS) {
39
+ if (rx.test(line)) findings.push(`${i + 1}: ${label}\n > ${line.trim().slice(0, 90)}`);
40
+ }
41
+ });
42
+
43
+ if (findings.length) {
44
+ process.stderr.write(
45
+ `voice-lint (bundled example): ${findings.length} finding(s)\n` +
46
+ findings.map(f => ' ' + f).join('\n') + '\n' +
47
+ '(this is the generic example linter; set settings.voiceLinter for your own rules)\n',
48
+ );
49
+ process.exit(2);
50
+ }
51
+ process.exit(0);
@@ -189,6 +189,35 @@ function resolveLocalPath(config, projectKey, project) {
189
189
  return path.join(DEFAULT_REPO_DIR, project.name || projectKey);
190
190
  }
191
191
 
192
+ // The global settings the `configure` tool manages, each with the question to
193
+ // ask the user when it is unset. Order is the order to ask in.
194
+ const SETTING_HELP = {
195
+ repoDir: 'Where should project clones land by default? (absolute path; clones go to repoDir/<name>)',
196
+ academicRoot: 'Where do your course folders live? (bootstrap_ssa searches academicRoot/Year <n>/Q*/<COURSE>*)',
197
+ ssaSubdir: 'What subfolder name should new SSAs go under inside each course folder? (e.g. "MY SSAs")',
198
+ templatesDir: 'Use your own scaffold templates? Give the directory holding main.tex / context-scaffold.md (blank keeps the bundled examples).',
199
+ voiceLinter: 'Use your own prose linter for voice_lint? Give the command (takes a file path, exits non-zero on findings; blank keeps the bundled example).',
200
+ gitToken: 'Overleaf git token (prefer the OVERLEAF_GIT_TOKEN env var; set here only if you must store it in projects.json).',
201
+ };
202
+ const SETTING_KEYS = Object.keys(SETTING_HELP);
203
+ const SETTING_PATHY = new Set(['repoDir', 'academicRoot', 'templatesDir']);
204
+
205
+ // Merge the provided settings into the current ones, expanding a leading ~ for
206
+ // path-like fields. Pure (homeDir injected) so it is unit-testable. Returns the
207
+ // new settings object and the list of keys actually changed.
208
+ export function mergeSettings(current, args, homeDir) {
209
+ const settings = { ...(current || {}) };
210
+ const provided = [];
211
+ for (const k of SETTING_KEYS) {
212
+ if (args[k] === undefined) continue;
213
+ let v = args[k];
214
+ if (typeof v === 'string' && SETTING_PATHY.has(k) && v.startsWith('~')) v = path.join(homeDir, v.slice(1));
215
+ settings[k] = v;
216
+ provided.push(k);
217
+ }
218
+ return { settings, provided };
219
+ }
220
+
192
221
  // Default project resolution:
193
222
  // 1. explicit projectName argument
194
223
  // 2. project whose `cwd` is a prefix of SESSION_CWD (longest match wins)
@@ -534,8 +563,13 @@ class OverleafGitClient {
534
563
  if (!command || !command.trim()) {
535
564
  throw new Error('No voice linter configured. Set settings.voiceLinter in projects.json or the OVERLEAF_VOICE_LINTER env var (e.g. a command that takes a file path and exits non-zero on findings).');
536
565
  }
537
- await this.cloneOrPull();
566
+ // Lint the local working copy as-is: never pulls, no network. Advisory and
567
+ // read-only, so it reflects on-disk state including edits not yet pushed. If
568
+ // the clone is absent, say so rather than silently fetching.
538
569
  const abs = path.join(this.repoPath, filePath);
570
+ if (!existsSync(abs)) {
571
+ throw new Error(`voice_lint: ${filePath} not found in the local clone (${this.repoPath}). voice_lint reads the local working copy and never pulls; open the project once (list_files / read_file) to clone it, then lint.`);
572
+ }
539
573
  const home = os.homedir();
540
574
  const parts = command.trim().split(/\s+/).map(t => (t.startsWith('~') ? home + t.slice(1) : t));
541
575
  const [prog, ...rest] = parts;
@@ -983,6 +1017,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
983
1017
  },
984
1018
  },
985
1019
  },
1020
+ {
1021
+ name: 'configure',
1022
+ description: 'Set up or update overleaf-forge\'s global settings: the scaffold templates directory, the voice_lint command, and the recurring-work settings bootstrap_ssa uses (academic root, SSA subdir, default clone dir). FIRST call this with NO arguments to see the current settings and which are unset, with a question for each; ask the user those questions in turn; THEN call it again with their answers to write them to projects.json. Omit a field to leave it unchanged; pass an empty string to clear it back to the bundled default. The git token is redacted in all output. Intended right after install to configure the server conversationally.',
1023
+ inputSchema: {
1024
+ type: 'object',
1025
+ properties: {
1026
+ repoDir: { type: 'string', description: 'Default clone location; clones go to repoDir/<name>. Absolute path (a leading ~ is expanded).' },
1027
+ academicRoot: { type: 'string', description: 'Root folder bootstrap_ssa searches: academicRoot/Year <n>/Q*/<COURSE>*. Absolute path (~ expanded).' },
1028
+ ssaSubdir: { type: 'string', description: 'Subfolder name created under each course folder for new SSAs, e.g. "MY SSAs".' },
1029
+ templatesDir: { type: 'string', description: 'Directory of your scaffold templates (main.tex, context-scaffold.md), overriding the bundled examples. ~ expanded. Empty string reverts to bundled.' },
1030
+ voiceLinter: { type: 'string', description: 'Prose-linter command for voice_lint (takes a file path, exits non-zero on findings), overriding the bundled example. Empty string reverts to bundled.' },
1031
+ gitToken: { type: 'string', description: 'Overleaf git token. Prefer the OVERLEAF_GIT_TOKEN env var; set here only to store it in projects.json.' },
1032
+ },
1033
+ },
1034
+ },
986
1035
  {
987
1036
  name: 'register_project',
988
1037
  description: 'Add or overwrite a project entry in projects.json. Lets you onboard a new Overleaf project without hand-editing JSON. Uses the global gitToken from settings. After registering, also call update_context to set the project notes.',
@@ -1194,7 +1243,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
1194
1243
  },
1195
1244
  {
1196
1245
  name: 'voice_lint',
1197
- description: 'Lint a .tex file with a user-configured prose linter (settings.voiceLinter in projects.json, or the OVERLEAF_VOICE_LINTER env var; no default). The command receives the file path and should exit non-zero on findings. Read-only and advisory: reports output, never blocks. Useful after editing prose via edit_file/write_file, which bypass any local editor hooks.',
1246
+ description: 'Lint a .tex file for prose issues. Runs a bundled generic example linter by default; override with settings.voiceLinter in projects.json or the OVERLEAF_VOICE_LINTER env var (a command that takes a file path and exits non-zero on findings). Lints the LOCAL working copy as-is and never pulls, so it reflects on-disk state including edits not yet pushed; if the project has not been cloned locally yet it errors rather than fetching. Read-only and advisory: reports output, never blocks. Useful after editing prose via edit_file/write_file, which bypass any local editor hooks.',
1198
1247
  inputSchema: {
1199
1248
  type: 'object',
1200
1249
  properties: { filePath: { type: 'string' }, projectName: { type: 'string' } },
@@ -1366,6 +1415,37 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1366
1415
  return { content: [{ type: 'text', text: out.join('\n') }] };
1367
1416
  }
1368
1417
 
1418
+ case 'configure': {
1419
+ const config = await loadConfig();
1420
+ config.settings = config.settings || {};
1421
+ const redact = (s) => { const r = { ...s }; if (r.gitToken) r.gitToken = '(set)'; return r; };
1422
+ const { settings, provided } = mergeSettings(config.settings, args, os.homedir());
1423
+
1424
+ if (provided.length === 0) {
1425
+ // Report mode: show current state and the questions for what is unset.
1426
+ const unset = SETTING_KEYS.filter(k => config.settings[k] === undefined || config.settings[k] === '');
1427
+ const lines = [
1428
+ '# overleaf-forge configuration',
1429
+ '',
1430
+ 'Current settings:',
1431
+ '```json',
1432
+ JSON.stringify(redact(config.settings), null, 2),
1433
+ '```',
1434
+ '',
1435
+ unset.length
1436
+ ? 'Not set yet. Ask the user each of these, then call configure again with their answers (omit any they want to skip):'
1437
+ : 'All settings are set. Pass any field to update it (empty string reverts a template/linter to the bundled default).',
1438
+ ...unset.map((k, i) => `${i + 1}. **${k}** — ${SETTING_HELP[k]}`),
1439
+ ];
1440
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
1441
+ }
1442
+
1443
+ // Apply mode: persist the provided settings.
1444
+ config.settings = settings;
1445
+ await saveConfig(config);
1446
+ return { content: [{ type: 'text', text: `Updated: ${provided.join(', ')}.\n\`\`\`json\n${JSON.stringify(redact(config.settings), null, 2)}\n\`\`\`\nTakes effect on the next call (projects.json is re-read each time).` }] };
1447
+ }
1448
+
1369
1449
  case 'register_project': {
1370
1450
  const config = await loadConfig();
1371
1451
  if (!args.key || !args.projectId) throw new Error('key and projectId are required');
@@ -1577,7 +1657,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1577
1657
  case 'voice_lint': {
1578
1658
  const config = await loadConfig();
1579
1659
  const { client } = await getClient(args.projectName);
1580
- const command = config.settings?.voiceLinter || process.env.OVERLEAF_VOICE_LINTER;
1660
+ // settings.voiceLinter / $OVERLEAF_VOICE_LINTER override the bundled
1661
+ // example linter, which ships with the package so voice_lint works out
1662
+ // of the box. The example implements generic prose checks; point the
1663
+ // setting at your own command to enforce a house style.
1664
+ const command = config.settings?.voiceLinter
1665
+ || process.env.OVERLEAF_VOICE_LINTER
1666
+ || `node ${path.join(PACKAGE_DIR, 'examples', 'voice-lint.mjs')}`;
1581
1667
  const r = await client.voiceLint(args.filePath, { command });
1582
1668
  return { content: [{ type: 'text', text: r.clean ? `✓ voice OK — ${args.filePath}${r.findings ? `\n${r.findings}` : ''}` : `voice findings in ${args.filePath}:\n${r.findings}` }] };
1583
1669
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overleaf-forge",
3
- "version": "2.7.3",
3
+ "version": "2.9.1",
4
4
  "description": "MCP server to read, edit, compile, and verify Overleaf/LaTeX projects over git: conflict-safe edits, figure upload, clean-build verification, citation and voice linting.",
5
5
  "type": "module",
6
6
  "main": "overleaf-mcp-server.js",
@@ -10,6 +10,7 @@
10
10
  "files": [
11
11
  "overleaf-mcp-server.js",
12
12
  "templates/",
13
+ "examples/",
13
14
  "writing-guidelines.md",
14
15
  "projects.example.json"
15
16
  ],
@@ -1,135 +1,30 @@
1
- %% ----- Document Class -----
1
+ %% Example document template for overleaf-forge's bootstrap_ssa scaffold.
2
+ %%
3
+ %% The tokens __SSA_TITLE__, __SSA_DATE__, and __OVERLEAF_READ_URL__ are
4
+ %% substituted at scaffold time; \author is rewritten when an author is passed.
5
+ %% This is a deliberately plain starting point. Replace it with your own house
6
+ %% style and point settings.templatesDir (or $OVERLEAF_MCP_TEMPLATES) at your copy.
7
+ %% The chapter/appendix/refs layout below matches what reset_ssa_content expects
8
+ %% (Chapters/ch*.tex, Chapters/app_*.tex, refs.bib, figures/).
2
9
  \documentclass[a4paper]{article}
3
10
 
4
- %% ----- Font -----
5
11
  \usepackage[T1]{fontenc}
6
- \usepackage[RMstyle=Light,SSstyle=Text,TTstyle=Light,DefaultFeatures={Ligatures=Common}]{plex-otf}
7
- \renewcommand*\familydefault{\sfdefault}
8
-
9
- \usepackage[basic,italic,symbolgreek]{mathastext}
10
-
11
- \makeatletter
12
- \@for\@tempa:=a,b,c,d,e,h,i,k,l,m,n,o,q,r,s,t,u,v,w,x\do{%
13
- \MTsetmathskips{\@tempa}{0.5mu}{0.5mu}}%
14
- \makeatother
15
-
16
- \MTsetmathskips{f}{2.5mu}{0.5mu}
17
- \MTsetmathskips{g}{1.5mu}{0.5mu}
18
- \MTsetmathskips{j}{2.5mu}{0.5mu}
19
- \MTsetmathskips{p}{1.5mu}{0mu}
20
- \MTsetmathskips{y}{1.5mu}{0.5mu}
21
- \MTsetmathskips{z}{1mu}{0.5mu}
22
-
23
- %% ----- Basics -----
12
+ \usepackage[margin=1in]{geometry}
24
13
  \usepackage{graphicx}
25
14
  \graphicspath{{figures/}}
26
15
  \usepackage{hyperref}
27
- \usepackage{subcaption}
28
16
  \usepackage{xurl}
29
- \usepackage{multicol}
30
- \usepackage{parskip}
31
- \usepackage{longtable}
17
+ \usepackage{amsmath}
32
18
  \usepackage{siunitx}
33
- \sisetup{mode=text} %% prevents mathastext/siunitx conflict (\mathrm undefined in XeTeX)
34
- \usepackage{xcolor}
35
- \usepackage[margin=1in]{geometry}
36
- \usepackage{minted}
37
- \usemintedstyle{fruity}
38
- \usepackage{listings}
39
- \usepackage{enumitem}
40
- \usepackage{wrapfig}
41
- \usepackage{ragged2e}
42
- \usepackage{array}
43
- \usepackage{hhline}
44
- \usepackage{colortbl}
45
- \usepackage{marginnote}
46
- \usepackage{emoji}
47
- \usepackage{tocloft}
48
- \usepackage{tcolorbox}
49
- \tcbuselibrary{skins,breakable}
50
- \usepackage{titlesec}
51
- \usepackage{titling}
52
19
  \usepackage{booktabs}
53
- \usepackage{tikz}
54
- \usetikzlibrary{positioning, calc, arrows.meta}
55
- \newcolumntype{L}[1]{>{\raggedright\arraybackslash}p{#1}}
56
- \newcolumntype{R}[1]{>{\raggedleft\arraybackslash}p{#1}}
57
- \usepackage{pdflscape}
58
- \usepackage{everypage}
59
- \newcommand{\Lpagenumber}{%
60
- \ifdim\textwidth=\linewidth\else\bgroup
61
- \dimendef\margin=0
62
- \ifodd\value{page}\margin=\oddsidemargin
63
- \else\margin=\evensidemargin\fi
64
- \raisebox{\dimexpr-\topmargin-\headheight-\headsep-0.5\linewidth}[0pt][0pt]{%
65
- \rlap{\hspace{\dimexpr\margin+\textheight+\footskip}%
66
- \llap{\rotatebox{90}{\thepage}}}}%
67
- \egroup\fi}
68
- \AddEverypageHook{\Lpagenumber}
20
+ \usepackage{longtable}
21
+ \usepackage{listings}
69
22
  \usepackage[backend=biber,style=ieee,sorting=none]{biblatex}
70
23
  \addbibresource{refs.bib}
71
24
 
72
- %% ----- Equation annotations -----
73
- \usepackage{annotate-equations}
74
-
75
- %% ----- Body text color -----
76
- \let\originalnormalfont\normalfont
77
- \renewcommand{\normalfont}{\originalnormalfont\color{black!60}}
78
-
79
- %% ----- Headings locked to full black -----
80
- \titleformat{\section}{\normalfont\Large\bfseries\color{black}}{\thesection}{1em}{}
81
- \titleformat{\subsection}{\normalfont\large\bfseries\color{black}}{\thesubsection}{1em}{}
82
- \titleformat{\subsubsection}{\normalfont\normalsize\bfseries\color{black}}{\thesubsubsection}{1em}{}
83
-
84
- %% ----- Title block locked to full black -----
85
- \pretitle{\begin{center}\color{black}\Large\bfseries}
86
- \posttitle{\end{center}}
87
- \preauthor{\begin{center}\color{black}\normalsize}
88
- \postauthor{\end{center}}
89
- \predate{\begin{center}\color{black}\normalsize}
90
- \postdate{\end{center}}
91
- \titleformat{\paragraph}[hang]{\normalfont\normalsize\bfseries\color{black}}{\theparagraph}{1em}{}
92
- \titlespacing*{\paragraph}{0pt}{2ex plus 0.5ex minus 0.2ex}{0.5em}
93
-
94
- %% ----- For Code -----
95
- \definecolor{codegreen}{rgb}{0,0.6,0}
96
- \definecolor{codegray}{rgb}{0.5,0.5,0.5}
97
- \definecolor{codepurple}{rgb}{0.58,0,0.82}
98
- \definecolor{backcolour}{rgb}{0.95,0.95,0.92}
99
- \lstdefinestyle{mystyle}{
100
- backgroundcolor=\color{backcolour},
101
- commentstyle=\color{codegreen},
102
- keywordstyle=\color{magenta},
103
- numberstyle=\tiny\color{codegray},
104
- stringstyle=\color{codepurple},
105
- basicstyle=\ttfamily\footnotesize,
106
- breakatwhitespace=false,
107
- breaklines=true,
108
- captionpos=b,
109
- keepspaces=true,
110
- numbers=left,
111
- numbersep=5pt,
112
- showspaces=false,
113
- showstringspaces=false,
114
- showtabs=false,
115
- tabsize=2
116
- }
117
- \lstset{style=mystyle}
118
- \allowdisplaybreaks
119
-
120
- %% ----- ToC settings -----
121
- \renewcommand{\cftsecdotsep}{\cftdotsep}
122
- \renewcommand{\contentsname}{}
123
- \renewcommand{\cftdot}{ -. }
124
- \renewcommand{\cftsecfont}{\color{black}}
125
- \renewcommand{\cftsubsecfont}{\color{black}}
126
- \renewcommand{\cftsecpagefont}{\color{black}}
127
- \renewcommand{\cftsubsecpagefont}{\color{black}}
128
-
129
- %% ----- Title Page Data -----
130
25
  \title{\textbf{__SSA_TITLE__}}
131
- \author{\textbf{Written and submitted by Your Name}}
132
- \date{\textbf{__SSA_DATE__}}
26
+ \author{Your Name}
27
+ \date{__SSA_DATE__}
133
28
 
134
29
  \begin{document}
135
30
  \maketitle
@@ -137,23 +32,10 @@
137
32
  \url{__OVERLEAF_READ_URL__}
138
33
  \end{center}
139
34
 
140
- \input{Chapters/ch1}
35
+ \tableofcontents
36
+ \clearpage
141
37
 
142
- \vspace{1em}
143
- \begin{tcolorbox}[
144
- arc=0pt,
145
- colback=white,
146
- colframe=gray,
147
- colbacktitle=gray,
148
- coltitle=white,
149
- title=DETAILS,
150
- boxrule=0.5pt,
151
- fonttitle=\bfseries\large,
152
- breakable
153
- ]
154
- \vspace{-3em}
155
- \tableofcontents
156
- \end{tcolorbox}
38
+ \input{Chapters/ch1}
157
39
 
158
40
  \clearpage
159
41
  \input{Chapters/ch2}
@@ -165,5 +47,4 @@
165
47
  \clearpage
166
48
  \input{Chapters/app_A}
167
49
 
168
-
169
50
  \end{document}