diff options
| author | YurenHao0426 <blackhao0426@gmail.com> | 2026-09-13 04:05:38 +0000 |
|---|---|---|
| committer | YurenHao0426 <blackhao0426@gmail.com> | 2026-09-13 04:05:38 +0000 |
| commit | f3b2fc01082e754f7dfe73caa4f0a4f207a2adfb (patch) | |
| tree | f6083a5abddc3541ad7996f71695e559b4b41ece /src/cli/args.ts | |
| parent | 34f22876ab9e15e9f14300a8c21cd7109800d1ab (diff) | |
Add headless CLI for Overleaf project management (lattex-cli)
New src/cli/ module providing a headless CLI for AI agents to work with
Overleaf projects from a server with no display. Commands: auth, projects,
clone, pull, status, push, compile.
Key design decisions:
- Uses Node.js https module (no Electron dependency), same approach as the
existing MCP server in src/mcp/lattex.mjs
- Reuses overleafProtocol.ts for Socket.IO v0.9 parsing (pure, no Electron)
- Separate esbuild bundle (out/cli/lattex-cli.mjs) — does not touch the
Electron app build
- 64 tests for arg parsing, file tree walking, diff logic, and log parsing
Files:
- src/cli/main.ts — CLI entry point with all commands
- src/cli/args.ts — argument parser
- src/cli/overleafApi.ts — Overleaf API client using https + ws
- src/cli/fileTree.ts — project root folder walker
- src/cli/localState.ts — per-clone state (.lattex-cli.json) and auth storage
- src/cli/diff.ts — local vs remote diff logic
- src/cli/logParser.ts — LaTeX compile log parser
- src/cli/test.ts — unit tests
- tsconfig.cli.json — TypeScript config for CLI
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/cli/args.ts')
| -rw-r--r-- | src/cli/args.ts | 39 |
1 files changed, 39 insertions, 0 deletions
diff --git a/src/cli/args.ts b/src/cli/args.ts new file mode 100644 index 0000000..0f7a767 --- /dev/null +++ b/src/cli/args.ts @@ -0,0 +1,39 @@ +// CLI argument parser + +export interface ParsedArgs { + command: string + positional: string[] + flags: Record<string, string | boolean> +} + +export function parseArgs(argv: string[]): ParsedArgs { + const args = argv.slice(2) + const command = args[0] || '' + const positional: string[] = [] + const flags: Record<string, string | boolean> = {} + + for (let i = 1; i < args.length; i++) { + const arg = args[i] + if (arg.startsWith('--')) { + const eqIdx = arg.indexOf('=') + if (eqIdx !== -1) { + flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1) + } else if (i + 1 < args.length && !args[i + 1].startsWith('--')) { + const key = arg.slice(2) + // Boolean flags that never take a value + const boolFlags = new Set(['json', 'dry-run', 'delete', 'force', 'help']) + if (boolFlags.has(key)) { + flags[key] = true + } else { + flags[key] = args[++i] + } + } else { + flags[arg.slice(2)] = true + } + } else { + positional.push(arg) + } + } + + return { command, positional, flags } +} |
