codelantern-agent-cli 1.0.0 → 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/README.md +167 -216
- package/dist/{chunk-Y3PEOLTZ.js → chunk-PELESOCI.js} +2365 -571
- package/dist/chunk-PELESOCI.js.map +1 -0
- package/dist/cli.js +678 -82
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +285 -23
- package/dist/index.js +46 -7
- package/package.json +2 -2
- package/dist/chunk-Y3PEOLTZ.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ALLOWED_CONFIG_KEYS,
|
|
4
|
+
CancelledError,
|
|
5
|
+
LanternAgent,
|
|
4
6
|
McpManager,
|
|
5
|
-
|
|
7
|
+
ProviderRegistry,
|
|
8
|
+
SkillManager,
|
|
6
9
|
addMcpServer,
|
|
7
10
|
createNewSession,
|
|
8
11
|
deleteSession,
|
|
9
12
|
exportSessionMarkdown,
|
|
10
13
|
formatErrorMessage,
|
|
14
|
+
getApiKey,
|
|
11
15
|
getConfigFilePath,
|
|
12
16
|
getLanternHomeDir,
|
|
13
17
|
getSessionsDir,
|
|
18
|
+
listConfiguredKeys,
|
|
14
19
|
listSessions,
|
|
15
20
|
loadAttachments,
|
|
16
21
|
loadConfig,
|
|
17
22
|
loadMcpConfig,
|
|
18
23
|
loadProjectInstructions,
|
|
19
24
|
loadSession,
|
|
25
|
+
maskApiKey,
|
|
26
|
+
removeApiKey,
|
|
20
27
|
removeMcpServer,
|
|
21
28
|
saveSession,
|
|
29
|
+
setApiKey,
|
|
22
30
|
setConfigValue,
|
|
23
31
|
toggleMcpServer
|
|
24
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-PELESOCI.js";
|
|
25
33
|
|
|
26
34
|
// src/cli.ts
|
|
27
35
|
import { Command } from "commander";
|
|
@@ -144,12 +152,13 @@ function printBanner(options) {
|
|
|
144
152
|
| | / _ \\ / _\` |/ _ \\ | | / _\` | '_ \\ | __/ _ \\ '__| '_ \\
|
|
145
153
|
| |__| (_) | (_| | __/ | |__| (_| | | | || || __/ | | | | |
|
|
146
154
|
\\____\\___/ \\__,_|\\___| |_____\\__,_|_| |_(_)__\\___|_| |_| |_|
|
|
147
|
-
\u{1F3EE} Personal AI Coding Assistant for Terminal (
|
|
155
|
+
\u{1F3EE} Personal AI Coding Assistant for Terminal (Multi-Provider)
|
|
148
156
|
`;
|
|
149
157
|
console.log(pc2.yellow(logo));
|
|
150
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)} ` : "";
|
|
151
160
|
console.log(`${pc2.bold("Product:")} Code Lantern CLI (${pc2.cyan("lantern")}) ${pc2.bold("Version:")} ${options.version}`);
|
|
152
|
-
console.log(`${pc2.bold("
|
|
161
|
+
console.log(`${providerDisplay}${pc2.bold("Model:")} ${pc2.green(options.model)}`);
|
|
153
162
|
console.log(`${pc2.bold("Workspace:")} ${pc2.blue(options.workspace)}`);
|
|
154
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) "));
|
|
155
164
|
console.log(`${pc2.bold("Status:")} ${modeBadge}`);
|
|
@@ -172,8 +181,11 @@ async function handleSlashCommand(input, context) {
|
|
|
172
181
|
console.log(pc3.bold("Slash Commands:"));
|
|
173
182
|
console.log(` ${pc3.cyan("/help")} Show this help overview`);
|
|
174
183
|
console.log(` ${pc3.cyan("/clear")} Clear the terminal screen`);
|
|
175
|
-
console.log(` ${pc3.cyan("/status")} Display
|
|
176
|
-
console.log(` ${pc3.cyan("/
|
|
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`);
|
|
177
189
|
console.log(` ${pc3.cyan("/plan [on|off]")} Toggle read-only planning mode`);
|
|
178
190
|
console.log(` ${pc3.cyan("/yolo [on|off]")} Toggle YOLO auto-approval mode`);
|
|
179
191
|
console.log(` ${pc3.cyan("/sessions")} List saved local sessions`);
|
|
@@ -181,25 +193,12 @@ async function handleSlashCommand(input, context) {
|
|
|
181
193
|
console.log(` ${pc3.cyan("/export [md|json]")} Export current conversation transcript`);
|
|
182
194
|
console.log(` ${pc3.cyan("/exit")} or ${pc3.cyan("/quit")} Exit Code Lantern CLI`);
|
|
183
195
|
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
196
|
console.log(pc3.bold("Keyboard Shortcuts:"));
|
|
198
197
|
console.log(` ${pc3.dim("Enter")} Submit prompt`);
|
|
199
198
|
console.log(` ${pc3.dim("\\ + Enter")} Continue prompt on next line (multiline)`);
|
|
200
199
|
console.log(` ${pc3.dim('""" ... """')} Multiline prompt block`);
|
|
201
200
|
console.log(` ${pc3.dim("Up/Down Arrows")} Navigate prompt history`);
|
|
202
|
-
console.log(` ${pc3.dim("Ctrl+C")} Cancel
|
|
201
|
+
console.log(` ${pc3.dim("Ctrl+C")} Cancel active request / double-tap to exit`);
|
|
203
202
|
console.log(` ${pc3.dim("Ctrl+D")} Exit session`);
|
|
204
203
|
console.log("");
|
|
205
204
|
return true;
|
|
@@ -209,22 +208,98 @@ async function handleSlashCommand(input, context) {
|
|
|
209
208
|
case "/status":
|
|
210
209
|
console.log("\n" + pc3.bold(pc3.yellow("\u{1F3EE} Code Lantern Status")));
|
|
211
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"));
|
|
212
|
-
console.log(`${pc3.bold("Session ID:")}
|
|
213
|
-
console.log(`${pc3.bold("Workspace:")}
|
|
214
|
-
console.log(`${pc3.bold("Active
|
|
215
|
-
console.log(`${pc3.bold("
|
|
216
|
-
console.log(`${pc3.bold("
|
|
217
|
-
console.log(`${pc3.bold("
|
|
218
|
-
console.log(`${pc3.bold("
|
|
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)")}`);
|
|
219
221
|
console.log("");
|
|
220
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
|
+
}
|
|
221
248
|
case "/model":
|
|
222
249
|
if (arg) {
|
|
223
250
|
context.setModel(arg.trim());
|
|
224
251
|
console.log(pc3.green(`\u2713 Model changed to: ${arg.trim()}`));
|
|
225
252
|
} else {
|
|
226
|
-
console.log(`Current model: ${pc3.cyan(context.model)}`);
|
|
227
|
-
console.log(pc3.dim(`Usage: /model <model-id> (
|
|
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."));
|
|
228
303
|
}
|
|
229
304
|
return true;
|
|
230
305
|
case "/plan":
|
|
@@ -302,9 +377,13 @@ async function handleSlashCommand(input, context) {
|
|
|
302
377
|
async function interactiveCommand(options) {
|
|
303
378
|
const config2 = loadConfig();
|
|
304
379
|
const workspace = options.cwd || process.cwd();
|
|
380
|
+
let provider = options.provider || config2.activeProvider || "gemini";
|
|
305
381
|
let model = options.model || config2.defaultModel || "gemini-3.8-flash";
|
|
306
382
|
let yolo = Boolean(options.yolo ?? config2.yolo);
|
|
307
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 || [];
|
|
308
387
|
let session;
|
|
309
388
|
if (options.session) {
|
|
310
389
|
const loaded = loadSession(options.session);
|
|
@@ -313,13 +392,18 @@ async function interactiveCommand(options) {
|
|
|
313
392
|
process.exit(1);
|
|
314
393
|
}
|
|
315
394
|
session = loaded;
|
|
395
|
+
if (session.provider) provider = session.provider;
|
|
396
|
+
if (session.model) model = session.model;
|
|
397
|
+
if (session.activeSkills) activeSkills = session.activeSkills;
|
|
316
398
|
console.log(pc4.green(`\u2713 Resumed session: ${session.id} (${session.turns.length} turns)`));
|
|
317
399
|
} else {
|
|
318
400
|
session = createNewSession(workspace, model);
|
|
401
|
+
session.provider = provider;
|
|
319
402
|
}
|
|
320
403
|
const attachments = loadAttachments(options.attach);
|
|
321
404
|
printBanner({
|
|
322
|
-
version: "1.0.
|
|
405
|
+
version: "1.0.1",
|
|
406
|
+
provider,
|
|
323
407
|
model,
|
|
324
408
|
workspace,
|
|
325
409
|
yolo,
|
|
@@ -329,15 +413,22 @@ async function interactiveCommand(options) {
|
|
|
329
413
|
console.log(pc4.cyan(`\u{1F4CE} Attached ${attachments.length} file(s): ${attachments.map((a) => a.name).join(", ")}
|
|
330
414
|
`));
|
|
331
415
|
}
|
|
332
|
-
const agent = new
|
|
416
|
+
const agent = new LanternAgent();
|
|
333
417
|
await agent.initMcp();
|
|
334
418
|
const promptUI = new TerminalPrompt();
|
|
335
419
|
const slashContext = {
|
|
336
420
|
session,
|
|
421
|
+
provider,
|
|
337
422
|
model,
|
|
338
423
|
workspace,
|
|
339
424
|
yolo,
|
|
340
425
|
planMode,
|
|
426
|
+
showTiming,
|
|
427
|
+
activeSkills,
|
|
428
|
+
setProvider: (p) => {
|
|
429
|
+
provider = p;
|
|
430
|
+
session.provider = p;
|
|
431
|
+
},
|
|
341
432
|
setModel: (m) => {
|
|
342
433
|
model = m;
|
|
343
434
|
session.model = m;
|
|
@@ -347,6 +438,13 @@ async function interactiveCommand(options) {
|
|
|
347
438
|
},
|
|
348
439
|
setPlanMode: (p) => {
|
|
349
440
|
planMode = p;
|
|
441
|
+
},
|
|
442
|
+
setShowTiming: (t) => {
|
|
443
|
+
showTiming = t;
|
|
444
|
+
},
|
|
445
|
+
setActiveSkills: (skills) => {
|
|
446
|
+
activeSkills = skills;
|
|
447
|
+
session.activeSkills = skills;
|
|
350
448
|
}
|
|
351
449
|
};
|
|
352
450
|
try {
|
|
@@ -370,25 +468,52 @@ async function interactiveCommand(options) {
|
|
|
370
468
|
}
|
|
371
469
|
continue;
|
|
372
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);
|
|
373
476
|
const context = {
|
|
374
477
|
workspace,
|
|
478
|
+
provider,
|
|
375
479
|
model,
|
|
376
480
|
yolo,
|
|
377
481
|
planMode,
|
|
378
482
|
jsonOutput: false,
|
|
379
483
|
maxSteps: config2.maxSteps || 25,
|
|
380
|
-
|
|
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
|
|
381
491
|
};
|
|
382
492
|
try {
|
|
383
493
|
console.log("");
|
|
384
494
|
const result = await agent.runTurn(session, trimmed, context, attachments);
|
|
495
|
+
const parts = [];
|
|
385
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) {
|
|
386
505
|
console.log(pc4.dim(`
|
|
387
|
-
[
|
|
506
|
+
[${parts.join(" | ")}]`));
|
|
388
507
|
}
|
|
389
508
|
console.log("");
|
|
390
509
|
} catch (err) {
|
|
391
|
-
|
|
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);
|
|
392
517
|
}
|
|
393
518
|
}
|
|
394
519
|
} finally {
|
|
@@ -400,48 +525,79 @@ async function interactiveCommand(options) {
|
|
|
400
525
|
// src/commands/ask.ts
|
|
401
526
|
import pc5 from "picocolors";
|
|
402
527
|
async function askCommand(question, options) {
|
|
528
|
+
const config2 = loadConfig();
|
|
403
529
|
const workspace = options.cwd || process.cwd();
|
|
404
|
-
const
|
|
405
|
-
const
|
|
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);
|
|
406
533
|
const jsonOutput = Boolean(options.json);
|
|
534
|
+
const showTiming = Boolean(options.timing ?? config2.showTiming);
|
|
535
|
+
const lowLatency = Boolean(options.fast ?? config2.lowLatency);
|
|
407
536
|
const attachments = loadAttachments(options.attach);
|
|
408
537
|
const session = createNewSession(workspace, model, `Ask: ${question.slice(0, 40)}`);
|
|
409
|
-
|
|
538
|
+
session.provider = provider;
|
|
539
|
+
const agent = new LanternAgent();
|
|
410
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);
|
|
411
546
|
const context = {
|
|
412
547
|
workspace,
|
|
548
|
+
provider,
|
|
413
549
|
model,
|
|
414
550
|
yolo,
|
|
415
551
|
planMode: false,
|
|
416
552
|
jsonOutput,
|
|
417
|
-
maxSteps: options.maxSteps || 25,
|
|
418
|
-
|
|
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
|
|
419
561
|
};
|
|
420
562
|
try {
|
|
421
563
|
const result = await agent.runTurn(session, question, context, attachments);
|
|
422
564
|
if (jsonOutput) {
|
|
423
565
|
console.log(JSON.stringify({
|
|
424
566
|
sessionId: session.id,
|
|
567
|
+
provider,
|
|
425
568
|
model,
|
|
426
569
|
question,
|
|
427
570
|
reply: result.reply,
|
|
428
571
|
toolCount: result.toolCount,
|
|
429
|
-
usage: result.usage
|
|
572
|
+
usage: result.usage,
|
|
573
|
+
timing: result.timing
|
|
430
574
|
}, null, 2));
|
|
431
575
|
} else {
|
|
576
|
+
const parts = [];
|
|
432
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) {
|
|
433
586
|
console.log(pc5.dim(`
|
|
434
|
-
[
|
|
587
|
+
[${parts.join(" | ")}]`));
|
|
435
588
|
}
|
|
436
589
|
}
|
|
437
590
|
} catch (err) {
|
|
438
591
|
if (jsonOutput) {
|
|
439
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]"));
|
|
440
595
|
} else {
|
|
441
596
|
console.error(formatErrorMessage(err));
|
|
442
597
|
}
|
|
443
598
|
process.exit(1);
|
|
444
599
|
} finally {
|
|
600
|
+
process.removeListener("SIGINT", onSigInt);
|
|
445
601
|
await agent.shutdown();
|
|
446
602
|
}
|
|
447
603
|
}
|
|
@@ -449,25 +605,42 @@ async function askCommand(question, options) {
|
|
|
449
605
|
// src/commands/plan.ts
|
|
450
606
|
import pc6 from "picocolors";
|
|
451
607
|
async function planCommand(task, options) {
|
|
608
|
+
const config2 = loadConfig();
|
|
452
609
|
const workspace = options.cwd || process.cwd();
|
|
453
|
-
const
|
|
610
|
+
const provider = options.provider || config2.activeProvider || "gemini";
|
|
611
|
+
const model = options.model || config2.defaultModel || "gemini-3.8-flash";
|
|
454
612
|
const jsonOutput = Boolean(options.json);
|
|
613
|
+
const showTiming = Boolean(options.timing ?? config2.showTiming);
|
|
455
614
|
const attachments = loadAttachments(options.attach);
|
|
456
615
|
const session = createNewSession(workspace, model, `Plan: ${task.slice(0, 40)}`);
|
|
616
|
+
session.provider = provider;
|
|
457
617
|
if (!jsonOutput) {
|
|
458
618
|
console.log(pc6.bold(pc6.yellow("\n\u{1F3EE} Code Lantern Plan Mode (Read-Only)")));
|
|
459
|
-
console.log(pc6.dim(
|
|
619
|
+
console.log(pc6.dim(`Inspecting workspace with ${provider} (${model})...
|
|
620
|
+
`));
|
|
460
621
|
}
|
|
461
|
-
const agent = new
|
|
622
|
+
const agent = new LanternAgent();
|
|
462
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);
|
|
463
629
|
const context = {
|
|
464
630
|
workspace,
|
|
631
|
+
provider,
|
|
465
632
|
model,
|
|
466
633
|
yolo: false,
|
|
467
634
|
planMode: true,
|
|
468
635
|
jsonOutput,
|
|
469
|
-
maxSteps: options.maxSteps || 25,
|
|
470
|
-
|
|
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
|
|
471
644
|
};
|
|
472
645
|
const planPrompt = `Please inspect the codebase as needed and generate a detailed, structured, read-only implementation plan for the following task:
|
|
473
646
|
|
|
@@ -477,23 +650,31 @@ ${task}`;
|
|
|
477
650
|
if (jsonOutput) {
|
|
478
651
|
console.log(JSON.stringify({
|
|
479
652
|
sessionId: session.id,
|
|
653
|
+
provider,
|
|
480
654
|
model,
|
|
481
655
|
task,
|
|
482
656
|
plan: result.reply,
|
|
483
|
-
usage: result.usage
|
|
657
|
+
usage: result.usage,
|
|
658
|
+
timing: result.timing
|
|
484
659
|
}, null, 2));
|
|
485
660
|
} else {
|
|
486
661
|
console.log(pc6.dim(`
|
|
487
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
|
+
}
|
|
488
666
|
}
|
|
489
667
|
} catch (err) {
|
|
490
668
|
if (jsonOutput) {
|
|
491
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]"));
|
|
492
672
|
} else {
|
|
493
673
|
console.error(formatErrorMessage(err));
|
|
494
674
|
}
|
|
495
675
|
process.exit(1);
|
|
496
676
|
} finally {
|
|
677
|
+
process.removeListener("SIGINT", onSigInt);
|
|
497
678
|
await agent.shutdown();
|
|
498
679
|
}
|
|
499
680
|
}
|
|
@@ -641,11 +822,13 @@ import fs2 from "fs";
|
|
|
641
822
|
import path2 from "path";
|
|
642
823
|
import { execSync } from "child_process";
|
|
643
824
|
import pc10 from "picocolors";
|
|
644
|
-
import { GoogleGenAI } from "@google/genai";
|
|
645
825
|
async function doctorCommand() {
|
|
646
826
|
console.log("\n" + pc10.bold(pc10.yellow("\u{1F3EE} Code Lantern Doctor \u2014 Diagnostics")));
|
|
647
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"));
|
|
648
828
|
let issuesFound = 0;
|
|
829
|
+
const config2 = loadConfig();
|
|
830
|
+
const registry = new ProviderRegistry();
|
|
831
|
+
const skillManager = new SkillManager();
|
|
649
832
|
const platformName = process.platform === "darwin" ? `macOS (${process.arch === "arm64" ? "Apple Silicon" : "Intel"})` : process.platform === "win32" ? `Windows (${process.arch})` : `${process.platform} (${process.arch})`;
|
|
650
833
|
console.log(`${pc10.green("\u2713")} Platform / OS: ${platformName}`);
|
|
651
834
|
const nodeVer = process.version;
|
|
@@ -656,28 +839,37 @@ async function doctorCommand() {
|
|
|
656
839
|
issuesFound++;
|
|
657
840
|
console.log(`${pc10.red("\u2717")} Node.js Runtime: ${nodeVer} (Node 18+ required)`);
|
|
658
841
|
}
|
|
659
|
-
const
|
|
660
|
-
|
|
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) {
|
|
661
849
|
issuesFound++;
|
|
662
|
-
console.log(`${pc10.red("\u2717")}
|
|
663
|
-
console.log(pc10.yellow(
|
|
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}'`));
|
|
664
852
|
} else {
|
|
665
|
-
const
|
|
666
|
-
console.log(`${pc10.green("\u2713")}
|
|
853
|
+
const src = activeKeyInfo.source ? `[${activeKeyInfo.source}]` : "";
|
|
854
|
+
console.log(`${pc10.green("\u2713")} API Key (${activeProviderId}): Found (${activeKeyInfo.maskedKey}) ${src}`);
|
|
667
855
|
try {
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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
|
+
}
|
|
675
863
|
} catch (err) {
|
|
676
864
|
issuesFound++;
|
|
677
|
-
console.log(`${pc10.red("\u2717")}
|
|
678
|
-
console.log(pc10.yellow(" \u2192 Verify your API key at https://aistudio.google.com/app/apikey"));
|
|
865
|
+
console.log(`${pc10.red("\u2717")} Provider Auth & Ping: Error: ${err.message}`);
|
|
679
866
|
}
|
|
680
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
|
+
}
|
|
681
873
|
try {
|
|
682
874
|
const gitVer = execSync("git --version", { stdio: "pipe" }).toString().trim();
|
|
683
875
|
console.log(`${pc10.green("\u2713")} Git CLI: Available (${gitVer})`);
|
|
@@ -686,30 +878,32 @@ async function doctorCommand() {
|
|
|
686
878
|
}
|
|
687
879
|
try {
|
|
688
880
|
const configFile = getConfigFilePath();
|
|
689
|
-
|
|
690
|
-
console.log(`${pc10.green("\u2713")} Config File: ${configFile} (Model: ${config2.defaultModel})`);
|
|
881
|
+
console.log(`${pc10.green("\u2713")} Config File: ${configFile} (Timeout: ${config2.timeoutMs}ms, TTFT: ${config2.firstTokenTimeoutMs}ms)`);
|
|
691
882
|
} catch (err) {
|
|
692
883
|
issuesFound++;
|
|
693
|
-
console.log(`${pc10.red("\u2717")} Config File: Invalid
|
|
884
|
+
console.log(`${pc10.red("\u2717")} Config File: Invalid: ${err.message}`);
|
|
694
885
|
}
|
|
695
886
|
try {
|
|
696
887
|
const sessionsDir = getSessionsDir();
|
|
697
|
-
const count = fs2.readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).length;
|
|
888
|
+
const count = fs2.existsSync(sessionsDir) ? fs2.readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).length : 0;
|
|
698
889
|
console.log(`${pc10.green("\u2713")} Session Storage: ${sessionsDir} (${count} stored session(s))`);
|
|
699
890
|
} catch (err) {
|
|
700
891
|
issuesFound++;
|
|
701
|
-
console.log(`${pc10.red("\u2717")} Session Storage: Failed
|
|
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>)`);
|
|
702
900
|
}
|
|
703
901
|
const mcpServers = loadMcpConfig();
|
|
704
902
|
const serverCount = Object.keys(mcpServers).length;
|
|
705
903
|
if (serverCount === 0) {
|
|
706
|
-
console.log(`${pc10.cyan("\u2139")} MCP
|
|
904
|
+
console.log(`${pc10.cyan("\u2139")} MCP Servers: No external MCP servers configured (optional)`);
|
|
707
905
|
} else {
|
|
708
|
-
console.log(`${pc10.green("\u2713")} MCP
|
|
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
|
-
}
|
|
906
|
+
console.log(`${pc10.green("\u2713")} MCP Servers: ${serverCount} server(s) configured`);
|
|
713
907
|
}
|
|
714
908
|
const cwd = process.cwd();
|
|
715
909
|
const instructions = loadProjectInstructions(cwd);
|
|
@@ -826,39 +1020,413 @@ async function mcpCommand(action, serverName, command, args = []) {
|
|
|
826
1020
|
process.exit(1);
|
|
827
1021
|
}
|
|
828
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
|
+
|
|
829
1380
|
// src/cli.ts
|
|
830
1381
|
var program = new Command();
|
|
831
1382
|
var config = loadConfig();
|
|
832
|
-
program.name("lantern").description("Code Lantern CLI \u2014 Personal AI coding assistant for the terminal
|
|
833
|
-
program.option("-m, --model <model>", "
|
|
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");
|
|
834
1385
|
program.command("ask <question...>").description("Ask a single question or task and stream the answer").action(async (questionParts) => {
|
|
835
1386
|
const opts = program.opts();
|
|
836
1387
|
const question = questionParts.join(" ");
|
|
837
1388
|
await askCommand(question, {
|
|
1389
|
+
provider: opts.provider,
|
|
838
1390
|
model: opts.model,
|
|
839
1391
|
cwd: opts.cwd,
|
|
840
1392
|
attach: opts.attach,
|
|
841
1393
|
yolo: opts.yolo,
|
|
842
|
-
json: opts.json
|
|
1394
|
+
json: opts.json,
|
|
1395
|
+
timeout: opts.timeout,
|
|
1396
|
+
firstTokenTimeout: opts.firstTokenTimeout,
|
|
1397
|
+
timing: opts.timing,
|
|
1398
|
+
fast: opts.fast,
|
|
1399
|
+
skill: opts.skill
|
|
843
1400
|
});
|
|
844
1401
|
});
|
|
845
1402
|
program.command("plan <task...>").description("Inspect the codebase and produce a read-only implementation plan").action(async (taskParts) => {
|
|
846
1403
|
const opts = program.opts();
|
|
847
1404
|
const task = taskParts.join(" ");
|
|
848
1405
|
await planCommand(task, {
|
|
1406
|
+
provider: opts.provider,
|
|
849
1407
|
model: opts.model,
|
|
850
1408
|
cwd: opts.cwd,
|
|
851
1409
|
attach: opts.attach,
|
|
852
|
-
json: opts.json
|
|
1410
|
+
json: opts.json,
|
|
1411
|
+
timeout: opts.timeout,
|
|
1412
|
+
firstTokenTimeout: opts.firstTokenTimeout,
|
|
1413
|
+
timing: opts.timing,
|
|
1414
|
+
skill: opts.skill
|
|
853
1415
|
});
|
|
854
1416
|
});
|
|
855
1417
|
program.command("resume [sessionId]").description("Continue a saved conversation session").action(async (sessionId) => {
|
|
856
1418
|
const opts = program.opts();
|
|
857
1419
|
await resumeCommand(sessionId, {
|
|
1420
|
+
provider: opts.provider,
|
|
858
1421
|
model: opts.model,
|
|
859
1422
|
cwd: opts.cwd,
|
|
860
1423
|
attach: opts.attach,
|
|
861
|
-
yolo: opts.yolo
|
|
1424
|
+
yolo: opts.yolo,
|
|
1425
|
+
timeout: opts.timeout,
|
|
1426
|
+
firstTokenTimeout: opts.firstTokenTimeout,
|
|
1427
|
+
timing: opts.timing,
|
|
1428
|
+
fast: opts.fast,
|
|
1429
|
+
skill: opts.skill
|
|
862
1430
|
});
|
|
863
1431
|
});
|
|
864
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) => {
|
|
@@ -872,13 +1440,29 @@ program.command("sessions").description("List, delete, or export saved local ses
|
|
|
872
1440
|
program.command("config [action] [key] [value]").description("View or update configuration (list, get, set, path)").action(async (action, key, value) => {
|
|
873
1441
|
await configCommand(action, key, value);
|
|
874
1442
|
});
|
|
875
|
-
program.command("doctor").description("Diagnose API
|
|
1443
|
+
program.command("doctor").description("Diagnose API keys, configuration, environment, and MCP server health").action(async () => {
|
|
876
1444
|
await doctorCommand();
|
|
877
1445
|
});
|
|
878
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) => {
|
|
879
1447
|
const rawArgs = rest.flat().filter((a) => typeof a === "string");
|
|
880
1448
|
await mcpCommand(action, serverName, cmd, rawArgs);
|
|
881
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
|
+
});
|
|
882
1466
|
program.action(async () => {
|
|
883
1467
|
const opts = program.opts();
|
|
884
1468
|
if (!process.stdin.isTTY) {
|
|
@@ -890,21 +1474,33 @@ program.action(async () => {
|
|
|
890
1474
|
const trimmedPiped = pipedData.trim();
|
|
891
1475
|
if (trimmedPiped) {
|
|
892
1476
|
await askCommand(trimmedPiped, {
|
|
1477
|
+
provider: opts.provider,
|
|
893
1478
|
model: opts.model,
|
|
894
1479
|
cwd: opts.cwd,
|
|
895
1480
|
attach: opts.attach,
|
|
896
1481
|
yolo: opts.yolo,
|
|
897
|
-
json: opts.json
|
|
1482
|
+
json: opts.json,
|
|
1483
|
+
timeout: opts.timeout,
|
|
1484
|
+
firstTokenTimeout: opts.firstTokenTimeout,
|
|
1485
|
+
timing: opts.timing,
|
|
1486
|
+
fast: opts.fast,
|
|
1487
|
+
skill: opts.skill
|
|
898
1488
|
});
|
|
899
1489
|
return;
|
|
900
1490
|
}
|
|
901
1491
|
}
|
|
902
1492
|
await interactiveCommand({
|
|
1493
|
+
provider: opts.provider,
|
|
903
1494
|
model: opts.model,
|
|
904
1495
|
cwd: opts.cwd,
|
|
905
1496
|
session: opts.session,
|
|
906
1497
|
attach: opts.attach,
|
|
907
|
-
yolo: opts.yolo
|
|
1498
|
+
yolo: opts.yolo,
|
|
1499
|
+
timeout: opts.timeout,
|
|
1500
|
+
firstTokenTimeout: opts.firstTokenTimeout,
|
|
1501
|
+
timing: opts.timing,
|
|
1502
|
+
fast: opts.fast,
|
|
1503
|
+
skill: opts.skill
|
|
908
1504
|
});
|
|
909
1505
|
});
|
|
910
1506
|
program.parseAsync(process.argv).catch((err) => {
|