@rama_nigg/open-cursor 2.1.2
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/LICENSE +28 -0
- package/README.md +347 -0
- package/dist/cli/discover.js +202 -0
- package/dist/cli/opencode-cursor.js +430 -0
- package/dist/index.js +19583 -0
- package/dist/plugin-entry.js +18936 -0
- package/package.json +60 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
29
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
30
|
+
|
|
31
|
+
// src/utils/errors.ts
|
|
32
|
+
function stripAnsi(str) {
|
|
33
|
+
if (typeof str !== "string")
|
|
34
|
+
return String(str ?? "");
|
|
35
|
+
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
36
|
+
}
|
|
37
|
+
function parseAgentError(stderr) {
|
|
38
|
+
const input = typeof stderr === "string" ? stderr : String(stderr ?? "");
|
|
39
|
+
const clean = stripAnsi(input).trim();
|
|
40
|
+
if (clean.includes("usage limit") || clean.includes("hit your usage limit")) {
|
|
41
|
+
const savingsMatch = clean.match(/saved \$(\d+(?:\.\d+)?)/i);
|
|
42
|
+
const resetMatch = clean.match(/reset[^0-9]*(\d{1,2}\/\d{1,2}\/\d{4})/i);
|
|
43
|
+
const modelMatch = clean.match(/continue with (\w+)/i);
|
|
44
|
+
const details = {};
|
|
45
|
+
if (savingsMatch)
|
|
46
|
+
details.savings = `$${savingsMatch[1]}`;
|
|
47
|
+
if (resetMatch)
|
|
48
|
+
details.resetDate = resetMatch[1];
|
|
49
|
+
if (modelMatch)
|
|
50
|
+
details.affectedModel = modelMatch[1];
|
|
51
|
+
return {
|
|
52
|
+
type: "quota",
|
|
53
|
+
recoverable: false,
|
|
54
|
+
message: clean,
|
|
55
|
+
userMessage: "You've hit your Cursor usage limit",
|
|
56
|
+
details,
|
|
57
|
+
suggestion: "Switch to a different model or set a Spend Limit in Cursor settings"
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (clean.includes("not logged in") || clean.includes("auth") || clean.includes("unauthorized")) {
|
|
61
|
+
return {
|
|
62
|
+
type: "auth",
|
|
63
|
+
recoverable: false,
|
|
64
|
+
message: clean,
|
|
65
|
+
userMessage: "Not authenticated with Cursor",
|
|
66
|
+
details: {},
|
|
67
|
+
suggestion: "Run: opencode auth login → Other → cursor-acp, or: cursor-agent login"
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (clean.includes("ECONNREFUSED") || clean.includes("network") || clean.includes("fetch failed")) {
|
|
71
|
+
return {
|
|
72
|
+
type: "network",
|
|
73
|
+
recoverable: true,
|
|
74
|
+
message: clean,
|
|
75
|
+
userMessage: "Connection to Cursor failed",
|
|
76
|
+
details: {},
|
|
77
|
+
suggestion: "Check your internet connection and try again"
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (clean.includes("model not found") || clean.includes("invalid model") || clean.includes("Cannot use this model")) {
|
|
81
|
+
const modelMatch = clean.match(/Cannot use this model: ([^.]+)/);
|
|
82
|
+
const availableMatch = clean.match(/Available models: (.+)/);
|
|
83
|
+
const details = {};
|
|
84
|
+
if (modelMatch)
|
|
85
|
+
details.requested = modelMatch[1];
|
|
86
|
+
if (availableMatch)
|
|
87
|
+
details.available = availableMatch[1].split(", ").slice(0, 5).join(", ") + "...";
|
|
88
|
+
return {
|
|
89
|
+
type: "model",
|
|
90
|
+
recoverable: false,
|
|
91
|
+
message: clean,
|
|
92
|
+
userMessage: modelMatch ? `Model '${modelMatch[1]}' not available` : "Requested model not available",
|
|
93
|
+
details,
|
|
94
|
+
suggestion: "Use cursor-acp/auto or check available models with: cursor-agent models"
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const recoverable = clean.includes("timeout") || clean.includes("ETIMEDOUT");
|
|
98
|
+
return {
|
|
99
|
+
type: "unknown",
|
|
100
|
+
recoverable,
|
|
101
|
+
message: clean,
|
|
102
|
+
userMessage: clean.substring(0, 200) || "An error occurred",
|
|
103
|
+
details: {}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function formatErrorForUser(error) {
|
|
107
|
+
let output = `cursor-acp error: ${error.userMessage || error.message || "Unknown error"}`;
|
|
108
|
+
const details = error.details || {};
|
|
109
|
+
if (Object.keys(details).length > 0) {
|
|
110
|
+
const detailParts = Object.entries(details).map(([k, v]) => `${k}: ${v}`).join(" | ");
|
|
111
|
+
output += `
|
|
112
|
+
${detailParts}`;
|
|
113
|
+
}
|
|
114
|
+
if (error.suggestion) {
|
|
115
|
+
output += `
|
|
116
|
+
Suggestion: ${error.suggestion}`;
|
|
117
|
+
}
|
|
118
|
+
return output;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/cli/opencode-cursor.ts
|
|
122
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
123
|
+
import {
|
|
124
|
+
copyFileSync,
|
|
125
|
+
existsSync,
|
|
126
|
+
lstatSync,
|
|
127
|
+
mkdirSync,
|
|
128
|
+
readFileSync,
|
|
129
|
+
rmSync,
|
|
130
|
+
symlinkSync,
|
|
131
|
+
writeFileSync
|
|
132
|
+
} from "fs";
|
|
133
|
+
import { homedir } from "os";
|
|
134
|
+
import { dirname, join, resolve } from "path";
|
|
135
|
+
import { fileURLToPath } from "url";
|
|
136
|
+
|
|
137
|
+
// src/cli/model-discovery.ts
|
|
138
|
+
import { execFileSync } from "child_process";
|
|
139
|
+
function parseCursorModelsOutput(output) {
|
|
140
|
+
const clean = stripAnsi(output);
|
|
141
|
+
const models = [];
|
|
142
|
+
const seen = new Set;
|
|
143
|
+
for (const line of clean.split(`
|
|
144
|
+
`)) {
|
|
145
|
+
const trimmed = line.trim();
|
|
146
|
+
if (!trimmed)
|
|
147
|
+
continue;
|
|
148
|
+
const match = trimmed.match(/^([a-zA-Z0-9._-]+)\s+-\s+(.+?)(?:\s+\((?:current|default)\))*\s*$/);
|
|
149
|
+
if (!match)
|
|
150
|
+
continue;
|
|
151
|
+
const id = match[1];
|
|
152
|
+
if (seen.has(id))
|
|
153
|
+
continue;
|
|
154
|
+
seen.add(id);
|
|
155
|
+
models.push({ id, name: match[2].trim() });
|
|
156
|
+
}
|
|
157
|
+
return models;
|
|
158
|
+
}
|
|
159
|
+
function discoverModelsFromCursorAgent() {
|
|
160
|
+
const raw = execFileSync("cursor-agent", ["models"], {
|
|
161
|
+
encoding: "utf8",
|
|
162
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
163
|
+
});
|
|
164
|
+
const models = parseCursorModelsOutput(raw);
|
|
165
|
+
if (models.length === 0) {
|
|
166
|
+
throw new Error("No models parsed from cursor-agent output");
|
|
167
|
+
}
|
|
168
|
+
return models;
|
|
169
|
+
}
|
|
170
|
+
function fallbackModels() {
|
|
171
|
+
return [
|
|
172
|
+
{ id: "auto", name: "Auto" },
|
|
173
|
+
{ id: "sonnet-4.5", name: "Claude 4.5 Sonnet" },
|
|
174
|
+
{ id: "opus-4.6", name: "Claude 4.6 Opus" },
|
|
175
|
+
{ id: "gpt-5.2", name: "GPT-5.2" }
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/cli/opencode-cursor.ts
|
|
180
|
+
var PROVIDER_ID = "cursor-acp";
|
|
181
|
+
var DEFAULT_BASE_URL = "http://127.0.0.1:32124/v1";
|
|
182
|
+
function printHelp() {
|
|
183
|
+
console.log(`opencode-cursor
|
|
184
|
+
|
|
185
|
+
Usage:
|
|
186
|
+
opencode-cursor install [--config <path>] [--plugin-dir <path>] [--base-url <url>] [--copy] [--skip-models] [--no-backup]
|
|
187
|
+
opencode-cursor sync-models [--config <path>] [--no-backup]
|
|
188
|
+
opencode-cursor uninstall [--config <path>] [--plugin-dir <path>] [--no-backup]
|
|
189
|
+
opencode-cursor status [--config <path>] [--plugin-dir <path>]
|
|
190
|
+
opencode-cursor help
|
|
191
|
+
`);
|
|
192
|
+
}
|
|
193
|
+
function parseArgs(argv) {
|
|
194
|
+
const [commandRaw, ...rest] = argv;
|
|
195
|
+
const command = normalizeCommand(commandRaw);
|
|
196
|
+
const options = {};
|
|
197
|
+
for (let i = 0;i < rest.length; i += 1) {
|
|
198
|
+
const arg = rest[i];
|
|
199
|
+
if (arg === "--copy") {
|
|
200
|
+
options.copy = true;
|
|
201
|
+
} else if (arg === "--skip-models") {
|
|
202
|
+
options.skipModels = true;
|
|
203
|
+
} else if (arg === "--no-backup") {
|
|
204
|
+
options.noBackup = true;
|
|
205
|
+
} else if (arg === "--config" && rest[i + 1]) {
|
|
206
|
+
options.config = rest[i + 1];
|
|
207
|
+
i += 1;
|
|
208
|
+
} else if (arg === "--plugin-dir" && rest[i + 1]) {
|
|
209
|
+
options.pluginDir = rest[i + 1];
|
|
210
|
+
i += 1;
|
|
211
|
+
} else if (arg === "--base-url" && rest[i + 1]) {
|
|
212
|
+
options.baseUrl = rest[i + 1];
|
|
213
|
+
i += 1;
|
|
214
|
+
} else {
|
|
215
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { command, options };
|
|
219
|
+
}
|
|
220
|
+
function normalizeCommand(value) {
|
|
221
|
+
switch ((value || "install").toLowerCase()) {
|
|
222
|
+
case "install":
|
|
223
|
+
case "sync-models":
|
|
224
|
+
case "uninstall":
|
|
225
|
+
case "status":
|
|
226
|
+
case "help":
|
|
227
|
+
return value ? value.toLowerCase() : "install";
|
|
228
|
+
default:
|
|
229
|
+
throw new Error(`Unknown command: ${value}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function getConfigHome() {
|
|
233
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
234
|
+
if (xdg && xdg.length > 0)
|
|
235
|
+
return xdg;
|
|
236
|
+
return join(homedir(), ".config");
|
|
237
|
+
}
|
|
238
|
+
function resolvePaths(options) {
|
|
239
|
+
const opencodeDir = join(getConfigHome(), "opencode");
|
|
240
|
+
const configPath = resolve(options.config || join(opencodeDir, "opencode.json"));
|
|
241
|
+
const pluginDir = resolve(options.pluginDir || join(opencodeDir, "plugin"));
|
|
242
|
+
const pluginPath = join(pluginDir, `${PROVIDER_ID}.js`);
|
|
243
|
+
return { opencodeDir, configPath, pluginDir, pluginPath };
|
|
244
|
+
}
|
|
245
|
+
function resolvePluginSource() {
|
|
246
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
247
|
+
const currentDir = dirname(currentFile);
|
|
248
|
+
const candidates = [
|
|
249
|
+
join(currentDir, "plugin-entry.js"),
|
|
250
|
+
join(currentDir, "..", "plugin-entry.js")
|
|
251
|
+
];
|
|
252
|
+
for (const candidate of candidates) {
|
|
253
|
+
if (existsSync(candidate)) {
|
|
254
|
+
return candidate;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
throw new Error("Unable to locate plugin-entry.js next to CLI distribution files");
|
|
258
|
+
}
|
|
259
|
+
function readConfig(configPath) {
|
|
260
|
+
if (!existsSync(configPath)) {
|
|
261
|
+
return { plugin: [], provider: {} };
|
|
262
|
+
}
|
|
263
|
+
const raw = readFileSync(configPath, "utf8");
|
|
264
|
+
try {
|
|
265
|
+
return JSON.parse(raw);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
throw new Error(`Invalid JSON in config: ${configPath} (${String(error)})`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function writeConfig(configPath, config, noBackup) {
|
|
271
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
272
|
+
if (!noBackup && existsSync(configPath)) {
|
|
273
|
+
const backupPath = `${configPath}.bak.${new Date().toISOString().replace(/[:]/g, "-")}`;
|
|
274
|
+
copyFileSync(configPath, backupPath);
|
|
275
|
+
console.log(`Backup written: ${backupPath}`);
|
|
276
|
+
}
|
|
277
|
+
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
278
|
+
`, "utf8");
|
|
279
|
+
}
|
|
280
|
+
function ensureProvider(config, baseUrl) {
|
|
281
|
+
config.plugin = Array.isArray(config.plugin) ? config.plugin : [];
|
|
282
|
+
if (!config.plugin.includes(PROVIDER_ID)) {
|
|
283
|
+
config.plugin.push(PROVIDER_ID);
|
|
284
|
+
}
|
|
285
|
+
config.provider = config.provider && typeof config.provider === "object" ? config.provider : {};
|
|
286
|
+
const current = config.provider[PROVIDER_ID] && typeof config.provider[PROVIDER_ID] === "object" ? config.provider[PROVIDER_ID] : {};
|
|
287
|
+
const options = current.options && typeof current.options === "object" ? current.options : {};
|
|
288
|
+
const models = current.models && typeof current.models === "object" ? current.models : {};
|
|
289
|
+
config.provider[PROVIDER_ID] = {
|
|
290
|
+
...current,
|
|
291
|
+
name: "Cursor",
|
|
292
|
+
npm: "@ai-sdk/openai-compatible",
|
|
293
|
+
options: {
|
|
294
|
+
...options,
|
|
295
|
+
baseURL: baseUrl
|
|
296
|
+
},
|
|
297
|
+
models
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function ensurePluginLink(pluginSource, pluginPath, copyMode) {
|
|
301
|
+
mkdirSync(dirname(pluginPath), { recursive: true });
|
|
302
|
+
rmSync(pluginPath, { force: true });
|
|
303
|
+
if (copyMode) {
|
|
304
|
+
copyFileSync(pluginSource, pluginPath);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
symlinkSync(pluginSource, pluginPath);
|
|
308
|
+
}
|
|
309
|
+
function discoverModelsSafe() {
|
|
310
|
+
try {
|
|
311
|
+
return discoverModelsFromCursorAgent();
|
|
312
|
+
} catch (error) {
|
|
313
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
314
|
+
console.warn(`Warning: cursor-agent models failed; using fallback models (${message})`);
|
|
315
|
+
return fallbackModels();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function installAiSdk(opencodeDir) {
|
|
319
|
+
try {
|
|
320
|
+
execFileSync2("bun", ["install", "@ai-sdk/openai-compatible"], {
|
|
321
|
+
cwd: opencodeDir,
|
|
322
|
+
stdio: "inherit"
|
|
323
|
+
});
|
|
324
|
+
} catch (error) {
|
|
325
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
326
|
+
console.warn(`Warning: failed to install @ai-sdk/openai-compatible via bun (${message})`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function commandInstall(options) {
|
|
330
|
+
const { opencodeDir, configPath, pluginPath } = resolvePaths(options);
|
|
331
|
+
const baseUrl = options.baseUrl || DEFAULT_BASE_URL;
|
|
332
|
+
const copyMode = options.copy === true;
|
|
333
|
+
const pluginSource = resolvePluginSource();
|
|
334
|
+
mkdirSync(opencodeDir, { recursive: true });
|
|
335
|
+
ensurePluginLink(pluginSource, pluginPath, copyMode);
|
|
336
|
+
const config = readConfig(configPath);
|
|
337
|
+
ensureProvider(config, baseUrl);
|
|
338
|
+
if (!options.skipModels) {
|
|
339
|
+
const models = discoverModelsSafe();
|
|
340
|
+
for (const model of models) {
|
|
341
|
+
config.provider[PROVIDER_ID].models[model.id] = { name: model.name };
|
|
342
|
+
}
|
|
343
|
+
console.log(`Models synced: ${models.length}`);
|
|
344
|
+
}
|
|
345
|
+
writeConfig(configPath, config, options.noBackup === true);
|
|
346
|
+
installAiSdk(opencodeDir);
|
|
347
|
+
console.log(`Installed ${PROVIDER_ID}`);
|
|
348
|
+
console.log(`Plugin path: ${pluginPath}${copyMode ? " (copy)" : " (symlink)"}`);
|
|
349
|
+
console.log(`Config path: ${configPath}`);
|
|
350
|
+
}
|
|
351
|
+
function commandSyncModels(options) {
|
|
352
|
+
const { configPath } = resolvePaths(options);
|
|
353
|
+
const config = readConfig(configPath);
|
|
354
|
+
ensureProvider(config, options.baseUrl || DEFAULT_BASE_URL);
|
|
355
|
+
const models = discoverModelsSafe();
|
|
356
|
+
for (const model of models) {
|
|
357
|
+
config.provider[PROVIDER_ID].models[model.id] = { name: model.name };
|
|
358
|
+
}
|
|
359
|
+
writeConfig(configPath, config, options.noBackup === true);
|
|
360
|
+
console.log(`Models synced: ${models.length}`);
|
|
361
|
+
console.log(`Config path: ${configPath}`);
|
|
362
|
+
}
|
|
363
|
+
function commandUninstall(options) {
|
|
364
|
+
const { configPath, pluginPath } = resolvePaths(options);
|
|
365
|
+
rmSync(pluginPath, { force: true });
|
|
366
|
+
if (existsSync(configPath)) {
|
|
367
|
+
const config = readConfig(configPath);
|
|
368
|
+
if (Array.isArray(config.plugin)) {
|
|
369
|
+
config.plugin = config.plugin.filter((name) => name !== PROVIDER_ID);
|
|
370
|
+
}
|
|
371
|
+
if (config.provider && typeof config.provider === "object") {
|
|
372
|
+
delete config.provider[PROVIDER_ID];
|
|
373
|
+
}
|
|
374
|
+
writeConfig(configPath, config, options.noBackup === true);
|
|
375
|
+
}
|
|
376
|
+
console.log(`Removed plugin link: ${pluginPath}`);
|
|
377
|
+
console.log(`Removed provider "${PROVIDER_ID}" from ${configPath}`);
|
|
378
|
+
}
|
|
379
|
+
function commandStatus(options) {
|
|
380
|
+
const { configPath, pluginPath } = resolvePaths(options);
|
|
381
|
+
const pluginExists = existsSync(pluginPath);
|
|
382
|
+
const pluginType = pluginExists ? lstatSync(pluginPath).isSymbolicLink() ? "symlink" : "file" : "missing";
|
|
383
|
+
let providerExists = false;
|
|
384
|
+
let pluginEnabled = false;
|
|
385
|
+
if (existsSync(configPath)) {
|
|
386
|
+
const config = readConfig(configPath);
|
|
387
|
+
providerExists = Boolean(config.provider?.[PROVIDER_ID]);
|
|
388
|
+
pluginEnabled = Array.isArray(config.plugin) && config.plugin.includes(PROVIDER_ID);
|
|
389
|
+
}
|
|
390
|
+
console.log(`Plugin file: ${pluginPath} (${pluginType})`);
|
|
391
|
+
console.log(`Provider in config: ${providerExists ? "yes" : "no"}`);
|
|
392
|
+
console.log(`Plugin enabled in config: ${pluginEnabled ? "yes" : "no"}`);
|
|
393
|
+
console.log(`Config path: ${configPath}`);
|
|
394
|
+
}
|
|
395
|
+
function main() {
|
|
396
|
+
let parsed;
|
|
397
|
+
try {
|
|
398
|
+
parsed = parseArgs(process.argv.slice(2));
|
|
399
|
+
} catch (error) {
|
|
400
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
401
|
+
console.error(message);
|
|
402
|
+
printHelp();
|
|
403
|
+
process.exit(1);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
switch (parsed.command) {
|
|
408
|
+
case "install":
|
|
409
|
+
commandInstall(parsed.options);
|
|
410
|
+
return;
|
|
411
|
+
case "sync-models":
|
|
412
|
+
commandSyncModels(parsed.options);
|
|
413
|
+
return;
|
|
414
|
+
case "uninstall":
|
|
415
|
+
commandUninstall(parsed.options);
|
|
416
|
+
return;
|
|
417
|
+
case "status":
|
|
418
|
+
commandStatus(parsed.options);
|
|
419
|
+
return;
|
|
420
|
+
case "help":
|
|
421
|
+
printHelp();
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
} catch (error) {
|
|
425
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
426
|
+
console.error(`Error: ${message}`);
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
main();
|