codelantern-agent-cli 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +363 -2
- package/dist/chunk-Y3PEOLTZ.js +2513 -0
- package/dist/chunk-Y3PEOLTZ.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +914 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +284 -0
- package/dist/index.js +92 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -4
package/dist/cli.js
ADDED
|
@@ -0,0 +1,914 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ALLOWED_CONFIG_KEYS,
|
|
4
|
+
McpManager,
|
|
5
|
+
OrbitAgent,
|
|
6
|
+
addMcpServer,
|
|
7
|
+
createNewSession,
|
|
8
|
+
deleteSession,
|
|
9
|
+
exportSessionMarkdown,
|
|
10
|
+
formatErrorMessage,
|
|
11
|
+
getConfigFilePath,
|
|
12
|
+
getLanternHomeDir,
|
|
13
|
+
getSessionsDir,
|
|
14
|
+
listSessions,
|
|
15
|
+
loadAttachments,
|
|
16
|
+
loadConfig,
|
|
17
|
+
loadMcpConfig,
|
|
18
|
+
loadProjectInstructions,
|
|
19
|
+
loadSession,
|
|
20
|
+
removeMcpServer,
|
|
21
|
+
saveSession,
|
|
22
|
+
setConfigValue,
|
|
23
|
+
toggleMcpServer
|
|
24
|
+
} from "./chunk-Y3PEOLTZ.js";
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
import { Command } from "commander";
|
|
28
|
+
|
|
29
|
+
// src/commands/interactive.ts
|
|
30
|
+
import pc4 from "picocolors";
|
|
31
|
+
|
|
32
|
+
// src/ui/prompt.ts
|
|
33
|
+
import readline from "readline";
|
|
34
|
+
import fs from "fs";
|
|
35
|
+
import path from "path";
|
|
36
|
+
import pc from "picocolors";
|
|
37
|
+
function getHistoryFilePath() {
|
|
38
|
+
return path.join(getLanternHomeDir(), "history.txt");
|
|
39
|
+
}
|
|
40
|
+
function loadHistory() {
|
|
41
|
+
const filePath = getHistoryFilePath();
|
|
42
|
+
if (!fs.existsSync(filePath)) return [];
|
|
43
|
+
try {
|
|
44
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
45
|
+
return raw.replace(/\r\n/g, "\n").split("\n").filter((line) => line.trim().length > 0);
|
|
46
|
+
} catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function appendHistory(line) {
|
|
51
|
+
const clean = line.trim();
|
|
52
|
+
if (!clean || clean.startsWith("/") || clean.length < 2) return;
|
|
53
|
+
const filePath = getHistoryFilePath();
|
|
54
|
+
try {
|
|
55
|
+
fs.appendFileSync(filePath, clean + "\n", { encoding: "utf8" });
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
var TerminalPrompt = class {
|
|
60
|
+
history;
|
|
61
|
+
constructor() {
|
|
62
|
+
this.history = loadHistory();
|
|
63
|
+
}
|
|
64
|
+
async askPrompt(promptPrefix = pc.yellow("lantern> ")) {
|
|
65
|
+
const rl = readline.createInterface({
|
|
66
|
+
input: process.stdin,
|
|
67
|
+
output: process.stdout,
|
|
68
|
+
history: this.history,
|
|
69
|
+
historySize: 200,
|
|
70
|
+
prompt: promptPrefix
|
|
71
|
+
});
|
|
72
|
+
let sigintCount = 0;
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
let accumulated = "";
|
|
75
|
+
let insideTripleQuotes = false;
|
|
76
|
+
rl.prompt();
|
|
77
|
+
rl.on("line", (rawLine) => {
|
|
78
|
+
sigintCount = 0;
|
|
79
|
+
const line = rawLine.replace(/\r/g, "");
|
|
80
|
+
if (!insideTripleQuotes && (line.trim().startsWith('"""') || line.trim().startsWith("```"))) {
|
|
81
|
+
insideTripleQuotes = true;
|
|
82
|
+
const content = line.trim().slice(3);
|
|
83
|
+
accumulated += content + "\n";
|
|
84
|
+
rl.setPrompt(pc.dim("... "));
|
|
85
|
+
rl.prompt();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (insideTripleQuotes) {
|
|
89
|
+
if (line.trim().endsWith('"""') || line.trim().endsWith("```")) {
|
|
90
|
+
const content = line.trim().slice(0, -3);
|
|
91
|
+
accumulated += content;
|
|
92
|
+
insideTripleQuotes = false;
|
|
93
|
+
rl.close();
|
|
94
|
+
appendHistory(accumulated.trim());
|
|
95
|
+
return resolve(accumulated.trim());
|
|
96
|
+
}
|
|
97
|
+
accumulated += line + "\n";
|
|
98
|
+
rl.prompt();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (line.endsWith("\\")) {
|
|
102
|
+
accumulated += line.slice(0, -1) + "\n";
|
|
103
|
+
rl.setPrompt(pc.dim("... "));
|
|
104
|
+
rl.prompt();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
accumulated += line;
|
|
108
|
+
rl.close();
|
|
109
|
+
const result = accumulated.trim();
|
|
110
|
+
if (result) {
|
|
111
|
+
appendHistory(result);
|
|
112
|
+
}
|
|
113
|
+
resolve(result);
|
|
114
|
+
});
|
|
115
|
+
rl.on("SIGINT", () => {
|
|
116
|
+
sigintCount++;
|
|
117
|
+
if (sigintCount >= 2 || insideTripleQuotes) {
|
|
118
|
+
console.log("\nExiting Code Lantern.");
|
|
119
|
+
rl.close();
|
|
120
|
+
resolve("/exit");
|
|
121
|
+
} else {
|
|
122
|
+
console.log(pc.dim("\n(Press Ctrl+C again or type /exit to quit)"));
|
|
123
|
+
accumulated = "";
|
|
124
|
+
insideTripleQuotes = false;
|
|
125
|
+
rl.setPrompt(promptPrefix);
|
|
126
|
+
rl.prompt();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
rl.on("close", () => {
|
|
130
|
+
if (!accumulated) {
|
|
131
|
+
resolve(null);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/ui/banner.ts
|
|
139
|
+
import pc2 from "picocolors";
|
|
140
|
+
function printBanner(options) {
|
|
141
|
+
const logo = `
|
|
142
|
+
____ _ _ _
|
|
143
|
+
/ ___|___ __| | ___ | | __ _ _ __ | |_ ___ _ __ _ __
|
|
144
|
+
| | / _ \\ / _\` |/ _ \\ | | / _\` | '_ \\ | __/ _ \\ '__| '_ \\
|
|
145
|
+
| |__| (_) | (_| | __/ | |__| (_| | | | || || __/ | | | | |
|
|
146
|
+
\\____\\___/ \\__,_|\\___| |_____\\__,_|_| |_(_)__\\___|_| |_| |_|
|
|
147
|
+
\u{1F3EE} Personal AI Coding Assistant for Terminal (Gemini)
|
|
148
|
+
`;
|
|
149
|
+
console.log(pc2.yellow(logo));
|
|
150
|
+
console.log(pc2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
151
|
+
console.log(`${pc2.bold("Product:")} Code Lantern CLI (${pc2.cyan("lantern")}) ${pc2.bold("Version:")} ${options.version}`);
|
|
152
|
+
console.log(`${pc2.bold("Default Model:")} ${pc2.green(options.model)}`);
|
|
153
|
+
console.log(`${pc2.bold("Workspace:")} ${pc2.blue(options.workspace)}`);
|
|
154
|
+
const modeBadge = options.planMode ? pc2.bgBlue(pc2.white(" PLAN MODE (Read-only) ")) : options.yolo ? pc2.bgRed(pc2.white(" YOLO MODE (Auto-approve) ")) : pc2.bgGreen(pc2.black(" SAFE MODE (Approval Prompts) "));
|
|
155
|
+
console.log(`${pc2.bold("Status:")} ${modeBadge}`);
|
|
156
|
+
console.log(pc2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
157
|
+
console.log(pc2.dim(`Type your task or question. Use ${pc2.bold(pc2.yellow("/help"))} for commands, ${pc2.bold(pc2.yellow("/exit"))} to quit.`));
|
|
158
|
+
console.log(pc2.dim(`Multiline tip: End a line with '\\' or enclose in '"""' for multiline blocks.
|
|
159
|
+
`));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/ui/slashCommands.ts
|
|
163
|
+
import pc3 from "picocolors";
|
|
164
|
+
async function handleSlashCommand(input, context) {
|
|
165
|
+
const parts = input.trim().split(/\s+/);
|
|
166
|
+
const command = parts[0].toLowerCase();
|
|
167
|
+
const arg = parts.slice(1).join(" ");
|
|
168
|
+
switch (command) {
|
|
169
|
+
case "/help":
|
|
170
|
+
console.log("\n" + pc3.bold(pc3.yellow("\u{1F3EE} Code Lantern CLI \u2014 Help & Commands")));
|
|
171
|
+
console.log(pc3.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
172
|
+
console.log(pc3.bold("Slash Commands:"));
|
|
173
|
+
console.log(` ${pc3.cyan("/help")} Show this help overview`);
|
|
174
|
+
console.log(` ${pc3.cyan("/clear")} Clear the terminal screen`);
|
|
175
|
+
console.log(` ${pc3.cyan("/status")} Display current session, workspace, and model status`);
|
|
176
|
+
console.log(` ${pc3.cyan("/model [name]")} View or change current Gemini model`);
|
|
177
|
+
console.log(` ${pc3.cyan("/plan [on|off]")} Toggle read-only planning mode`);
|
|
178
|
+
console.log(` ${pc3.cyan("/yolo [on|off]")} Toggle YOLO auto-approval mode`);
|
|
179
|
+
console.log(` ${pc3.cyan("/sessions")} List saved local sessions`);
|
|
180
|
+
console.log(` ${pc3.cyan("/history")} Display recent turns in current session`);
|
|
181
|
+
console.log(` ${pc3.cyan("/export [md|json]")} Export current conversation transcript`);
|
|
182
|
+
console.log(` ${pc3.cyan("/exit")} or ${pc3.cyan("/quit")} Exit Code Lantern CLI`);
|
|
183
|
+
console.log("");
|
|
184
|
+
console.log(pc3.bold("Available Agent Tools:"));
|
|
185
|
+
console.log(` ${pc3.yellow("read_file")} Read file contents with line ranges`);
|
|
186
|
+
console.log(` ${pc3.yellow("write_file")} Create new files (requires approval in safe mode)`);
|
|
187
|
+
console.log(` ${pc3.yellow("edit_file")} Apply targeted patch with diff preview`);
|
|
188
|
+
console.log(` ${pc3.yellow("list_directory")} Inspect directories honoring .gitignore`);
|
|
189
|
+
console.log(` ${pc3.yellow("file_search")} Find files matching glob/pattern`);
|
|
190
|
+
console.log(` ${pc3.yellow("grep_search")} Search code patterns across files`);
|
|
191
|
+
console.log(` ${pc3.yellow("project_tree")} Generate ASCII project tree`);
|
|
192
|
+
console.log(` ${pc3.yellow("run_command")} Run shell commands (requires approval)`);
|
|
193
|
+
console.log(` ${pc3.yellow("git_status")} Inspect Git status`);
|
|
194
|
+
console.log(` ${pc3.yellow("git_diff")} Inspect Git working tree / staged diffs`);
|
|
195
|
+
console.log(` ${pc3.yellow("web_search")} Search documentation / error references`);
|
|
196
|
+
console.log("");
|
|
197
|
+
console.log(pc3.bold("Keyboard Shortcuts:"));
|
|
198
|
+
console.log(` ${pc3.dim("Enter")} Submit prompt`);
|
|
199
|
+
console.log(` ${pc3.dim("\\ + Enter")} Continue prompt on next line (multiline)`);
|
|
200
|
+
console.log(` ${pc3.dim('""" ... """')} Multiline prompt block`);
|
|
201
|
+
console.log(` ${pc3.dim("Up/Down Arrows")} Navigate prompt history`);
|
|
202
|
+
console.log(` ${pc3.dim("Ctrl+C")} Cancel current line / double-tap to exit`);
|
|
203
|
+
console.log(` ${pc3.dim("Ctrl+D")} Exit session`);
|
|
204
|
+
console.log("");
|
|
205
|
+
return true;
|
|
206
|
+
case "/clear":
|
|
207
|
+
console.clear();
|
|
208
|
+
return true;
|
|
209
|
+
case "/status":
|
|
210
|
+
console.log("\n" + pc3.bold(pc3.yellow("\u{1F3EE} Code Lantern Status")));
|
|
211
|
+
console.log(pc3.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
212
|
+
console.log(`${pc3.bold("Session ID:")} ${pc3.cyan(context.session.id)}`);
|
|
213
|
+
console.log(`${pc3.bold("Workspace:")} ${pc3.blue(context.workspace)}`);
|
|
214
|
+
console.log(`${pc3.bold("Active Model:")} ${pc3.green(context.model)}`);
|
|
215
|
+
console.log(`${pc3.bold("Turns:")} ${context.session.turns.length}`);
|
|
216
|
+
console.log(`${pc3.bold("Total Tokens:")} ${context.session.totalUsage.totalTokens.toLocaleString()}`);
|
|
217
|
+
console.log(`${pc3.bold("YOLO Mode:")} ${context.yolo ? pc3.red("ON (Auto-approve)") : pc3.green("OFF (Prompt approval)")}`);
|
|
218
|
+
console.log(`${pc3.bold("Plan Mode:")} ${context.planMode ? pc3.blue("ON (Read-only)") : pc3.dim("OFF (Full execution)")}`);
|
|
219
|
+
console.log("");
|
|
220
|
+
return true;
|
|
221
|
+
case "/model":
|
|
222
|
+
if (arg) {
|
|
223
|
+
context.setModel(arg.trim());
|
|
224
|
+
console.log(pc3.green(`\u2713 Model changed to: ${arg.trim()}`));
|
|
225
|
+
} else {
|
|
226
|
+
console.log(`Current model: ${pc3.cyan(context.model)}`);
|
|
227
|
+
console.log(pc3.dim(`Usage: /model <model-id> (e.g. /model gemini-3.8-flash)`));
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
case "/plan":
|
|
231
|
+
if (arg === "off") {
|
|
232
|
+
context.setPlanMode(false);
|
|
233
|
+
console.log(pc3.green("\u2713 Plan mode disabled (execution tools enabled)."));
|
|
234
|
+
} else if (arg === "on") {
|
|
235
|
+
context.setPlanMode(true);
|
|
236
|
+
console.log(pc3.blue("\u2713 Plan mode enabled (read-only inspection)."));
|
|
237
|
+
} else {
|
|
238
|
+
const next = !context.planMode;
|
|
239
|
+
context.setPlanMode(next);
|
|
240
|
+
console.log(next ? pc3.blue("\u2713 Plan mode enabled (read-only inspection).") : pc3.green("\u2713 Plan mode disabled."));
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
case "/yolo":
|
|
244
|
+
if (arg === "off") {
|
|
245
|
+
context.setYolo(false);
|
|
246
|
+
console.log(pc3.green("\u2713 YOLO mode disabled (safe approval prompts active)."));
|
|
247
|
+
} else if (arg === "on") {
|
|
248
|
+
context.setYolo(true);
|
|
249
|
+
console.log(pc3.red("\u26A1 YOLO mode enabled (auto-approving edits & commands)."));
|
|
250
|
+
} else {
|
|
251
|
+
const next = !context.yolo;
|
|
252
|
+
context.setYolo(next);
|
|
253
|
+
console.log(next ? pc3.red("\u26A1 YOLO mode enabled.") : pc3.green("\u2713 Safe mode enabled."));
|
|
254
|
+
}
|
|
255
|
+
return true;
|
|
256
|
+
case "/sessions": {
|
|
257
|
+
const list = listSessions();
|
|
258
|
+
console.log("\n" + pc3.bold("Recent Sessions:"));
|
|
259
|
+
if (list.length === 0) {
|
|
260
|
+
console.log(pc3.dim(" (No saved sessions)"));
|
|
261
|
+
} else {
|
|
262
|
+
for (const s of list.slice(0, 8)) {
|
|
263
|
+
const isCurrent = s.id === context.session.id ? pc3.green(" (current)") : "";
|
|
264
|
+
console.log(` ${pc3.cyan(s.id)} - ${s.title}${isCurrent} [${s.turnCount} turns]`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
console.log("");
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
case "/history":
|
|
271
|
+
console.log("\n" + pc3.bold("Session History:"));
|
|
272
|
+
if (context.session.turns.length === 0) {
|
|
273
|
+
console.log(pc3.dim(" (No messages yet in this session)"));
|
|
274
|
+
} else {
|
|
275
|
+
for (const t of context.session.turns) {
|
|
276
|
+
const roleBadge = t.role === "user" ? pc3.cyan("[User]") : t.role === "model" ? pc3.yellow("[Lantern]") : pc3.dim("[Tool]");
|
|
277
|
+
const preview = t.text ? t.text.slice(0, 100).replace(/\n/g, " ") : `(Tool calls: ${t.toolCalls?.map((c) => c.name).join(", ")})`;
|
|
278
|
+
console.log(` ${roleBadge} ${preview}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
console.log("");
|
|
282
|
+
return true;
|
|
283
|
+
case "/export": {
|
|
284
|
+
const format = arg.toLowerCase() === "json" ? "json" : "markdown";
|
|
285
|
+
if (format === "json") {
|
|
286
|
+
console.log("\n" + JSON.stringify(context.session, null, 2));
|
|
287
|
+
} else {
|
|
288
|
+
console.log("\n" + exportSessionMarkdown(context.session));
|
|
289
|
+
}
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
case "/exit":
|
|
293
|
+
case "/quit":
|
|
294
|
+
return false;
|
|
295
|
+
default:
|
|
296
|
+
console.log(pc3.yellow(`Unknown slash command '${command}'. Type ${pc3.bold("/help")} for available commands.`));
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/commands/interactive.ts
|
|
302
|
+
async function interactiveCommand(options) {
|
|
303
|
+
const config2 = loadConfig();
|
|
304
|
+
const workspace = options.cwd || process.cwd();
|
|
305
|
+
let model = options.model || config2.defaultModel || "gemini-3.8-flash";
|
|
306
|
+
let yolo = Boolean(options.yolo ?? config2.yolo);
|
|
307
|
+
let planMode = Boolean(options.plan);
|
|
308
|
+
let session;
|
|
309
|
+
if (options.session) {
|
|
310
|
+
const loaded = loadSession(options.session);
|
|
311
|
+
if (!loaded) {
|
|
312
|
+
console.error(pc4.red(`Error: Session '${options.session}' not found.`));
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
session = loaded;
|
|
316
|
+
console.log(pc4.green(`\u2713 Resumed session: ${session.id} (${session.turns.length} turns)`));
|
|
317
|
+
} else {
|
|
318
|
+
session = createNewSession(workspace, model);
|
|
319
|
+
}
|
|
320
|
+
const attachments = loadAttachments(options.attach);
|
|
321
|
+
printBanner({
|
|
322
|
+
version: "1.0.0",
|
|
323
|
+
model,
|
|
324
|
+
workspace,
|
|
325
|
+
yolo,
|
|
326
|
+
planMode
|
|
327
|
+
});
|
|
328
|
+
if (attachments.length > 0) {
|
|
329
|
+
console.log(pc4.cyan(`\u{1F4CE} Attached ${attachments.length} file(s): ${attachments.map((a) => a.name).join(", ")}
|
|
330
|
+
`));
|
|
331
|
+
}
|
|
332
|
+
const agent = new OrbitAgent();
|
|
333
|
+
await agent.initMcp();
|
|
334
|
+
const promptUI = new TerminalPrompt();
|
|
335
|
+
const slashContext = {
|
|
336
|
+
session,
|
|
337
|
+
model,
|
|
338
|
+
workspace,
|
|
339
|
+
yolo,
|
|
340
|
+
planMode,
|
|
341
|
+
setModel: (m) => {
|
|
342
|
+
model = m;
|
|
343
|
+
session.model = m;
|
|
344
|
+
},
|
|
345
|
+
setYolo: (y) => {
|
|
346
|
+
yolo = y;
|
|
347
|
+
},
|
|
348
|
+
setPlanMode: (p) => {
|
|
349
|
+
planMode = p;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
try {
|
|
353
|
+
while (true) {
|
|
354
|
+
const modeIndicator = planMode ? pc4.blue("[plan] ") : yolo ? pc4.red("[yolo] ") : "";
|
|
355
|
+
const promptStr = `${pc4.yellow("lantern")} ${modeIndicator}${pc4.dim("\u276F")} `;
|
|
356
|
+
const input = await promptUI.askPrompt(promptStr);
|
|
357
|
+
if (input === null || input === "/exit" || input === "/quit") {
|
|
358
|
+
console.log(pc4.dim("\nGoodbye! Session saved: " + session.id));
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
const trimmed = input.trim();
|
|
362
|
+
if (!trimmed) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (trimmed.startsWith("/")) {
|
|
366
|
+
const shouldContinue = await handleSlashCommand(trimmed, slashContext);
|
|
367
|
+
if (!shouldContinue) {
|
|
368
|
+
console.log(pc4.dim("Session saved. Exiting Code Lantern."));
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const context = {
|
|
374
|
+
workspace,
|
|
375
|
+
model,
|
|
376
|
+
yolo,
|
|
377
|
+
planMode,
|
|
378
|
+
jsonOutput: false,
|
|
379
|
+
maxSteps: config2.maxSteps || 25,
|
|
380
|
+
attachments
|
|
381
|
+
};
|
|
382
|
+
try {
|
|
383
|
+
console.log("");
|
|
384
|
+
const result = await agent.runTurn(session, trimmed, context, attachments);
|
|
385
|
+
if (result.usage.totalTokens > 0) {
|
|
386
|
+
console.log(pc4.dim(`
|
|
387
|
+
[Tokens: ${result.usage.totalTokens.toLocaleString()} | Total turns: ${session.turns.length}]`));
|
|
388
|
+
}
|
|
389
|
+
console.log("");
|
|
390
|
+
} catch (err) {
|
|
391
|
+
console.error("\n" + formatErrorMessage(err) + "\n");
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
} finally {
|
|
395
|
+
saveSession(session);
|
|
396
|
+
await agent.shutdown();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/commands/ask.ts
|
|
401
|
+
import pc5 from "picocolors";
|
|
402
|
+
async function askCommand(question, options) {
|
|
403
|
+
const workspace = options.cwd || process.cwd();
|
|
404
|
+
const model = options.model || "gemini-3.8-flash";
|
|
405
|
+
const yolo = Boolean(options.yolo);
|
|
406
|
+
const jsonOutput = Boolean(options.json);
|
|
407
|
+
const attachments = loadAttachments(options.attach);
|
|
408
|
+
const session = createNewSession(workspace, model, `Ask: ${question.slice(0, 40)}`);
|
|
409
|
+
const agent = new OrbitAgent();
|
|
410
|
+
await agent.initMcp();
|
|
411
|
+
const context = {
|
|
412
|
+
workspace,
|
|
413
|
+
model,
|
|
414
|
+
yolo,
|
|
415
|
+
planMode: false,
|
|
416
|
+
jsonOutput,
|
|
417
|
+
maxSteps: options.maxSteps || 25,
|
|
418
|
+
attachments
|
|
419
|
+
};
|
|
420
|
+
try {
|
|
421
|
+
const result = await agent.runTurn(session, question, context, attachments);
|
|
422
|
+
if (jsonOutput) {
|
|
423
|
+
console.log(JSON.stringify({
|
|
424
|
+
sessionId: session.id,
|
|
425
|
+
model,
|
|
426
|
+
question,
|
|
427
|
+
reply: result.reply,
|
|
428
|
+
toolCount: result.toolCount,
|
|
429
|
+
usage: result.usage
|
|
430
|
+
}, null, 2));
|
|
431
|
+
} else {
|
|
432
|
+
if (result.usage.totalTokens > 0) {
|
|
433
|
+
console.log(pc5.dim(`
|
|
434
|
+
[Tokens: ${result.usage.totalTokens} | Tools executed: ${result.toolCount}]`));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
} catch (err) {
|
|
438
|
+
if (jsonOutput) {
|
|
439
|
+
console.error(JSON.stringify({ error: err.message }));
|
|
440
|
+
} else {
|
|
441
|
+
console.error(formatErrorMessage(err));
|
|
442
|
+
}
|
|
443
|
+
process.exit(1);
|
|
444
|
+
} finally {
|
|
445
|
+
await agent.shutdown();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// src/commands/plan.ts
|
|
450
|
+
import pc6 from "picocolors";
|
|
451
|
+
async function planCommand(task, options) {
|
|
452
|
+
const workspace = options.cwd || process.cwd();
|
|
453
|
+
const model = options.model || "gemini-3.8-flash";
|
|
454
|
+
const jsonOutput = Boolean(options.json);
|
|
455
|
+
const attachments = loadAttachments(options.attach);
|
|
456
|
+
const session = createNewSession(workspace, model, `Plan: ${task.slice(0, 40)}`);
|
|
457
|
+
if (!jsonOutput) {
|
|
458
|
+
console.log(pc6.bold(pc6.yellow("\n\u{1F3EE} Code Lantern Plan Mode (Read-Only)")));
|
|
459
|
+
console.log(pc6.dim("Inspecting workspace and formulating step-by-step implementation plan...\n"));
|
|
460
|
+
}
|
|
461
|
+
const agent = new OrbitAgent();
|
|
462
|
+
await agent.initMcp();
|
|
463
|
+
const context = {
|
|
464
|
+
workspace,
|
|
465
|
+
model,
|
|
466
|
+
yolo: false,
|
|
467
|
+
planMode: true,
|
|
468
|
+
jsonOutput,
|
|
469
|
+
maxSteps: options.maxSteps || 25,
|
|
470
|
+
attachments
|
|
471
|
+
};
|
|
472
|
+
const planPrompt = `Please inspect the codebase as needed and generate a detailed, structured, read-only implementation plan for the following task:
|
|
473
|
+
|
|
474
|
+
${task}`;
|
|
475
|
+
try {
|
|
476
|
+
const result = await agent.runTurn(session, planPrompt, context, attachments);
|
|
477
|
+
if (jsonOutput) {
|
|
478
|
+
console.log(JSON.stringify({
|
|
479
|
+
sessionId: session.id,
|
|
480
|
+
model,
|
|
481
|
+
task,
|
|
482
|
+
plan: result.reply,
|
|
483
|
+
usage: result.usage
|
|
484
|
+
}, null, 2));
|
|
485
|
+
} else {
|
|
486
|
+
console.log(pc6.dim(`
|
|
487
|
+
[Plan complete. Session ID: ${session.id}]`));
|
|
488
|
+
}
|
|
489
|
+
} catch (err) {
|
|
490
|
+
if (jsonOutput) {
|
|
491
|
+
console.error(JSON.stringify({ error: err.message }));
|
|
492
|
+
} else {
|
|
493
|
+
console.error(formatErrorMessage(err));
|
|
494
|
+
}
|
|
495
|
+
process.exit(1);
|
|
496
|
+
} finally {
|
|
497
|
+
await agent.shutdown();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/commands/resume.ts
|
|
502
|
+
import pc7 from "picocolors";
|
|
503
|
+
async function resumeCommand(sessionId, options = {}) {
|
|
504
|
+
const sessions = listSessions();
|
|
505
|
+
if (sessions.length === 0) {
|
|
506
|
+
console.log(pc7.yellow("No previous sessions found. Starting a fresh session...\n"));
|
|
507
|
+
await interactiveCommand(options);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
let targetId = sessionId;
|
|
511
|
+
if (!targetId) {
|
|
512
|
+
targetId = sessions[0].id;
|
|
513
|
+
console.log(pc7.cyan(`Resuming most recent session: ${sessions[0].title} (${targetId})`));
|
|
514
|
+
} else {
|
|
515
|
+
const existing = loadSession(targetId);
|
|
516
|
+
if (!existing) {
|
|
517
|
+
console.error(pc7.red(`Error: Session '${targetId}' not found.`));
|
|
518
|
+
console.log("\nAvailable sessions:");
|
|
519
|
+
for (const s of sessions.slice(0, 5)) {
|
|
520
|
+
console.log(` - ${pc7.cyan(s.id)} (${s.title})`);
|
|
521
|
+
}
|
|
522
|
+
process.exit(1);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
await interactiveCommand({
|
|
526
|
+
...options,
|
|
527
|
+
session: targetId
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/commands/sessions.ts
|
|
532
|
+
import pc8 from "picocolors";
|
|
533
|
+
async function sessionsCommand(options) {
|
|
534
|
+
if (options.delete) {
|
|
535
|
+
const success = deleteSession(options.delete);
|
|
536
|
+
if (success) {
|
|
537
|
+
console.log(pc8.green(`\u2713 Deleted session: ${options.delete}`));
|
|
538
|
+
} else {
|
|
539
|
+
console.error(pc8.red(`\u2717 Could not find session: ${options.delete}`));
|
|
540
|
+
process.exit(1);
|
|
541
|
+
}
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (options.export) {
|
|
545
|
+
const session = loadSession(options.export);
|
|
546
|
+
if (!session) {
|
|
547
|
+
console.error(pc8.red(`\u2717 Session '${options.export}' not found.`));
|
|
548
|
+
process.exit(1);
|
|
549
|
+
}
|
|
550
|
+
if (options.json) {
|
|
551
|
+
console.log(JSON.stringify(session, null, 2));
|
|
552
|
+
} else {
|
|
553
|
+
console.log(exportSessionMarkdown(session));
|
|
554
|
+
}
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const sessions = listSessions();
|
|
558
|
+
if (sessions.length === 0) {
|
|
559
|
+
console.log(pc8.dim("No saved Code Lantern sessions found."));
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (options.json) {
|
|
563
|
+
console.log(JSON.stringify(sessions, null, 2));
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
console.log("\n" + pc8.bold(pc8.yellow("\u{1F3EE} Saved Code Lantern Sessions")));
|
|
567
|
+
console.log(pc8.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
568
|
+
console.log(
|
|
569
|
+
pc8.bold("Session ID".padEnd(28)) + pc8.bold("Turns".padEnd(8)) + pc8.bold("Model".padEnd(20)) + pc8.bold("Updated".padEnd(20)) + pc8.bold("Title")
|
|
570
|
+
);
|
|
571
|
+
console.log(pc8.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
572
|
+
for (const s of sessions) {
|
|
573
|
+
const dateStr = new Date(s.updatedAt).toLocaleString().slice(0, 18);
|
|
574
|
+
console.log(
|
|
575
|
+
pc8.cyan(s.id.slice(0, 26).padEnd(28)) + String(s.turnCount).padEnd(8) + s.model.slice(0, 18).padEnd(20) + dateStr.padEnd(20) + pc8.dim(s.title.slice(0, 30))
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
console.log(pc8.dim("\nTo resume a session: lantern resume <session-id>"));
|
|
579
|
+
console.log(pc8.dim("To export a session: lantern sessions --export <session-id>"));
|
|
580
|
+
console.log(pc8.dim("To delete a session: lantern sessions --delete <session-id>\n"));
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// src/commands/configCmd.ts
|
|
584
|
+
import pc9 from "picocolors";
|
|
585
|
+
async function configCommand(action, key, value) {
|
|
586
|
+
const configPath = getConfigFilePath();
|
|
587
|
+
if (!action || action === "list") {
|
|
588
|
+
const config2 = loadConfig();
|
|
589
|
+
console.log("\n" + pc9.bold(pc9.yellow("\u{1F3EE} Code Lantern Configuration")));
|
|
590
|
+
console.log(pc9.dim(`File: ${configPath}`));
|
|
591
|
+
console.log(pc9.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
592
|
+
for (const [k, v] of Object.entries(config2)) {
|
|
593
|
+
if (k === "mcpServers") {
|
|
594
|
+
const count = Object.keys(v).length;
|
|
595
|
+
console.log(` ${pc9.cyan(k.padEnd(24))} = ${pc9.dim(`{ ${count} server(s) configured }`)}`);
|
|
596
|
+
} else {
|
|
597
|
+
console.log(` ${pc9.cyan(k.padEnd(24))} = ${pc9.green(JSON.stringify(v))}`);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
console.log(pc9.dim("\nUpdate setting: lantern config set <key> <value>"));
|
|
601
|
+
console.log(pc9.dim(`Supported keys: ${ALLOWED_CONFIG_KEYS.join(", ")}
|
|
602
|
+
`));
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (action === "path") {
|
|
606
|
+
console.log(configPath);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (action === "get") {
|
|
610
|
+
if (!key) {
|
|
611
|
+
console.error(pc9.red("Error: Missing config key. Usage: orbit config get <key>"));
|
|
612
|
+
process.exit(1);
|
|
613
|
+
}
|
|
614
|
+
const config2 = loadConfig();
|
|
615
|
+
if (!(key in config2)) {
|
|
616
|
+
console.error(pc9.red(`Error: Unknown configuration key '${key}'`));
|
|
617
|
+
process.exit(1);
|
|
618
|
+
}
|
|
619
|
+
console.log(JSON.stringify(config2[key]));
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
if (action === "set") {
|
|
623
|
+
if (!key || value === void 0) {
|
|
624
|
+
console.error(pc9.red("Error: Missing key or value. Usage: orbit config set <key> <value>"));
|
|
625
|
+
process.exit(1);
|
|
626
|
+
}
|
|
627
|
+
const res = setConfigValue(key, value);
|
|
628
|
+
if (!res.success) {
|
|
629
|
+
console.error(pc9.red(`Error: ${res.error}`));
|
|
630
|
+
process.exit(1);
|
|
631
|
+
}
|
|
632
|
+
console.log(pc9.green(`\u2713 Set ${key} = ${value}`));
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
console.error(pc9.red(`Error: Unknown config action '${action}'. Available: list, get, set, path`));
|
|
636
|
+
process.exit(1);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/commands/doctor.ts
|
|
640
|
+
import fs2 from "fs";
|
|
641
|
+
import path2 from "path";
|
|
642
|
+
import { execSync } from "child_process";
|
|
643
|
+
import pc10 from "picocolors";
|
|
644
|
+
import { GoogleGenAI } from "@google/genai";
|
|
645
|
+
async function doctorCommand() {
|
|
646
|
+
console.log("\n" + pc10.bold(pc10.yellow("\u{1F3EE} Code Lantern Doctor \u2014 Diagnostics")));
|
|
647
|
+
console.log(pc10.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
648
|
+
let issuesFound = 0;
|
|
649
|
+
const platformName = process.platform === "darwin" ? `macOS (${process.arch === "arm64" ? "Apple Silicon" : "Intel"})` : process.platform === "win32" ? `Windows (${process.arch})` : `${process.platform} (${process.arch})`;
|
|
650
|
+
console.log(`${pc10.green("\u2713")} Platform / OS: ${platformName}`);
|
|
651
|
+
const nodeVer = process.version;
|
|
652
|
+
const major = parseInt(nodeVer.replace("v", "").split(".")[0], 10);
|
|
653
|
+
if (major >= 18) {
|
|
654
|
+
console.log(`${pc10.green("\u2713")} Node.js Runtime: ${nodeVer} (compatible)`);
|
|
655
|
+
} else {
|
|
656
|
+
issuesFound++;
|
|
657
|
+
console.log(`${pc10.red("\u2717")} Node.js Runtime: ${nodeVer} (Node 18+ required)`);
|
|
658
|
+
}
|
|
659
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
660
|
+
if (!apiKey || apiKey.trim().length === 0) {
|
|
661
|
+
issuesFound++;
|
|
662
|
+
console.log(`${pc10.red("\u2717")} GEMINI_API_KEY: Missing!`);
|
|
663
|
+
console.log(pc10.yellow(' \u2192 To fix: export GEMINI_API_KEY="your-api-key" from https://aistudio.google.com/app/apikey'));
|
|
664
|
+
} else {
|
|
665
|
+
const masked = apiKey.length > 8 ? `${apiKey.slice(0, 4)}...${apiKey.slice(-4)}` : "configured";
|
|
666
|
+
console.log(`${pc10.green("\u2713")} GEMINI_API_KEY: Found (${masked})`);
|
|
667
|
+
try {
|
|
668
|
+
const ai = new GoogleGenAI({ apiKey: apiKey.trim() });
|
|
669
|
+
const testModel = "gemini-3.8-flash";
|
|
670
|
+
await ai.models.countTokens({
|
|
671
|
+
model: testModel,
|
|
672
|
+
contents: "test"
|
|
673
|
+
});
|
|
674
|
+
console.log(`${pc10.green("\u2713")} Gemini API Auth: Verified (contacted '${testModel}')`);
|
|
675
|
+
} catch (err) {
|
|
676
|
+
issuesFound++;
|
|
677
|
+
console.log(`${pc10.red("\u2717")} Gemini API Auth: Failed to contact API: ${err.message}`);
|
|
678
|
+
console.log(pc10.yellow(" \u2192 Verify your API key at https://aistudio.google.com/app/apikey"));
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
try {
|
|
682
|
+
const gitVer = execSync("git --version", { stdio: "pipe" }).toString().trim();
|
|
683
|
+
console.log(`${pc10.green("\u2713")} Git CLI: Available (${gitVer})`);
|
|
684
|
+
} catch {
|
|
685
|
+
console.log(`${pc10.yellow("!")} Git CLI: Not found in PATH (some git tools will be unavailable)`);
|
|
686
|
+
}
|
|
687
|
+
try {
|
|
688
|
+
const configFile = getConfigFilePath();
|
|
689
|
+
const config2 = loadConfig();
|
|
690
|
+
console.log(`${pc10.green("\u2713")} Config File: ${configFile} (Model: ${config2.defaultModel})`);
|
|
691
|
+
} catch (err) {
|
|
692
|
+
issuesFound++;
|
|
693
|
+
console.log(`${pc10.red("\u2717")} Config File: Invalid config: ${err.message}`);
|
|
694
|
+
}
|
|
695
|
+
try {
|
|
696
|
+
const sessionsDir = getSessionsDir();
|
|
697
|
+
const count = fs2.readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).length;
|
|
698
|
+
console.log(`${pc10.green("\u2713")} Session Storage: ${sessionsDir} (${count} stored session(s))`);
|
|
699
|
+
} catch (err) {
|
|
700
|
+
issuesFound++;
|
|
701
|
+
console.log(`${pc10.red("\u2717")} Session Storage: Failed to access: ${err.message}`);
|
|
702
|
+
}
|
|
703
|
+
const mcpServers = loadMcpConfig();
|
|
704
|
+
const serverCount = Object.keys(mcpServers).length;
|
|
705
|
+
if (serverCount === 0) {
|
|
706
|
+
console.log(`${pc10.cyan("\u2139")} MCP Configuration: No external MCP servers configured (optional)`);
|
|
707
|
+
} else {
|
|
708
|
+
console.log(`${pc10.green("\u2713")} MCP Configuration: ${serverCount} server(s) configured`);
|
|
709
|
+
for (const [name, s] of Object.entries(mcpServers)) {
|
|
710
|
+
const status = s.disabled ? pc10.dim("(disabled)") : pc10.green("(enabled)");
|
|
711
|
+
console.log(` \u2022 ${name}: ${s.command} ${(s.args || []).join(" ")} ${status}`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
const cwd = process.cwd();
|
|
715
|
+
const instructions = loadProjectInstructions(cwd);
|
|
716
|
+
if (instructions.found) {
|
|
717
|
+
console.log(`${pc10.green("\u2713")} Project Instructions: Found (${path2.basename(instructions.filePath || "")}) in ${cwd}`);
|
|
718
|
+
} else {
|
|
719
|
+
console.log(`${pc10.dim("\u2139")} Project Instructions: None found in workspace (e.g. LANTERN.md, GEMINI.md)`);
|
|
720
|
+
}
|
|
721
|
+
console.log(pc10.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
722
|
+
if (issuesFound === 0) {
|
|
723
|
+
console.log(pc10.bold(pc10.green("\u2728 All systems healthy! Code Lantern CLI is ready to assist.")));
|
|
724
|
+
} else {
|
|
725
|
+
console.log(pc10.bold(pc10.yellow(`\u26A0\uFE0F Doctor found ${issuesFound} issue(s) to address before optimal use.`)));
|
|
726
|
+
}
|
|
727
|
+
console.log("");
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// src/commands/mcpCmd.ts
|
|
731
|
+
import pc11 from "picocolors";
|
|
732
|
+
async function mcpCommand(action, serverName, command, args = []) {
|
|
733
|
+
const servers = loadMcpConfig();
|
|
734
|
+
if (!action || action === "list") {
|
|
735
|
+
console.log("\n" + pc11.bold(pc11.yellow("\u{1F3EE} Code Lantern MCP Servers")));
|
|
736
|
+
console.log(pc11.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
737
|
+
const entries = Object.entries(servers);
|
|
738
|
+
if (entries.length === 0) {
|
|
739
|
+
console.log(pc11.dim("No MCP servers configured."));
|
|
740
|
+
console.log(pc11.dim("\nTo add a server: lantern mcp add <name> <command> [args...]"));
|
|
741
|
+
console.log(pc11.dim("Example: lantern mcp add sqlite npx -y @modelcontextprotocol/server-sqlite /path/to/db.sqlite\n"));
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
for (const [name, s] of entries) {
|
|
745
|
+
const statusBadge = s.disabled ? pc11.red("[disabled]") : pc11.green("[enabled]");
|
|
746
|
+
console.log(`${pc11.bold(name)} ${statusBadge}`);
|
|
747
|
+
console.log(` Command: ${pc11.cyan(s.command)} ${(s.args || []).join(" ")}`);
|
|
748
|
+
if (s.env && Object.keys(s.env).length > 0) {
|
|
749
|
+
console.log(` Env: ${JSON.stringify(s.env)}`);
|
|
750
|
+
}
|
|
751
|
+
console.log("");
|
|
752
|
+
}
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
if (action === "add") {
|
|
756
|
+
if (!serverName || !command) {
|
|
757
|
+
console.error(pc11.red("Error: Missing arguments. Usage: orbit mcp add <name> <command> [args...]"));
|
|
758
|
+
process.exit(1);
|
|
759
|
+
}
|
|
760
|
+
addMcpServer(serverName, {
|
|
761
|
+
command,
|
|
762
|
+
args: args.length > 0 ? args : void 0
|
|
763
|
+
});
|
|
764
|
+
console.log(pc11.green(`\u2713 Added MCP server '${serverName}': ${command} ${args.join(" ")}`));
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (action === "remove" || action === "rm") {
|
|
768
|
+
if (!serverName) {
|
|
769
|
+
console.error(pc11.red("Error: Missing server name. Usage: orbit mcp remove <name>"));
|
|
770
|
+
process.exit(1);
|
|
771
|
+
}
|
|
772
|
+
const removed = removeMcpServer(serverName);
|
|
773
|
+
if (removed) {
|
|
774
|
+
console.log(pc11.green(`\u2713 Removed MCP server '${serverName}'`));
|
|
775
|
+
} else {
|
|
776
|
+
console.error(pc11.red(`\u2717 MCP server '${serverName}' not found`));
|
|
777
|
+
process.exit(1);
|
|
778
|
+
}
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
if (action === "disable") {
|
|
782
|
+
if (!serverName) {
|
|
783
|
+
console.error(pc11.red("Error: Missing server name. Usage: orbit mcp disable <name>"));
|
|
784
|
+
process.exit(1);
|
|
785
|
+
}
|
|
786
|
+
toggleMcpServer(serverName, true);
|
|
787
|
+
console.log(pc11.yellow(`\u2713 Disabled MCP server '${serverName}'`));
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
if (action === "enable") {
|
|
791
|
+
if (!serverName) {
|
|
792
|
+
console.error(pc11.red("Error: Missing server name. Usage: orbit mcp enable <name>"));
|
|
793
|
+
process.exit(1);
|
|
794
|
+
}
|
|
795
|
+
toggleMcpServer(serverName, false);
|
|
796
|
+
console.log(pc11.green(`\u2713 Enabled MCP server '${serverName}'`));
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (action === "test") {
|
|
800
|
+
if (!serverName) {
|
|
801
|
+
console.error(pc11.red("Error: Missing server name. Usage: orbit mcp test <name>"));
|
|
802
|
+
process.exit(1);
|
|
803
|
+
}
|
|
804
|
+
const server = servers[serverName];
|
|
805
|
+
if (!server) {
|
|
806
|
+
console.error(pc11.red(`\u2717 MCP server '${serverName}' not found in configuration`));
|
|
807
|
+
process.exit(1);
|
|
808
|
+
}
|
|
809
|
+
console.log(`Connecting to MCP server '${serverName}'...`);
|
|
810
|
+
const mgr = new McpManager();
|
|
811
|
+
try {
|
|
812
|
+
const tools = await mgr.connectServer(serverName, server);
|
|
813
|
+
console.log(pc11.green(`\u2713 Connected successfully! Exposing ${tools.length} tool(s):`));
|
|
814
|
+
for (const t of tools) {
|
|
815
|
+
console.log(` - ${pc11.cyan(t.definition.name)}: ${t.definition.description}`);
|
|
816
|
+
}
|
|
817
|
+
} catch (err) {
|
|
818
|
+
console.error(pc11.red(`\u2717 Failed to connect to '${serverName}': ${err.message}`));
|
|
819
|
+
process.exit(1);
|
|
820
|
+
} finally {
|
|
821
|
+
await mgr.closeAll();
|
|
822
|
+
}
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
console.error(pc11.red(`Error: Unknown action '${action}'. Available: list, add, remove, enable, disable, test`));
|
|
826
|
+
process.exit(1);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// src/cli.ts
|
|
830
|
+
var program = new Command();
|
|
831
|
+
var config = loadConfig();
|
|
832
|
+
program.name("lantern").description("Code Lantern CLI \u2014 Personal AI coding assistant for the terminal powered by Google Gemini").version("1.0.0");
|
|
833
|
+
program.option("-m, --model <model>", "Gemini model ID to use", config.defaultModel || "gemini-3.8-flash").option("-C, --cwd <path>", "Working directory for Code Lantern agent", process.cwd()).option("-s, --session <id>", "Resume or use a specific session ID").option("-a, --attach <files...>", "Attach one or more files/images/documents to the request").option("--yolo", "Enable YOLO mode (auto-approve file edits and shell commands)", false).option("--json", "Output results formatted as JSON", false);
|
|
834
|
+
program.command("ask <question...>").description("Ask a single question or task and stream the answer").action(async (questionParts) => {
|
|
835
|
+
const opts = program.opts();
|
|
836
|
+
const question = questionParts.join(" ");
|
|
837
|
+
await askCommand(question, {
|
|
838
|
+
model: opts.model,
|
|
839
|
+
cwd: opts.cwd,
|
|
840
|
+
attach: opts.attach,
|
|
841
|
+
yolo: opts.yolo,
|
|
842
|
+
json: opts.json
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
program.command("plan <task...>").description("Inspect the codebase and produce a read-only implementation plan").action(async (taskParts) => {
|
|
846
|
+
const opts = program.opts();
|
|
847
|
+
const task = taskParts.join(" ");
|
|
848
|
+
await planCommand(task, {
|
|
849
|
+
model: opts.model,
|
|
850
|
+
cwd: opts.cwd,
|
|
851
|
+
attach: opts.attach,
|
|
852
|
+
json: opts.json
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
program.command("resume [sessionId]").description("Continue a saved conversation session").action(async (sessionId) => {
|
|
856
|
+
const opts = program.opts();
|
|
857
|
+
await resumeCommand(sessionId, {
|
|
858
|
+
model: opts.model,
|
|
859
|
+
cwd: opts.cwd,
|
|
860
|
+
attach: opts.attach,
|
|
861
|
+
yolo: opts.yolo
|
|
862
|
+
});
|
|
863
|
+
});
|
|
864
|
+
program.command("sessions").description("List, delete, or export saved local sessions").option("-d, --delete <id>", "Delete session by ID").option("-e, --export <id>", "Export session transcript by ID").action(async (cmdOpts) => {
|
|
865
|
+
const globalOpts = program.opts();
|
|
866
|
+
await sessionsCommand({
|
|
867
|
+
delete: cmdOpts.delete,
|
|
868
|
+
export: cmdOpts.export,
|
|
869
|
+
json: globalOpts.json
|
|
870
|
+
});
|
|
871
|
+
});
|
|
872
|
+
program.command("config [action] [key] [value]").description("View or update configuration (list, get, set, path)").action(async (action, key, value) => {
|
|
873
|
+
await configCommand(action, key, value);
|
|
874
|
+
});
|
|
875
|
+
program.command("doctor").description("Diagnose API key, configuration, environment, and MCP server health").action(async () => {
|
|
876
|
+
await doctorCommand();
|
|
877
|
+
});
|
|
878
|
+
program.command("mcp [action] [serverName] [command] [args...]").description("Manage configured MCP servers (list, add, remove, enable, disable, test)").allowUnknownOption().action(async (action, serverName, cmd, ...rest) => {
|
|
879
|
+
const rawArgs = rest.flat().filter((a) => typeof a === "string");
|
|
880
|
+
await mcpCommand(action, serverName, cmd, rawArgs);
|
|
881
|
+
});
|
|
882
|
+
program.action(async () => {
|
|
883
|
+
const opts = program.opts();
|
|
884
|
+
if (!process.stdin.isTTY) {
|
|
885
|
+
let pipedData = "";
|
|
886
|
+
process.stdin.setEncoding("utf8");
|
|
887
|
+
for await (const chunk of process.stdin) {
|
|
888
|
+
pipedData += chunk;
|
|
889
|
+
}
|
|
890
|
+
const trimmedPiped = pipedData.trim();
|
|
891
|
+
if (trimmedPiped) {
|
|
892
|
+
await askCommand(trimmedPiped, {
|
|
893
|
+
model: opts.model,
|
|
894
|
+
cwd: opts.cwd,
|
|
895
|
+
attach: opts.attach,
|
|
896
|
+
yolo: opts.yolo,
|
|
897
|
+
json: opts.json
|
|
898
|
+
});
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
await interactiveCommand({
|
|
903
|
+
model: opts.model,
|
|
904
|
+
cwd: opts.cwd,
|
|
905
|
+
session: opts.session,
|
|
906
|
+
attach: opts.attach,
|
|
907
|
+
yolo: opts.yolo
|
|
908
|
+
});
|
|
909
|
+
});
|
|
910
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
911
|
+
console.error(`Error: ${err.message || err}`);
|
|
912
|
+
process.exit(1);
|
|
913
|
+
});
|
|
914
|
+
//# sourceMappingURL=cli.js.map
|