codelantern-agent-cli 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +314 -2
- package/dist/chunk-PELESOCI.js +4307 -0
- package/dist/chunk-PELESOCI.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1510 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +546 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -4
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1510 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ALLOWED_CONFIG_KEYS,
|
|
4
|
+
CancelledError,
|
|
5
|
+
LanternAgent,
|
|
6
|
+
McpManager,
|
|
7
|
+
ProviderRegistry,
|
|
8
|
+
SkillManager,
|
|
9
|
+
addMcpServer,
|
|
10
|
+
createNewSession,
|
|
11
|
+
deleteSession,
|
|
12
|
+
exportSessionMarkdown,
|
|
13
|
+
formatErrorMessage,
|
|
14
|
+
getApiKey,
|
|
15
|
+
getConfigFilePath,
|
|
16
|
+
getLanternHomeDir,
|
|
17
|
+
getSessionsDir,
|
|
18
|
+
listConfiguredKeys,
|
|
19
|
+
listSessions,
|
|
20
|
+
loadAttachments,
|
|
21
|
+
loadConfig,
|
|
22
|
+
loadMcpConfig,
|
|
23
|
+
loadProjectInstructions,
|
|
24
|
+
loadSession,
|
|
25
|
+
maskApiKey,
|
|
26
|
+
removeApiKey,
|
|
27
|
+
removeMcpServer,
|
|
28
|
+
saveSession,
|
|
29
|
+
setApiKey,
|
|
30
|
+
setConfigValue,
|
|
31
|
+
toggleMcpServer
|
|
32
|
+
} from "./chunk-PELESOCI.js";
|
|
33
|
+
|
|
34
|
+
// src/cli.ts
|
|
35
|
+
import { Command } from "commander";
|
|
36
|
+
|
|
37
|
+
// src/commands/interactive.ts
|
|
38
|
+
import pc4 from "picocolors";
|
|
39
|
+
|
|
40
|
+
// src/ui/prompt.ts
|
|
41
|
+
import readline from "readline";
|
|
42
|
+
import fs from "fs";
|
|
43
|
+
import path from "path";
|
|
44
|
+
import pc from "picocolors";
|
|
45
|
+
function getHistoryFilePath() {
|
|
46
|
+
return path.join(getLanternHomeDir(), "history.txt");
|
|
47
|
+
}
|
|
48
|
+
function loadHistory() {
|
|
49
|
+
const filePath = getHistoryFilePath();
|
|
50
|
+
if (!fs.existsSync(filePath)) return [];
|
|
51
|
+
try {
|
|
52
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
53
|
+
return raw.replace(/\r\n/g, "\n").split("\n").filter((line) => line.trim().length > 0);
|
|
54
|
+
} catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function appendHistory(line) {
|
|
59
|
+
const clean = line.trim();
|
|
60
|
+
if (!clean || clean.startsWith("/") || clean.length < 2) return;
|
|
61
|
+
const filePath = getHistoryFilePath();
|
|
62
|
+
try {
|
|
63
|
+
fs.appendFileSync(filePath, clean + "\n", { encoding: "utf8" });
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
var TerminalPrompt = class {
|
|
68
|
+
history;
|
|
69
|
+
constructor() {
|
|
70
|
+
this.history = loadHistory();
|
|
71
|
+
}
|
|
72
|
+
async askPrompt(promptPrefix = pc.yellow("lantern> ")) {
|
|
73
|
+
const rl = readline.createInterface({
|
|
74
|
+
input: process.stdin,
|
|
75
|
+
output: process.stdout,
|
|
76
|
+
history: this.history,
|
|
77
|
+
historySize: 200,
|
|
78
|
+
prompt: promptPrefix
|
|
79
|
+
});
|
|
80
|
+
let sigintCount = 0;
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
let accumulated = "";
|
|
83
|
+
let insideTripleQuotes = false;
|
|
84
|
+
rl.prompt();
|
|
85
|
+
rl.on("line", (rawLine) => {
|
|
86
|
+
sigintCount = 0;
|
|
87
|
+
const line = rawLine.replace(/\r/g, "");
|
|
88
|
+
if (!insideTripleQuotes && (line.trim().startsWith('"""') || line.trim().startsWith("```"))) {
|
|
89
|
+
insideTripleQuotes = true;
|
|
90
|
+
const content = line.trim().slice(3);
|
|
91
|
+
accumulated += content + "\n";
|
|
92
|
+
rl.setPrompt(pc.dim("... "));
|
|
93
|
+
rl.prompt();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (insideTripleQuotes) {
|
|
97
|
+
if (line.trim().endsWith('"""') || line.trim().endsWith("```")) {
|
|
98
|
+
const content = line.trim().slice(0, -3);
|
|
99
|
+
accumulated += content;
|
|
100
|
+
insideTripleQuotes = false;
|
|
101
|
+
rl.close();
|
|
102
|
+
appendHistory(accumulated.trim());
|
|
103
|
+
return resolve(accumulated.trim());
|
|
104
|
+
}
|
|
105
|
+
accumulated += line + "\n";
|
|
106
|
+
rl.prompt();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (line.endsWith("\\")) {
|
|
110
|
+
accumulated += line.slice(0, -1) + "\n";
|
|
111
|
+
rl.setPrompt(pc.dim("... "));
|
|
112
|
+
rl.prompt();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
accumulated += line;
|
|
116
|
+
rl.close();
|
|
117
|
+
const result = accumulated.trim();
|
|
118
|
+
if (result) {
|
|
119
|
+
appendHistory(result);
|
|
120
|
+
}
|
|
121
|
+
resolve(result);
|
|
122
|
+
});
|
|
123
|
+
rl.on("SIGINT", () => {
|
|
124
|
+
sigintCount++;
|
|
125
|
+
if (sigintCount >= 2 || insideTripleQuotes) {
|
|
126
|
+
console.log("\nExiting Code Lantern.");
|
|
127
|
+
rl.close();
|
|
128
|
+
resolve("/exit");
|
|
129
|
+
} else {
|
|
130
|
+
console.log(pc.dim("\n(Press Ctrl+C again or type /exit to quit)"));
|
|
131
|
+
accumulated = "";
|
|
132
|
+
insideTripleQuotes = false;
|
|
133
|
+
rl.setPrompt(promptPrefix);
|
|
134
|
+
rl.prompt();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
rl.on("close", () => {
|
|
138
|
+
if (!accumulated) {
|
|
139
|
+
resolve(null);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// src/ui/banner.ts
|
|
147
|
+
import pc2 from "picocolors";
|
|
148
|
+
function printBanner(options) {
|
|
149
|
+
const logo = `
|
|
150
|
+
____ _ _ _
|
|
151
|
+
/ ___|___ __| | ___ | | __ _ _ __ | |_ ___ _ __ _ __
|
|
152
|
+
| | / _ \\ / _\` |/ _ \\ | | / _\` | '_ \\ | __/ _ \\ '__| '_ \\
|
|
153
|
+
| |__| (_) | (_| | __/ | |__| (_| | | | || || __/ | | | | |
|
|
154
|
+
\\____\\___/ \\__,_|\\___| |_____\\__,_|_| |_(_)__\\___|_| |_| |_|
|
|
155
|
+
\u{1F3EE} Personal AI Coding Assistant for Terminal (Multi-Provider)
|
|
156
|
+
`;
|
|
157
|
+
console.log(pc2.yellow(logo));
|
|
158
|
+
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"));
|
|
159
|
+
const providerDisplay = options.provider ? `${pc2.bold("Provider:")} ${pc2.magenta(options.provider)} ` : "";
|
|
160
|
+
console.log(`${pc2.bold("Product:")} Code Lantern CLI (${pc2.cyan("lantern")}) ${pc2.bold("Version:")} ${options.version}`);
|
|
161
|
+
console.log(`${providerDisplay}${pc2.bold("Model:")} ${pc2.green(options.model)}`);
|
|
162
|
+
console.log(`${pc2.bold("Workspace:")} ${pc2.blue(options.workspace)}`);
|
|
163
|
+
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) "));
|
|
164
|
+
console.log(`${pc2.bold("Status:")} ${modeBadge}`);
|
|
165
|
+
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"));
|
|
166
|
+
console.log(pc2.dim(`Type your task or question. Use ${pc2.bold(pc2.yellow("/help"))} for commands, ${pc2.bold(pc2.yellow("/exit"))} to quit.`));
|
|
167
|
+
console.log(pc2.dim(`Multiline tip: End a line with '\\' or enclose in '"""' for multiline blocks.
|
|
168
|
+
`));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/ui/slashCommands.ts
|
|
172
|
+
import pc3 from "picocolors";
|
|
173
|
+
async function handleSlashCommand(input, context) {
|
|
174
|
+
const parts = input.trim().split(/\s+/);
|
|
175
|
+
const command = parts[0].toLowerCase();
|
|
176
|
+
const arg = parts.slice(1).join(" ");
|
|
177
|
+
switch (command) {
|
|
178
|
+
case "/help":
|
|
179
|
+
console.log("\n" + pc3.bold(pc3.yellow("\u{1F3EE} Code Lantern CLI \u2014 Help & Commands")));
|
|
180
|
+
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"));
|
|
181
|
+
console.log(pc3.bold("Slash Commands:"));
|
|
182
|
+
console.log(` ${pc3.cyan("/help")} Show this help overview`);
|
|
183
|
+
console.log(` ${pc3.cyan("/clear")} Clear the terminal screen`);
|
|
184
|
+
console.log(` ${pc3.cyan("/status")} Display session, provider, model, and skill status`);
|
|
185
|
+
console.log(` ${pc3.cyan("/provider [name]")} View or switch active AI provider (gemini, openai, anthropic, openrouter)`);
|
|
186
|
+
console.log(` ${pc3.cyan("/model [id]")} View or switch active model`);
|
|
187
|
+
console.log(` ${pc3.cyan("/skills [name]")} List, enable, or disable active skills`);
|
|
188
|
+
console.log(` ${pc3.cyan("/timing [on|off]")} Toggle request timing diagnostics display`);
|
|
189
|
+
console.log(` ${pc3.cyan("/plan [on|off]")} Toggle read-only planning mode`);
|
|
190
|
+
console.log(` ${pc3.cyan("/yolo [on|off]")} Toggle YOLO auto-approval mode`);
|
|
191
|
+
console.log(` ${pc3.cyan("/sessions")} List saved local sessions`);
|
|
192
|
+
console.log(` ${pc3.cyan("/history")} Display recent turns in current session`);
|
|
193
|
+
console.log(` ${pc3.cyan("/export [md|json]")} Export current conversation transcript`);
|
|
194
|
+
console.log(` ${pc3.cyan("/exit")} or ${pc3.cyan("/quit")} Exit Code Lantern CLI`);
|
|
195
|
+
console.log("");
|
|
196
|
+
console.log(pc3.bold("Keyboard Shortcuts:"));
|
|
197
|
+
console.log(` ${pc3.dim("Enter")} Submit prompt`);
|
|
198
|
+
console.log(` ${pc3.dim("\\ + Enter")} Continue prompt on next line (multiline)`);
|
|
199
|
+
console.log(` ${pc3.dim('""" ... """')} Multiline prompt block`);
|
|
200
|
+
console.log(` ${pc3.dim("Up/Down Arrows")} Navigate prompt history`);
|
|
201
|
+
console.log(` ${pc3.dim("Ctrl+C")} Cancel active request / double-tap to exit`);
|
|
202
|
+
console.log(` ${pc3.dim("Ctrl+D")} Exit session`);
|
|
203
|
+
console.log("");
|
|
204
|
+
return true;
|
|
205
|
+
case "/clear":
|
|
206
|
+
console.clear();
|
|
207
|
+
return true;
|
|
208
|
+
case "/status":
|
|
209
|
+
console.log("\n" + pc3.bold(pc3.yellow("\u{1F3EE} Code Lantern Status")));
|
|
210
|
+
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"));
|
|
211
|
+
console.log(`${pc3.bold("Session ID:")} ${pc3.cyan(context.session.id)}`);
|
|
212
|
+
console.log(`${pc3.bold("Workspace:")} ${pc3.blue(context.workspace)}`);
|
|
213
|
+
console.log(`${pc3.bold("Active Provider:")} ${pc3.magenta(context.provider)}`);
|
|
214
|
+
console.log(`${pc3.bold("Active Model:")} ${pc3.green(context.model)}`);
|
|
215
|
+
console.log(`${pc3.bold("Active Skills:")} ${context.activeSkills.length > 0 ? pc3.yellow(context.activeSkills.join(", ")) : pc3.dim("none")}`);
|
|
216
|
+
console.log(`${pc3.bold("Turns:")} ${context.session.turns.length}`);
|
|
217
|
+
console.log(`${pc3.bold("Total Tokens:")} ${context.session.totalUsage.totalTokens.toLocaleString()}`);
|
|
218
|
+
console.log(`${pc3.bold("Timing Logs:")} ${context.showTiming ? pc3.green("ON") : pc3.dim("OFF")}`);
|
|
219
|
+
console.log(`${pc3.bold("YOLO Mode:")} ${context.yolo ? pc3.red("ON (Auto-approve)") : pc3.green("OFF (Prompt approval)")}`);
|
|
220
|
+
console.log(`${pc3.bold("Plan Mode:")} ${context.planMode ? pc3.blue("ON (Read-only)") : pc3.dim("OFF (Full execution)")}`);
|
|
221
|
+
console.log("");
|
|
222
|
+
return true;
|
|
223
|
+
case "/provider": {
|
|
224
|
+
const registry = new ProviderRegistry();
|
|
225
|
+
if (arg) {
|
|
226
|
+
const norm = arg.toLowerCase().trim();
|
|
227
|
+
if (registry.hasProvider(norm)) {
|
|
228
|
+
context.setProvider(norm);
|
|
229
|
+
const p = registry.getProvider(norm);
|
|
230
|
+
context.setModel(p.defaultModel);
|
|
231
|
+
console.log(pc3.green(`\u2713 Switched provider to '${p.displayName}' (default model: ${p.defaultModel})`));
|
|
232
|
+
} else {
|
|
233
|
+
console.log(pc3.red(`Provider '${arg}' not found. Available: ${registry.listProviders().map((p) => p.id).join(", ")}`));
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
const providers = registry.listProviders();
|
|
237
|
+
console.log(`
|
|
238
|
+
Current provider: ${pc3.magenta(context.provider)}`);
|
|
239
|
+
console.log("Available providers:");
|
|
240
|
+
for (const p of providers) {
|
|
241
|
+
const active = p.id === context.provider ? pc3.green(" (active)") : "";
|
|
242
|
+
console.log(` - ${pc3.bold(p.id)}: ${p.displayName} [${p.isConfigured ? pc3.green("Ready") : pc3.yellow("Needs key")}]${active}`);
|
|
243
|
+
}
|
|
244
|
+
console.log(pc3.dim(`Usage: /provider <name> (e.g. /provider openai)`));
|
|
245
|
+
}
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
case "/model":
|
|
249
|
+
if (arg) {
|
|
250
|
+
context.setModel(arg.trim());
|
|
251
|
+
console.log(pc3.green(`\u2713 Model changed to: ${arg.trim()}`));
|
|
252
|
+
} else {
|
|
253
|
+
console.log(`Current model: ${pc3.cyan(context.model)} (Provider: ${context.provider})`);
|
|
254
|
+
console.log(pc3.dim(`Usage: /model <model-id> (or run 'lantern models list' to view available models)`));
|
|
255
|
+
}
|
|
256
|
+
return true;
|
|
257
|
+
case "/skills": {
|
|
258
|
+
const manager = new SkillManager();
|
|
259
|
+
const skills = manager.listSkills(context.workspace);
|
|
260
|
+
if (arg) {
|
|
261
|
+
const target = arg.trim();
|
|
262
|
+
const skill = manager.getSkill(target, context.workspace);
|
|
263
|
+
if (skill) {
|
|
264
|
+
const isCurrentlyActive = context.activeSkills.includes(skill.name);
|
|
265
|
+
if (isCurrentlyActive) {
|
|
266
|
+
context.setActiveSkills(context.activeSkills.filter((s) => s !== skill.name));
|
|
267
|
+
console.log(pc3.yellow(`\u2713 Deactivated skill '${skill.name}' for this session.`));
|
|
268
|
+
} else {
|
|
269
|
+
context.setActiveSkills([...context.activeSkills, skill.name]);
|
|
270
|
+
console.log(pc3.green(`\u2713 Activated skill '${skill.name}' for this session.`));
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
console.log(pc3.red(`Skill '${target}' not found.`));
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
console.log("\n" + pc3.bold(pc3.yellow("\u{1F3EE} Code Lantern Skills:")));
|
|
277
|
+
if (skills.length === 0) {
|
|
278
|
+
console.log(pc3.dim(" (No skills currently installed. Add one with: lantern skills add <path>)"));
|
|
279
|
+
} else {
|
|
280
|
+
for (const s of skills) {
|
|
281
|
+
const isSessionActive = context.activeSkills.includes(s.name);
|
|
282
|
+
const status = isSessionActive ? pc3.green("\u25CF ACTIVE") : s.enabled ? pc3.cyan("\u25CB default") : pc3.dim("\u25CB inactive");
|
|
283
|
+
console.log(` ${status} ${pc3.bold(s.name)} - ${pc3.dim(s.description.slice(0, 50))}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
console.log(pc3.dim(`
|
|
287
|
+
Toggle skill for session: /skills <name>
|
|
288
|
+
`));
|
|
289
|
+
}
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
case "/timing":
|
|
293
|
+
if (arg === "off") {
|
|
294
|
+
context.setShowTiming(false);
|
|
295
|
+
console.log(pc3.yellow("\u2713 Timing diagnostics disabled."));
|
|
296
|
+
} else if (arg === "on") {
|
|
297
|
+
context.setShowTiming(true);
|
|
298
|
+
console.log(pc3.green("\u2713 Timing diagnostics enabled."));
|
|
299
|
+
} else {
|
|
300
|
+
const next = !context.showTiming;
|
|
301
|
+
context.setShowTiming(next);
|
|
302
|
+
console.log(next ? pc3.green("\u2713 Timing diagnostics enabled.") : pc3.yellow("\u2713 Timing diagnostics disabled."));
|
|
303
|
+
}
|
|
304
|
+
return true;
|
|
305
|
+
case "/plan":
|
|
306
|
+
if (arg === "off") {
|
|
307
|
+
context.setPlanMode(false);
|
|
308
|
+
console.log(pc3.green("\u2713 Plan mode disabled (execution tools enabled)."));
|
|
309
|
+
} else if (arg === "on") {
|
|
310
|
+
context.setPlanMode(true);
|
|
311
|
+
console.log(pc3.blue("\u2713 Plan mode enabled (read-only inspection)."));
|
|
312
|
+
} else {
|
|
313
|
+
const next = !context.planMode;
|
|
314
|
+
context.setPlanMode(next);
|
|
315
|
+
console.log(next ? pc3.blue("\u2713 Plan mode enabled (read-only inspection).") : pc3.green("\u2713 Plan mode disabled."));
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
case "/yolo":
|
|
319
|
+
if (arg === "off") {
|
|
320
|
+
context.setYolo(false);
|
|
321
|
+
console.log(pc3.green("\u2713 YOLO mode disabled (safe approval prompts active)."));
|
|
322
|
+
} else if (arg === "on") {
|
|
323
|
+
context.setYolo(true);
|
|
324
|
+
console.log(pc3.red("\u26A1 YOLO mode enabled (auto-approving edits & commands)."));
|
|
325
|
+
} else {
|
|
326
|
+
const next = !context.yolo;
|
|
327
|
+
context.setYolo(next);
|
|
328
|
+
console.log(next ? pc3.red("\u26A1 YOLO mode enabled.") : pc3.green("\u2713 Safe mode enabled."));
|
|
329
|
+
}
|
|
330
|
+
return true;
|
|
331
|
+
case "/sessions": {
|
|
332
|
+
const list = listSessions();
|
|
333
|
+
console.log("\n" + pc3.bold("Recent Sessions:"));
|
|
334
|
+
if (list.length === 0) {
|
|
335
|
+
console.log(pc3.dim(" (No saved sessions)"));
|
|
336
|
+
} else {
|
|
337
|
+
for (const s of list.slice(0, 8)) {
|
|
338
|
+
const isCurrent = s.id === context.session.id ? pc3.green(" (current)") : "";
|
|
339
|
+
console.log(` ${pc3.cyan(s.id)} - ${s.title}${isCurrent} [${s.turnCount} turns]`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
console.log("");
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
case "/history":
|
|
346
|
+
console.log("\n" + pc3.bold("Session History:"));
|
|
347
|
+
if (context.session.turns.length === 0) {
|
|
348
|
+
console.log(pc3.dim(" (No messages yet in this session)"));
|
|
349
|
+
} else {
|
|
350
|
+
for (const t of context.session.turns) {
|
|
351
|
+
const roleBadge = t.role === "user" ? pc3.cyan("[User]") : t.role === "model" ? pc3.yellow("[Lantern]") : pc3.dim("[Tool]");
|
|
352
|
+
const preview = t.text ? t.text.slice(0, 100).replace(/\n/g, " ") : `(Tool calls: ${t.toolCalls?.map((c) => c.name).join(", ")})`;
|
|
353
|
+
console.log(` ${roleBadge} ${preview}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
console.log("");
|
|
357
|
+
return true;
|
|
358
|
+
case "/export": {
|
|
359
|
+
const format = arg.toLowerCase() === "json" ? "json" : "markdown";
|
|
360
|
+
if (format === "json") {
|
|
361
|
+
console.log("\n" + JSON.stringify(context.session, null, 2));
|
|
362
|
+
} else {
|
|
363
|
+
console.log("\n" + exportSessionMarkdown(context.session));
|
|
364
|
+
}
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
case "/exit":
|
|
368
|
+
case "/quit":
|
|
369
|
+
return false;
|
|
370
|
+
default:
|
|
371
|
+
console.log(pc3.yellow(`Unknown slash command '${command}'. Type ${pc3.bold("/help")} for available commands.`));
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/commands/interactive.ts
|
|
377
|
+
async function interactiveCommand(options) {
|
|
378
|
+
const config2 = loadConfig();
|
|
379
|
+
const workspace = options.cwd || process.cwd();
|
|
380
|
+
let provider = options.provider || config2.activeProvider || "gemini";
|
|
381
|
+
let model = options.model || config2.defaultModel || "gemini-3.8-flash";
|
|
382
|
+
let yolo = Boolean(options.yolo ?? config2.yolo);
|
|
383
|
+
let planMode = Boolean(options.plan);
|
|
384
|
+
let showTiming = Boolean(options.timing ?? config2.showTiming);
|
|
385
|
+
let lowLatency = Boolean(options.fast ?? config2.lowLatency);
|
|
386
|
+
let activeSkills = options.skill || config2.activeSkills || [];
|
|
387
|
+
let session;
|
|
388
|
+
if (options.session) {
|
|
389
|
+
const loaded = loadSession(options.session);
|
|
390
|
+
if (!loaded) {
|
|
391
|
+
console.error(pc4.red(`Error: Session '${options.session}' not found.`));
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
session = loaded;
|
|
395
|
+
if (session.provider) provider = session.provider;
|
|
396
|
+
if (session.model) model = session.model;
|
|
397
|
+
if (session.activeSkills) activeSkills = session.activeSkills;
|
|
398
|
+
console.log(pc4.green(`\u2713 Resumed session: ${session.id} (${session.turns.length} turns)`));
|
|
399
|
+
} else {
|
|
400
|
+
session = createNewSession(workspace, model);
|
|
401
|
+
session.provider = provider;
|
|
402
|
+
}
|
|
403
|
+
const attachments = loadAttachments(options.attach);
|
|
404
|
+
printBanner({
|
|
405
|
+
version: "1.0.1",
|
|
406
|
+
provider,
|
|
407
|
+
model,
|
|
408
|
+
workspace,
|
|
409
|
+
yolo,
|
|
410
|
+
planMode
|
|
411
|
+
});
|
|
412
|
+
if (attachments.length > 0) {
|
|
413
|
+
console.log(pc4.cyan(`\u{1F4CE} Attached ${attachments.length} file(s): ${attachments.map((a) => a.name).join(", ")}
|
|
414
|
+
`));
|
|
415
|
+
}
|
|
416
|
+
const agent = new LanternAgent();
|
|
417
|
+
await agent.initMcp();
|
|
418
|
+
const promptUI = new TerminalPrompt();
|
|
419
|
+
const slashContext = {
|
|
420
|
+
session,
|
|
421
|
+
provider,
|
|
422
|
+
model,
|
|
423
|
+
workspace,
|
|
424
|
+
yolo,
|
|
425
|
+
planMode,
|
|
426
|
+
showTiming,
|
|
427
|
+
activeSkills,
|
|
428
|
+
setProvider: (p) => {
|
|
429
|
+
provider = p;
|
|
430
|
+
session.provider = p;
|
|
431
|
+
},
|
|
432
|
+
setModel: (m) => {
|
|
433
|
+
model = m;
|
|
434
|
+
session.model = m;
|
|
435
|
+
},
|
|
436
|
+
setYolo: (y) => {
|
|
437
|
+
yolo = y;
|
|
438
|
+
},
|
|
439
|
+
setPlanMode: (p) => {
|
|
440
|
+
planMode = p;
|
|
441
|
+
},
|
|
442
|
+
setShowTiming: (t) => {
|
|
443
|
+
showTiming = t;
|
|
444
|
+
},
|
|
445
|
+
setActiveSkills: (skills) => {
|
|
446
|
+
activeSkills = skills;
|
|
447
|
+
session.activeSkills = skills;
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
try {
|
|
451
|
+
while (true) {
|
|
452
|
+
const modeIndicator = planMode ? pc4.blue("[plan] ") : yolo ? pc4.red("[yolo] ") : "";
|
|
453
|
+
const promptStr = `${pc4.yellow("lantern")} ${modeIndicator}${pc4.dim("\u276F")} `;
|
|
454
|
+
const input = await promptUI.askPrompt(promptStr);
|
|
455
|
+
if (input === null || input === "/exit" || input === "/quit") {
|
|
456
|
+
console.log(pc4.dim("\nGoodbye! Session saved: " + session.id));
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
const trimmed = input.trim();
|
|
460
|
+
if (!trimmed) {
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (trimmed.startsWith("/")) {
|
|
464
|
+
const shouldContinue = await handleSlashCommand(trimmed, slashContext);
|
|
465
|
+
if (!shouldContinue) {
|
|
466
|
+
console.log(pc4.dim("Session saved. Exiting Code Lantern."));
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const abortController = new AbortController();
|
|
472
|
+
const onSigInt = () => {
|
|
473
|
+
abortController.abort(new CancelledError("Request cancelled by user (Ctrl+C)."));
|
|
474
|
+
};
|
|
475
|
+
process.on("SIGINT", onSigInt);
|
|
476
|
+
const context = {
|
|
477
|
+
workspace,
|
|
478
|
+
provider,
|
|
479
|
+
model,
|
|
480
|
+
yolo,
|
|
481
|
+
planMode,
|
|
482
|
+
jsonOutput: false,
|
|
483
|
+
maxSteps: config2.maxSteps || 25,
|
|
484
|
+
timeoutMs: options.timeout || config2.timeoutMs || 6e4,
|
|
485
|
+
firstTokenTimeoutMs: options.firstTokenTimeout || config2.firstTokenTimeoutMs || 3e4,
|
|
486
|
+
showTiming,
|
|
487
|
+
lowLatency,
|
|
488
|
+
activeSkills,
|
|
489
|
+
attachments,
|
|
490
|
+
signal: abortController.signal
|
|
491
|
+
};
|
|
492
|
+
try {
|
|
493
|
+
console.log("");
|
|
494
|
+
const result = await agent.runTurn(session, trimmed, context, attachments);
|
|
495
|
+
const parts = [];
|
|
496
|
+
if (result.usage.totalTokens > 0) {
|
|
497
|
+
parts.push(`Tokens: ${result.usage.totalTokens.toLocaleString()}`);
|
|
498
|
+
}
|
|
499
|
+
if (showTiming && result.timing) {
|
|
500
|
+
const ttft = result.timing.ttftMs ? `TTFT: ${result.timing.ttftMs}ms` : "";
|
|
501
|
+
const total = `Total: ${(result.timing.totalMs / 1e3).toFixed(2)}s`;
|
|
502
|
+
parts.push(`[${[provider, model, ttft, total].filter(Boolean).join(" | ")}]`);
|
|
503
|
+
}
|
|
504
|
+
if (parts.length > 0) {
|
|
505
|
+
console.log(pc4.dim(`
|
|
506
|
+
[${parts.join(" | ")}]`));
|
|
507
|
+
}
|
|
508
|
+
console.log("");
|
|
509
|
+
} catch (err) {
|
|
510
|
+
if (err instanceof CancelledError || abortController.signal.aborted) {
|
|
511
|
+
console.log(pc4.yellow("\n[Cancelled by user]"));
|
|
512
|
+
} else {
|
|
513
|
+
console.error("\n" + formatErrorMessage(err) + "\n");
|
|
514
|
+
}
|
|
515
|
+
} finally {
|
|
516
|
+
process.removeListener("SIGINT", onSigInt);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
} finally {
|
|
520
|
+
saveSession(session);
|
|
521
|
+
await agent.shutdown();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// src/commands/ask.ts
|
|
526
|
+
import pc5 from "picocolors";
|
|
527
|
+
async function askCommand(question, options) {
|
|
528
|
+
const config2 = loadConfig();
|
|
529
|
+
const workspace = options.cwd || process.cwd();
|
|
530
|
+
const provider = options.provider || config2.activeProvider || "gemini";
|
|
531
|
+
const model = options.model || config2.defaultModel || "gemini-3.8-flash";
|
|
532
|
+
const yolo = Boolean(options.yolo ?? config2.yolo);
|
|
533
|
+
const jsonOutput = Boolean(options.json);
|
|
534
|
+
const showTiming = Boolean(options.timing ?? config2.showTiming);
|
|
535
|
+
const lowLatency = Boolean(options.fast ?? config2.lowLatency);
|
|
536
|
+
const attachments = loadAttachments(options.attach);
|
|
537
|
+
const session = createNewSession(workspace, model, `Ask: ${question.slice(0, 40)}`);
|
|
538
|
+
session.provider = provider;
|
|
539
|
+
const agent = new LanternAgent();
|
|
540
|
+
await agent.initMcp();
|
|
541
|
+
const abortController = new AbortController();
|
|
542
|
+
const onSigInt = () => {
|
|
543
|
+
abortController.abort(new CancelledError("Request cancelled by user (Ctrl+C)."));
|
|
544
|
+
};
|
|
545
|
+
process.on("SIGINT", onSigInt);
|
|
546
|
+
const context = {
|
|
547
|
+
workspace,
|
|
548
|
+
provider,
|
|
549
|
+
model,
|
|
550
|
+
yolo,
|
|
551
|
+
planMode: false,
|
|
552
|
+
jsonOutput,
|
|
553
|
+
maxSteps: options.maxSteps || config2.maxSteps || 25,
|
|
554
|
+
timeoutMs: options.timeout || config2.timeoutMs || 6e4,
|
|
555
|
+
firstTokenTimeoutMs: options.firstTokenTimeout || config2.firstTokenTimeoutMs || 3e4,
|
|
556
|
+
showTiming,
|
|
557
|
+
lowLatency,
|
|
558
|
+
activeSkills: options.skill || config2.activeSkills || [],
|
|
559
|
+
attachments,
|
|
560
|
+
signal: abortController.signal
|
|
561
|
+
};
|
|
562
|
+
try {
|
|
563
|
+
const result = await agent.runTurn(session, question, context, attachments);
|
|
564
|
+
if (jsonOutput) {
|
|
565
|
+
console.log(JSON.stringify({
|
|
566
|
+
sessionId: session.id,
|
|
567
|
+
provider,
|
|
568
|
+
model,
|
|
569
|
+
question,
|
|
570
|
+
reply: result.reply,
|
|
571
|
+
toolCount: result.toolCount,
|
|
572
|
+
usage: result.usage,
|
|
573
|
+
timing: result.timing
|
|
574
|
+
}, null, 2));
|
|
575
|
+
} else {
|
|
576
|
+
const parts = [];
|
|
577
|
+
if (result.usage.totalTokens > 0) {
|
|
578
|
+
parts.push(`Tokens: ${result.usage.totalTokens.toLocaleString()}`);
|
|
579
|
+
}
|
|
580
|
+
if (showTiming && result.timing) {
|
|
581
|
+
const ttft = result.timing.ttftMs ? `TTFT: ${result.timing.ttftMs}ms` : "";
|
|
582
|
+
const total = `Total: ${(result.timing.totalMs / 1e3).toFixed(2)}s`;
|
|
583
|
+
parts.push(`[${[provider, model, ttft, total].filter(Boolean).join(" | ")}]`);
|
|
584
|
+
}
|
|
585
|
+
if (parts.length > 0) {
|
|
586
|
+
console.log(pc5.dim(`
|
|
587
|
+
[${parts.join(" | ")}]`));
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
} catch (err) {
|
|
591
|
+
if (jsonOutput) {
|
|
592
|
+
console.error(JSON.stringify({ error: err.message }));
|
|
593
|
+
} else if (err instanceof CancelledError || abortController.signal.aborted) {
|
|
594
|
+
console.log(pc5.yellow("\n[Cancelled by user]"));
|
|
595
|
+
} else {
|
|
596
|
+
console.error(formatErrorMessage(err));
|
|
597
|
+
}
|
|
598
|
+
process.exit(1);
|
|
599
|
+
} finally {
|
|
600
|
+
process.removeListener("SIGINT", onSigInt);
|
|
601
|
+
await agent.shutdown();
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// src/commands/plan.ts
|
|
606
|
+
import pc6 from "picocolors";
|
|
607
|
+
async function planCommand(task, options) {
|
|
608
|
+
const config2 = loadConfig();
|
|
609
|
+
const workspace = options.cwd || process.cwd();
|
|
610
|
+
const provider = options.provider || config2.activeProvider || "gemini";
|
|
611
|
+
const model = options.model || config2.defaultModel || "gemini-3.8-flash";
|
|
612
|
+
const jsonOutput = Boolean(options.json);
|
|
613
|
+
const showTiming = Boolean(options.timing ?? config2.showTiming);
|
|
614
|
+
const attachments = loadAttachments(options.attach);
|
|
615
|
+
const session = createNewSession(workspace, model, `Plan: ${task.slice(0, 40)}`);
|
|
616
|
+
session.provider = provider;
|
|
617
|
+
if (!jsonOutput) {
|
|
618
|
+
console.log(pc6.bold(pc6.yellow("\n\u{1F3EE} Code Lantern Plan Mode (Read-Only)")));
|
|
619
|
+
console.log(pc6.dim(`Inspecting workspace with ${provider} (${model})...
|
|
620
|
+
`));
|
|
621
|
+
}
|
|
622
|
+
const agent = new LanternAgent();
|
|
623
|
+
await agent.initMcp();
|
|
624
|
+
const abortController = new AbortController();
|
|
625
|
+
const onSigInt = () => {
|
|
626
|
+
abortController.abort(new CancelledError("Plan cancelled by user (Ctrl+C)."));
|
|
627
|
+
};
|
|
628
|
+
process.on("SIGINT", onSigInt);
|
|
629
|
+
const context = {
|
|
630
|
+
workspace,
|
|
631
|
+
provider,
|
|
632
|
+
model,
|
|
633
|
+
yolo: false,
|
|
634
|
+
planMode: true,
|
|
635
|
+
jsonOutput,
|
|
636
|
+
maxSteps: options.maxSteps || config2.maxSteps || 25,
|
|
637
|
+
timeoutMs: options.timeout || config2.timeoutMs || 6e4,
|
|
638
|
+
firstTokenTimeoutMs: options.firstTokenTimeout || config2.firstTokenTimeoutMs || 3e4,
|
|
639
|
+
showTiming,
|
|
640
|
+
lowLatency: false,
|
|
641
|
+
activeSkills: options.skill || config2.activeSkills || [],
|
|
642
|
+
attachments,
|
|
643
|
+
signal: abortController.signal
|
|
644
|
+
};
|
|
645
|
+
const planPrompt = `Please inspect the codebase as needed and generate a detailed, structured, read-only implementation plan for the following task:
|
|
646
|
+
|
|
647
|
+
${task}`;
|
|
648
|
+
try {
|
|
649
|
+
const result = await agent.runTurn(session, planPrompt, context, attachments);
|
|
650
|
+
if (jsonOutput) {
|
|
651
|
+
console.log(JSON.stringify({
|
|
652
|
+
sessionId: session.id,
|
|
653
|
+
provider,
|
|
654
|
+
model,
|
|
655
|
+
task,
|
|
656
|
+
plan: result.reply,
|
|
657
|
+
usage: result.usage,
|
|
658
|
+
timing: result.timing
|
|
659
|
+
}, null, 2));
|
|
660
|
+
} else {
|
|
661
|
+
console.log(pc6.dim(`
|
|
662
|
+
[Plan complete. Session ID: ${session.id}]`));
|
|
663
|
+
if (showTiming && result.timing) {
|
|
664
|
+
console.log(pc6.dim(`[Total Plan Time: ${(result.timing.totalMs / 1e3).toFixed(2)}s]`));
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
} catch (err) {
|
|
668
|
+
if (jsonOutput) {
|
|
669
|
+
console.error(JSON.stringify({ error: err.message }));
|
|
670
|
+
} else if (err instanceof CancelledError || abortController.signal.aborted) {
|
|
671
|
+
console.log(pc6.yellow("\n[Plan cancelled by user]"));
|
|
672
|
+
} else {
|
|
673
|
+
console.error(formatErrorMessage(err));
|
|
674
|
+
}
|
|
675
|
+
process.exit(1);
|
|
676
|
+
} finally {
|
|
677
|
+
process.removeListener("SIGINT", onSigInt);
|
|
678
|
+
await agent.shutdown();
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/commands/resume.ts
|
|
683
|
+
import pc7 from "picocolors";
|
|
684
|
+
async function resumeCommand(sessionId, options = {}) {
|
|
685
|
+
const sessions = listSessions();
|
|
686
|
+
if (sessions.length === 0) {
|
|
687
|
+
console.log(pc7.yellow("No previous sessions found. Starting a fresh session...\n"));
|
|
688
|
+
await interactiveCommand(options);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
let targetId = sessionId;
|
|
692
|
+
if (!targetId) {
|
|
693
|
+
targetId = sessions[0].id;
|
|
694
|
+
console.log(pc7.cyan(`Resuming most recent session: ${sessions[0].title} (${targetId})`));
|
|
695
|
+
} else {
|
|
696
|
+
const existing = loadSession(targetId);
|
|
697
|
+
if (!existing) {
|
|
698
|
+
console.error(pc7.red(`Error: Session '${targetId}' not found.`));
|
|
699
|
+
console.log("\nAvailable sessions:");
|
|
700
|
+
for (const s of sessions.slice(0, 5)) {
|
|
701
|
+
console.log(` - ${pc7.cyan(s.id)} (${s.title})`);
|
|
702
|
+
}
|
|
703
|
+
process.exit(1);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
await interactiveCommand({
|
|
707
|
+
...options,
|
|
708
|
+
session: targetId
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// src/commands/sessions.ts
|
|
713
|
+
import pc8 from "picocolors";
|
|
714
|
+
async function sessionsCommand(options) {
|
|
715
|
+
if (options.delete) {
|
|
716
|
+
const success = deleteSession(options.delete);
|
|
717
|
+
if (success) {
|
|
718
|
+
console.log(pc8.green(`\u2713 Deleted session: ${options.delete}`));
|
|
719
|
+
} else {
|
|
720
|
+
console.error(pc8.red(`\u2717 Could not find session: ${options.delete}`));
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (options.export) {
|
|
726
|
+
const session = loadSession(options.export);
|
|
727
|
+
if (!session) {
|
|
728
|
+
console.error(pc8.red(`\u2717 Session '${options.export}' not found.`));
|
|
729
|
+
process.exit(1);
|
|
730
|
+
}
|
|
731
|
+
if (options.json) {
|
|
732
|
+
console.log(JSON.stringify(session, null, 2));
|
|
733
|
+
} else {
|
|
734
|
+
console.log(exportSessionMarkdown(session));
|
|
735
|
+
}
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const sessions = listSessions();
|
|
739
|
+
if (sessions.length === 0) {
|
|
740
|
+
console.log(pc8.dim("No saved Code Lantern sessions found."));
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
if (options.json) {
|
|
744
|
+
console.log(JSON.stringify(sessions, null, 2));
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
console.log("\n" + pc8.bold(pc8.yellow("\u{1F3EE} Saved Code Lantern Sessions")));
|
|
748
|
+
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"));
|
|
749
|
+
console.log(
|
|
750
|
+
pc8.bold("Session ID".padEnd(28)) + pc8.bold("Turns".padEnd(8)) + pc8.bold("Model".padEnd(20)) + pc8.bold("Updated".padEnd(20)) + pc8.bold("Title")
|
|
751
|
+
);
|
|
752
|
+
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"));
|
|
753
|
+
for (const s of sessions) {
|
|
754
|
+
const dateStr = new Date(s.updatedAt).toLocaleString().slice(0, 18);
|
|
755
|
+
console.log(
|
|
756
|
+
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))
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
console.log(pc8.dim("\nTo resume a session: lantern resume <session-id>"));
|
|
760
|
+
console.log(pc8.dim("To export a session: lantern sessions --export <session-id>"));
|
|
761
|
+
console.log(pc8.dim("To delete a session: lantern sessions --delete <session-id>\n"));
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/commands/configCmd.ts
|
|
765
|
+
import pc9 from "picocolors";
|
|
766
|
+
async function configCommand(action, key, value) {
|
|
767
|
+
const configPath = getConfigFilePath();
|
|
768
|
+
if (!action || action === "list") {
|
|
769
|
+
const config2 = loadConfig();
|
|
770
|
+
console.log("\n" + pc9.bold(pc9.yellow("\u{1F3EE} Code Lantern Configuration")));
|
|
771
|
+
console.log(pc9.dim(`File: ${configPath}`));
|
|
772
|
+
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"));
|
|
773
|
+
for (const [k, v] of Object.entries(config2)) {
|
|
774
|
+
if (k === "mcpServers") {
|
|
775
|
+
const count = Object.keys(v).length;
|
|
776
|
+
console.log(` ${pc9.cyan(k.padEnd(24))} = ${pc9.dim(`{ ${count} server(s) configured }`)}`);
|
|
777
|
+
} else {
|
|
778
|
+
console.log(` ${pc9.cyan(k.padEnd(24))} = ${pc9.green(JSON.stringify(v))}`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
console.log(pc9.dim("\nUpdate setting: lantern config set <key> <value>"));
|
|
782
|
+
console.log(pc9.dim(`Supported keys: ${ALLOWED_CONFIG_KEYS.join(", ")}
|
|
783
|
+
`));
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (action === "path") {
|
|
787
|
+
console.log(configPath);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
if (action === "get") {
|
|
791
|
+
if (!key) {
|
|
792
|
+
console.error(pc9.red("Error: Missing config key. Usage: orbit config get <key>"));
|
|
793
|
+
process.exit(1);
|
|
794
|
+
}
|
|
795
|
+
const config2 = loadConfig();
|
|
796
|
+
if (!(key in config2)) {
|
|
797
|
+
console.error(pc9.red(`Error: Unknown configuration key '${key}'`));
|
|
798
|
+
process.exit(1);
|
|
799
|
+
}
|
|
800
|
+
console.log(JSON.stringify(config2[key]));
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (action === "set") {
|
|
804
|
+
if (!key || value === void 0) {
|
|
805
|
+
console.error(pc9.red("Error: Missing key or value. Usage: orbit config set <key> <value>"));
|
|
806
|
+
process.exit(1);
|
|
807
|
+
}
|
|
808
|
+
const res = setConfigValue(key, value);
|
|
809
|
+
if (!res.success) {
|
|
810
|
+
console.error(pc9.red(`Error: ${res.error}`));
|
|
811
|
+
process.exit(1);
|
|
812
|
+
}
|
|
813
|
+
console.log(pc9.green(`\u2713 Set ${key} = ${value}`));
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
console.error(pc9.red(`Error: Unknown config action '${action}'. Available: list, get, set, path`));
|
|
817
|
+
process.exit(1);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/commands/doctor.ts
|
|
821
|
+
import fs2 from "fs";
|
|
822
|
+
import path2 from "path";
|
|
823
|
+
import { execSync } from "child_process";
|
|
824
|
+
import pc10 from "picocolors";
|
|
825
|
+
async function doctorCommand() {
|
|
826
|
+
console.log("\n" + pc10.bold(pc10.yellow("\u{1F3EE} Code Lantern Doctor \u2014 Diagnostics")));
|
|
827
|
+
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"));
|
|
828
|
+
let issuesFound = 0;
|
|
829
|
+
const config2 = loadConfig();
|
|
830
|
+
const registry = new ProviderRegistry();
|
|
831
|
+
const skillManager = new SkillManager();
|
|
832
|
+
const platformName = process.platform === "darwin" ? `macOS (${process.arch === "arm64" ? "Apple Silicon" : "Intel"})` : process.platform === "win32" ? `Windows (${process.arch})` : `${process.platform} (${process.arch})`;
|
|
833
|
+
console.log(`${pc10.green("\u2713")} Platform / OS: ${platformName}`);
|
|
834
|
+
const nodeVer = process.version;
|
|
835
|
+
const major = parseInt(nodeVer.replace("v", "").split(".")[0], 10);
|
|
836
|
+
if (major >= 18) {
|
|
837
|
+
console.log(`${pc10.green("\u2713")} Node.js Runtime: ${nodeVer} (compatible)`);
|
|
838
|
+
} else {
|
|
839
|
+
issuesFound++;
|
|
840
|
+
console.log(`${pc10.red("\u2717")} Node.js Runtime: ${nodeVer} (Node 18+ required)`);
|
|
841
|
+
}
|
|
842
|
+
const activeProviderId = config2.activeProvider || "gemini";
|
|
843
|
+
const activeProvider = registry.getProvider(activeProviderId);
|
|
844
|
+
const keys = listConfiguredKeys();
|
|
845
|
+
console.log(`${pc10.green("\u2713")} Active Provider: ${pc10.bold(activeProvider.displayName)} (${activeProviderId})`);
|
|
846
|
+
console.log(`${pc10.green("\u2713")} Active Model: ${config2.defaultModel}`);
|
|
847
|
+
const activeKeyInfo = keys[activeProviderId];
|
|
848
|
+
if (!activeKeyInfo || !activeKeyInfo.configured) {
|
|
849
|
+
issuesFound++;
|
|
850
|
+
console.log(`${pc10.red("\u2717")} API Key (${activeProviderId}): Missing!`);
|
|
851
|
+
console.log(pc10.yellow(` \u2192 To fix: set environment variable or run 'lantern keys set ${activeProviderId}'`));
|
|
852
|
+
} else {
|
|
853
|
+
const src = activeKeyInfo.source ? `[${activeKeyInfo.source}]` : "";
|
|
854
|
+
console.log(`${pc10.green("\u2713")} API Key (${activeProviderId}): Found (${activeKeyInfo.maskedKey}) ${src}`);
|
|
855
|
+
try {
|
|
856
|
+
const pingRes = await activeProvider.testConnection();
|
|
857
|
+
if (pingRes.success) {
|
|
858
|
+
console.log(`${pc10.green("\u2713")} Provider Auth & Ping: Verified (${pingRes.latencyMs}ms latency)`);
|
|
859
|
+
} else {
|
|
860
|
+
issuesFound++;
|
|
861
|
+
console.log(`${pc10.red("\u2717")} Provider Auth & Ping: Failed: ${pingRes.error}`);
|
|
862
|
+
}
|
|
863
|
+
} catch (err) {
|
|
864
|
+
issuesFound++;
|
|
865
|
+
console.log(`${pc10.red("\u2717")} Provider Auth & Ping: Error: ${err.message}`);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
const otherConfigured = Object.entries(keys).filter(([p, k]) => p !== activeProviderId && k.configured);
|
|
869
|
+
if (otherConfigured.length > 0) {
|
|
870
|
+
const others = otherConfigured.map(([p]) => p).join(", ");
|
|
871
|
+
console.log(`${pc10.cyan("\u2139")} Additional Providers: Configured (${others})`);
|
|
872
|
+
}
|
|
873
|
+
try {
|
|
874
|
+
const gitVer = execSync("git --version", { stdio: "pipe" }).toString().trim();
|
|
875
|
+
console.log(`${pc10.green("\u2713")} Git CLI: Available (${gitVer})`);
|
|
876
|
+
} catch {
|
|
877
|
+
console.log(`${pc10.yellow("!")} Git CLI: Not found in PATH (some git tools will be unavailable)`);
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
const configFile = getConfigFilePath();
|
|
881
|
+
console.log(`${pc10.green("\u2713")} Config File: ${configFile} (Timeout: ${config2.timeoutMs}ms, TTFT: ${config2.firstTokenTimeoutMs}ms)`);
|
|
882
|
+
} catch (err) {
|
|
883
|
+
issuesFound++;
|
|
884
|
+
console.log(`${pc10.red("\u2717")} Config File: Invalid: ${err.message}`);
|
|
885
|
+
}
|
|
886
|
+
try {
|
|
887
|
+
const sessionsDir = getSessionsDir();
|
|
888
|
+
const count = fs2.existsSync(sessionsDir) ? fs2.readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).length : 0;
|
|
889
|
+
console.log(`${pc10.green("\u2713")} Session Storage: ${sessionsDir} (${count} stored session(s))`);
|
|
890
|
+
} catch (err) {
|
|
891
|
+
issuesFound++;
|
|
892
|
+
console.log(`${pc10.red("\u2717")} Session Storage: Failed: ${err.message}`);
|
|
893
|
+
}
|
|
894
|
+
const installedSkills = skillManager.listSkills();
|
|
895
|
+
if (installedSkills.length > 0) {
|
|
896
|
+
const activeCount = installedSkills.filter((s) => s.enabled).length;
|
|
897
|
+
console.log(`${pc10.green("\u2713")} Skills: ${installedSkills.length} installed (${activeCount} active default)`);
|
|
898
|
+
} else {
|
|
899
|
+
console.log(`${pc10.cyan("\u2139")} Skills: None installed (add with: lantern skills add <path>)`);
|
|
900
|
+
}
|
|
901
|
+
const mcpServers = loadMcpConfig();
|
|
902
|
+
const serverCount = Object.keys(mcpServers).length;
|
|
903
|
+
if (serverCount === 0) {
|
|
904
|
+
console.log(`${pc10.cyan("\u2139")} MCP Servers: No external MCP servers configured (optional)`);
|
|
905
|
+
} else {
|
|
906
|
+
console.log(`${pc10.green("\u2713")} MCP Servers: ${serverCount} server(s) configured`);
|
|
907
|
+
}
|
|
908
|
+
const cwd = process.cwd();
|
|
909
|
+
const instructions = loadProjectInstructions(cwd);
|
|
910
|
+
if (instructions.found) {
|
|
911
|
+
console.log(`${pc10.green("\u2713")} Project Instructions: Found (${path2.basename(instructions.filePath || "")}) in ${cwd}`);
|
|
912
|
+
} else {
|
|
913
|
+
console.log(`${pc10.dim("\u2139")} Project Instructions: None found in workspace (e.g. LANTERN.md, GEMINI.md)`);
|
|
914
|
+
}
|
|
915
|
+
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"));
|
|
916
|
+
if (issuesFound === 0) {
|
|
917
|
+
console.log(pc10.bold(pc10.green("\u2728 All systems healthy! Code Lantern CLI is ready to assist.")));
|
|
918
|
+
} else {
|
|
919
|
+
console.log(pc10.bold(pc10.yellow(`\u26A0\uFE0F Doctor found ${issuesFound} issue(s) to address before optimal use.`)));
|
|
920
|
+
}
|
|
921
|
+
console.log("");
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// src/commands/mcpCmd.ts
|
|
925
|
+
import pc11 from "picocolors";
|
|
926
|
+
async function mcpCommand(action, serverName, command, args = []) {
|
|
927
|
+
const servers = loadMcpConfig();
|
|
928
|
+
if (!action || action === "list") {
|
|
929
|
+
console.log("\n" + pc11.bold(pc11.yellow("\u{1F3EE} Code Lantern MCP Servers")));
|
|
930
|
+
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"));
|
|
931
|
+
const entries = Object.entries(servers);
|
|
932
|
+
if (entries.length === 0) {
|
|
933
|
+
console.log(pc11.dim("No MCP servers configured."));
|
|
934
|
+
console.log(pc11.dim("\nTo add a server: lantern mcp add <name> <command> [args...]"));
|
|
935
|
+
console.log(pc11.dim("Example: lantern mcp add sqlite npx -y @modelcontextprotocol/server-sqlite /path/to/db.sqlite\n"));
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
for (const [name, s] of entries) {
|
|
939
|
+
const statusBadge = s.disabled ? pc11.red("[disabled]") : pc11.green("[enabled]");
|
|
940
|
+
console.log(`${pc11.bold(name)} ${statusBadge}`);
|
|
941
|
+
console.log(` Command: ${pc11.cyan(s.command)} ${(s.args || []).join(" ")}`);
|
|
942
|
+
if (s.env && Object.keys(s.env).length > 0) {
|
|
943
|
+
console.log(` Env: ${JSON.stringify(s.env)}`);
|
|
944
|
+
}
|
|
945
|
+
console.log("");
|
|
946
|
+
}
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (action === "add") {
|
|
950
|
+
if (!serverName || !command) {
|
|
951
|
+
console.error(pc11.red("Error: Missing arguments. Usage: orbit mcp add <name> <command> [args...]"));
|
|
952
|
+
process.exit(1);
|
|
953
|
+
}
|
|
954
|
+
addMcpServer(serverName, {
|
|
955
|
+
command,
|
|
956
|
+
args: args.length > 0 ? args : void 0
|
|
957
|
+
});
|
|
958
|
+
console.log(pc11.green(`\u2713 Added MCP server '${serverName}': ${command} ${args.join(" ")}`));
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
if (action === "remove" || action === "rm") {
|
|
962
|
+
if (!serverName) {
|
|
963
|
+
console.error(pc11.red("Error: Missing server name. Usage: orbit mcp remove <name>"));
|
|
964
|
+
process.exit(1);
|
|
965
|
+
}
|
|
966
|
+
const removed = removeMcpServer(serverName);
|
|
967
|
+
if (removed) {
|
|
968
|
+
console.log(pc11.green(`\u2713 Removed MCP server '${serverName}'`));
|
|
969
|
+
} else {
|
|
970
|
+
console.error(pc11.red(`\u2717 MCP server '${serverName}' not found`));
|
|
971
|
+
process.exit(1);
|
|
972
|
+
}
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
if (action === "disable") {
|
|
976
|
+
if (!serverName) {
|
|
977
|
+
console.error(pc11.red("Error: Missing server name. Usage: orbit mcp disable <name>"));
|
|
978
|
+
process.exit(1);
|
|
979
|
+
}
|
|
980
|
+
toggleMcpServer(serverName, true);
|
|
981
|
+
console.log(pc11.yellow(`\u2713 Disabled MCP server '${serverName}'`));
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
if (action === "enable") {
|
|
985
|
+
if (!serverName) {
|
|
986
|
+
console.error(pc11.red("Error: Missing server name. Usage: orbit mcp enable <name>"));
|
|
987
|
+
process.exit(1);
|
|
988
|
+
}
|
|
989
|
+
toggleMcpServer(serverName, false);
|
|
990
|
+
console.log(pc11.green(`\u2713 Enabled MCP server '${serverName}'`));
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
if (action === "test") {
|
|
994
|
+
if (!serverName) {
|
|
995
|
+
console.error(pc11.red("Error: Missing server name. Usage: orbit mcp test <name>"));
|
|
996
|
+
process.exit(1);
|
|
997
|
+
}
|
|
998
|
+
const server = servers[serverName];
|
|
999
|
+
if (!server) {
|
|
1000
|
+
console.error(pc11.red(`\u2717 MCP server '${serverName}' not found in configuration`));
|
|
1001
|
+
process.exit(1);
|
|
1002
|
+
}
|
|
1003
|
+
console.log(`Connecting to MCP server '${serverName}'...`);
|
|
1004
|
+
const mgr = new McpManager();
|
|
1005
|
+
try {
|
|
1006
|
+
const tools = await mgr.connectServer(serverName, server);
|
|
1007
|
+
console.log(pc11.green(`\u2713 Connected successfully! Exposing ${tools.length} tool(s):`));
|
|
1008
|
+
for (const t of tools) {
|
|
1009
|
+
console.log(` - ${pc11.cyan(t.definition.name)}: ${t.definition.description}`);
|
|
1010
|
+
}
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
console.error(pc11.red(`\u2717 Failed to connect to '${serverName}': ${err.message}`));
|
|
1013
|
+
process.exit(1);
|
|
1014
|
+
} finally {
|
|
1015
|
+
await mgr.closeAll();
|
|
1016
|
+
}
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
console.error(pc11.red(`Error: Unknown action '${action}'. Available: list, add, remove, enable, disable, test`));
|
|
1020
|
+
process.exit(1);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// src/commands/providersCmd.ts
|
|
1024
|
+
import pc12 from "picocolors";
|
|
1025
|
+
import readline2 from "readline";
|
|
1026
|
+
function promptLine(query, hide = false) {
|
|
1027
|
+
const rl = readline2.createInterface({
|
|
1028
|
+
input: process.stdin,
|
|
1029
|
+
output: process.stdout
|
|
1030
|
+
});
|
|
1031
|
+
return new Promise((resolve) => {
|
|
1032
|
+
rl.question(query, (answer) => {
|
|
1033
|
+
rl.close();
|
|
1034
|
+
resolve(answer.trim());
|
|
1035
|
+
});
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
async function providersCommand(action, target, options) {
|
|
1039
|
+
const registry = new ProviderRegistry();
|
|
1040
|
+
const subAction = (action || "list").toLowerCase();
|
|
1041
|
+
if (subAction === "list") {
|
|
1042
|
+
const providers = registry.listProviders();
|
|
1043
|
+
console.log("\n" + pc12.bold(pc12.yellow("\u{1F3EE} Configured AI Providers")));
|
|
1044
|
+
console.log(pc12.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"));
|
|
1045
|
+
console.log(
|
|
1046
|
+
pc12.bold("Provider".padEnd(16)) + pc12.bold("Status".padEnd(16)) + pc12.bold("Default Model".padEnd(30)) + pc12.bold("Key Source")
|
|
1047
|
+
);
|
|
1048
|
+
console.log(pc12.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"));
|
|
1049
|
+
for (const p of providers) {
|
|
1050
|
+
const activeMarker = p.isActive ? pc12.green(" (active)") : "";
|
|
1051
|
+
const nameCol = pc12.bold(p.name) + activeMarker;
|
|
1052
|
+
const statusCol = p.isConfigured ? pc12.green("\u2713 Ready") : pc12.yellow("! Needs Key");
|
|
1053
|
+
const keyInfo = getApiKey(p.id);
|
|
1054
|
+
const sourceCol = keyInfo.source ? pc12.cyan(`[${keyInfo.source}]`) : pc12.dim("(missing)");
|
|
1055
|
+
console.log(
|
|
1056
|
+
nameCol.padEnd(p.isActive ? 25 : 16) + statusCol.padEnd(25) + p.defaultModel.padEnd(30) + sourceCol
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
console.log(pc12.dim("\nTo set default: lantern providers set-default <name>"));
|
|
1060
|
+
console.log(pc12.dim("To test connection: lantern providers test [name]"));
|
|
1061
|
+
console.log(pc12.dim("To add custom provider: lantern providers add <name>\n"));
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
if (subAction === "set-default" || subAction === "use") {
|
|
1065
|
+
if (!target) {
|
|
1066
|
+
console.error(pc12.red("Error: Missing provider name. Usage: lantern providers set-default <name>"));
|
|
1067
|
+
process.exit(1);
|
|
1068
|
+
}
|
|
1069
|
+
const norm = target.toLowerCase();
|
|
1070
|
+
if (!registry.hasProvider(norm)) {
|
|
1071
|
+
console.error(pc12.red(`Error: Provider '${target}' is not registered.`));
|
|
1072
|
+
process.exit(1);
|
|
1073
|
+
}
|
|
1074
|
+
setConfigValue("activeProvider", norm);
|
|
1075
|
+
const p = registry.getProvider(norm);
|
|
1076
|
+
setConfigValue("defaultModel", p.defaultModel);
|
|
1077
|
+
console.log(pc12.green(`\u2713 Set active provider to '${p.displayName}' (default model: ${p.defaultModel})`));
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
if (subAction === "test") {
|
|
1081
|
+
const targetProvider = target || loadConfig().activeProvider || "gemini";
|
|
1082
|
+
const p = registry.getProvider(targetProvider);
|
|
1083
|
+
console.log(`Testing connection to ${pc12.cyan(p.displayName)}...`);
|
|
1084
|
+
const result = await p.testConnection();
|
|
1085
|
+
if (result.success) {
|
|
1086
|
+
console.log(pc12.green(`\u2713 Connection verified! Latency: ${result.latencyMs}ms`));
|
|
1087
|
+
if (result.modelsCount !== void 0) {
|
|
1088
|
+
console.log(pc12.dim(` Available models discovered: ${result.modelsCount}`));
|
|
1089
|
+
}
|
|
1090
|
+
} else {
|
|
1091
|
+
console.log(pc12.red(`\u2717 Connection failed: ${result.error}`));
|
|
1092
|
+
console.log(pc12.yellow(` Check your credentials with 'lantern keys list'`));
|
|
1093
|
+
process.exit(1);
|
|
1094
|
+
}
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
if (subAction === "add") {
|
|
1098
|
+
const name = target || await promptLine("Provider ID / Name: ");
|
|
1099
|
+
if (!name) {
|
|
1100
|
+
console.error(pc12.red("Error: Provider name is required."));
|
|
1101
|
+
process.exit(1);
|
|
1102
|
+
}
|
|
1103
|
+
const baseUrl = options?.baseUrl || await promptLine("Base URL (e.g. http://localhost:11434/v1): ");
|
|
1104
|
+
const defaultModel = options?.model || await promptLine("Default Model ID (e.g. llama3, qwen2.5): ");
|
|
1105
|
+
const apiKey = options?.apiKey || await promptLine("API Key (optional, press Enter to skip): ");
|
|
1106
|
+
registry.registerCustomProvider({
|
|
1107
|
+
id: name.toLowerCase(),
|
|
1108
|
+
name,
|
|
1109
|
+
type: "custom",
|
|
1110
|
+
baseUrl,
|
|
1111
|
+
defaultModel: defaultModel || "default"
|
|
1112
|
+
});
|
|
1113
|
+
if (apiKey) {
|
|
1114
|
+
setApiKey(name.toLowerCase(), apiKey);
|
|
1115
|
+
}
|
|
1116
|
+
console.log(pc12.green(`\u2713 Registered custom OpenAI-compatible provider '${name}'`));
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
if (subAction === "remove" || subAction === "rm") {
|
|
1120
|
+
if (!target) {
|
|
1121
|
+
console.error(pc12.red("Error: Missing provider name. Usage: lantern providers remove <name>"));
|
|
1122
|
+
process.exit(1);
|
|
1123
|
+
}
|
|
1124
|
+
const removed = registry.removeCustomProvider(target);
|
|
1125
|
+
if (removed) {
|
|
1126
|
+
console.log(pc12.green(`\u2713 Removed provider '${target}'`));
|
|
1127
|
+
} else {
|
|
1128
|
+
console.log(pc12.red(`Cannot remove built-in provider '${target}'. Only custom providers can be removed.`));
|
|
1129
|
+
process.exit(1);
|
|
1130
|
+
}
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
console.error(pc12.red(`Unknown action '${action}'. Available: list, set-default, test, add, remove`));
|
|
1134
|
+
process.exit(1);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// src/commands/modelsCmd.ts
|
|
1138
|
+
import pc13 from "picocolors";
|
|
1139
|
+
async function modelsCommand(action, modelId, options) {
|
|
1140
|
+
const registry = new ProviderRegistry();
|
|
1141
|
+
const config2 = loadConfig();
|
|
1142
|
+
const subAction = (action || "list").toLowerCase();
|
|
1143
|
+
const providerId = options?.provider || config2.activeProvider || "gemini";
|
|
1144
|
+
const provider = registry.getProvider(providerId);
|
|
1145
|
+
if (subAction === "list") {
|
|
1146
|
+
if (!options?.json) {
|
|
1147
|
+
console.log(`
|
|
1148
|
+
Fetching models for ${pc13.cyan(provider.displayName)}...`);
|
|
1149
|
+
}
|
|
1150
|
+
const models = await provider.listModels(Boolean(options?.refresh));
|
|
1151
|
+
if (options?.json) {
|
|
1152
|
+
console.log(JSON.stringify(models, null, 2));
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
console.log("\n" + pc13.bold(pc13.yellow(`\u{1F3EE} Available Models for ${provider.displayName}`)));
|
|
1156
|
+
console.log(pc13.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"));
|
|
1157
|
+
console.log(
|
|
1158
|
+
pc13.bold("Model ID".padEnd(36)) + pc13.bold("Tools".padEnd(8)) + pc13.bold("Vision".padEnd(8)) + pc13.bold("Reasoning".padEnd(12)) + pc13.bold("Context Window")
|
|
1159
|
+
);
|
|
1160
|
+
console.log(pc13.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"));
|
|
1161
|
+
const currentModel = config2.defaultModel;
|
|
1162
|
+
for (const m of models) {
|
|
1163
|
+
const isCurrent = m.id === currentModel;
|
|
1164
|
+
const marker = isCurrent ? pc13.green(" (active)") : "";
|
|
1165
|
+
const idCol = (m.id + marker).slice(0, 34);
|
|
1166
|
+
const toolsCol = m.capabilities.tools ? pc13.green("\u2713") : pc13.dim("\u2717");
|
|
1167
|
+
const visionCol = m.capabilities.images ? pc13.green("\u2713") : pc13.dim("\u2717");
|
|
1168
|
+
const reasoningCol = m.capabilities.reasoning ? pc13.magenta("\u2713") : pc13.dim("\u2717");
|
|
1169
|
+
const ctxCol = m.contextWindow ? `${(m.contextWindow / 1e3).toFixed(0)}k` : pc13.dim("standard");
|
|
1170
|
+
console.log(
|
|
1171
|
+
idCol.padEnd(isCurrent ? 45 : 36) + toolsCol.padEnd(16) + visionCol.padEnd(16) + reasoningCol.padEnd(20) + ctxCol
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
console.log(pc13.dim("\nTo set active model: lantern model set <model-id>"));
|
|
1175
|
+
console.log(pc13.dim(`Example: lantern model set ${models[0]?.id || "gemini-3.8-flash"}
|
|
1176
|
+
`));
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
if (subAction === "set") {
|
|
1180
|
+
if (!modelId) {
|
|
1181
|
+
console.error(pc13.red("Error: Missing model ID. Usage: lantern model set <model-id> [--provider <provider>]"));
|
|
1182
|
+
process.exit(1);
|
|
1183
|
+
}
|
|
1184
|
+
if (options?.provider) {
|
|
1185
|
+
setConfigValue("activeProvider", options.provider.toLowerCase());
|
|
1186
|
+
}
|
|
1187
|
+
setConfigValue("defaultModel", modelId.trim());
|
|
1188
|
+
console.log(pc13.green(`\u2713 Set active model to '${modelId.trim()}' (provider: ${provider.displayName})`));
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
console.error(pc13.red(`Unknown action '${action}'. Available: list, set`));
|
|
1192
|
+
process.exit(1);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// src/commands/keysCmd.ts
|
|
1196
|
+
import readline3 from "readline";
|
|
1197
|
+
import pc14 from "picocolors";
|
|
1198
|
+
function promptPassword(prompt) {
|
|
1199
|
+
const rl = readline3.createInterface({
|
|
1200
|
+
input: process.stdin,
|
|
1201
|
+
output: process.stdout
|
|
1202
|
+
});
|
|
1203
|
+
return new Promise((resolve) => {
|
|
1204
|
+
rl.question(prompt, (answer) => {
|
|
1205
|
+
rl.close();
|
|
1206
|
+
resolve(answer.trim());
|
|
1207
|
+
});
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
async function keysCommand(action, provider, key) {
|
|
1211
|
+
const subAction = (action || "list").toLowerCase();
|
|
1212
|
+
if (subAction === "list") {
|
|
1213
|
+
const keys = listConfiguredKeys();
|
|
1214
|
+
console.log("\n" + pc14.bold(pc14.yellow("\u{1F3EE} Configured Provider API Keys")));
|
|
1215
|
+
console.log(pc14.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"));
|
|
1216
|
+
console.log(
|
|
1217
|
+
pc14.bold("Provider".padEnd(16)) + pc14.bold("Source".padEnd(14)) + pc14.bold("Masked Key")
|
|
1218
|
+
);
|
|
1219
|
+
console.log(pc14.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"));
|
|
1220
|
+
for (const [p, info] of Object.entries(keys)) {
|
|
1221
|
+
const srcText = info.source ? pc14.cyan(`[${info.source}]`) : pc14.dim("none");
|
|
1222
|
+
console.log(
|
|
1223
|
+
pc14.bold(p.padEnd(16)) + srcText.padEnd(22) + (info.configured ? pc14.green(info.maskedKey) : pc14.dim("(not configured)"))
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
console.log(pc14.dim("\nTo set key: lantern keys set <provider> [key]"));
|
|
1227
|
+
console.log(pc14.dim("To remove key: lantern keys remove <provider>\n"));
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
if (subAction === "set") {
|
|
1231
|
+
if (!provider) {
|
|
1232
|
+
console.error(pc14.red("Error: Missing provider name. Usage: lantern keys set <provider> [key]"));
|
|
1233
|
+
process.exit(1);
|
|
1234
|
+
}
|
|
1235
|
+
let apiKey = key;
|
|
1236
|
+
if (!apiKey) {
|
|
1237
|
+
apiKey = await promptPassword(`Enter API key for '${provider}': `);
|
|
1238
|
+
}
|
|
1239
|
+
if (!apiKey) {
|
|
1240
|
+
console.error(pc14.red("Error: API key cannot be empty."));
|
|
1241
|
+
process.exit(1);
|
|
1242
|
+
}
|
|
1243
|
+
setApiKey(provider, apiKey);
|
|
1244
|
+
console.log(pc14.green(`\u2713 Successfully saved encrypted API key for '${provider}' (${maskApiKey(apiKey)})`));
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
if (subAction === "remove" || subAction === "rm") {
|
|
1248
|
+
if (!provider) {
|
|
1249
|
+
console.error(pc14.red("Error: Missing provider name. Usage: lantern keys remove <provider>"));
|
|
1250
|
+
process.exit(1);
|
|
1251
|
+
}
|
|
1252
|
+
const removed = removeApiKey(provider);
|
|
1253
|
+
if (removed) {
|
|
1254
|
+
console.log(pc14.green(`\u2713 Removed stored key for '${provider}'`));
|
|
1255
|
+
} else {
|
|
1256
|
+
console.log(pc14.yellow(`No stored key found for '${provider}'. (If set in environment variables, unset the env var).`));
|
|
1257
|
+
}
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
console.error(pc14.red(`Unknown action '${action}'. Available: list, set, remove`));
|
|
1261
|
+
process.exit(1);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// src/commands/skillsCmd.ts
|
|
1265
|
+
import pc15 from "picocolors";
|
|
1266
|
+
async function skillsCommand(action, target) {
|
|
1267
|
+
const manager = new SkillManager();
|
|
1268
|
+
const subAction = (action || "list").toLowerCase();
|
|
1269
|
+
if (subAction === "list") {
|
|
1270
|
+
const skills = manager.listSkills();
|
|
1271
|
+
console.log("\n" + pc15.bold(pc15.yellow("\u{1F3EE} Installed Code Lantern Skills")));
|
|
1272
|
+
console.log(pc15.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"));
|
|
1273
|
+
console.log(
|
|
1274
|
+
pc15.bold("Skill Name".padEnd(24)) + pc15.bold("Source".padEnd(12)) + pc15.bold("Status".padEnd(14)) + pc15.bold("Description")
|
|
1275
|
+
);
|
|
1276
|
+
console.log(pc15.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"));
|
|
1277
|
+
if (skills.length === 0) {
|
|
1278
|
+
console.log(pc15.dim("No skills currently installed."));
|
|
1279
|
+
console.log(pc15.dim(`
|
|
1280
|
+
Add a skill using: lantern skills add <local-folder-or-git-url>`));
|
|
1281
|
+
console.log(pc15.dim(`User skills folder: ${manager.getUserSkillsDir()}
|
|
1282
|
+
`));
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
for (const s of skills) {
|
|
1286
|
+
const statusText = s.enabled ? pc15.green("\u2713 Active") : pc15.dim("Disabled");
|
|
1287
|
+
const sourceText = s.source === "project" ? pc15.cyan("[project]") : pc15.blue("[user]");
|
|
1288
|
+
console.log(
|
|
1289
|
+
pc15.bold(s.name.padEnd(24)) + sourceText.padEnd(20) + statusText.padEnd(22) + pc15.dim(s.description.slice(0, 40))
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
console.log(pc15.dim("\nTo inspect: lantern skills show <name>"));
|
|
1293
|
+
console.log(pc15.dim("To enable: lantern skills enable <name>"));
|
|
1294
|
+
console.log(pc15.dim("To disable: lantern skills disable <name>"));
|
|
1295
|
+
console.log(pc15.dim("To add: lantern skills add <folder-path-or-git-url>\n"));
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
if (subAction === "show") {
|
|
1299
|
+
if (!target) {
|
|
1300
|
+
console.error(pc15.red("Error: Missing skill name. Usage: lantern skills show <name>"));
|
|
1301
|
+
process.exit(1);
|
|
1302
|
+
}
|
|
1303
|
+
const skill = manager.getSkill(target);
|
|
1304
|
+
if (!skill) {
|
|
1305
|
+
console.error(pc15.red(`Error: Skill '${target}' not found.`));
|
|
1306
|
+
process.exit(1);
|
|
1307
|
+
}
|
|
1308
|
+
console.log("\n" + pc15.bold(pc15.yellow(`\u{1F3EE} Skill: ${skill.name}`)));
|
|
1309
|
+
console.log(pc15.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"));
|
|
1310
|
+
console.log(`${pc15.bold("Source:")} ${skill.source}`);
|
|
1311
|
+
console.log(`${pc15.bold("Path:")} ${skill.path}`);
|
|
1312
|
+
console.log(`${pc15.bold("Status:")} ${skill.enabled ? pc15.green("Enabled") : pc15.dim("Disabled")}`);
|
|
1313
|
+
console.log(`${pc15.bold("Description:")} ${skill.description}`);
|
|
1314
|
+
if (skill.references && skill.references.length > 0) {
|
|
1315
|
+
console.log(`${pc15.bold("References:")} ${skill.references.length} reference file(s) available`);
|
|
1316
|
+
}
|
|
1317
|
+
console.log(pc15.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"));
|
|
1318
|
+
console.log("\n" + pc15.cyan("Instructions Content:"));
|
|
1319
|
+
console.log(skill.content.slice(0, 1e3) + (skill.content.length > 1e3 ? "\n... (truncated)" : ""));
|
|
1320
|
+
console.log("");
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
if (subAction === "enable") {
|
|
1324
|
+
if (!target) {
|
|
1325
|
+
console.error(pc15.red("Error: Missing skill name. Usage: lantern skills enable <name>"));
|
|
1326
|
+
process.exit(1);
|
|
1327
|
+
}
|
|
1328
|
+
const skill = manager.getSkill(target);
|
|
1329
|
+
if (!skill) {
|
|
1330
|
+
console.error(pc15.red(`Error: Skill '${target}' not found.`));
|
|
1331
|
+
process.exit(1);
|
|
1332
|
+
}
|
|
1333
|
+
manager.enableSkill(skill.name);
|
|
1334
|
+
console.log(pc15.green(`\u2713 Enabled skill '${skill.name}'`));
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
if (subAction === "disable") {
|
|
1338
|
+
if (!target) {
|
|
1339
|
+
console.error(pc15.red("Error: Missing skill name. Usage: lantern skills disable <name>"));
|
|
1340
|
+
process.exit(1);
|
|
1341
|
+
}
|
|
1342
|
+
manager.disableSkill(target);
|
|
1343
|
+
console.log(pc15.yellow(`\u2713 Disabled skill '${target}'`));
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
if (subAction === "add") {
|
|
1347
|
+
if (!target) {
|
|
1348
|
+
console.error(pc15.red("Error: Missing path or repository URL. Usage: lantern skills add <path-or-git-url>"));
|
|
1349
|
+
process.exit(1);
|
|
1350
|
+
}
|
|
1351
|
+
console.log(`Adding skill from '${target}'...`);
|
|
1352
|
+
const isGit = target.startsWith("http://") || target.startsWith("https://") || target.startsWith("git@");
|
|
1353
|
+
const result = isGit ? manager.addSkillFromGit(target) : manager.addSkillFromPath(target);
|
|
1354
|
+
if (result.success) {
|
|
1355
|
+
console.log(pc15.green(`\u2713 Successfully added and enabled skill '${result.skillName}'`));
|
|
1356
|
+
} else {
|
|
1357
|
+
console.error(pc15.red(`\u2717 Failed to add skill: ${result.error}`));
|
|
1358
|
+
process.exit(1);
|
|
1359
|
+
}
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
if (subAction === "remove" || subAction === "rm") {
|
|
1363
|
+
if (!target) {
|
|
1364
|
+
console.error(pc15.red("Error: Missing skill name. Usage: lantern skills remove <name>"));
|
|
1365
|
+
process.exit(1);
|
|
1366
|
+
}
|
|
1367
|
+
const removed = manager.removeSkill(target);
|
|
1368
|
+
if (removed) {
|
|
1369
|
+
console.log(pc15.green(`\u2713 Removed skill '${target}'`));
|
|
1370
|
+
} else {
|
|
1371
|
+
console.error(pc15.red(`\u2717 Skill '${target}' not found or could not be removed.`));
|
|
1372
|
+
process.exit(1);
|
|
1373
|
+
}
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
console.error(pc15.red(`Unknown action '${action}'. Available: list, show, add, enable, disable, remove`));
|
|
1377
|
+
process.exit(1);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// src/cli.ts
|
|
1381
|
+
var program = new Command();
|
|
1382
|
+
var config = loadConfig();
|
|
1383
|
+
program.name("lantern").description("Code Lantern CLI \u2014 Personal AI coding assistant for the terminal with multi-provider and skills support").version("1.0.1");
|
|
1384
|
+
program.option("-p, --provider <provider>", "AI provider to use (gemini, openai, anthropic, openrouter, or custom)", config.activeProvider || "gemini").option("-m, --model <model>", "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).option("--timeout <ms>", "Maximum total request duration in milliseconds (default: 60000)", (val) => parseInt(val, 10)).option("--first-token-timeout <ms>", "Maximum wait time for initial token in milliseconds (default: 30000)", (val) => parseInt(val, 10)).option("--timing", "Display detailed execution and latency timing diagnostics", false).option("--fast", "Enable low-latency streaming mode where supported", false).option("--skill <skills...>", "Activate specific skills by name for this run");
|
|
1385
|
+
program.command("ask <question...>").description("Ask a single question or task and stream the answer").action(async (questionParts) => {
|
|
1386
|
+
const opts = program.opts();
|
|
1387
|
+
const question = questionParts.join(" ");
|
|
1388
|
+
await askCommand(question, {
|
|
1389
|
+
provider: opts.provider,
|
|
1390
|
+
model: opts.model,
|
|
1391
|
+
cwd: opts.cwd,
|
|
1392
|
+
attach: opts.attach,
|
|
1393
|
+
yolo: opts.yolo,
|
|
1394
|
+
json: opts.json,
|
|
1395
|
+
timeout: opts.timeout,
|
|
1396
|
+
firstTokenTimeout: opts.firstTokenTimeout,
|
|
1397
|
+
timing: opts.timing,
|
|
1398
|
+
fast: opts.fast,
|
|
1399
|
+
skill: opts.skill
|
|
1400
|
+
});
|
|
1401
|
+
});
|
|
1402
|
+
program.command("plan <task...>").description("Inspect the codebase and produce a read-only implementation plan").action(async (taskParts) => {
|
|
1403
|
+
const opts = program.opts();
|
|
1404
|
+
const task = taskParts.join(" ");
|
|
1405
|
+
await planCommand(task, {
|
|
1406
|
+
provider: opts.provider,
|
|
1407
|
+
model: opts.model,
|
|
1408
|
+
cwd: opts.cwd,
|
|
1409
|
+
attach: opts.attach,
|
|
1410
|
+
json: opts.json,
|
|
1411
|
+
timeout: opts.timeout,
|
|
1412
|
+
firstTokenTimeout: opts.firstTokenTimeout,
|
|
1413
|
+
timing: opts.timing,
|
|
1414
|
+
skill: opts.skill
|
|
1415
|
+
});
|
|
1416
|
+
});
|
|
1417
|
+
program.command("resume [sessionId]").description("Continue a saved conversation session").action(async (sessionId) => {
|
|
1418
|
+
const opts = program.opts();
|
|
1419
|
+
await resumeCommand(sessionId, {
|
|
1420
|
+
provider: opts.provider,
|
|
1421
|
+
model: opts.model,
|
|
1422
|
+
cwd: opts.cwd,
|
|
1423
|
+
attach: opts.attach,
|
|
1424
|
+
yolo: opts.yolo,
|
|
1425
|
+
timeout: opts.timeout,
|
|
1426
|
+
firstTokenTimeout: opts.firstTokenTimeout,
|
|
1427
|
+
timing: opts.timing,
|
|
1428
|
+
fast: opts.fast,
|
|
1429
|
+
skill: opts.skill
|
|
1430
|
+
});
|
|
1431
|
+
});
|
|
1432
|
+
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) => {
|
|
1433
|
+
const globalOpts = program.opts();
|
|
1434
|
+
await sessionsCommand({
|
|
1435
|
+
delete: cmdOpts.delete,
|
|
1436
|
+
export: cmdOpts.export,
|
|
1437
|
+
json: globalOpts.json
|
|
1438
|
+
});
|
|
1439
|
+
});
|
|
1440
|
+
program.command("config [action] [key] [value]").description("View or update configuration (list, get, set, path)").action(async (action, key, value) => {
|
|
1441
|
+
await configCommand(action, key, value);
|
|
1442
|
+
});
|
|
1443
|
+
program.command("doctor").description("Diagnose API keys, configuration, environment, and MCP server health").action(async () => {
|
|
1444
|
+
await doctorCommand();
|
|
1445
|
+
});
|
|
1446
|
+
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) => {
|
|
1447
|
+
const rawArgs = rest.flat().filter((a) => typeof a === "string");
|
|
1448
|
+
await mcpCommand(action, serverName, cmd, rawArgs);
|
|
1449
|
+
});
|
|
1450
|
+
program.command("providers [action] [target]").description("Manage AI providers (list, set-default, test, add, remove)").option("--base-url <url>", "Base URL for custom OpenAI-compatible provider").option("--api-key <key>", "API key for provider").option("--model <model>", "Default model ID for provider").action(async (action, target, cmdOpts) => {
|
|
1451
|
+
await providersCommand(action, target, cmdOpts);
|
|
1452
|
+
});
|
|
1453
|
+
program.command("models [action] [modelId]").alias("model").description("List available models or switch the active model").option("-p, --provider <provider>", "Provider to inspect models for").option("-r, --refresh", "Bypass cache and query live model list", false).option("--json", "Output models list as JSON", false).action(async (action, modelId, cmdOpts) => {
|
|
1454
|
+
const globalOpts = program.opts();
|
|
1455
|
+
const provider = cmdOpts?.provider || globalOpts.provider;
|
|
1456
|
+
const json = Boolean(cmdOpts?.json || globalOpts.json);
|
|
1457
|
+
const refresh = Boolean(cmdOpts?.refresh);
|
|
1458
|
+
await modelsCommand(action, modelId, { provider, refresh, json });
|
|
1459
|
+
});
|
|
1460
|
+
program.command("keys [action] [provider] [key]").description("Securely manage API keys with local AES-256-GCM encryption (list, set, remove)").action(async (action, provider, key) => {
|
|
1461
|
+
await keysCommand(action, provider, key);
|
|
1462
|
+
});
|
|
1463
|
+
program.command("skills [action] [target]").description("Manage portable skills in SKILL.md format (list, show, add, enable, disable, remove)").action(async (action, target) => {
|
|
1464
|
+
await skillsCommand(action, target);
|
|
1465
|
+
});
|
|
1466
|
+
program.action(async () => {
|
|
1467
|
+
const opts = program.opts();
|
|
1468
|
+
if (!process.stdin.isTTY) {
|
|
1469
|
+
let pipedData = "";
|
|
1470
|
+
process.stdin.setEncoding("utf8");
|
|
1471
|
+
for await (const chunk of process.stdin) {
|
|
1472
|
+
pipedData += chunk;
|
|
1473
|
+
}
|
|
1474
|
+
const trimmedPiped = pipedData.trim();
|
|
1475
|
+
if (trimmedPiped) {
|
|
1476
|
+
await askCommand(trimmedPiped, {
|
|
1477
|
+
provider: opts.provider,
|
|
1478
|
+
model: opts.model,
|
|
1479
|
+
cwd: opts.cwd,
|
|
1480
|
+
attach: opts.attach,
|
|
1481
|
+
yolo: opts.yolo,
|
|
1482
|
+
json: opts.json,
|
|
1483
|
+
timeout: opts.timeout,
|
|
1484
|
+
firstTokenTimeout: opts.firstTokenTimeout,
|
|
1485
|
+
timing: opts.timing,
|
|
1486
|
+
fast: opts.fast,
|
|
1487
|
+
skill: opts.skill
|
|
1488
|
+
});
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
await interactiveCommand({
|
|
1493
|
+
provider: opts.provider,
|
|
1494
|
+
model: opts.model,
|
|
1495
|
+
cwd: opts.cwd,
|
|
1496
|
+
session: opts.session,
|
|
1497
|
+
attach: opts.attach,
|
|
1498
|
+
yolo: opts.yolo,
|
|
1499
|
+
timeout: opts.timeout,
|
|
1500
|
+
firstTokenTimeout: opts.firstTokenTimeout,
|
|
1501
|
+
timing: opts.timing,
|
|
1502
|
+
fast: opts.fast,
|
|
1503
|
+
skill: opts.skill
|
|
1504
|
+
});
|
|
1505
|
+
});
|
|
1506
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
1507
|
+
console.error(`Error: ${err.message || err}`);
|
|
1508
|
+
process.exit(1);
|
|
1509
|
+
});
|
|
1510
|
+
//# sourceMappingURL=cli.js.map
|