1#!/usr/bin/env node
2
3// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
4//
5// SPDX-License-Identifier: GPL-3.0-or-later
6
7import { RumiloError } from "../util/errors.js";
8import { runRepoCommand } from "./commands/repo.js";
9import { runWebCommand } from "./commands/web.js";
10import { parseArgs } from "./parse-args.js";
11
12const VERSION = "0.1.0";
13
14async function main() {
15 const { command, options, positional } = parseArgs(process.argv);
16
17 // Handle version flag coming before any command (e.g., rumilo -v)
18 // When short flags come right after process.argv, they end up in 'command'
19 if (
20 command &&
21 (command === "-v" || command === "--version") &&
22 Object.keys(options).length === 0 &&
23 positional.length === 0
24 ) {
25 console.log(`rumilo v${VERSION}`);
26 process.exit(0);
27 }
28
29 const actualCommand = command?.startsWith("-") ? undefined : command;
30
31 // Handle version/short version as flag (before command) or as command
32 if (
33 options["version"] ||
34 actualCommand === "version" ||
35 actualCommand === "v"
36 ) {
37 console.log(`rumilo v${VERSION}`);
38 process.exit(0);
39 }
40
41 if (
42 !actualCommand ||
43 actualCommand === "help" ||
44 actualCommand === "--help" ||
45 actualCommand === "-h" ||
46 options["help"]
47 ) {
48 console.log(`Rúmilo v${VERSION} — dispatch AI research subagents
49
50Commands:
51 web Search the web and synthesize an answer
52 repo Clone and explore a git repository
53
54Usage:
55 rumilo web <query> [-u <url>] [options]
56 rumilo repo -u <uri> <query> [options]
57
58Options:
59 -u <url> Seed URL to pre-fetch (web) or repository to clone (repo)
60 --model <provider:model> Override the default model
61 --ref <ref> Checkout a specific ref after cloning (repo only)
62 --full Full clone instead of shallow (repo only)
63 --verbose Show tool calls and results on stderr
64 --no-cleanup Preserve the workspace directory after exit
65 -v, --version Print version and exit
66
67Configuration:
68 $XDG_CONFIG_HOME/rumilo/config.toml`);
69 process.exit(0);
70 }
71
72 try {
73 if (command === "web") {
74 const query = positional.join(" ");
75 if (!query) {
76 throw new RumiloError(
77 "Missing query. Usage: rumilo web <query>",
78 "CLI_ERROR",
79 );
80 }
81
82 await runWebCommand({
83 query,
84 url: options["uri"] ? String(options["uri"]) : undefined,
85 model: options["model"] ? String(options["model"]) : undefined,
86 verbose: Boolean(options["verbose"]),
87 cleanup: !options["no-cleanup"],
88 });
89 return;
90 }
91
92 if (command === "repo") {
93 const query = positional.join(" ");
94 const uri = options["uri"] ? String(options["uri"]) : undefined;
95 if (!uri) {
96 throw new RumiloError(
97 "Missing repo URI. Usage: rumilo repo -u <uri> <query>",
98 "CLI_ERROR",
99 );
100 }
101 if (!query) {
102 throw new RumiloError(
103 "Missing query. Usage: rumilo repo -u <uri> <query>",
104 "CLI_ERROR",
105 );
106 }
107
108 await runRepoCommand({
109 query,
110 uri,
111 ref: options["ref"] ? String(options["ref"]) : undefined,
112 full: Boolean(options["full"]),
113 model: options["model"] ? String(options["model"]) : undefined,
114 verbose: Boolean(options["verbose"]),
115 cleanup: !options["no-cleanup"],
116 });
117 return;
118 }
119
120 throw new RumiloError(`Unknown command: ${command}`, "CLI_ERROR");
121 } catch (error: any) {
122 const message = error instanceof Error ? error.message : String(error);
123 console.error(message);
124 process.exitCode = 1;
125 }
126}
127
128main();