llmist 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/{chunk-QVDGTUQN.js → chunk-LKIBXQ5I.js} +3 -2
- package/dist/chunk-LKIBXQ5I.js.map +1 -0
- package/dist/{chunk-LQE7TKKW.js → chunk-MH4TQ5AD.js} +429 -60
- package/dist/chunk-MH4TQ5AD.js.map +1 -0
- package/dist/{chunk-A4GRCCXF.js → chunk-VF2WOCHM.js} +2 -2
- package/dist/cli.cjs +1201 -165
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +773 -107
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +431 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -4
- package/dist/index.d.ts +19 -4
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/{mock-stream-C0vOqI3L.d.cts → mock-stream-C8mBXRzJ.d.cts} +58 -3
- package/dist/{mock-stream-C0vOqI3L.d.ts → mock-stream-C8mBXRzJ.d.ts} +58 -3
- package/dist/testing/index.cjs +429 -60
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +2 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/package.json +5 -1
- package/dist/chunk-LQE7TKKW.js.map +0 -1
- package/dist/chunk-QVDGTUQN.js.map +0 -1
- /package/dist/{chunk-A4GRCCXF.js.map → chunk-VF2WOCHM.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createGadget
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-LKIBXQ5I.js";
|
|
5
5
|
import {
|
|
6
6
|
AgentBuilder,
|
|
7
7
|
BaseGadget,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
init_model_shortcuts,
|
|
23
23
|
init_registry,
|
|
24
24
|
resolveModel
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-MH4TQ5AD.js";
|
|
26
26
|
|
|
27
27
|
// src/cli/constants.ts
|
|
28
28
|
var CLI_NAME = "llmist";
|
|
@@ -34,7 +34,7 @@ var COMMANDS = {
|
|
|
34
34
|
};
|
|
35
35
|
var LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
|
|
36
36
|
var DEFAULT_MODEL = "openai:gpt-5-nano";
|
|
37
|
-
var DEFAULT_PARAMETER_FORMAT = "
|
|
37
|
+
var DEFAULT_PARAMETER_FORMAT = "toml";
|
|
38
38
|
var OPTION_FLAGS = {
|
|
39
39
|
model: "-m, --model <identifier>",
|
|
40
40
|
systemPrompt: "-s, --system <prompt>",
|
|
@@ -55,7 +55,7 @@ var OPTION_DESCRIPTIONS = {
|
|
|
55
55
|
maxTokens: "Maximum number of output tokens requested from the model.",
|
|
56
56
|
maxIterations: "Maximum number of agent loop iterations before exiting.",
|
|
57
57
|
gadgetModule: "Path or module specifier for a gadget export. Repeat to register multiple gadgets.",
|
|
58
|
-
parameterFormat: "Format for gadget parameter schemas: 'json', 'yaml', or 'auto'.",
|
|
58
|
+
parameterFormat: "Format for gadget parameter schemas: 'json', 'yaml', 'toml', or 'auto'.",
|
|
59
59
|
logLevel: "Log level: silly, trace, debug, info, warn, error, fatal.",
|
|
60
60
|
logFile: "Path to log file. When set, logs are written to file instead of stderr.",
|
|
61
61
|
noBuiltins: "Disable built-in gadgets (AskUser, TellUser).",
|
|
@@ -69,7 +69,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError3 } from "commande
|
|
|
69
69
|
// package.json
|
|
70
70
|
var package_default = {
|
|
71
71
|
name: "llmist",
|
|
72
|
-
version: "0.4.
|
|
72
|
+
version: "0.4.1",
|
|
73
73
|
description: "Universal TypeScript LLM client with streaming-first agent framework. Works with any model - no structured outputs or native tool calling required. Implements its own flexible grammar for function calling.",
|
|
74
74
|
type: "module",
|
|
75
75
|
main: "dist/index.cjs",
|
|
@@ -153,7 +153,10 @@ var package_default = {
|
|
|
153
153
|
"@google/genai": "^1.27.0",
|
|
154
154
|
chalk: "^5.6.2",
|
|
155
155
|
commander: "^12.1.0",
|
|
156
|
+
"js-toml": "^1.0.2",
|
|
156
157
|
"js-yaml": "^4.1.0",
|
|
158
|
+
marked: "^17.0.1",
|
|
159
|
+
"marked-terminal": "^7.3.0",
|
|
157
160
|
openai: "^6.0.0",
|
|
158
161
|
tiktoken: "^1.0.22",
|
|
159
162
|
tslog: "^4.10.2",
|
|
@@ -166,6 +169,7 @@ var package_default = {
|
|
|
166
169
|
"@semantic-release/changelog": "^6.0.3",
|
|
167
170
|
"@semantic-release/git": "^10.0.1",
|
|
168
171
|
"@types/js-yaml": "^4.0.9",
|
|
172
|
+
"@types/marked-terminal": "^6.1.1",
|
|
169
173
|
"@types/node": "^20.12.7",
|
|
170
174
|
"bun-types": "^1.3.2",
|
|
171
175
|
dotenv: "^17.2.3",
|
|
@@ -181,7 +185,7 @@ init_builder();
|
|
|
181
185
|
init_registry();
|
|
182
186
|
init_constants();
|
|
183
187
|
import { createInterface } from "node:readline/promises";
|
|
184
|
-
import
|
|
188
|
+
import chalk3 from "chalk";
|
|
185
189
|
|
|
186
190
|
// src/cli/builtin-gadgets.ts
|
|
187
191
|
import { z } from "zod";
|
|
@@ -190,8 +194,20 @@ var askUser = createGadget({
|
|
|
190
194
|
name: "AskUser",
|
|
191
195
|
description: "Ask the user a question when you need more information or clarification. The user's response will be provided back to you.",
|
|
192
196
|
schema: z.object({
|
|
193
|
-
question: z.string().describe("The question to ask the user")
|
|
197
|
+
question: z.string().describe("The question to ask the user in plain-text or Markdown")
|
|
194
198
|
}),
|
|
199
|
+
examples: [
|
|
200
|
+
{
|
|
201
|
+
comment: "Ask for clarification about the task",
|
|
202
|
+
params: { question: "Which file would you like me to modify?" }
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
comment: "Ask user to choose between options",
|
|
206
|
+
params: {
|
|
207
|
+
question: "I found multiple matches. Which one should I use?\n- src/utils/helper.ts\n- src/lib/helper.ts"
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
],
|
|
195
211
|
execute: ({ question }) => {
|
|
196
212
|
throw new HumanInputException(question);
|
|
197
213
|
}
|
|
@@ -200,10 +216,28 @@ var tellUser = createGadget({
|
|
|
200
216
|
name: "TellUser",
|
|
201
217
|
description: "Tell the user something important. Set done=true when your work is complete and you want to end the conversation.",
|
|
202
218
|
schema: z.object({
|
|
203
|
-
message: z.string().describe("The message to display to the user"),
|
|
204
|
-
done: z.boolean().describe("Set to true to end the conversation, false to continue"),
|
|
219
|
+
message: z.string().describe("The message to display to the user in Markdown"),
|
|
220
|
+
done: z.boolean().default(false).describe("Set to true to end the conversation, false to continue"),
|
|
205
221
|
type: z.enum(["info", "success", "warning", "error"]).default("info").describe("Message type: info, success, warning, or error")
|
|
206
222
|
}),
|
|
223
|
+
examples: [
|
|
224
|
+
{
|
|
225
|
+
comment: "Report successful completion and end the conversation",
|
|
226
|
+
params: {
|
|
227
|
+
message: "I've completed the refactoring. All tests pass.",
|
|
228
|
+
done: true,
|
|
229
|
+
type: "success"
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
comment: "Warn the user about something without ending",
|
|
234
|
+
params: {
|
|
235
|
+
message: "Found 3 files with potential issues. Continuing analysis...",
|
|
236
|
+
done: false,
|
|
237
|
+
type: "warning"
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
],
|
|
207
241
|
execute: ({ message, done, type }) => {
|
|
208
242
|
const prefixes = {
|
|
209
243
|
info: "\u2139\uFE0F ",
|
|
@@ -316,6 +350,9 @@ async function loadGadgets(specifiers, cwd, importer = (specifier) => import(spe
|
|
|
316
350
|
return gadgets;
|
|
317
351
|
}
|
|
318
352
|
|
|
353
|
+
// src/cli/option-helpers.ts
|
|
354
|
+
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
355
|
+
|
|
319
356
|
// src/cli/utils.ts
|
|
320
357
|
init_constants();
|
|
321
358
|
import chalk2 from "chalk";
|
|
@@ -323,6 +360,44 @@ import { InvalidArgumentError } from "commander";
|
|
|
323
360
|
|
|
324
361
|
// src/cli/ui/formatters.ts
|
|
325
362
|
import chalk from "chalk";
|
|
363
|
+
import { marked } from "marked";
|
|
364
|
+
import { markedTerminal } from "marked-terminal";
|
|
365
|
+
var markedConfigured = false;
|
|
366
|
+
function ensureMarkedConfigured() {
|
|
367
|
+
if (!markedConfigured) {
|
|
368
|
+
chalk.level = process.env.NO_COLOR ? 0 : 3;
|
|
369
|
+
marked.use(
|
|
370
|
+
markedTerminal({
|
|
371
|
+
// Text styling
|
|
372
|
+
strong: chalk.bold,
|
|
373
|
+
em: chalk.italic,
|
|
374
|
+
del: chalk.dim.gray.strikethrough,
|
|
375
|
+
// Code styling
|
|
376
|
+
code: chalk.yellow,
|
|
377
|
+
codespan: chalk.yellow,
|
|
378
|
+
// Headings
|
|
379
|
+
heading: chalk.green.bold,
|
|
380
|
+
firstHeading: chalk.magenta.underline.bold,
|
|
381
|
+
// Links
|
|
382
|
+
link: chalk.blue,
|
|
383
|
+
href: chalk.blue.underline,
|
|
384
|
+
// Block elements
|
|
385
|
+
blockquote: chalk.gray.italic,
|
|
386
|
+
// List formatting - reduce indentation and add bullet styling
|
|
387
|
+
tab: 2,
|
|
388
|
+
// Reduce from default 4 to 2 spaces
|
|
389
|
+
listitem: chalk.reset
|
|
390
|
+
// Keep items readable (no dim)
|
|
391
|
+
})
|
|
392
|
+
);
|
|
393
|
+
markedConfigured = true;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function renderMarkdown(text) {
|
|
397
|
+
ensureMarkedConfigured();
|
|
398
|
+
const rendered = marked.parse(text);
|
|
399
|
+
return rendered.trimEnd();
|
|
400
|
+
}
|
|
326
401
|
function formatTokens(tokens) {
|
|
327
402
|
return tokens >= 1e3 ? `${(tokens / 1e3).toFixed(1)}k` : `${tokens}`;
|
|
328
403
|
}
|
|
@@ -341,7 +416,14 @@ function formatCost(cost) {
|
|
|
341
416
|
function renderSummary(metadata) {
|
|
342
417
|
const parts = [];
|
|
343
418
|
if (metadata.iterations !== void 0) {
|
|
344
|
-
|
|
419
|
+
const iterPart = chalk.cyan(`#${metadata.iterations}`);
|
|
420
|
+
if (metadata.model) {
|
|
421
|
+
parts.push(`${iterPart} ${chalk.magenta(metadata.model)}`);
|
|
422
|
+
} else {
|
|
423
|
+
parts.push(iterPart);
|
|
424
|
+
}
|
|
425
|
+
} else if (metadata.model) {
|
|
426
|
+
parts.push(chalk.magenta(metadata.model));
|
|
345
427
|
}
|
|
346
428
|
if (metadata.usage) {
|
|
347
429
|
const { inputTokens, outputTokens } = metadata.usage;
|
|
@@ -362,22 +444,128 @@ function renderSummary(metadata) {
|
|
|
362
444
|
}
|
|
363
445
|
return parts.join(chalk.dim(" | "));
|
|
364
446
|
}
|
|
447
|
+
function renderOverallSummary(metadata) {
|
|
448
|
+
const parts = [];
|
|
449
|
+
if (metadata.totalTokens !== void 0 && metadata.totalTokens > 0) {
|
|
450
|
+
parts.push(chalk.dim("total:") + chalk.magenta(` ${formatTokens(metadata.totalTokens)}`));
|
|
451
|
+
}
|
|
452
|
+
if (metadata.iterations !== void 0 && metadata.iterations > 0) {
|
|
453
|
+
parts.push(chalk.cyan(`#${metadata.iterations}`));
|
|
454
|
+
}
|
|
455
|
+
if (metadata.elapsedSeconds !== void 0 && metadata.elapsedSeconds > 0) {
|
|
456
|
+
parts.push(chalk.dim(`${metadata.elapsedSeconds}s`));
|
|
457
|
+
}
|
|
458
|
+
if (metadata.cost !== void 0 && metadata.cost > 0) {
|
|
459
|
+
parts.push(chalk.cyan(`$${formatCost(metadata.cost)}`));
|
|
460
|
+
}
|
|
461
|
+
if (parts.length === 0) {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
return parts.join(chalk.dim(" | "));
|
|
465
|
+
}
|
|
466
|
+
function formatParametersInline(params) {
|
|
467
|
+
if (!params || Object.keys(params).length === 0) {
|
|
468
|
+
return "";
|
|
469
|
+
}
|
|
470
|
+
return Object.entries(params).map(([key, value]) => {
|
|
471
|
+
let formatted;
|
|
472
|
+
if (typeof value === "string") {
|
|
473
|
+
formatted = value.length > 30 ? `${value.slice(0, 30)}\u2026` : value;
|
|
474
|
+
} else if (typeof value === "boolean" || typeof value === "number") {
|
|
475
|
+
formatted = String(value);
|
|
476
|
+
} else {
|
|
477
|
+
const json = JSON.stringify(value);
|
|
478
|
+
formatted = json.length > 30 ? `${json.slice(0, 30)}\u2026` : json;
|
|
479
|
+
}
|
|
480
|
+
return `${chalk.dim(key)}${chalk.dim("=")}${chalk.cyan(formatted)}`;
|
|
481
|
+
}).join(chalk.dim(", "));
|
|
482
|
+
}
|
|
483
|
+
function formatBytes(bytes) {
|
|
484
|
+
if (bytes < 1024) {
|
|
485
|
+
return `${bytes} bytes`;
|
|
486
|
+
}
|
|
487
|
+
if (bytes < 1024 * 1024) {
|
|
488
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
489
|
+
}
|
|
490
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
491
|
+
}
|
|
365
492
|
function formatGadgetSummary(result) {
|
|
366
493
|
const gadgetLabel = chalk.magenta.bold(result.gadgetName);
|
|
367
494
|
const timeLabel = chalk.dim(`${Math.round(result.executionTimeMs)}ms`);
|
|
495
|
+
const paramsStr = formatParametersInline(result.parameters);
|
|
496
|
+
const paramsLabel = paramsStr ? `${chalk.dim("(")}${paramsStr}${chalk.dim(")")}` : "";
|
|
368
497
|
if (result.error) {
|
|
369
|
-
|
|
498
|
+
const errorMsg = result.error.length > 50 ? `${result.error.slice(0, 50)}\u2026` : result.error;
|
|
499
|
+
return `${chalk.red("\u2717")} ${gadgetLabel}${paramsLabel} ${chalk.red("error:")} ${errorMsg} ${timeLabel}`;
|
|
500
|
+
}
|
|
501
|
+
let outputLabel;
|
|
502
|
+
if (result.tokenCount !== void 0 && result.tokenCount > 0) {
|
|
503
|
+
outputLabel = chalk.green(`${formatTokens(result.tokenCount)} tokens`);
|
|
504
|
+
} else if (result.result) {
|
|
505
|
+
const outputBytes = Buffer.byteLength(result.result, "utf-8");
|
|
506
|
+
outputLabel = outputBytes > 0 ? chalk.green(formatBytes(outputBytes)) : chalk.dim("no output");
|
|
507
|
+
} else {
|
|
508
|
+
outputLabel = chalk.dim("no output");
|
|
370
509
|
}
|
|
371
|
-
|
|
372
|
-
|
|
510
|
+
const icon = result.breaksLoop ? chalk.yellow("\u23F9") : chalk.green("\u2713");
|
|
511
|
+
const summaryLine = `${icon} ${gadgetLabel}${paramsLabel} ${chalk.dim("\u2192")} ${outputLabel} ${timeLabel}`;
|
|
512
|
+
if (result.gadgetName === "TellUser" && result.parameters?.message) {
|
|
513
|
+
const message = String(result.parameters.message);
|
|
514
|
+
const rendered = renderMarkdown(message);
|
|
515
|
+
return `${summaryLine}
|
|
516
|
+
${rendered}`;
|
|
373
517
|
}
|
|
374
|
-
|
|
375
|
-
const shouldTruncate = result.gadgetName !== "TellUser";
|
|
376
|
-
const resultText = result.result ? shouldTruncate && result.result.length > maxLen ? `${result.result.slice(0, maxLen)}...` : result.result : "";
|
|
377
|
-
return `${chalk.green("\u2713")} ${gadgetLabel} ${chalk.dim("\u2192")} ${resultText} ${timeLabel}`;
|
|
518
|
+
return summaryLine;
|
|
378
519
|
}
|
|
379
520
|
|
|
380
521
|
// src/cli/utils.ts
|
|
522
|
+
var RARE_EMOJI = [
|
|
523
|
+
"\u{1F531}",
|
|
524
|
+
"\u2697\uFE0F",
|
|
525
|
+
"\u{1F9FF}",
|
|
526
|
+
"\u{1F530}",
|
|
527
|
+
"\u269B\uFE0F",
|
|
528
|
+
"\u{1F3FA}",
|
|
529
|
+
"\u{1F9EB}",
|
|
530
|
+
"\u{1F52C}",
|
|
531
|
+
"\u2695\uFE0F",
|
|
532
|
+
"\u{1F5DD}\uFE0F",
|
|
533
|
+
"\u2696\uFE0F",
|
|
534
|
+
"\u{1F52E}",
|
|
535
|
+
"\u{1FAAC}",
|
|
536
|
+
"\u{1F9EC}",
|
|
537
|
+
"\u2699\uFE0F",
|
|
538
|
+
"\u{1F529}",
|
|
539
|
+
"\u{1FA9B}",
|
|
540
|
+
"\u26CF\uFE0F",
|
|
541
|
+
"\u{1FA83}",
|
|
542
|
+
"\u{1F3F9}",
|
|
543
|
+
"\u{1F6E1}\uFE0F",
|
|
544
|
+
"\u2694\uFE0F",
|
|
545
|
+
"\u{1F5E1}\uFE0F",
|
|
546
|
+
"\u{1FA93}",
|
|
547
|
+
"\u{1F5C3}\uFE0F",
|
|
548
|
+
"\u{1F4DC}",
|
|
549
|
+
"\u{1F4EF}",
|
|
550
|
+
"\u{1F3B4}",
|
|
551
|
+
"\u{1F004}",
|
|
552
|
+
"\u{1F3B2}"
|
|
553
|
+
];
|
|
554
|
+
function generateMarkers() {
|
|
555
|
+
const pick = (count) => {
|
|
556
|
+
const result = [];
|
|
557
|
+
const pool = [...RARE_EMOJI];
|
|
558
|
+
for (let i = 0; i < count && pool.length > 0; i++) {
|
|
559
|
+
const idx = Math.floor(Math.random() * pool.length);
|
|
560
|
+
result.push(pool.splice(idx, 1)[0]);
|
|
561
|
+
}
|
|
562
|
+
return result.join("");
|
|
563
|
+
};
|
|
564
|
+
return {
|
|
565
|
+
startPrefix: pick(5),
|
|
566
|
+
endPrefix: pick(5)
|
|
567
|
+
};
|
|
568
|
+
}
|
|
381
569
|
function createNumericParser({
|
|
382
570
|
label,
|
|
383
571
|
integer = false,
|
|
@@ -544,6 +732,13 @@ var StreamProgress = class {
|
|
|
544
732
|
if (this.totalStartTime === 0) return 0;
|
|
545
733
|
return Number(((Date.now() - this.totalStartTime) / 1e3).toFixed(1));
|
|
546
734
|
}
|
|
735
|
+
/**
|
|
736
|
+
* Get elapsed time in seconds for the current call.
|
|
737
|
+
* @returns Elapsed time in seconds with 1 decimal place
|
|
738
|
+
*/
|
|
739
|
+
getCallElapsedSeconds() {
|
|
740
|
+
return Number(((Date.now() - this.callStartTime) / 1e3).toFixed(1));
|
|
741
|
+
}
|
|
547
742
|
/**
|
|
548
743
|
* Starts the progress indicator animation after a brief delay.
|
|
549
744
|
*/
|
|
@@ -578,7 +773,12 @@ var StreamProgress = class {
|
|
|
578
773
|
const elapsed = ((Date.now() - this.callStartTime) / 1e3).toFixed(1);
|
|
579
774
|
const outTokens = this.callOutputTokensEstimated ? Math.round(this.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callOutputTokens;
|
|
580
775
|
const parts = [];
|
|
581
|
-
|
|
776
|
+
const iterPart = chalk2.cyan(`#${this.currentIteration}`);
|
|
777
|
+
if (this.model) {
|
|
778
|
+
parts.push(`${iterPart} ${chalk2.magenta(this.model)}`);
|
|
779
|
+
} else {
|
|
780
|
+
parts.push(iterPart);
|
|
781
|
+
}
|
|
582
782
|
if (this.callInputTokens > 0) {
|
|
583
783
|
const prefix = this.callInputTokensEstimated ? "~" : "";
|
|
584
784
|
parts.push(chalk2.dim("\u2191") + chalk2.yellow(` ${prefix}${formatTokens(this.callInputTokens)}`));
|
|
@@ -591,7 +791,7 @@ var StreamProgress = class {
|
|
|
591
791
|
if (this.totalCost > 0) {
|
|
592
792
|
parts.push(chalk2.cyan(`$${formatCost(this.totalCost)}`));
|
|
593
793
|
}
|
|
594
|
-
this.target.write(`\r${
|
|
794
|
+
this.target.write(`\r${parts.join(chalk2.dim(" | "))} ${chalk2.cyan(spinner)}`);
|
|
595
795
|
}
|
|
596
796
|
renderCumulativeMode(spinner) {
|
|
597
797
|
const elapsed = ((Date.now() - this.totalStartTime) / 1e3).toFixed(1);
|
|
@@ -609,7 +809,7 @@ var StreamProgress = class {
|
|
|
609
809
|
parts.push(chalk2.dim("cost:") + chalk2.cyan(` $${formatCost(this.totalCost)}`));
|
|
610
810
|
}
|
|
611
811
|
parts.push(chalk2.dim(`${elapsed}s`));
|
|
612
|
-
this.target.write(`\r${
|
|
812
|
+
this.target.write(`\r${parts.join(chalk2.dim(" | "))} ${chalk2.cyan(spinner)}`);
|
|
613
813
|
}
|
|
614
814
|
/**
|
|
615
815
|
* Pauses the progress indicator and clears the line.
|
|
@@ -719,15 +919,91 @@ async function executeAction(action, env) {
|
|
|
719
919
|
}
|
|
720
920
|
}
|
|
721
921
|
|
|
722
|
-
// src/cli/
|
|
723
|
-
var PARAMETER_FORMAT_VALUES = ["json", "yaml", "auto"];
|
|
922
|
+
// src/cli/option-helpers.ts
|
|
923
|
+
var PARAMETER_FORMAT_VALUES = ["json", "yaml", "toml", "auto"];
|
|
724
924
|
function parseParameterFormat(value) {
|
|
725
925
|
const normalized = value.toLowerCase();
|
|
726
926
|
if (!PARAMETER_FORMAT_VALUES.includes(normalized)) {
|
|
727
|
-
throw new InvalidArgumentError2(
|
|
927
|
+
throw new InvalidArgumentError2(
|
|
928
|
+
`Parameter format must be one of: ${PARAMETER_FORMAT_VALUES.join(", ")}`
|
|
929
|
+
);
|
|
728
930
|
}
|
|
729
931
|
return normalized;
|
|
730
932
|
}
|
|
933
|
+
function addCompleteOptions(cmd, defaults) {
|
|
934
|
+
return cmd.option(OPTION_FLAGS.model, OPTION_DESCRIPTIONS.model, defaults?.model ?? DEFAULT_MODEL).option(OPTION_FLAGS.systemPrompt, OPTION_DESCRIPTIONS.systemPrompt, defaults?.system).option(
|
|
935
|
+
OPTION_FLAGS.temperature,
|
|
936
|
+
OPTION_DESCRIPTIONS.temperature,
|
|
937
|
+
createNumericParser({ label: "Temperature", min: 0, max: 2 }),
|
|
938
|
+
defaults?.temperature
|
|
939
|
+
).option(
|
|
940
|
+
OPTION_FLAGS.maxTokens,
|
|
941
|
+
OPTION_DESCRIPTIONS.maxTokens,
|
|
942
|
+
createNumericParser({ label: "Max tokens", integer: true, min: 1 }),
|
|
943
|
+
defaults?.["max-tokens"]
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
function addAgentOptions(cmd, defaults) {
|
|
947
|
+
const gadgetAccumulator = (value, previous = []) => [
|
|
948
|
+
...previous,
|
|
949
|
+
value
|
|
950
|
+
];
|
|
951
|
+
const defaultGadgets = defaults?.gadget ?? [];
|
|
952
|
+
return cmd.option(OPTION_FLAGS.model, OPTION_DESCRIPTIONS.model, defaults?.model ?? DEFAULT_MODEL).option(OPTION_FLAGS.systemPrompt, OPTION_DESCRIPTIONS.systemPrompt, defaults?.system).option(
|
|
953
|
+
OPTION_FLAGS.temperature,
|
|
954
|
+
OPTION_DESCRIPTIONS.temperature,
|
|
955
|
+
createNumericParser({ label: "Temperature", min: 0, max: 2 }),
|
|
956
|
+
defaults?.temperature
|
|
957
|
+
).option(
|
|
958
|
+
OPTION_FLAGS.maxIterations,
|
|
959
|
+
OPTION_DESCRIPTIONS.maxIterations,
|
|
960
|
+
createNumericParser({ label: "Max iterations", integer: true, min: 1 }),
|
|
961
|
+
defaults?.["max-iterations"]
|
|
962
|
+
).option(OPTION_FLAGS.gadgetModule, OPTION_DESCRIPTIONS.gadgetModule, gadgetAccumulator, [
|
|
963
|
+
...defaultGadgets
|
|
964
|
+
]).option(
|
|
965
|
+
OPTION_FLAGS.parameterFormat,
|
|
966
|
+
OPTION_DESCRIPTIONS.parameterFormat,
|
|
967
|
+
parseParameterFormat,
|
|
968
|
+
defaults?.["parameter-format"] ?? DEFAULT_PARAMETER_FORMAT
|
|
969
|
+
).option(OPTION_FLAGS.noBuiltins, OPTION_DESCRIPTIONS.noBuiltins, defaults?.builtins !== false).option(
|
|
970
|
+
OPTION_FLAGS.noBuiltinInteraction,
|
|
971
|
+
OPTION_DESCRIPTIONS.noBuiltinInteraction,
|
|
972
|
+
defaults?.["builtin-interaction"] !== false
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
function configToCompleteOptions(config) {
|
|
976
|
+
const result = {};
|
|
977
|
+
if (config.model !== void 0) result.model = config.model;
|
|
978
|
+
if (config.system !== void 0) result.system = config.system;
|
|
979
|
+
if (config.temperature !== void 0) result.temperature = config.temperature;
|
|
980
|
+
if (config["max-tokens"] !== void 0) result.maxTokens = config["max-tokens"];
|
|
981
|
+
return result;
|
|
982
|
+
}
|
|
983
|
+
function configToAgentOptions(config) {
|
|
984
|
+
const result = {};
|
|
985
|
+
if (config.model !== void 0) result.model = config.model;
|
|
986
|
+
if (config.system !== void 0) result.system = config.system;
|
|
987
|
+
if (config.temperature !== void 0) result.temperature = config.temperature;
|
|
988
|
+
if (config["max-iterations"] !== void 0) result.maxIterations = config["max-iterations"];
|
|
989
|
+
if (config.gadget !== void 0) result.gadget = config.gadget;
|
|
990
|
+
if (config["parameter-format"] !== void 0) result.parameterFormat = config["parameter-format"];
|
|
991
|
+
if (config.builtins !== void 0) result.builtins = config.builtins;
|
|
992
|
+
if (config["builtin-interaction"] !== void 0)
|
|
993
|
+
result.builtinInteraction = config["builtin-interaction"];
|
|
994
|
+
return result;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// src/cli/agent-command.ts
|
|
998
|
+
async function promptApproval(env, prompt) {
|
|
999
|
+
const rl = createInterface({ input: env.stdin, output: env.stderr });
|
|
1000
|
+
try {
|
|
1001
|
+
const answer = await rl.question(prompt);
|
|
1002
|
+
return answer.toLowerCase().startsWith("y");
|
|
1003
|
+
} finally {
|
|
1004
|
+
rl.close();
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
731
1007
|
function createHumanInputHandler(env, progress) {
|
|
732
1008
|
const stdout = env.stdout;
|
|
733
1009
|
if (!isInteractive(env.stdin) || typeof stdout.isTTY !== "boolean" || !stdout.isTTY) {
|
|
@@ -738,7 +1014,7 @@ function createHumanInputHandler(env, progress) {
|
|
|
738
1014
|
const rl = createInterface({ input: env.stdin, output: env.stdout });
|
|
739
1015
|
try {
|
|
740
1016
|
const questionLine = question.trim() ? `
|
|
741
|
-
${question.trim()}` : "";
|
|
1017
|
+
${renderMarkdown(question.trim())}` : "";
|
|
742
1018
|
let isFirst = true;
|
|
743
1019
|
while (true) {
|
|
744
1020
|
const statsPrompt = progress.formatPrompt();
|
|
@@ -756,7 +1032,7 @@ ${statsPrompt}` : statsPrompt;
|
|
|
756
1032
|
}
|
|
757
1033
|
};
|
|
758
1034
|
}
|
|
759
|
-
async function
|
|
1035
|
+
async function executeAgent(promptArg, options, env) {
|
|
760
1036
|
const prompt = await resolvePrompt(promptArg, env);
|
|
761
1037
|
const client = env.createClient();
|
|
762
1038
|
const registry = new GadgetRegistry();
|
|
@@ -778,7 +1054,6 @@ async function handleAgentCommand(promptArg, options, env) {
|
|
|
778
1054
|
const printer = new StreamPrinter(env.stdout);
|
|
779
1055
|
const stderrTTY = env.stderr.isTTY === true;
|
|
780
1056
|
const progress = new StreamProgress(env.stderr, stderrTTY, client.modelRegistry);
|
|
781
|
-
let finishReason;
|
|
782
1057
|
let usage;
|
|
783
1058
|
let iterations = 0;
|
|
784
1059
|
const countMessagesTokens = async (model, messages) => {
|
|
@@ -789,6 +1064,15 @@ async function handleAgentCommand(promptArg, options, env) {
|
|
|
789
1064
|
return Math.round(totalChars / FALLBACK_CHARS_PER_TOKEN);
|
|
790
1065
|
}
|
|
791
1066
|
};
|
|
1067
|
+
const countGadgetOutputTokens = async (output) => {
|
|
1068
|
+
if (!output) return void 0;
|
|
1069
|
+
try {
|
|
1070
|
+
const messages = [{ role: "assistant", content: output }];
|
|
1071
|
+
return await client.countTokens(options.model, messages);
|
|
1072
|
+
} catch {
|
|
1073
|
+
return void 0;
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
792
1076
|
const builder = new AgentBuilder(client).withModel(options.model).withLogger(env.createLogger("llmist:cli:agent")).withHooks({
|
|
793
1077
|
observers: {
|
|
794
1078
|
// onLLMCallStart: Start progress indicator for each LLM call
|
|
@@ -817,7 +1101,6 @@ async function handleAgentCommand(promptArg, options, env) {
|
|
|
817
1101
|
// onLLMCallComplete: Finalize metrics after each LLM call
|
|
818
1102
|
// This is where you'd typically log metrics or update dashboards
|
|
819
1103
|
onLLMCallComplete: async (context) => {
|
|
820
|
-
finishReason = context.finishReason;
|
|
821
1104
|
usage = context.usage;
|
|
822
1105
|
iterations = Math.max(iterations, context.iteration + 1);
|
|
823
1106
|
if (context.usage) {
|
|
@@ -828,7 +1111,76 @@ async function handleAgentCommand(promptArg, options, env) {
|
|
|
828
1111
|
progress.setOutputTokens(context.usage.outputTokens, false);
|
|
829
1112
|
}
|
|
830
1113
|
}
|
|
1114
|
+
let callCost;
|
|
1115
|
+
if (context.usage && client.modelRegistry) {
|
|
1116
|
+
try {
|
|
1117
|
+
const modelName = options.model.includes(":") ? options.model.split(":")[1] : options.model;
|
|
1118
|
+
const costResult = client.modelRegistry.estimateCost(
|
|
1119
|
+
modelName,
|
|
1120
|
+
context.usage.inputTokens,
|
|
1121
|
+
context.usage.outputTokens
|
|
1122
|
+
);
|
|
1123
|
+
if (costResult) callCost = costResult.totalCost;
|
|
1124
|
+
} catch {
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
const callElapsed = progress.getCallElapsedSeconds();
|
|
831
1128
|
progress.endCall(context.usage);
|
|
1129
|
+
if (stderrTTY) {
|
|
1130
|
+
const summary = renderSummary({
|
|
1131
|
+
iterations: context.iteration + 1,
|
|
1132
|
+
model: options.model,
|
|
1133
|
+
usage: context.usage,
|
|
1134
|
+
elapsedSeconds: callElapsed,
|
|
1135
|
+
cost: callCost,
|
|
1136
|
+
finishReason: context.finishReason
|
|
1137
|
+
});
|
|
1138
|
+
if (summary) {
|
|
1139
|
+
env.stderr.write(`${summary}
|
|
1140
|
+
`);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
},
|
|
1145
|
+
// SHOWCASE: Controller-based approval gating for dangerous gadgets
|
|
1146
|
+
//
|
|
1147
|
+
// This demonstrates how to add safety layers WITHOUT modifying gadgets.
|
|
1148
|
+
// The RunCommand gadget is simple - it just executes commands. The CLI
|
|
1149
|
+
// adds the approval flow externally via beforeGadgetExecution controller.
|
|
1150
|
+
//
|
|
1151
|
+
// This pattern is composable: you can apply the same gating logic to
|
|
1152
|
+
// any gadget (DeleteFile, SendEmail, etc.) without changing the gadgets.
|
|
1153
|
+
controllers: {
|
|
1154
|
+
beforeGadgetExecution: async (ctx) => {
|
|
1155
|
+
if (ctx.gadgetName !== "RunCommand") {
|
|
1156
|
+
return { action: "proceed" };
|
|
1157
|
+
}
|
|
1158
|
+
const stdinTTY = isInteractive(env.stdin);
|
|
1159
|
+
const stderrTTY2 = env.stderr.isTTY === true;
|
|
1160
|
+
if (!stdinTTY || !stderrTTY2) {
|
|
1161
|
+
return {
|
|
1162
|
+
action: "skip",
|
|
1163
|
+
syntheticResult: "status=denied\n\nRunCommand requires interactive approval. Run in a terminal to approve commands."
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
const command = ctx.parameters.command;
|
|
1167
|
+
progress.pause();
|
|
1168
|
+
env.stderr.write(`
|
|
1169
|
+
${chalk3.yellow("\u{1F512} Execute:")} ${command}
|
|
1170
|
+
`);
|
|
1171
|
+
const approved = await promptApproval(env, " Approve? [y/n] ");
|
|
1172
|
+
if (!approved) {
|
|
1173
|
+
env.stderr.write(` ${chalk3.red("\u2717 Denied")}
|
|
1174
|
+
|
|
1175
|
+
`);
|
|
1176
|
+
return {
|
|
1177
|
+
action: "skip",
|
|
1178
|
+
syntheticResult: "status=denied\n\nCommand denied by user. Ask what they'd like to do instead."
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
env.stderr.write(` ${chalk3.green("\u2713 Approved")}
|
|
1182
|
+
`);
|
|
1183
|
+
return { action: "proceed" };
|
|
832
1184
|
}
|
|
833
1185
|
}
|
|
834
1186
|
});
|
|
@@ -849,6 +1201,10 @@ async function handleAgentCommand(promptArg, options, env) {
|
|
|
849
1201
|
if (gadgets.length > 0) {
|
|
850
1202
|
builder.withGadgets(...gadgets);
|
|
851
1203
|
}
|
|
1204
|
+
builder.withParameterFormat(options.parameterFormat);
|
|
1205
|
+
const markers = generateMarkers();
|
|
1206
|
+
builder.withGadgetStartPrefix(markers.startPrefix);
|
|
1207
|
+
builder.withGadgetEndPrefix(markers.endPrefix);
|
|
852
1208
|
const agent = builder.ask(prompt);
|
|
853
1209
|
for await (const event of agent.run()) {
|
|
854
1210
|
if (event.type === "text") {
|
|
@@ -857,20 +1213,22 @@ async function handleAgentCommand(promptArg, options, env) {
|
|
|
857
1213
|
} else if (event.type === "gadget_result") {
|
|
858
1214
|
progress.pause();
|
|
859
1215
|
if (stderrTTY) {
|
|
860
|
-
|
|
1216
|
+
const tokenCount = await countGadgetOutputTokens(event.result.result);
|
|
1217
|
+
env.stderr.write(`${formatGadgetSummary({ ...event.result, tokenCount })}
|
|
861
1218
|
`);
|
|
862
1219
|
}
|
|
863
1220
|
}
|
|
864
1221
|
}
|
|
865
1222
|
progress.complete();
|
|
866
1223
|
printer.ensureNewline();
|
|
867
|
-
if (stderrTTY) {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
1224
|
+
if (stderrTTY && iterations > 1) {
|
|
1225
|
+
env.stderr.write(`${chalk3.dim("\u2500".repeat(40))}
|
|
1226
|
+
`);
|
|
1227
|
+
const summary = renderOverallSummary({
|
|
1228
|
+
totalTokens: usage?.totalTokens,
|
|
871
1229
|
iterations,
|
|
872
|
-
|
|
873
|
-
|
|
1230
|
+
elapsedSeconds: progress.getTotalElapsedSeconds(),
|
|
1231
|
+
cost: progress.getTotalCost()
|
|
874
1232
|
});
|
|
875
1233
|
if (summary) {
|
|
876
1234
|
env.stderr.write(`${summary}
|
|
@@ -878,27 +1236,11 @@ async function handleAgentCommand(promptArg, options, env) {
|
|
|
878
1236
|
}
|
|
879
1237
|
}
|
|
880
1238
|
}
|
|
881
|
-
function registerAgentCommand(program, env) {
|
|
882
|
-
program.command(COMMANDS.agent).description("Run the llmist agent loop with optional gadgets.").argument("[prompt]", "Prompt for the agent loop. Falls back to stdin when available.")
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
).option(
|
|
887
|
-
OPTION_FLAGS.maxIterations,
|
|
888
|
-
OPTION_DESCRIPTIONS.maxIterations,
|
|
889
|
-
createNumericParser({ label: "Max iterations", integer: true, min: 1 })
|
|
890
|
-
).option(
|
|
891
|
-
OPTION_FLAGS.gadgetModule,
|
|
892
|
-
OPTION_DESCRIPTIONS.gadgetModule,
|
|
893
|
-
(value, previous = []) => [...previous, value],
|
|
894
|
-
[]
|
|
895
|
-
).option(
|
|
896
|
-
OPTION_FLAGS.parameterFormat,
|
|
897
|
-
OPTION_DESCRIPTIONS.parameterFormat,
|
|
898
|
-
parseParameterFormat,
|
|
899
|
-
DEFAULT_PARAMETER_FORMAT
|
|
900
|
-
).option(OPTION_FLAGS.noBuiltins, OPTION_DESCRIPTIONS.noBuiltins).option(OPTION_FLAGS.noBuiltinInteraction, OPTION_DESCRIPTIONS.noBuiltinInteraction).action(
|
|
901
|
-
(prompt, options) => executeAction(() => handleAgentCommand(prompt, options, env), env)
|
|
1239
|
+
function registerAgentCommand(program, env, config) {
|
|
1240
|
+
const cmd = program.command(COMMANDS.agent).description("Run the llmist agent loop with optional gadgets.").argument("[prompt]", "Prompt for the agent loop. Falls back to stdin when available.");
|
|
1241
|
+
addAgentOptions(cmd, config);
|
|
1242
|
+
cmd.action(
|
|
1243
|
+
(prompt, options) => executeAction(() => executeAgent(prompt, options, env), env)
|
|
902
1244
|
);
|
|
903
1245
|
}
|
|
904
1246
|
|
|
@@ -906,7 +1248,7 @@ function registerAgentCommand(program, env) {
|
|
|
906
1248
|
init_messages();
|
|
907
1249
|
init_model_shortcuts();
|
|
908
1250
|
init_constants();
|
|
909
|
-
async function
|
|
1251
|
+
async function executeComplete(promptArg, options, env) {
|
|
910
1252
|
const prompt = await resolvePrompt(promptArg, env);
|
|
911
1253
|
const client = env.createClient();
|
|
912
1254
|
const model = resolveModel(options.model);
|
|
@@ -960,25 +1302,307 @@ async function handleCompleteCommand(promptArg, options, env) {
|
|
|
960
1302
|
}
|
|
961
1303
|
}
|
|
962
1304
|
}
|
|
963
|
-
function registerCompleteCommand(program, env) {
|
|
964
|
-
program.command(COMMANDS.complete).description("Stream a single completion from a specified model.").argument("[prompt]", "Prompt to send to the LLM. If omitted, stdin is used when available.")
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
).option(
|
|
969
|
-
OPTION_FLAGS.maxTokens,
|
|
970
|
-
OPTION_DESCRIPTIONS.maxTokens,
|
|
971
|
-
createNumericParser({ label: "Max tokens", integer: true, min: 1 })
|
|
972
|
-
).action(
|
|
973
|
-
(prompt, options) => executeAction(
|
|
974
|
-
() => handleCompleteCommand(prompt, options, env),
|
|
975
|
-
env
|
|
976
|
-
)
|
|
1305
|
+
function registerCompleteCommand(program, env, config) {
|
|
1306
|
+
const cmd = program.command(COMMANDS.complete).description("Stream a single completion from a specified model.").argument("[prompt]", "Prompt to send to the LLM. If omitted, stdin is used when available.");
|
|
1307
|
+
addCompleteOptions(cmd, config);
|
|
1308
|
+
cmd.action(
|
|
1309
|
+
(prompt, options) => executeAction(() => executeComplete(prompt, options, env), env)
|
|
977
1310
|
);
|
|
978
1311
|
}
|
|
979
1312
|
|
|
1313
|
+
// src/cli/config.ts
|
|
1314
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
1315
|
+
import { homedir } from "node:os";
|
|
1316
|
+
import { join } from "node:path";
|
|
1317
|
+
import { load as parseToml } from "js-toml";
|
|
1318
|
+
var GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set(["log-level", "log-file"]);
|
|
1319
|
+
var VALID_LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
|
|
1320
|
+
var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set(["model", "system", "temperature", "max-tokens"]);
|
|
1321
|
+
var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
1322
|
+
"model",
|
|
1323
|
+
"system",
|
|
1324
|
+
"temperature",
|
|
1325
|
+
"max-iterations",
|
|
1326
|
+
"gadget",
|
|
1327
|
+
"parameter-format",
|
|
1328
|
+
"builtins",
|
|
1329
|
+
"builtin-interaction"
|
|
1330
|
+
]);
|
|
1331
|
+
var CUSTOM_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
1332
|
+
...COMPLETE_CONFIG_KEYS,
|
|
1333
|
+
...AGENT_CONFIG_KEYS,
|
|
1334
|
+
"type",
|
|
1335
|
+
"description"
|
|
1336
|
+
]);
|
|
1337
|
+
var VALID_PARAMETER_FORMATS = ["json", "yaml", "toml", "auto"];
|
|
1338
|
+
function getConfigPath() {
|
|
1339
|
+
return join(homedir(), ".llmist", "cli.toml");
|
|
1340
|
+
}
|
|
1341
|
+
var ConfigError = class extends Error {
|
|
1342
|
+
constructor(message, path2) {
|
|
1343
|
+
super(path2 ? `${path2}: ${message}` : message);
|
|
1344
|
+
this.path = path2;
|
|
1345
|
+
this.name = "ConfigError";
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
function validateString(value, key, section) {
|
|
1349
|
+
if (typeof value !== "string") {
|
|
1350
|
+
throw new ConfigError(`[${section}].${key} must be a string`);
|
|
1351
|
+
}
|
|
1352
|
+
return value;
|
|
1353
|
+
}
|
|
1354
|
+
function validateNumber(value, key, section, opts) {
|
|
1355
|
+
if (typeof value !== "number") {
|
|
1356
|
+
throw new ConfigError(`[${section}].${key} must be a number`);
|
|
1357
|
+
}
|
|
1358
|
+
if (opts?.integer && !Number.isInteger(value)) {
|
|
1359
|
+
throw new ConfigError(`[${section}].${key} must be an integer`);
|
|
1360
|
+
}
|
|
1361
|
+
if (opts?.min !== void 0 && value < opts.min) {
|
|
1362
|
+
throw new ConfigError(`[${section}].${key} must be >= ${opts.min}`);
|
|
1363
|
+
}
|
|
1364
|
+
if (opts?.max !== void 0 && value > opts.max) {
|
|
1365
|
+
throw new ConfigError(`[${section}].${key} must be <= ${opts.max}`);
|
|
1366
|
+
}
|
|
1367
|
+
return value;
|
|
1368
|
+
}
|
|
1369
|
+
function validateBoolean(value, key, section) {
|
|
1370
|
+
if (typeof value !== "boolean") {
|
|
1371
|
+
throw new ConfigError(`[${section}].${key} must be a boolean`);
|
|
1372
|
+
}
|
|
1373
|
+
return value;
|
|
1374
|
+
}
|
|
1375
|
+
function validateStringArray(value, key, section) {
|
|
1376
|
+
if (!Array.isArray(value)) {
|
|
1377
|
+
throw new ConfigError(`[${section}].${key} must be an array`);
|
|
1378
|
+
}
|
|
1379
|
+
for (let i = 0; i < value.length; i++) {
|
|
1380
|
+
if (typeof value[i] !== "string") {
|
|
1381
|
+
throw new ConfigError(`[${section}].${key}[${i}] must be a string`);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
return value;
|
|
1385
|
+
}
|
|
1386
|
+
function validateBaseConfig(raw, section) {
|
|
1387
|
+
const result = {};
|
|
1388
|
+
if ("model" in raw) {
|
|
1389
|
+
result.model = validateString(raw.model, "model", section);
|
|
1390
|
+
}
|
|
1391
|
+
if ("system" in raw) {
|
|
1392
|
+
result.system = validateString(raw.system, "system", section);
|
|
1393
|
+
}
|
|
1394
|
+
if ("temperature" in raw) {
|
|
1395
|
+
result.temperature = validateNumber(raw.temperature, "temperature", section, {
|
|
1396
|
+
min: 0,
|
|
1397
|
+
max: 2
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
return result;
|
|
1401
|
+
}
|
|
1402
|
+
function validateGlobalConfig(raw, section) {
|
|
1403
|
+
if (typeof raw !== "object" || raw === null) {
|
|
1404
|
+
throw new ConfigError(`[${section}] must be a table`);
|
|
1405
|
+
}
|
|
1406
|
+
const rawObj = raw;
|
|
1407
|
+
for (const key of Object.keys(rawObj)) {
|
|
1408
|
+
if (!GLOBAL_CONFIG_KEYS.has(key)) {
|
|
1409
|
+
throw new ConfigError(`[${section}].${key} is not a valid option`);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
const result = {};
|
|
1413
|
+
if ("log-level" in rawObj) {
|
|
1414
|
+
const level = validateString(rawObj["log-level"], "log-level", section);
|
|
1415
|
+
if (!VALID_LOG_LEVELS.includes(level)) {
|
|
1416
|
+
throw new ConfigError(
|
|
1417
|
+
`[${section}].log-level must be one of: ${VALID_LOG_LEVELS.join(", ")}`
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
result["log-level"] = level;
|
|
1421
|
+
}
|
|
1422
|
+
if ("log-file" in rawObj) {
|
|
1423
|
+
result["log-file"] = validateString(rawObj["log-file"], "log-file", section);
|
|
1424
|
+
}
|
|
1425
|
+
return result;
|
|
1426
|
+
}
|
|
1427
|
+
function validateCompleteConfig(raw, section) {
|
|
1428
|
+
if (typeof raw !== "object" || raw === null) {
|
|
1429
|
+
throw new ConfigError(`[${section}] must be a table`);
|
|
1430
|
+
}
|
|
1431
|
+
const rawObj = raw;
|
|
1432
|
+
for (const key of Object.keys(rawObj)) {
|
|
1433
|
+
if (!COMPLETE_CONFIG_KEYS.has(key)) {
|
|
1434
|
+
throw new ConfigError(`[${section}].${key} is not a valid option`);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
const result = { ...validateBaseConfig(rawObj, section) };
|
|
1438
|
+
if ("max-tokens" in rawObj) {
|
|
1439
|
+
result["max-tokens"] = validateNumber(rawObj["max-tokens"], "max-tokens", section, {
|
|
1440
|
+
integer: true,
|
|
1441
|
+
min: 1
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
return result;
|
|
1445
|
+
}
|
|
1446
|
+
function validateAgentConfig(raw, section) {
|
|
1447
|
+
if (typeof raw !== "object" || raw === null) {
|
|
1448
|
+
throw new ConfigError(`[${section}] must be a table`);
|
|
1449
|
+
}
|
|
1450
|
+
const rawObj = raw;
|
|
1451
|
+
for (const key of Object.keys(rawObj)) {
|
|
1452
|
+
if (!AGENT_CONFIG_KEYS.has(key)) {
|
|
1453
|
+
throw new ConfigError(`[${section}].${key} is not a valid option`);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
const result = { ...validateBaseConfig(rawObj, section) };
|
|
1457
|
+
if ("max-iterations" in rawObj) {
|
|
1458
|
+
result["max-iterations"] = validateNumber(rawObj["max-iterations"], "max-iterations", section, {
|
|
1459
|
+
integer: true,
|
|
1460
|
+
min: 1
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
if ("gadget" in rawObj) {
|
|
1464
|
+
result.gadget = validateStringArray(rawObj.gadget, "gadget", section);
|
|
1465
|
+
}
|
|
1466
|
+
if ("parameter-format" in rawObj) {
|
|
1467
|
+
const format = validateString(rawObj["parameter-format"], "parameter-format", section);
|
|
1468
|
+
if (!VALID_PARAMETER_FORMATS.includes(format)) {
|
|
1469
|
+
throw new ConfigError(
|
|
1470
|
+
`[${section}].parameter-format must be one of: ${VALID_PARAMETER_FORMATS.join(", ")}`
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
result["parameter-format"] = format;
|
|
1474
|
+
}
|
|
1475
|
+
if ("builtins" in rawObj) {
|
|
1476
|
+
result.builtins = validateBoolean(rawObj.builtins, "builtins", section);
|
|
1477
|
+
}
|
|
1478
|
+
if ("builtin-interaction" in rawObj) {
|
|
1479
|
+
result["builtin-interaction"] = validateBoolean(
|
|
1480
|
+
rawObj["builtin-interaction"],
|
|
1481
|
+
"builtin-interaction",
|
|
1482
|
+
section
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
return result;
|
|
1486
|
+
}
|
|
1487
|
+
function validateCustomConfig(raw, section) {
|
|
1488
|
+
if (typeof raw !== "object" || raw === null) {
|
|
1489
|
+
throw new ConfigError(`[${section}] must be a table`);
|
|
1490
|
+
}
|
|
1491
|
+
const rawObj = raw;
|
|
1492
|
+
for (const key of Object.keys(rawObj)) {
|
|
1493
|
+
if (!CUSTOM_CONFIG_KEYS.has(key)) {
|
|
1494
|
+
throw new ConfigError(`[${section}].${key} is not a valid option`);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
let type = "agent";
|
|
1498
|
+
if ("type" in rawObj) {
|
|
1499
|
+
const typeValue = validateString(rawObj.type, "type", section);
|
|
1500
|
+
if (typeValue !== "agent" && typeValue !== "complete") {
|
|
1501
|
+
throw new ConfigError(`[${section}].type must be "agent" or "complete"`);
|
|
1502
|
+
}
|
|
1503
|
+
type = typeValue;
|
|
1504
|
+
}
|
|
1505
|
+
const result = {
|
|
1506
|
+
...validateBaseConfig(rawObj, section),
|
|
1507
|
+
type
|
|
1508
|
+
};
|
|
1509
|
+
if ("description" in rawObj) {
|
|
1510
|
+
result.description = validateString(rawObj.description, "description", section);
|
|
1511
|
+
}
|
|
1512
|
+
if ("max-iterations" in rawObj) {
|
|
1513
|
+
result["max-iterations"] = validateNumber(rawObj["max-iterations"], "max-iterations", section, {
|
|
1514
|
+
integer: true,
|
|
1515
|
+
min: 1
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
if ("gadget" in rawObj) {
|
|
1519
|
+
result.gadget = validateStringArray(rawObj.gadget, "gadget", section);
|
|
1520
|
+
}
|
|
1521
|
+
if ("parameter-format" in rawObj) {
|
|
1522
|
+
const format = validateString(rawObj["parameter-format"], "parameter-format", section);
|
|
1523
|
+
if (!VALID_PARAMETER_FORMATS.includes(format)) {
|
|
1524
|
+
throw new ConfigError(
|
|
1525
|
+
`[${section}].parameter-format must be one of: ${VALID_PARAMETER_FORMATS.join(", ")}`
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
result["parameter-format"] = format;
|
|
1529
|
+
}
|
|
1530
|
+
if ("builtins" in rawObj) {
|
|
1531
|
+
result.builtins = validateBoolean(rawObj.builtins, "builtins", section);
|
|
1532
|
+
}
|
|
1533
|
+
if ("builtin-interaction" in rawObj) {
|
|
1534
|
+
result["builtin-interaction"] = validateBoolean(
|
|
1535
|
+
rawObj["builtin-interaction"],
|
|
1536
|
+
"builtin-interaction",
|
|
1537
|
+
section
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
if ("max-tokens" in rawObj) {
|
|
1541
|
+
result["max-tokens"] = validateNumber(rawObj["max-tokens"], "max-tokens", section, {
|
|
1542
|
+
integer: true,
|
|
1543
|
+
min: 1
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
return result;
|
|
1547
|
+
}
|
|
1548
|
+
function validateConfig(raw, configPath) {
|
|
1549
|
+
if (typeof raw !== "object" || raw === null) {
|
|
1550
|
+
throw new ConfigError("Config must be a TOML table", configPath);
|
|
1551
|
+
}
|
|
1552
|
+
const rawObj = raw;
|
|
1553
|
+
const result = {};
|
|
1554
|
+
for (const [key, value] of Object.entries(rawObj)) {
|
|
1555
|
+
try {
|
|
1556
|
+
if (key === "global") {
|
|
1557
|
+
result.global = validateGlobalConfig(value, key);
|
|
1558
|
+
} else if (key === "complete") {
|
|
1559
|
+
result.complete = validateCompleteConfig(value, key);
|
|
1560
|
+
} else if (key === "agent") {
|
|
1561
|
+
result.agent = validateAgentConfig(value, key);
|
|
1562
|
+
} else {
|
|
1563
|
+
result[key] = validateCustomConfig(value, key);
|
|
1564
|
+
}
|
|
1565
|
+
} catch (error) {
|
|
1566
|
+
if (error instanceof ConfigError) {
|
|
1567
|
+
throw new ConfigError(error.message, configPath);
|
|
1568
|
+
}
|
|
1569
|
+
throw error;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return result;
|
|
1573
|
+
}
|
|
1574
|
+
function loadConfig() {
|
|
1575
|
+
const configPath = getConfigPath();
|
|
1576
|
+
if (!existsSync(configPath)) {
|
|
1577
|
+
return {};
|
|
1578
|
+
}
|
|
1579
|
+
let content;
|
|
1580
|
+
try {
|
|
1581
|
+
content = readFileSync(configPath, "utf-8");
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
throw new ConfigError(
|
|
1584
|
+
`Failed to read config file: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1585
|
+
configPath
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
let raw;
|
|
1589
|
+
try {
|
|
1590
|
+
raw = parseToml(content);
|
|
1591
|
+
} catch (error) {
|
|
1592
|
+
throw new ConfigError(
|
|
1593
|
+
`Invalid TOML syntax: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1594
|
+
configPath
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
return validateConfig(raw, configPath);
|
|
1598
|
+
}
|
|
1599
|
+
function getCustomCommandNames(config) {
|
|
1600
|
+
const reserved = /* @__PURE__ */ new Set(["global", "complete", "agent"]);
|
|
1601
|
+
return Object.keys(config).filter((key) => !reserved.has(key));
|
|
1602
|
+
}
|
|
1603
|
+
|
|
980
1604
|
// src/cli/models-command.ts
|
|
981
|
-
import
|
|
1605
|
+
import chalk4 from "chalk";
|
|
982
1606
|
init_model_shortcuts();
|
|
983
1607
|
async function handleModelsCommand(options, env) {
|
|
984
1608
|
const client = env.createClient();
|
|
@@ -998,13 +1622,13 @@ function renderTable(models, verbose, stream) {
|
|
|
998
1622
|
}
|
|
999
1623
|
grouped.get(provider).push(model);
|
|
1000
1624
|
}
|
|
1001
|
-
stream.write(
|
|
1002
|
-
stream.write(
|
|
1625
|
+
stream.write(chalk4.bold.cyan("\nAvailable Models\n"));
|
|
1626
|
+
stream.write(chalk4.cyan("=".repeat(80)) + "\n\n");
|
|
1003
1627
|
const providers = Array.from(grouped.keys()).sort();
|
|
1004
1628
|
for (const provider of providers) {
|
|
1005
1629
|
const providerModels = grouped.get(provider);
|
|
1006
1630
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
1007
|
-
stream.write(
|
|
1631
|
+
stream.write(chalk4.bold.yellow(`${providerName} Models
|
|
1008
1632
|
`));
|
|
1009
1633
|
if (verbose) {
|
|
1010
1634
|
renderVerboseTable(providerModels, stream);
|
|
@@ -1013,11 +1637,11 @@ function renderTable(models, verbose, stream) {
|
|
|
1013
1637
|
}
|
|
1014
1638
|
stream.write("\n");
|
|
1015
1639
|
}
|
|
1016
|
-
stream.write(
|
|
1017
|
-
stream.write(
|
|
1640
|
+
stream.write(chalk4.bold.magenta("Model Shortcuts\n"));
|
|
1641
|
+
stream.write(chalk4.dim("\u2500".repeat(80)) + "\n");
|
|
1018
1642
|
const shortcuts = Object.entries(MODEL_ALIASES).sort((a, b) => a[0].localeCompare(b[0]));
|
|
1019
1643
|
for (const [shortcut, fullName] of shortcuts) {
|
|
1020
|
-
stream.write(
|
|
1644
|
+
stream.write(chalk4.cyan(` ${shortcut.padEnd(15)}`) + chalk4.dim(" \u2192 ") + chalk4.white(fullName) + "\n");
|
|
1021
1645
|
}
|
|
1022
1646
|
stream.write("\n");
|
|
1023
1647
|
}
|
|
@@ -1027,45 +1651,45 @@ function renderCompactTable(models, stream) {
|
|
|
1027
1651
|
const contextWidth = 13;
|
|
1028
1652
|
const inputWidth = 10;
|
|
1029
1653
|
const outputWidth = 10;
|
|
1030
|
-
stream.write(
|
|
1654
|
+
stream.write(chalk4.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
|
|
1031
1655
|
stream.write(
|
|
1032
|
-
|
|
1656
|
+
chalk4.bold(
|
|
1033
1657
|
"Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Context".padEnd(contextWidth) + " " + "Input".padEnd(inputWidth) + " " + "Output".padEnd(outputWidth)
|
|
1034
1658
|
) + "\n"
|
|
1035
1659
|
);
|
|
1036
|
-
stream.write(
|
|
1660
|
+
stream.write(chalk4.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
|
|
1037
1661
|
for (const model of models) {
|
|
1038
1662
|
const contextFormatted = formatTokens2(model.contextWindow);
|
|
1039
1663
|
const inputPrice = `$${model.pricing.input.toFixed(2)}`;
|
|
1040
1664
|
const outputPrice = `$${model.pricing.output.toFixed(2)}`;
|
|
1041
1665
|
stream.write(
|
|
1042
|
-
|
|
1666
|
+
chalk4.green(model.modelId.padEnd(idWidth)) + " " + chalk4.white(model.displayName.padEnd(nameWidth)) + " " + chalk4.yellow(contextFormatted.padEnd(contextWidth)) + " " + chalk4.cyan(inputPrice.padEnd(inputWidth)) + " " + chalk4.cyan(outputPrice.padEnd(outputWidth)) + "\n"
|
|
1043
1667
|
);
|
|
1044
1668
|
}
|
|
1045
|
-
stream.write(
|
|
1046
|
-
stream.write(
|
|
1669
|
+
stream.write(chalk4.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
|
|
1670
|
+
stream.write(chalk4.dim(` * Prices are per 1M tokens
|
|
1047
1671
|
`));
|
|
1048
1672
|
}
|
|
1049
1673
|
function renderVerboseTable(models, stream) {
|
|
1050
1674
|
for (const model of models) {
|
|
1051
|
-
stream.write(
|
|
1675
|
+
stream.write(chalk4.bold.green(`
|
|
1052
1676
|
${model.modelId}
|
|
1053
1677
|
`));
|
|
1054
|
-
stream.write(
|
|
1055
|
-
stream.write(` ${
|
|
1678
|
+
stream.write(chalk4.dim(" " + "\u2500".repeat(60)) + "\n");
|
|
1679
|
+
stream.write(` ${chalk4.dim("Name:")} ${chalk4.white(model.displayName)}
|
|
1056
1680
|
`);
|
|
1057
|
-
stream.write(` ${
|
|
1681
|
+
stream.write(` ${chalk4.dim("Context:")} ${chalk4.yellow(formatTokens2(model.contextWindow))}
|
|
1058
1682
|
`);
|
|
1059
|
-
stream.write(` ${
|
|
1683
|
+
stream.write(` ${chalk4.dim("Max Output:")} ${chalk4.yellow(formatTokens2(model.maxOutputTokens))}
|
|
1060
1684
|
`);
|
|
1061
|
-
stream.write(` ${
|
|
1685
|
+
stream.write(` ${chalk4.dim("Pricing:")} ${chalk4.cyan(`$${model.pricing.input.toFixed(2)} input`)} ${chalk4.dim("/")} ${chalk4.cyan(`$${model.pricing.output.toFixed(2)} output`)} ${chalk4.dim("(per 1M tokens)")}
|
|
1062
1686
|
`);
|
|
1063
1687
|
if (model.pricing.cachedInput !== void 0) {
|
|
1064
|
-
stream.write(` ${
|
|
1688
|
+
stream.write(` ${chalk4.dim("Cached Input:")} ${chalk4.cyan(`$${model.pricing.cachedInput.toFixed(2)} per 1M tokens`)}
|
|
1065
1689
|
`);
|
|
1066
1690
|
}
|
|
1067
1691
|
if (model.knowledgeCutoff) {
|
|
1068
|
-
stream.write(` ${
|
|
1692
|
+
stream.write(` ${chalk4.dim("Knowledge:")} ${model.knowledgeCutoff}
|
|
1069
1693
|
`);
|
|
1070
1694
|
}
|
|
1071
1695
|
const features = [];
|
|
@@ -1076,20 +1700,20 @@ function renderVerboseTable(models, stream) {
|
|
|
1076
1700
|
if (model.features.structuredOutputs) features.push("structured-outputs");
|
|
1077
1701
|
if (model.features.fineTuning) features.push("fine-tuning");
|
|
1078
1702
|
if (features.length > 0) {
|
|
1079
|
-
stream.write(` ${
|
|
1703
|
+
stream.write(` ${chalk4.dim("Features:")} ${chalk4.blue(features.join(", "))}
|
|
1080
1704
|
`);
|
|
1081
1705
|
}
|
|
1082
1706
|
if (model.metadata) {
|
|
1083
1707
|
if (model.metadata.family) {
|
|
1084
|
-
stream.write(` ${
|
|
1708
|
+
stream.write(` ${chalk4.dim("Family:")} ${model.metadata.family}
|
|
1085
1709
|
`);
|
|
1086
1710
|
}
|
|
1087
1711
|
if (model.metadata.releaseDate) {
|
|
1088
|
-
stream.write(` ${
|
|
1712
|
+
stream.write(` ${chalk4.dim("Released:")} ${model.metadata.releaseDate}
|
|
1089
1713
|
`);
|
|
1090
1714
|
}
|
|
1091
1715
|
if (model.metadata.notes) {
|
|
1092
|
-
stream.write(` ${
|
|
1716
|
+
stream.write(` ${chalk4.dim("Notes:")} ${chalk4.italic(model.metadata.notes)}
|
|
1093
1717
|
`);
|
|
1094
1718
|
}
|
|
1095
1719
|
}
|
|
@@ -1137,11 +1761,43 @@ function registerModelsCommand(program, env) {
|
|
|
1137
1761
|
);
|
|
1138
1762
|
}
|
|
1139
1763
|
|
|
1764
|
+
// src/cli/custom-command.ts
|
|
1765
|
+
function registerCustomCommand(program, name, config, env) {
|
|
1766
|
+
const type = config.type ?? "agent";
|
|
1767
|
+
const description = config.description ?? `Custom ${type} command`;
|
|
1768
|
+
const cmd = program.command(name).description(description).argument("[prompt]", "Prompt for the command. Falls back to stdin when available.");
|
|
1769
|
+
if (type === "complete") {
|
|
1770
|
+
addCompleteOptions(cmd, config);
|
|
1771
|
+
cmd.action(
|
|
1772
|
+
(prompt, cliOptions) => executeAction(async () => {
|
|
1773
|
+
const configDefaults = configToCompleteOptions(config);
|
|
1774
|
+
const options = {
|
|
1775
|
+
...configDefaults,
|
|
1776
|
+
...cliOptions
|
|
1777
|
+
};
|
|
1778
|
+
await executeComplete(prompt, options, env);
|
|
1779
|
+
}, env)
|
|
1780
|
+
);
|
|
1781
|
+
} else {
|
|
1782
|
+
addAgentOptions(cmd, config);
|
|
1783
|
+
cmd.action(
|
|
1784
|
+
(prompt, cliOptions) => executeAction(async () => {
|
|
1785
|
+
const configDefaults = configToAgentOptions(config);
|
|
1786
|
+
const options = {
|
|
1787
|
+
...configDefaults,
|
|
1788
|
+
...cliOptions
|
|
1789
|
+
};
|
|
1790
|
+
await executeAgent(prompt, options, env);
|
|
1791
|
+
}, env)
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1140
1796
|
// src/cli/environment.ts
|
|
1141
1797
|
init_client();
|
|
1142
1798
|
init_logger();
|
|
1143
1799
|
import readline from "node:readline";
|
|
1144
|
-
import
|
|
1800
|
+
import chalk5 from "chalk";
|
|
1145
1801
|
var LOG_LEVEL_MAP = {
|
|
1146
1802
|
silly: 0,
|
|
1147
1803
|
trace: 1,
|
|
@@ -1185,14 +1841,14 @@ function createPromptFunction(stdin, stdout) {
|
|
|
1185
1841
|
output: stdout
|
|
1186
1842
|
});
|
|
1187
1843
|
stdout.write("\n");
|
|
1188
|
-
stdout.write(`${
|
|
1844
|
+
stdout.write(`${chalk5.cyan("\u2500".repeat(60))}
|
|
1189
1845
|
`);
|
|
1190
|
-
stdout.write(
|
|
1846
|
+
stdout.write(chalk5.cyan.bold("\u{1F916} Agent asks:\n"));
|
|
1191
1847
|
stdout.write(`${question}
|
|
1192
1848
|
`);
|
|
1193
|
-
stdout.write(`${
|
|
1849
|
+
stdout.write(`${chalk5.cyan("\u2500".repeat(60))}
|
|
1194
1850
|
`);
|
|
1195
|
-
rl.question(
|
|
1851
|
+
rl.question(chalk5.green.bold("You: "), (answer) => {
|
|
1196
1852
|
rl.close();
|
|
1197
1853
|
resolve(answer);
|
|
1198
1854
|
});
|
|
@@ -1227,29 +1883,39 @@ function parseLogLevel(value) {
|
|
|
1227
1883
|
}
|
|
1228
1884
|
return normalized;
|
|
1229
1885
|
}
|
|
1230
|
-
function createProgram(env) {
|
|
1886
|
+
function createProgram(env, config) {
|
|
1231
1887
|
const program = new Command();
|
|
1232
1888
|
program.name(CLI_NAME).description(CLI_DESCRIPTION).version(package_default.version).option(OPTION_FLAGS.logLevel, OPTION_DESCRIPTIONS.logLevel, parseLogLevel).option(OPTION_FLAGS.logFile, OPTION_DESCRIPTIONS.logFile).configureOutput({
|
|
1233
1889
|
writeOut: (str) => env.stdout.write(str),
|
|
1234
1890
|
writeErr: (str) => env.stderr.write(str)
|
|
1235
1891
|
});
|
|
1236
|
-
registerCompleteCommand(program, env);
|
|
1237
|
-
registerAgentCommand(program, env);
|
|
1892
|
+
registerCompleteCommand(program, env, config?.complete);
|
|
1893
|
+
registerAgentCommand(program, env, config?.agent);
|
|
1238
1894
|
registerModelsCommand(program, env);
|
|
1895
|
+
if (config) {
|
|
1896
|
+
const customNames = getCustomCommandNames(config);
|
|
1897
|
+
for (const name of customNames) {
|
|
1898
|
+
const cmdConfig = config[name];
|
|
1899
|
+
registerCustomCommand(program, name, cmdConfig, env);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1239
1902
|
return program;
|
|
1240
1903
|
}
|
|
1241
1904
|
async function runCLI(overrides = {}) {
|
|
1905
|
+
const opts = "env" in overrides || "config" in overrides ? overrides : { env: overrides };
|
|
1906
|
+
const config = opts.config !== void 0 ? opts.config : loadConfig();
|
|
1907
|
+
const envOverrides = opts.env ?? {};
|
|
1242
1908
|
const preParser = new Command();
|
|
1243
1909
|
preParser.option(OPTION_FLAGS.logLevel, OPTION_DESCRIPTIONS.logLevel, parseLogLevel).option(OPTION_FLAGS.logFile, OPTION_DESCRIPTIONS.logFile).allowUnknownOption().allowExcessArguments().helpOption(false);
|
|
1244
1910
|
preParser.parse(process.argv);
|
|
1245
1911
|
const globalOpts = preParser.opts();
|
|
1246
1912
|
const loggerConfig = {
|
|
1247
|
-
logLevel: globalOpts.logLevel,
|
|
1248
|
-
logFile: globalOpts.logFile
|
|
1913
|
+
logLevel: globalOpts.logLevel ?? config.global?.["log-level"],
|
|
1914
|
+
logFile: globalOpts.logFile ?? config.global?.["log-file"]
|
|
1249
1915
|
};
|
|
1250
1916
|
const defaultEnv = createDefaultEnvironment(loggerConfig);
|
|
1251
|
-
const env = { ...defaultEnv, ...
|
|
1252
|
-
const program = createProgram(env);
|
|
1917
|
+
const env = { ...defaultEnv, ...envOverrides };
|
|
1918
|
+
const program = createProgram(env, config);
|
|
1253
1919
|
await program.parseAsync(env.argv);
|
|
1254
1920
|
}
|
|
1255
1921
|
|