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/dist/cli.cjs CHANGED
@@ -368,6 +368,7 @@ var init_prompt_config = __esm({
368
368
  criticalUsage: "INVOKE gadgets using the markers - do not describe what you want to do.",
369
369
  formatDescriptionYaml: "Parameters in YAML format (one per line)",
370
370
  formatDescriptionJson: "Parameters in JSON format (valid JSON object)",
371
+ formatDescriptionToml: "Parameters in TOML format (key = value pairs, use triple-quotes for multiline)",
371
372
  rules: () => [
372
373
  "Output ONLY plain text with the exact markers - never use function/tool calling",
373
374
  "You can invoke multiple gadgets in a single response",
@@ -375,6 +376,7 @@ var init_prompt_config = __esm({
375
376
  ],
376
377
  schemaLabelJson: "\n\nInput Schema (JSON):",
377
378
  schemaLabelYaml: "\n\nInput Schema (YAML):",
379
+ schemaLabelToml: "\n\nInput Schema (TOML):",
378
380
  customExamples: null
379
381
  };
380
382
  }
@@ -395,6 +397,15 @@ var init_messages = __esm({
395
397
  constructor(promptConfig) {
396
398
  this.promptConfig = promptConfig ?? {};
397
399
  }
400
+ /**
401
+ * Set custom prefixes for gadget markers.
402
+ * Used to configure history builder to match system prompt markers.
403
+ */
404
+ withPrefixes(startPrefix, endPrefix) {
405
+ this.startPrefix = startPrefix;
406
+ this.endPrefix = endPrefix;
407
+ return this;
408
+ }
398
409
  addSystem(content, metadata) {
399
410
  this.messages.push({ role: "system", content, metadata });
400
411
  return this;
@@ -432,7 +443,14 @@ var init_messages = __esm({
432
443
  for (const gadget of gadgets) {
433
444
  const gadgetName = gadget.name ?? gadget.constructor.name;
434
445
  const instruction = gadget.getInstruction(parameterFormat);
435
- const schemaMarker = parameterFormat === "yaml" ? "\n\nInput Schema (YAML):" : "\n\nInput Schema (JSON):";
446
+ const schemaMarkers = {
447
+ yaml: "\n\nInput Schema (YAML):",
448
+ json: "\n\nInput Schema (JSON):",
449
+ toml: "\n\nInput Schema (TOML):",
450
+ auto: "\n\nInput Schema (JSON):"
451
+ // auto defaults to JSON schema display
452
+ };
453
+ const schemaMarker = schemaMarkers[parameterFormat];
436
454
  const schemaIndex = instruction.indexOf(schemaMarker);
437
455
  const description = (schemaIndex !== -1 ? instruction.substring(0, schemaIndex) : instruction).trim();
438
456
  const schema = schemaIndex !== -1 ? instruction.substring(schemaIndex + schemaMarker.length).trim() : "";
@@ -452,15 +470,26 @@ ${schema}`);
452
470
  }
453
471
  buildUsageSection(parameterFormat, context) {
454
472
  const parts = [];
455
- const formatDescription = parameterFormat === "yaml" ? resolvePromptTemplate(
456
- this.promptConfig.formatDescriptionYaml,
457
- DEFAULT_PROMPTS.formatDescriptionYaml,
458
- context
459
- ) : resolvePromptTemplate(
460
- this.promptConfig.formatDescriptionJson,
461
- DEFAULT_PROMPTS.formatDescriptionJson,
462
- context
463
- );
473
+ const formatDescriptionMap = {
474
+ yaml: {
475
+ config: this.promptConfig.formatDescriptionYaml,
476
+ defaultValue: DEFAULT_PROMPTS.formatDescriptionYaml
477
+ },
478
+ json: {
479
+ config: this.promptConfig.formatDescriptionJson,
480
+ defaultValue: DEFAULT_PROMPTS.formatDescriptionJson
481
+ },
482
+ toml: {
483
+ config: this.promptConfig.formatDescriptionToml,
484
+ defaultValue: DEFAULT_PROMPTS.formatDescriptionToml
485
+ },
486
+ auto: {
487
+ config: this.promptConfig.formatDescriptionJson,
488
+ defaultValue: DEFAULT_PROMPTS.formatDescriptionJson
489
+ }
490
+ };
491
+ const { config, defaultValue } = formatDescriptionMap[parameterFormat];
492
+ const formatDescription = resolvePromptTemplate(config, defaultValue, context);
464
493
  parts.push("\n\nHOW TO INVOKE GADGETS");
465
494
  parts.push("\n=====================\n");
466
495
  const criticalUsage = resolvePromptTemplate(
@@ -488,38 +517,110 @@ CRITICAL: ${criticalUsage}
488
517
  return this.promptConfig.customExamples(context);
489
518
  }
490
519
  const parts = [];
491
- const singleExample = parameterFormat === "yaml" ? `${this.startPrefix}translate
520
+ const singleExamples = {
521
+ yaml: `${this.startPrefix}translate
492
522
  from: English
493
523
  to: Polish
494
- content: Paris is the capital of France.
495
- ${this.endPrefix}` : `${this.startPrefix}translate
496
- {"from": "English", "to": "Polish", "content": "Paris is the capital of France."}
497
- ${this.endPrefix}`;
524
+ content: "Paris is the capital of France: a beautiful city."
525
+ ${this.endPrefix}`,
526
+ json: `${this.startPrefix}translate
527
+ {"from": "English", "to": "Polish", "content": "Paris is the capital of France: a beautiful city."}
528
+ ${this.endPrefix}`,
529
+ toml: `${this.startPrefix}translate
530
+ from = "English"
531
+ to = "Polish"
532
+ content = "Paris is the capital of France: a beautiful city."
533
+ ${this.endPrefix}`,
534
+ auto: `${this.startPrefix}translate
535
+ {"from": "English", "to": "Polish", "content": "Paris is the capital of France: a beautiful city."}
536
+ ${this.endPrefix}`
537
+ };
498
538
  parts.push(`
499
539
 
500
540
  EXAMPLE (Single Gadget):
501
541
 
502
- ${singleExample}`);
503
- const multipleExample = parameterFormat === "yaml" ? `${this.startPrefix}translate
542
+ ${singleExamples[parameterFormat]}`);
543
+ const multipleExamples = {
544
+ yaml: `${this.startPrefix}translate
504
545
  from: English
505
546
  to: Polish
506
- content: Paris is the capital of France.
547
+ content: "Paris is the capital of France: a beautiful city."
507
548
  ${this.endPrefix}
508
549
  ${this.startPrefix}analyze
509
550
  type: economic_analysis
510
551
  matter: "Polish Economy"
511
- question: Polish arms exports 2025.
512
- ${this.endPrefix}` : `${this.startPrefix}translate
513
- {"from": "English", "to": "Polish", "content": "Paris is the capital of France."}
552
+ question: |
553
+ Analyze the following:
554
+ - Polish arms exports 2025
555
+ - Economic implications
556
+ ${this.endPrefix}`,
557
+ json: `${this.startPrefix}translate
558
+ {"from": "English", "to": "Polish", "content": "Paris is the capital of France: a beautiful city."}
559
+ ${this.endPrefix}
560
+ ${this.startPrefix}analyze
561
+ {"type": "economic_analysis", "matter": "Polish Economy", "question": "Analyze the following: Polish arms exports 2025, economic implications"}
562
+ ${this.endPrefix}`,
563
+ toml: `${this.startPrefix}translate
564
+ from = "English"
565
+ to = "Polish"
566
+ content = "Paris is the capital of France: a beautiful city."
514
567
  ${this.endPrefix}
515
568
  ${this.startPrefix}analyze
516
- {"type": "economic_analysis", "matter": "Polish Economy", "question": "Polish arms exports 2025."}
517
- ${this.endPrefix}`;
569
+ type = "economic_analysis"
570
+ matter = "Polish Economy"
571
+ question = """
572
+ Analyze the following:
573
+ - Polish arms exports 2025
574
+ - Economic implications
575
+ """
576
+ ${this.endPrefix}`,
577
+ auto: `${this.startPrefix}translate
578
+ {"from": "English", "to": "Polish", "content": "Paris is the capital of France: a beautiful city."}
579
+ ${this.endPrefix}
580
+ ${this.startPrefix}analyze
581
+ {"type": "economic_analysis", "matter": "Polish Economy", "question": "Analyze the following: Polish arms exports 2025, economic implications"}
582
+ ${this.endPrefix}`
583
+ };
518
584
  parts.push(`
519
585
 
520
586
  EXAMPLE (Multiple Gadgets):
521
587
 
522
- ${multipleExample}`);
588
+ ${multipleExamples[parameterFormat]}`);
589
+ if (parameterFormat === "yaml") {
590
+ parts.push(`
591
+
592
+ YAML MULTILINE SYNTAX:
593
+ For string values with special characters (colons, dashes, quotes) or multiple lines,
594
+ use the pipe (|) syntax. ALL content lines MUST be indented with 2 spaces:
595
+
596
+ CORRECT - all lines indented:
597
+ question: |
598
+ Which option do you prefer?
599
+ - Option A: fast processing
600
+ - Option B: thorough analysis
601
+ Please choose one.
602
+
603
+ WRONG - inconsistent indentation breaks YAML:
604
+ question: |
605
+ Which option do you prefer?
606
+ - Option A: fast
607
+ Please choose one. <-- ERROR: not indented, breaks out of the block`);
608
+ } else if (parameterFormat === "toml") {
609
+ parts.push(`
610
+
611
+ TOML MULTILINE SYNTAX:
612
+ For string values with multiple lines or special characters, use triple-quotes ("""):
613
+
614
+ filePath = "README.md"
615
+ content = """
616
+ # Project Title
617
+
618
+ This content can contain:
619
+ - Markdown lists
620
+ - Special characters: # : -
621
+ - Multiple paragraphs
622
+ """`);
623
+ }
523
624
  return parts.join("");
524
625
  }
525
626
  buildRulesSection(context) {
@@ -563,6 +664,16 @@ ${this.endPrefix}`
563
664
  return `${key}: ${JSON.stringify(value)}`;
564
665
  }).join("\n");
565
666
  }
667
+ if (format === "toml") {
668
+ return Object.entries(parameters).map(([key, value]) => {
669
+ if (typeof value === "string" && value.includes("\n")) {
670
+ return `${key} = """
671
+ ${value}
672
+ """`;
673
+ }
674
+ return `${key} = ${JSON.stringify(value)}`;
675
+ }).join("\n");
676
+ }
566
677
  return JSON.stringify(parameters);
567
678
  }
568
679
  build() {
@@ -664,11 +775,14 @@ var init_conversation_manager = __esm({
664
775
  initialMessages;
665
776
  historyBuilder;
666
777
  parameterFormat;
667
- constructor(baseMessages, initialMessages, parameterFormat = "json") {
778
+ constructor(baseMessages, initialMessages, options = {}) {
668
779
  this.baseMessages = baseMessages;
669
780
  this.initialMessages = initialMessages;
670
- this.parameterFormat = parameterFormat;
781
+ this.parameterFormat = options.parameterFormat ?? "json";
671
782
  this.historyBuilder = new LLMMessageBuilder();
783
+ if (options.startPrefix && options.endPrefix) {
784
+ this.historyBuilder.withPrefixes(options.startPrefix, options.endPrefix);
785
+ }
672
786
  }
673
787
  addUserMessage(content) {
674
788
  this.historyBuilder.addUser(content);
@@ -1139,11 +1253,109 @@ var init_executor = __esm({
1139
1253
  });
1140
1254
 
1141
1255
  // src/gadgets/parser.ts
1142
- var yaml, globalInvocationCounter, StreamParser;
1256
+ function preprocessYaml(yamlStr) {
1257
+ const lines = yamlStr.split("\n");
1258
+ const result = [];
1259
+ let i = 0;
1260
+ while (i < lines.length) {
1261
+ const line = lines[i];
1262
+ const match = line.match(/^(\s*)([\w-]+):\s+(.+)$/);
1263
+ if (match) {
1264
+ const [, indent, key, value] = match;
1265
+ if (value === "|" || value === ">" || value === "|-" || value === ">-") {
1266
+ result.push(line);
1267
+ i++;
1268
+ const keyIndentLen2 = indent.length;
1269
+ const blockLines = [];
1270
+ let minContentIndent = Infinity;
1271
+ while (i < lines.length) {
1272
+ const blockLine = lines[i];
1273
+ const blockIndentMatch = blockLine.match(/^(\s*)/);
1274
+ const blockIndentLen = blockIndentMatch ? blockIndentMatch[1].length : 0;
1275
+ if (blockLine.trim() === "") {
1276
+ blockLines.push({ content: "", originalIndent: 0 });
1277
+ i++;
1278
+ continue;
1279
+ }
1280
+ if (blockIndentLen > keyIndentLen2) {
1281
+ const content = blockLine.substring(blockIndentLen);
1282
+ blockLines.push({ content, originalIndent: blockIndentLen });
1283
+ if (content.trim().length > 0) {
1284
+ minContentIndent = Math.min(minContentIndent, blockIndentLen);
1285
+ }
1286
+ i++;
1287
+ } else {
1288
+ break;
1289
+ }
1290
+ }
1291
+ const targetIndent = keyIndentLen2 + 2;
1292
+ for (const blockLine of blockLines) {
1293
+ if (blockLine.content === "") {
1294
+ result.push("");
1295
+ } else {
1296
+ result.push(" ".repeat(targetIndent) + blockLine.content);
1297
+ }
1298
+ }
1299
+ continue;
1300
+ }
1301
+ if (value.startsWith('"') || value.startsWith("'") || value === "true" || value === "false" || /^-?\d+(\.\d+)?$/.test(value)) {
1302
+ result.push(line);
1303
+ i++;
1304
+ continue;
1305
+ }
1306
+ const keyIndentLen = indent.length;
1307
+ const continuationLines = [];
1308
+ let j = i + 1;
1309
+ while (j < lines.length) {
1310
+ const nextLine = lines[j];
1311
+ if (nextLine.trim() === "") {
1312
+ continuationLines.push(nextLine);
1313
+ j++;
1314
+ continue;
1315
+ }
1316
+ const nextIndentMatch = nextLine.match(/^(\s*)/);
1317
+ const nextIndentLen = nextIndentMatch ? nextIndentMatch[1].length : 0;
1318
+ if (nextIndentLen > keyIndentLen) {
1319
+ continuationLines.push(nextLine);
1320
+ j++;
1321
+ } else {
1322
+ break;
1323
+ }
1324
+ }
1325
+ if (continuationLines.length > 0 && continuationLines.some((l) => l.trim().length > 0)) {
1326
+ result.push(`${indent}${key}: |`);
1327
+ result.push(`${indent} ${value}`);
1328
+ for (const contLine of continuationLines) {
1329
+ if (contLine.trim() === "") {
1330
+ result.push("");
1331
+ } else {
1332
+ const contIndentMatch = contLine.match(/^(\s*)/);
1333
+ const contIndent = contIndentMatch ? contIndentMatch[1] : "";
1334
+ const contContent = contLine.substring(contIndent.length);
1335
+ result.push(`${indent} ${contContent}`);
1336
+ }
1337
+ }
1338
+ i = j;
1339
+ continue;
1340
+ }
1341
+ if (value.includes(": ") || value.endsWith(":")) {
1342
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1343
+ result.push(`${indent}${key}: "${escaped}"`);
1344
+ i++;
1345
+ continue;
1346
+ }
1347
+ }
1348
+ result.push(line);
1349
+ i++;
1350
+ }
1351
+ return result.join("\n");
1352
+ }
1353
+ var yaml, import_js_toml, globalInvocationCounter, StreamParser;
1143
1354
  var init_parser = __esm({
1144
1355
  "src/gadgets/parser.ts"() {
1145
1356
  "use strict";
1146
1357
  yaml = __toESM(require("js-yaml"), 1);
1358
+ import_js_toml = require("js-toml");
1147
1359
  init_constants();
1148
1360
  globalInvocationCounter = 0;
1149
1361
  StreamParser = class {
@@ -1165,6 +1377,17 @@ var init_parser = __esm({
1165
1377
  this.lastReportedTextLength = index;
1166
1378
  return segment.trim().length > 0 ? segment : void 0;
1167
1379
  }
1380
+ /**
1381
+ * Parse gadget name, handling both old format (name:invocationId) and new format (just name).
1382
+ * For new format, generates a unique invocation ID.
1383
+ */
1384
+ parseGadgetName(gadgetName) {
1385
+ if (gadgetName.includes(":")) {
1386
+ const parts = gadgetName.split(":");
1387
+ return { actualName: parts[0], invocationId: parts[1] };
1388
+ }
1389
+ return { actualName: gadgetName, invocationId: `gadget_${++globalInvocationCounter}` };
1390
+ }
1168
1391
  /**
1169
1392
  * Parse parameter string according to configured format
1170
1393
  */
@@ -1178,20 +1401,31 @@ var init_parser = __esm({
1178
1401
  }
1179
1402
  if (this.parameterFormat === "yaml") {
1180
1403
  try {
1181
- return { parameters: yaml.load(raw) };
1404
+ return { parameters: yaml.load(preprocessYaml(raw)) };
1182
1405
  } catch (error) {
1183
1406
  return { parseError: error instanceof Error ? error.message : "Failed to parse YAML" };
1184
1407
  }
1185
1408
  }
1409
+ if (this.parameterFormat === "toml") {
1410
+ try {
1411
+ return { parameters: (0, import_js_toml.load)(raw) };
1412
+ } catch (error) {
1413
+ return { parseError: error instanceof Error ? error.message : "Failed to parse TOML" };
1414
+ }
1415
+ }
1186
1416
  try {
1187
1417
  return { parameters: JSON.parse(raw) };
1188
1418
  } catch {
1189
1419
  try {
1190
- return { parameters: yaml.load(raw) };
1191
- } catch (error) {
1192
- return {
1193
- parseError: error instanceof Error ? error.message : "Failed to parse as JSON or YAML"
1194
- };
1420
+ return { parameters: (0, import_js_toml.load)(raw) };
1421
+ } catch {
1422
+ try {
1423
+ return { parameters: yaml.load(preprocessYaml(raw)) };
1424
+ } catch (error) {
1425
+ return {
1426
+ parseError: error instanceof Error ? error.message : "Failed to parse as JSON, TOML, or YAML"
1427
+ };
1428
+ }
1195
1429
  }
1196
1430
  }
1197
1431
  }
@@ -1210,16 +1444,7 @@ var init_parser = __esm({
1210
1444
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
1211
1445
  if (metadataEndIndex === -1) break;
1212
1446
  const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
1213
- let invocationId;
1214
- let actualGadgetName;
1215
- if (gadgetName.includes(":")) {
1216
- const parts = gadgetName.split(":");
1217
- actualGadgetName = parts[0];
1218
- invocationId = parts[1];
1219
- } else {
1220
- actualGadgetName = gadgetName;
1221
- invocationId = `gadget_${++globalInvocationCounter}`;
1222
- }
1447
+ const { actualName: actualGadgetName, invocationId } = this.parseGadgetName(gadgetName);
1223
1448
  const contentStartIndex = metadataEndIndex + 1;
1224
1449
  let partEndIndex;
1225
1450
  let endMarkerLength = 0;
@@ -1229,23 +1454,29 @@ var init_parser = __esm({
1229
1454
  if (partEndIndex === -1) break;
1230
1455
  endMarkerLength = oldEndMarker.length;
1231
1456
  } else {
1232
- partEndIndex = contentStartIndex;
1457
+ const nextStartPos = this.buffer.indexOf(this.startPrefix, contentStartIndex);
1458
+ let validEndPos = -1;
1459
+ let searchPos = contentStartIndex;
1233
1460
  while (true) {
1234
- const endPos = this.buffer.indexOf(this.endPrefix, partEndIndex);
1235
- if (endPos === -1) {
1236
- partEndIndex = -1;
1237
- break;
1238
- }
1461
+ const endPos = this.buffer.indexOf(this.endPrefix, searchPos);
1462
+ if (endPos === -1) break;
1239
1463
  const afterEnd = this.buffer.substring(endPos + this.endPrefix.length);
1240
1464
  if (afterEnd.startsWith("\n") || afterEnd.startsWith("\r") || afterEnd.startsWith(this.startPrefix) || afterEnd.length === 0) {
1241
- partEndIndex = endPos;
1242
- endMarkerLength = this.endPrefix.length;
1465
+ validEndPos = endPos;
1243
1466
  break;
1244
1467
  } else {
1245
- partEndIndex = endPos + this.endPrefix.length;
1468
+ searchPos = endPos + this.endPrefix.length;
1246
1469
  }
1247
1470
  }
1248
- if (partEndIndex === -1) break;
1471
+ if (nextStartPos !== -1 && (validEndPos === -1 || nextStartPos < validEndPos)) {
1472
+ partEndIndex = nextStartPos;
1473
+ endMarkerLength = 0;
1474
+ } else if (validEndPos !== -1) {
1475
+ partEndIndex = validEndPos;
1476
+ endMarkerLength = this.endPrefix.length;
1477
+ } else {
1478
+ break;
1479
+ }
1249
1480
  }
1250
1481
  const parametersRaw = this.buffer.substring(contentStartIndex, partEndIndex).trim();
1251
1482
  const { parameters, parseError } = this.parseParameters(parametersRaw);
@@ -1268,8 +1499,35 @@ var init_parser = __esm({
1268
1499
  this.lastReportedTextLength = 0;
1269
1500
  }
1270
1501
  }
1271
- // Finalize parsing and return remaining text
1502
+ // Finalize parsing and return remaining text or incomplete gadgets
1272
1503
  *finalize() {
1504
+ const startIndex = this.buffer.indexOf(this.startPrefix, this.lastReportedTextLength);
1505
+ if (startIndex !== -1) {
1506
+ const textBefore = this.takeTextUntil(startIndex);
1507
+ if (textBefore !== void 0) {
1508
+ yield { type: "text", content: textBefore };
1509
+ }
1510
+ const metadataStartIndex = startIndex + this.startPrefix.length;
1511
+ const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
1512
+ if (metadataEndIndex !== -1) {
1513
+ const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
1514
+ const { actualName: actualGadgetName, invocationId } = this.parseGadgetName(gadgetName);
1515
+ const contentStartIndex = metadataEndIndex + 1;
1516
+ const parametersRaw = this.buffer.substring(contentStartIndex).trim();
1517
+ const { parameters, parseError } = this.parseParameters(parametersRaw);
1518
+ yield {
1519
+ type: "gadget_call",
1520
+ call: {
1521
+ gadgetName: actualGadgetName,
1522
+ invocationId,
1523
+ parametersYaml: parametersRaw,
1524
+ parameters,
1525
+ parseError
1526
+ }
1527
+ };
1528
+ return;
1529
+ }
1530
+ }
1273
1531
  const remainingText = this.takeTextUntil(this.buffer.length);
1274
1532
  if (remainingText !== void 0) {
1275
1533
  yield { type: "text", content: remainingText };
@@ -1750,11 +2008,11 @@ var init_agent = __esm({
1750
2008
  role: message.role,
1751
2009
  content: message.content
1752
2010
  }));
1753
- this.conversation = new ConversationManager(
1754
- baseMessages,
1755
- initialMessages,
1756
- this.parameterFormat
1757
- );
2011
+ this.conversation = new ConversationManager(baseMessages, initialMessages, {
2012
+ parameterFormat: this.parameterFormat,
2013
+ startPrefix: options.gadgetStartPrefix,
2014
+ endPrefix: options.gadgetEndPrefix
2015
+ });
1758
2016
  this.userPromptProvided = !!options.userPrompt;
1759
2017
  if (options.userPrompt) {
1760
2018
  this.conversation.addUserMessage(options.userPrompt);
@@ -4275,7 +4533,7 @@ var COMMANDS = {
4275
4533
  };
4276
4534
  var LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
4277
4535
  var DEFAULT_MODEL = "openai:gpt-5-nano";
4278
- var DEFAULT_PARAMETER_FORMAT = "json";
4536
+ var DEFAULT_PARAMETER_FORMAT = "toml";
4279
4537
  var OPTION_FLAGS = {
4280
4538
  model: "-m, --model <identifier>",
4281
4539
  systemPrompt: "-s, --system <prompt>",
@@ -4296,7 +4554,7 @@ var OPTION_DESCRIPTIONS = {
4296
4554
  maxTokens: "Maximum number of output tokens requested from the model.",
4297
4555
  maxIterations: "Maximum number of agent loop iterations before exiting.",
4298
4556
  gadgetModule: "Path or module specifier for a gadget export. Repeat to register multiple gadgets.",
4299
- parameterFormat: "Format for gadget parameter schemas: 'json', 'yaml', or 'auto'.",
4557
+ parameterFormat: "Format for gadget parameter schemas: 'json', 'yaml', 'toml', or 'auto'.",
4300
4558
  logLevel: "Log level: silly, trace, debug, info, warn, error, fatal.",
4301
4559
  logFile: "Path to log file. When set, logs are written to file instead of stderr.",
4302
4560
  noBuiltins: "Disable built-in gadgets (AskUser, TellUser).",
@@ -4310,7 +4568,7 @@ var import_commander3 = require("commander");
4310
4568
  // package.json
4311
4569
  var package_default = {
4312
4570
  name: "llmist",
4313
- version: "0.4.0",
4571
+ version: "0.4.1",
4314
4572
  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.",
4315
4573
  type: "module",
4316
4574
  main: "dist/index.cjs",
@@ -4394,7 +4652,10 @@ var package_default = {
4394
4652
  "@google/genai": "^1.27.0",
4395
4653
  chalk: "^5.6.2",
4396
4654
  commander: "^12.1.0",
4655
+ "js-toml": "^1.0.2",
4397
4656
  "js-yaml": "^4.1.0",
4657
+ marked: "^17.0.1",
4658
+ "marked-terminal": "^7.3.0",
4398
4659
  openai: "^6.0.0",
4399
4660
  tiktoken: "^1.0.22",
4400
4661
  tslog: "^4.10.2",
@@ -4407,6 +4668,7 @@ var package_default = {
4407
4668
  "@semantic-release/changelog": "^6.0.3",
4408
4669
  "@semantic-release/git": "^10.0.1",
4409
4670
  "@types/js-yaml": "^4.0.9",
4671
+ "@types/marked-terminal": "^6.1.1",
4410
4672
  "@types/node": "^20.12.7",
4411
4673
  "bun-types": "^1.3.2",
4412
4674
  dotenv: "^17.2.3",
@@ -4419,7 +4681,7 @@ var package_default = {
4419
4681
 
4420
4682
  // src/cli/agent-command.ts
4421
4683
  var import_promises = require("readline/promises");
4422
- var import_commander2 = require("commander");
4684
+ var import_chalk3 = __toESM(require("chalk"), 1);
4423
4685
  init_builder();
4424
4686
  init_registry();
4425
4687
  init_constants2();
@@ -4509,6 +4771,83 @@ function mergeDescriptions(schema, jsonSchema) {
4509
4771
 
4510
4772
  // src/gadgets/gadget.ts
4511
4773
  init_schema_validator();
4774
+ function formatYamlValue(value, indent = "") {
4775
+ if (typeof value === "string") {
4776
+ const lines = value.split("\n");
4777
+ if (lines.length === 1 && !value.includes(":") && !value.startsWith("-")) {
4778
+ return value;
4779
+ }
4780
+ const indentedLines = lines.map((line) => `${indent} ${line}`).join("\n");
4781
+ return `|
4782
+ ${indentedLines}`;
4783
+ }
4784
+ if (typeof value === "number" || typeof value === "boolean") {
4785
+ return String(value);
4786
+ }
4787
+ if (value === null || value === void 0) {
4788
+ return "null";
4789
+ }
4790
+ if (Array.isArray(value)) {
4791
+ if (value.length === 0) return "[]";
4792
+ const items = value.map((item) => `${indent}- ${formatYamlValue(item, indent + " ")}`);
4793
+ return "\n" + items.join("\n");
4794
+ }
4795
+ if (typeof value === "object") {
4796
+ const entries = Object.entries(value);
4797
+ if (entries.length === 0) return "{}";
4798
+ const lines = entries.map(([k, v]) => {
4799
+ const formattedValue = formatYamlValue(v, indent + " ");
4800
+ if (formattedValue.startsWith("\n") || formattedValue.startsWith("|")) {
4801
+ return `${indent}${k}: ${formattedValue}`;
4802
+ }
4803
+ return `${indent}${k}: ${formattedValue}`;
4804
+ });
4805
+ return "\n" + lines.join("\n");
4806
+ }
4807
+ return yaml2.dump(value).trimEnd();
4808
+ }
4809
+ function formatParamsAsYaml(params) {
4810
+ const lines = [];
4811
+ for (const [key, value] of Object.entries(params)) {
4812
+ const formattedValue = formatYamlValue(value, "");
4813
+ if (formattedValue.startsWith("\n")) {
4814
+ lines.push(`${key}:${formattedValue}`);
4815
+ } else {
4816
+ lines.push(`${key}: ${formattedValue}`);
4817
+ }
4818
+ }
4819
+ return lines.join("\n");
4820
+ }
4821
+ function formatTomlValue(value) {
4822
+ if (typeof value === "string") {
4823
+ if (value.includes("\n")) {
4824
+ return `"""
4825
+ ${value}
4826
+ """`;
4827
+ }
4828
+ return JSON.stringify(value);
4829
+ }
4830
+ if (typeof value === "number" || typeof value === "boolean") {
4831
+ return String(value);
4832
+ }
4833
+ if (value === null || value === void 0) {
4834
+ return '""';
4835
+ }
4836
+ if (Array.isArray(value)) {
4837
+ return JSON.stringify(value);
4838
+ }
4839
+ if (typeof value === "object") {
4840
+ return JSON.stringify(value);
4841
+ }
4842
+ return JSON.stringify(value);
4843
+ }
4844
+ function formatParamsAsToml(params) {
4845
+ const lines = [];
4846
+ for (const [key, value] of Object.entries(params)) {
4847
+ lines.push(`${key} = ${formatTomlValue(value)}`);
4848
+ }
4849
+ return lines.join("\n");
4850
+ }
4512
4851
  var BaseGadget = class {
4513
4852
  /**
4514
4853
  * The name of the gadget. Used for identification when LLM calls it.
@@ -4528,6 +4867,14 @@ var BaseGadget = class {
4528
4867
  * Set to 0 or undefined to disable timeout for this gadget.
4529
4868
  */
4530
4869
  timeoutMs;
4870
+ /**
4871
+ * Optional usage examples to help LLMs understand proper invocation.
4872
+ * Examples are rendered in getInstruction() alongside the schema.
4873
+ *
4874
+ * Note: Uses broader `unknown` type to allow typed examples from subclasses
4875
+ * while maintaining runtime compatibility.
4876
+ */
4877
+ examples;
4531
4878
  /**
4532
4879
  * Auto-generated instruction text for the LLM.
4533
4880
  * Combines name, description, and parameter schema into a formatted instruction.
@@ -4540,7 +4887,7 @@ var BaseGadget = class {
4540
4887
  * Generate instruction text for the LLM with format-specific schema.
4541
4888
  * Combines name, description, and parameter schema into a formatted instruction.
4542
4889
  *
4543
- * @param format - Format for the schema representation ('json' | 'yaml' | 'auto')
4890
+ * @param format - Format for the schema representation ('json' | 'yaml' | 'toml' | 'auto')
4544
4891
  * @returns Formatted instruction string
4545
4892
  */
4546
4893
  getInstruction(format = "json") {
@@ -4555,12 +4902,38 @@ var BaseGadget = class {
4555
4902
  if (format === "json" || format === "auto") {
4556
4903
  parts.push("\n\nInput Schema (JSON):");
4557
4904
  parts.push(JSON.stringify(jsonSchema, null, 2));
4905
+ } else if (format === "toml") {
4906
+ parts.push("\n\nInput Schema (TOML):");
4907
+ parts.push(JSON.stringify(jsonSchema, null, 2));
4558
4908
  } else {
4559
4909
  const yamlSchema = yaml2.dump(jsonSchema).trimEnd();
4560
4910
  parts.push("\n\nInput Schema (YAML):");
4561
4911
  parts.push(yamlSchema);
4562
4912
  }
4563
4913
  }
4914
+ if (this.examples && this.examples.length > 0) {
4915
+ parts.push("\n\nExamples:");
4916
+ this.examples.forEach((example, index) => {
4917
+ if (index > 0) {
4918
+ parts.push("");
4919
+ }
4920
+ if (example.comment) {
4921
+ parts.push(`# ${example.comment}`);
4922
+ }
4923
+ parts.push("Input:");
4924
+ if (format === "json" || format === "auto") {
4925
+ parts.push(JSON.stringify(example.params, null, 2));
4926
+ } else if (format === "toml") {
4927
+ parts.push(formatParamsAsToml(example.params));
4928
+ } else {
4929
+ parts.push(formatParamsAsYaml(example.params));
4930
+ }
4931
+ if (example.output !== void 0) {
4932
+ parts.push("Output:");
4933
+ parts.push(example.output);
4934
+ }
4935
+ });
4936
+ }
4564
4937
  return parts.join("\n");
4565
4938
  }
4566
4939
  };
@@ -4572,6 +4945,7 @@ function createGadget(config) {
4572
4945
  description = config.description;
4573
4946
  parameterSchema = config.schema;
4574
4947
  timeoutMs = config.timeoutMs;
4948
+ examples = config.examples;
4575
4949
  execute(params) {
4576
4950
  return config.execute(params);
4577
4951
  }
@@ -4585,8 +4959,20 @@ var askUser = createGadget({
4585
4959
  name: "AskUser",
4586
4960
  description: "Ask the user a question when you need more information or clarification. The user's response will be provided back to you.",
4587
4961
  schema: import_zod.z.object({
4588
- question: import_zod.z.string().describe("The question to ask the user")
4962
+ question: import_zod.z.string().describe("The question to ask the user in plain-text or Markdown")
4589
4963
  }),
4964
+ examples: [
4965
+ {
4966
+ comment: "Ask for clarification about the task",
4967
+ params: { question: "Which file would you like me to modify?" }
4968
+ },
4969
+ {
4970
+ comment: "Ask user to choose between options",
4971
+ params: {
4972
+ question: "I found multiple matches. Which one should I use?\n- src/utils/helper.ts\n- src/lib/helper.ts"
4973
+ }
4974
+ }
4975
+ ],
4590
4976
  execute: ({ question }) => {
4591
4977
  throw new HumanInputException(question);
4592
4978
  }
@@ -4595,10 +4981,28 @@ var tellUser = createGadget({
4595
4981
  name: "TellUser",
4596
4982
  description: "Tell the user something important. Set done=true when your work is complete and you want to end the conversation.",
4597
4983
  schema: import_zod.z.object({
4598
- message: import_zod.z.string().describe("The message to display to the user"),
4599
- done: import_zod.z.boolean().describe("Set to true to end the conversation, false to continue"),
4984
+ message: import_zod.z.string().describe("The message to display to the user in Markdown"),
4985
+ done: import_zod.z.boolean().default(false).describe("Set to true to end the conversation, false to continue"),
4600
4986
  type: import_zod.z.enum(["info", "success", "warning", "error"]).default("info").describe("Message type: info, success, warning, or error")
4601
4987
  }),
4988
+ examples: [
4989
+ {
4990
+ comment: "Report successful completion and end the conversation",
4991
+ params: {
4992
+ message: "I've completed the refactoring. All tests pass.",
4993
+ done: true,
4994
+ type: "success"
4995
+ }
4996
+ },
4997
+ {
4998
+ comment: "Warn the user about something without ending",
4999
+ params: {
5000
+ message: "Found 3 files with potential issues. Continuing analysis...",
5001
+ done: false,
5002
+ type: "warning"
5003
+ }
5004
+ }
5005
+ ],
4602
5006
  execute: ({ message, done, type }) => {
4603
5007
  const prefixes = {
4604
5008
  info: "\u2139\uFE0F ",
@@ -4711,6 +5115,9 @@ async function loadGadgets(specifiers, cwd, importer = (specifier) => import(spe
4711
5115
  return gadgets;
4712
5116
  }
4713
5117
 
5118
+ // src/cli/option-helpers.ts
5119
+ var import_commander2 = require("commander");
5120
+
4714
5121
  // src/cli/utils.ts
4715
5122
  var import_chalk2 = __toESM(require("chalk"), 1);
4716
5123
  var import_commander = require("commander");
@@ -4718,6 +5125,44 @@ init_constants2();
4718
5125
 
4719
5126
  // src/cli/ui/formatters.ts
4720
5127
  var import_chalk = __toESM(require("chalk"), 1);
5128
+ var import_marked = require("marked");
5129
+ var import_marked_terminal = require("marked-terminal");
5130
+ var markedConfigured = false;
5131
+ function ensureMarkedConfigured() {
5132
+ if (!markedConfigured) {
5133
+ import_chalk.default.level = process.env.NO_COLOR ? 0 : 3;
5134
+ import_marked.marked.use(
5135
+ (0, import_marked_terminal.markedTerminal)({
5136
+ // Text styling
5137
+ strong: import_chalk.default.bold,
5138
+ em: import_chalk.default.italic,
5139
+ del: import_chalk.default.dim.gray.strikethrough,
5140
+ // Code styling
5141
+ code: import_chalk.default.yellow,
5142
+ codespan: import_chalk.default.yellow,
5143
+ // Headings
5144
+ heading: import_chalk.default.green.bold,
5145
+ firstHeading: import_chalk.default.magenta.underline.bold,
5146
+ // Links
5147
+ link: import_chalk.default.blue,
5148
+ href: import_chalk.default.blue.underline,
5149
+ // Block elements
5150
+ blockquote: import_chalk.default.gray.italic,
5151
+ // List formatting - reduce indentation and add bullet styling
5152
+ tab: 2,
5153
+ // Reduce from default 4 to 2 spaces
5154
+ listitem: import_chalk.default.reset
5155
+ // Keep items readable (no dim)
5156
+ })
5157
+ );
5158
+ markedConfigured = true;
5159
+ }
5160
+ }
5161
+ function renderMarkdown(text) {
5162
+ ensureMarkedConfigured();
5163
+ const rendered = import_marked.marked.parse(text);
5164
+ return rendered.trimEnd();
5165
+ }
4721
5166
  function formatTokens(tokens) {
4722
5167
  return tokens >= 1e3 ? `${(tokens / 1e3).toFixed(1)}k` : `${tokens}`;
4723
5168
  }
@@ -4736,7 +5181,14 @@ function formatCost(cost) {
4736
5181
  function renderSummary(metadata) {
4737
5182
  const parts = [];
4738
5183
  if (metadata.iterations !== void 0) {
4739
- parts.push(import_chalk.default.cyan(`#${metadata.iterations}`));
5184
+ const iterPart = import_chalk.default.cyan(`#${metadata.iterations}`);
5185
+ if (metadata.model) {
5186
+ parts.push(`${iterPart} ${import_chalk.default.magenta(metadata.model)}`);
5187
+ } else {
5188
+ parts.push(iterPart);
5189
+ }
5190
+ } else if (metadata.model) {
5191
+ parts.push(import_chalk.default.magenta(metadata.model));
4740
5192
  }
4741
5193
  if (metadata.usage) {
4742
5194
  const { inputTokens, outputTokens } = metadata.usage;
@@ -4757,22 +5209,128 @@ function renderSummary(metadata) {
4757
5209
  }
4758
5210
  return parts.join(import_chalk.default.dim(" | "));
4759
5211
  }
5212
+ function renderOverallSummary(metadata) {
5213
+ const parts = [];
5214
+ if (metadata.totalTokens !== void 0 && metadata.totalTokens > 0) {
5215
+ parts.push(import_chalk.default.dim("total:") + import_chalk.default.magenta(` ${formatTokens(metadata.totalTokens)}`));
5216
+ }
5217
+ if (metadata.iterations !== void 0 && metadata.iterations > 0) {
5218
+ parts.push(import_chalk.default.cyan(`#${metadata.iterations}`));
5219
+ }
5220
+ if (metadata.elapsedSeconds !== void 0 && metadata.elapsedSeconds > 0) {
5221
+ parts.push(import_chalk.default.dim(`${metadata.elapsedSeconds}s`));
5222
+ }
5223
+ if (metadata.cost !== void 0 && metadata.cost > 0) {
5224
+ parts.push(import_chalk.default.cyan(`$${formatCost(metadata.cost)}`));
5225
+ }
5226
+ if (parts.length === 0) {
5227
+ return null;
5228
+ }
5229
+ return parts.join(import_chalk.default.dim(" | "));
5230
+ }
5231
+ function formatParametersInline(params) {
5232
+ if (!params || Object.keys(params).length === 0) {
5233
+ return "";
5234
+ }
5235
+ return Object.entries(params).map(([key, value]) => {
5236
+ let formatted;
5237
+ if (typeof value === "string") {
5238
+ formatted = value.length > 30 ? `${value.slice(0, 30)}\u2026` : value;
5239
+ } else if (typeof value === "boolean" || typeof value === "number") {
5240
+ formatted = String(value);
5241
+ } else {
5242
+ const json = JSON.stringify(value);
5243
+ formatted = json.length > 30 ? `${json.slice(0, 30)}\u2026` : json;
5244
+ }
5245
+ return `${import_chalk.default.dim(key)}${import_chalk.default.dim("=")}${import_chalk.default.cyan(formatted)}`;
5246
+ }).join(import_chalk.default.dim(", "));
5247
+ }
5248
+ function formatBytes(bytes) {
5249
+ if (bytes < 1024) {
5250
+ return `${bytes} bytes`;
5251
+ }
5252
+ if (bytes < 1024 * 1024) {
5253
+ return `${(bytes / 1024).toFixed(1)} KB`;
5254
+ }
5255
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
5256
+ }
4760
5257
  function formatGadgetSummary(result) {
4761
5258
  const gadgetLabel = import_chalk.default.magenta.bold(result.gadgetName);
4762
5259
  const timeLabel = import_chalk.default.dim(`${Math.round(result.executionTimeMs)}ms`);
5260
+ const paramsStr = formatParametersInline(result.parameters);
5261
+ const paramsLabel = paramsStr ? `${import_chalk.default.dim("(")}${paramsStr}${import_chalk.default.dim(")")}` : "";
4763
5262
  if (result.error) {
4764
- return `${import_chalk.default.red("\u2717")} ${gadgetLabel} ${import_chalk.default.red("error:")} ${result.error} ${timeLabel}`;
5263
+ const errorMsg = result.error.length > 50 ? `${result.error.slice(0, 50)}\u2026` : result.error;
5264
+ return `${import_chalk.default.red("\u2717")} ${gadgetLabel}${paramsLabel} ${import_chalk.default.red("error:")} ${errorMsg} ${timeLabel}`;
5265
+ }
5266
+ let outputLabel;
5267
+ if (result.tokenCount !== void 0 && result.tokenCount > 0) {
5268
+ outputLabel = import_chalk.default.green(`${formatTokens(result.tokenCount)} tokens`);
5269
+ } else if (result.result) {
5270
+ const outputBytes = Buffer.byteLength(result.result, "utf-8");
5271
+ outputLabel = outputBytes > 0 ? import_chalk.default.green(formatBytes(outputBytes)) : import_chalk.default.dim("no output");
5272
+ } else {
5273
+ outputLabel = import_chalk.default.dim("no output");
4765
5274
  }
4766
- if (result.breaksLoop) {
4767
- return `${import_chalk.default.yellow("\u23F9")} ${gadgetLabel} ${import_chalk.default.yellow("finished:")} ${result.result} ${timeLabel}`;
5275
+ const icon = result.breaksLoop ? import_chalk.default.yellow("\u23F9") : import_chalk.default.green("\u2713");
5276
+ const summaryLine = `${icon} ${gadgetLabel}${paramsLabel} ${import_chalk.default.dim("\u2192")} ${outputLabel} ${timeLabel}`;
5277
+ if (result.gadgetName === "TellUser" && result.parameters?.message) {
5278
+ const message = String(result.parameters.message);
5279
+ const rendered = renderMarkdown(message);
5280
+ return `${summaryLine}
5281
+ ${rendered}`;
4768
5282
  }
4769
- const maxLen = 80;
4770
- const shouldTruncate = result.gadgetName !== "TellUser";
4771
- const resultText = result.result ? shouldTruncate && result.result.length > maxLen ? `${result.result.slice(0, maxLen)}...` : result.result : "";
4772
- return `${import_chalk.default.green("\u2713")} ${gadgetLabel} ${import_chalk.default.dim("\u2192")} ${resultText} ${timeLabel}`;
5283
+ return summaryLine;
4773
5284
  }
4774
5285
 
4775
5286
  // src/cli/utils.ts
5287
+ var RARE_EMOJI = [
5288
+ "\u{1F531}",
5289
+ "\u2697\uFE0F",
5290
+ "\u{1F9FF}",
5291
+ "\u{1F530}",
5292
+ "\u269B\uFE0F",
5293
+ "\u{1F3FA}",
5294
+ "\u{1F9EB}",
5295
+ "\u{1F52C}",
5296
+ "\u2695\uFE0F",
5297
+ "\u{1F5DD}\uFE0F",
5298
+ "\u2696\uFE0F",
5299
+ "\u{1F52E}",
5300
+ "\u{1FAAC}",
5301
+ "\u{1F9EC}",
5302
+ "\u2699\uFE0F",
5303
+ "\u{1F529}",
5304
+ "\u{1FA9B}",
5305
+ "\u26CF\uFE0F",
5306
+ "\u{1FA83}",
5307
+ "\u{1F3F9}",
5308
+ "\u{1F6E1}\uFE0F",
5309
+ "\u2694\uFE0F",
5310
+ "\u{1F5E1}\uFE0F",
5311
+ "\u{1FA93}",
5312
+ "\u{1F5C3}\uFE0F",
5313
+ "\u{1F4DC}",
5314
+ "\u{1F4EF}",
5315
+ "\u{1F3B4}",
5316
+ "\u{1F004}",
5317
+ "\u{1F3B2}"
5318
+ ];
5319
+ function generateMarkers() {
5320
+ const pick = (count) => {
5321
+ const result = [];
5322
+ const pool = [...RARE_EMOJI];
5323
+ for (let i = 0; i < count && pool.length > 0; i++) {
5324
+ const idx = Math.floor(Math.random() * pool.length);
5325
+ result.push(pool.splice(idx, 1)[0]);
5326
+ }
5327
+ return result.join("");
5328
+ };
5329
+ return {
5330
+ startPrefix: pick(5),
5331
+ endPrefix: pick(5)
5332
+ };
5333
+ }
4776
5334
  function createNumericParser({
4777
5335
  label,
4778
5336
  integer = false,
@@ -4939,6 +5497,13 @@ var StreamProgress = class {
4939
5497
  if (this.totalStartTime === 0) return 0;
4940
5498
  return Number(((Date.now() - this.totalStartTime) / 1e3).toFixed(1));
4941
5499
  }
5500
+ /**
5501
+ * Get elapsed time in seconds for the current call.
5502
+ * @returns Elapsed time in seconds with 1 decimal place
5503
+ */
5504
+ getCallElapsedSeconds() {
5505
+ return Number(((Date.now() - this.callStartTime) / 1e3).toFixed(1));
5506
+ }
4942
5507
  /**
4943
5508
  * Starts the progress indicator animation after a brief delay.
4944
5509
  */
@@ -4973,7 +5538,12 @@ var StreamProgress = class {
4973
5538
  const elapsed = ((Date.now() - this.callStartTime) / 1e3).toFixed(1);
4974
5539
  const outTokens = this.callOutputTokensEstimated ? Math.round(this.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callOutputTokens;
4975
5540
  const parts = [];
4976
- parts.push(import_chalk2.default.cyan(`#${this.currentIteration}`));
5541
+ const iterPart = import_chalk2.default.cyan(`#${this.currentIteration}`);
5542
+ if (this.model) {
5543
+ parts.push(`${iterPart} ${import_chalk2.default.magenta(this.model)}`);
5544
+ } else {
5545
+ parts.push(iterPart);
5546
+ }
4977
5547
  if (this.callInputTokens > 0) {
4978
5548
  const prefix = this.callInputTokensEstimated ? "~" : "";
4979
5549
  parts.push(import_chalk2.default.dim("\u2191") + import_chalk2.default.yellow(` ${prefix}${formatTokens(this.callInputTokens)}`));
@@ -4986,7 +5556,7 @@ var StreamProgress = class {
4986
5556
  if (this.totalCost > 0) {
4987
5557
  parts.push(import_chalk2.default.cyan(`$${formatCost(this.totalCost)}`));
4988
5558
  }
4989
- this.target.write(`\r${import_chalk2.default.cyan(spinner)} ${parts.join(import_chalk2.default.dim(" | "))}`);
5559
+ this.target.write(`\r${parts.join(import_chalk2.default.dim(" | "))} ${import_chalk2.default.cyan(spinner)}`);
4990
5560
  }
4991
5561
  renderCumulativeMode(spinner) {
4992
5562
  const elapsed = ((Date.now() - this.totalStartTime) / 1e3).toFixed(1);
@@ -5004,7 +5574,7 @@ var StreamProgress = class {
5004
5574
  parts.push(import_chalk2.default.dim("cost:") + import_chalk2.default.cyan(` $${formatCost(this.totalCost)}`));
5005
5575
  }
5006
5576
  parts.push(import_chalk2.default.dim(`${elapsed}s`));
5007
- this.target.write(`\r${import_chalk2.default.cyan(spinner)} ${parts.join(import_chalk2.default.dim(" | "))}`);
5577
+ this.target.write(`\r${parts.join(import_chalk2.default.dim(" | "))} ${import_chalk2.default.cyan(spinner)}`);
5008
5578
  }
5009
5579
  /**
5010
5580
  * Pauses the progress indicator and clears the line.
@@ -5114,15 +5684,91 @@ async function executeAction(action, env) {
5114
5684
  }
5115
5685
  }
5116
5686
 
5117
- // src/cli/agent-command.ts
5118
- var PARAMETER_FORMAT_VALUES = ["json", "yaml", "auto"];
5687
+ // src/cli/option-helpers.ts
5688
+ var PARAMETER_FORMAT_VALUES = ["json", "yaml", "toml", "auto"];
5119
5689
  function parseParameterFormat(value) {
5120
5690
  const normalized = value.toLowerCase();
5121
5691
  if (!PARAMETER_FORMAT_VALUES.includes(normalized)) {
5122
- throw new import_commander2.InvalidArgumentError("Parameter format must be one of 'json', 'yaml', or 'auto'.");
5692
+ throw new import_commander2.InvalidArgumentError(
5693
+ `Parameter format must be one of: ${PARAMETER_FORMAT_VALUES.join(", ")}`
5694
+ );
5123
5695
  }
5124
5696
  return normalized;
5125
5697
  }
5698
+ function addCompleteOptions(cmd, defaults) {
5699
+ return cmd.option(OPTION_FLAGS.model, OPTION_DESCRIPTIONS.model, defaults?.model ?? DEFAULT_MODEL).option(OPTION_FLAGS.systemPrompt, OPTION_DESCRIPTIONS.systemPrompt, defaults?.system).option(
5700
+ OPTION_FLAGS.temperature,
5701
+ OPTION_DESCRIPTIONS.temperature,
5702
+ createNumericParser({ label: "Temperature", min: 0, max: 2 }),
5703
+ defaults?.temperature
5704
+ ).option(
5705
+ OPTION_FLAGS.maxTokens,
5706
+ OPTION_DESCRIPTIONS.maxTokens,
5707
+ createNumericParser({ label: "Max tokens", integer: true, min: 1 }),
5708
+ defaults?.["max-tokens"]
5709
+ );
5710
+ }
5711
+ function addAgentOptions(cmd, defaults) {
5712
+ const gadgetAccumulator = (value, previous = []) => [
5713
+ ...previous,
5714
+ value
5715
+ ];
5716
+ const defaultGadgets = defaults?.gadget ?? [];
5717
+ return cmd.option(OPTION_FLAGS.model, OPTION_DESCRIPTIONS.model, defaults?.model ?? DEFAULT_MODEL).option(OPTION_FLAGS.systemPrompt, OPTION_DESCRIPTIONS.systemPrompt, defaults?.system).option(
5718
+ OPTION_FLAGS.temperature,
5719
+ OPTION_DESCRIPTIONS.temperature,
5720
+ createNumericParser({ label: "Temperature", min: 0, max: 2 }),
5721
+ defaults?.temperature
5722
+ ).option(
5723
+ OPTION_FLAGS.maxIterations,
5724
+ OPTION_DESCRIPTIONS.maxIterations,
5725
+ createNumericParser({ label: "Max iterations", integer: true, min: 1 }),
5726
+ defaults?.["max-iterations"]
5727
+ ).option(OPTION_FLAGS.gadgetModule, OPTION_DESCRIPTIONS.gadgetModule, gadgetAccumulator, [
5728
+ ...defaultGadgets
5729
+ ]).option(
5730
+ OPTION_FLAGS.parameterFormat,
5731
+ OPTION_DESCRIPTIONS.parameterFormat,
5732
+ parseParameterFormat,
5733
+ defaults?.["parameter-format"] ?? DEFAULT_PARAMETER_FORMAT
5734
+ ).option(OPTION_FLAGS.noBuiltins, OPTION_DESCRIPTIONS.noBuiltins, defaults?.builtins !== false).option(
5735
+ OPTION_FLAGS.noBuiltinInteraction,
5736
+ OPTION_DESCRIPTIONS.noBuiltinInteraction,
5737
+ defaults?.["builtin-interaction"] !== false
5738
+ );
5739
+ }
5740
+ function configToCompleteOptions(config) {
5741
+ const result = {};
5742
+ if (config.model !== void 0) result.model = config.model;
5743
+ if (config.system !== void 0) result.system = config.system;
5744
+ if (config.temperature !== void 0) result.temperature = config.temperature;
5745
+ if (config["max-tokens"] !== void 0) result.maxTokens = config["max-tokens"];
5746
+ return result;
5747
+ }
5748
+ function configToAgentOptions(config) {
5749
+ const result = {};
5750
+ if (config.model !== void 0) result.model = config.model;
5751
+ if (config.system !== void 0) result.system = config.system;
5752
+ if (config.temperature !== void 0) result.temperature = config.temperature;
5753
+ if (config["max-iterations"] !== void 0) result.maxIterations = config["max-iterations"];
5754
+ if (config.gadget !== void 0) result.gadget = config.gadget;
5755
+ if (config["parameter-format"] !== void 0) result.parameterFormat = config["parameter-format"];
5756
+ if (config.builtins !== void 0) result.builtins = config.builtins;
5757
+ if (config["builtin-interaction"] !== void 0)
5758
+ result.builtinInteraction = config["builtin-interaction"];
5759
+ return result;
5760
+ }
5761
+
5762
+ // src/cli/agent-command.ts
5763
+ async function promptApproval(env, prompt) {
5764
+ const rl = (0, import_promises.createInterface)({ input: env.stdin, output: env.stderr });
5765
+ try {
5766
+ const answer = await rl.question(prompt);
5767
+ return answer.toLowerCase().startsWith("y");
5768
+ } finally {
5769
+ rl.close();
5770
+ }
5771
+ }
5126
5772
  function createHumanInputHandler(env, progress) {
5127
5773
  const stdout = env.stdout;
5128
5774
  if (!isInteractive(env.stdin) || typeof stdout.isTTY !== "boolean" || !stdout.isTTY) {
@@ -5133,7 +5779,7 @@ function createHumanInputHandler(env, progress) {
5133
5779
  const rl = (0, import_promises.createInterface)({ input: env.stdin, output: env.stdout });
5134
5780
  try {
5135
5781
  const questionLine = question.trim() ? `
5136
- ${question.trim()}` : "";
5782
+ ${renderMarkdown(question.trim())}` : "";
5137
5783
  let isFirst = true;
5138
5784
  while (true) {
5139
5785
  const statsPrompt = progress.formatPrompt();
@@ -5151,7 +5797,7 @@ ${statsPrompt}` : statsPrompt;
5151
5797
  }
5152
5798
  };
5153
5799
  }
5154
- async function handleAgentCommand(promptArg, options, env) {
5800
+ async function executeAgent(promptArg, options, env) {
5155
5801
  const prompt = await resolvePrompt(promptArg, env);
5156
5802
  const client = env.createClient();
5157
5803
  const registry = new GadgetRegistry();
@@ -5173,7 +5819,6 @@ async function handleAgentCommand(promptArg, options, env) {
5173
5819
  const printer = new StreamPrinter(env.stdout);
5174
5820
  const stderrTTY = env.stderr.isTTY === true;
5175
5821
  const progress = new StreamProgress(env.stderr, stderrTTY, client.modelRegistry);
5176
- let finishReason;
5177
5822
  let usage;
5178
5823
  let iterations = 0;
5179
5824
  const countMessagesTokens = async (model, messages) => {
@@ -5184,6 +5829,15 @@ async function handleAgentCommand(promptArg, options, env) {
5184
5829
  return Math.round(totalChars / FALLBACK_CHARS_PER_TOKEN);
5185
5830
  }
5186
5831
  };
5832
+ const countGadgetOutputTokens = async (output) => {
5833
+ if (!output) return void 0;
5834
+ try {
5835
+ const messages = [{ role: "assistant", content: output }];
5836
+ return await client.countTokens(options.model, messages);
5837
+ } catch {
5838
+ return void 0;
5839
+ }
5840
+ };
5187
5841
  const builder = new AgentBuilder(client).withModel(options.model).withLogger(env.createLogger("llmist:cli:agent")).withHooks({
5188
5842
  observers: {
5189
5843
  // onLLMCallStart: Start progress indicator for each LLM call
@@ -5212,7 +5866,6 @@ async function handleAgentCommand(promptArg, options, env) {
5212
5866
  // onLLMCallComplete: Finalize metrics after each LLM call
5213
5867
  // This is where you'd typically log metrics or update dashboards
5214
5868
  onLLMCallComplete: async (context) => {
5215
- finishReason = context.finishReason;
5216
5869
  usage = context.usage;
5217
5870
  iterations = Math.max(iterations, context.iteration + 1);
5218
5871
  if (context.usage) {
@@ -5223,7 +5876,76 @@ async function handleAgentCommand(promptArg, options, env) {
5223
5876
  progress.setOutputTokens(context.usage.outputTokens, false);
5224
5877
  }
5225
5878
  }
5879
+ let callCost;
5880
+ if (context.usage && client.modelRegistry) {
5881
+ try {
5882
+ const modelName = options.model.includes(":") ? options.model.split(":")[1] : options.model;
5883
+ const costResult = client.modelRegistry.estimateCost(
5884
+ modelName,
5885
+ context.usage.inputTokens,
5886
+ context.usage.outputTokens
5887
+ );
5888
+ if (costResult) callCost = costResult.totalCost;
5889
+ } catch {
5890
+ }
5891
+ }
5892
+ const callElapsed = progress.getCallElapsedSeconds();
5226
5893
  progress.endCall(context.usage);
5894
+ if (stderrTTY) {
5895
+ const summary = renderSummary({
5896
+ iterations: context.iteration + 1,
5897
+ model: options.model,
5898
+ usage: context.usage,
5899
+ elapsedSeconds: callElapsed,
5900
+ cost: callCost,
5901
+ finishReason: context.finishReason
5902
+ });
5903
+ if (summary) {
5904
+ env.stderr.write(`${summary}
5905
+ `);
5906
+ }
5907
+ }
5908
+ }
5909
+ },
5910
+ // SHOWCASE: Controller-based approval gating for dangerous gadgets
5911
+ //
5912
+ // This demonstrates how to add safety layers WITHOUT modifying gadgets.
5913
+ // The RunCommand gadget is simple - it just executes commands. The CLI
5914
+ // adds the approval flow externally via beforeGadgetExecution controller.
5915
+ //
5916
+ // This pattern is composable: you can apply the same gating logic to
5917
+ // any gadget (DeleteFile, SendEmail, etc.) without changing the gadgets.
5918
+ controllers: {
5919
+ beforeGadgetExecution: async (ctx) => {
5920
+ if (ctx.gadgetName !== "RunCommand") {
5921
+ return { action: "proceed" };
5922
+ }
5923
+ const stdinTTY = isInteractive(env.stdin);
5924
+ const stderrTTY2 = env.stderr.isTTY === true;
5925
+ if (!stdinTTY || !stderrTTY2) {
5926
+ return {
5927
+ action: "skip",
5928
+ syntheticResult: "status=denied\n\nRunCommand requires interactive approval. Run in a terminal to approve commands."
5929
+ };
5930
+ }
5931
+ const command = ctx.parameters.command;
5932
+ progress.pause();
5933
+ env.stderr.write(`
5934
+ ${import_chalk3.default.yellow("\u{1F512} Execute:")} ${command}
5935
+ `);
5936
+ const approved = await promptApproval(env, " Approve? [y/n] ");
5937
+ if (!approved) {
5938
+ env.stderr.write(` ${import_chalk3.default.red("\u2717 Denied")}
5939
+
5940
+ `);
5941
+ return {
5942
+ action: "skip",
5943
+ syntheticResult: "status=denied\n\nCommand denied by user. Ask what they'd like to do instead."
5944
+ };
5945
+ }
5946
+ env.stderr.write(` ${import_chalk3.default.green("\u2713 Approved")}
5947
+ `);
5948
+ return { action: "proceed" };
5227
5949
  }
5228
5950
  }
5229
5951
  });
@@ -5244,6 +5966,10 @@ async function handleAgentCommand(promptArg, options, env) {
5244
5966
  if (gadgets.length > 0) {
5245
5967
  builder.withGadgets(...gadgets);
5246
5968
  }
5969
+ builder.withParameterFormat(options.parameterFormat);
5970
+ const markers = generateMarkers();
5971
+ builder.withGadgetStartPrefix(markers.startPrefix);
5972
+ builder.withGadgetEndPrefix(markers.endPrefix);
5247
5973
  const agent = builder.ask(prompt);
5248
5974
  for await (const event of agent.run()) {
5249
5975
  if (event.type === "text") {
@@ -5252,20 +5978,22 @@ async function handleAgentCommand(promptArg, options, env) {
5252
5978
  } else if (event.type === "gadget_result") {
5253
5979
  progress.pause();
5254
5980
  if (stderrTTY) {
5255
- env.stderr.write(`${formatGadgetSummary(event.result)}
5981
+ const tokenCount = await countGadgetOutputTokens(event.result.result);
5982
+ env.stderr.write(`${formatGadgetSummary({ ...event.result, tokenCount })}
5256
5983
  `);
5257
5984
  }
5258
5985
  }
5259
5986
  }
5260
5987
  progress.complete();
5261
5988
  printer.ensureNewline();
5262
- if (stderrTTY) {
5263
- const summary = renderSummary({
5264
- finishReason,
5265
- usage,
5989
+ if (stderrTTY && iterations > 1) {
5990
+ env.stderr.write(`${import_chalk3.default.dim("\u2500".repeat(40))}
5991
+ `);
5992
+ const summary = renderOverallSummary({
5993
+ totalTokens: usage?.totalTokens,
5266
5994
  iterations,
5267
- cost: progress.getTotalCost(),
5268
- elapsedSeconds: progress.getTotalElapsedSeconds()
5995
+ elapsedSeconds: progress.getTotalElapsedSeconds(),
5996
+ cost: progress.getTotalCost()
5269
5997
  });
5270
5998
  if (summary) {
5271
5999
  env.stderr.write(`${summary}
@@ -5273,27 +6001,11 @@ async function handleAgentCommand(promptArg, options, env) {
5273
6001
  }
5274
6002
  }
5275
6003
  }
5276
- function registerAgentCommand(program, env) {
5277
- 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.").option(OPTION_FLAGS.model, OPTION_DESCRIPTIONS.model, DEFAULT_MODEL).option(OPTION_FLAGS.systemPrompt, OPTION_DESCRIPTIONS.systemPrompt).option(
5278
- OPTION_FLAGS.temperature,
5279
- OPTION_DESCRIPTIONS.temperature,
5280
- createNumericParser({ label: "Temperature", min: 0, max: 2 })
5281
- ).option(
5282
- OPTION_FLAGS.maxIterations,
5283
- OPTION_DESCRIPTIONS.maxIterations,
5284
- createNumericParser({ label: "Max iterations", integer: true, min: 1 })
5285
- ).option(
5286
- OPTION_FLAGS.gadgetModule,
5287
- OPTION_DESCRIPTIONS.gadgetModule,
5288
- (value, previous = []) => [...previous, value],
5289
- []
5290
- ).option(
5291
- OPTION_FLAGS.parameterFormat,
5292
- OPTION_DESCRIPTIONS.parameterFormat,
5293
- parseParameterFormat,
5294
- DEFAULT_PARAMETER_FORMAT
5295
- ).option(OPTION_FLAGS.noBuiltins, OPTION_DESCRIPTIONS.noBuiltins).option(OPTION_FLAGS.noBuiltinInteraction, OPTION_DESCRIPTIONS.noBuiltinInteraction).action(
5296
- (prompt, options) => executeAction(() => handleAgentCommand(prompt, options, env), env)
6004
+ function registerAgentCommand(program, env, config) {
6005
+ 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.");
6006
+ addAgentOptions(cmd, config);
6007
+ cmd.action(
6008
+ (prompt, options) => executeAction(() => executeAgent(prompt, options, env), env)
5297
6009
  );
5298
6010
  }
5299
6011
 
@@ -5301,7 +6013,7 @@ function registerAgentCommand(program, env) {
5301
6013
  init_messages();
5302
6014
  init_model_shortcuts();
5303
6015
  init_constants2();
5304
- async function handleCompleteCommand(promptArg, options, env) {
6016
+ async function executeComplete(promptArg, options, env) {
5305
6017
  const prompt = await resolvePrompt(promptArg, env);
5306
6018
  const client = env.createClient();
5307
6019
  const model = resolveModel(options.model);
@@ -5355,25 +6067,307 @@ async function handleCompleteCommand(promptArg, options, env) {
5355
6067
  }
5356
6068
  }
5357
6069
  }
5358
- function registerCompleteCommand(program, env) {
5359
- 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.").option(OPTION_FLAGS.model, OPTION_DESCRIPTIONS.model, DEFAULT_MODEL).option(OPTION_FLAGS.systemPrompt, OPTION_DESCRIPTIONS.systemPrompt).option(
5360
- OPTION_FLAGS.temperature,
5361
- OPTION_DESCRIPTIONS.temperature,
5362
- createNumericParser({ label: "Temperature", min: 0, max: 2 })
5363
- ).option(
5364
- OPTION_FLAGS.maxTokens,
5365
- OPTION_DESCRIPTIONS.maxTokens,
5366
- createNumericParser({ label: "Max tokens", integer: true, min: 1 })
5367
- ).action(
5368
- (prompt, options) => executeAction(
5369
- () => handleCompleteCommand(prompt, options, env),
5370
- env
5371
- )
6070
+ function registerCompleteCommand(program, env, config) {
6071
+ 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.");
6072
+ addCompleteOptions(cmd, config);
6073
+ cmd.action(
6074
+ (prompt, options) => executeAction(() => executeComplete(prompt, options, env), env)
5372
6075
  );
5373
6076
  }
5374
6077
 
6078
+ // src/cli/config.ts
6079
+ var import_node_fs3 = require("fs");
6080
+ var import_node_os = require("os");
6081
+ var import_node_path3 = require("path");
6082
+ var import_js_toml2 = require("js-toml");
6083
+ var GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set(["log-level", "log-file"]);
6084
+ var VALID_LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
6085
+ var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set(["model", "system", "temperature", "max-tokens"]);
6086
+ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
6087
+ "model",
6088
+ "system",
6089
+ "temperature",
6090
+ "max-iterations",
6091
+ "gadget",
6092
+ "parameter-format",
6093
+ "builtins",
6094
+ "builtin-interaction"
6095
+ ]);
6096
+ var CUSTOM_CONFIG_KEYS = /* @__PURE__ */ new Set([
6097
+ ...COMPLETE_CONFIG_KEYS,
6098
+ ...AGENT_CONFIG_KEYS,
6099
+ "type",
6100
+ "description"
6101
+ ]);
6102
+ var VALID_PARAMETER_FORMATS = ["json", "yaml", "toml", "auto"];
6103
+ function getConfigPath() {
6104
+ return (0, import_node_path3.join)((0, import_node_os.homedir)(), ".llmist", "cli.toml");
6105
+ }
6106
+ var ConfigError = class extends Error {
6107
+ constructor(message, path2) {
6108
+ super(path2 ? `${path2}: ${message}` : message);
6109
+ this.path = path2;
6110
+ this.name = "ConfigError";
6111
+ }
6112
+ };
6113
+ function validateString(value, key, section) {
6114
+ if (typeof value !== "string") {
6115
+ throw new ConfigError(`[${section}].${key} must be a string`);
6116
+ }
6117
+ return value;
6118
+ }
6119
+ function validateNumber(value, key, section, opts) {
6120
+ if (typeof value !== "number") {
6121
+ throw new ConfigError(`[${section}].${key} must be a number`);
6122
+ }
6123
+ if (opts?.integer && !Number.isInteger(value)) {
6124
+ throw new ConfigError(`[${section}].${key} must be an integer`);
6125
+ }
6126
+ if (opts?.min !== void 0 && value < opts.min) {
6127
+ throw new ConfigError(`[${section}].${key} must be >= ${opts.min}`);
6128
+ }
6129
+ if (opts?.max !== void 0 && value > opts.max) {
6130
+ throw new ConfigError(`[${section}].${key} must be <= ${opts.max}`);
6131
+ }
6132
+ return value;
6133
+ }
6134
+ function validateBoolean(value, key, section) {
6135
+ if (typeof value !== "boolean") {
6136
+ throw new ConfigError(`[${section}].${key} must be a boolean`);
6137
+ }
6138
+ return value;
6139
+ }
6140
+ function validateStringArray(value, key, section) {
6141
+ if (!Array.isArray(value)) {
6142
+ throw new ConfigError(`[${section}].${key} must be an array`);
6143
+ }
6144
+ for (let i = 0; i < value.length; i++) {
6145
+ if (typeof value[i] !== "string") {
6146
+ throw new ConfigError(`[${section}].${key}[${i}] must be a string`);
6147
+ }
6148
+ }
6149
+ return value;
6150
+ }
6151
+ function validateBaseConfig(raw, section) {
6152
+ const result = {};
6153
+ if ("model" in raw) {
6154
+ result.model = validateString(raw.model, "model", section);
6155
+ }
6156
+ if ("system" in raw) {
6157
+ result.system = validateString(raw.system, "system", section);
6158
+ }
6159
+ if ("temperature" in raw) {
6160
+ result.temperature = validateNumber(raw.temperature, "temperature", section, {
6161
+ min: 0,
6162
+ max: 2
6163
+ });
6164
+ }
6165
+ return result;
6166
+ }
6167
+ function validateGlobalConfig(raw, section) {
6168
+ if (typeof raw !== "object" || raw === null) {
6169
+ throw new ConfigError(`[${section}] must be a table`);
6170
+ }
6171
+ const rawObj = raw;
6172
+ for (const key of Object.keys(rawObj)) {
6173
+ if (!GLOBAL_CONFIG_KEYS.has(key)) {
6174
+ throw new ConfigError(`[${section}].${key} is not a valid option`);
6175
+ }
6176
+ }
6177
+ const result = {};
6178
+ if ("log-level" in rawObj) {
6179
+ const level = validateString(rawObj["log-level"], "log-level", section);
6180
+ if (!VALID_LOG_LEVELS.includes(level)) {
6181
+ throw new ConfigError(
6182
+ `[${section}].log-level must be one of: ${VALID_LOG_LEVELS.join(", ")}`
6183
+ );
6184
+ }
6185
+ result["log-level"] = level;
6186
+ }
6187
+ if ("log-file" in rawObj) {
6188
+ result["log-file"] = validateString(rawObj["log-file"], "log-file", section);
6189
+ }
6190
+ return result;
6191
+ }
6192
+ function validateCompleteConfig(raw, section) {
6193
+ if (typeof raw !== "object" || raw === null) {
6194
+ throw new ConfigError(`[${section}] must be a table`);
6195
+ }
6196
+ const rawObj = raw;
6197
+ for (const key of Object.keys(rawObj)) {
6198
+ if (!COMPLETE_CONFIG_KEYS.has(key)) {
6199
+ throw new ConfigError(`[${section}].${key} is not a valid option`);
6200
+ }
6201
+ }
6202
+ const result = { ...validateBaseConfig(rawObj, section) };
6203
+ if ("max-tokens" in rawObj) {
6204
+ result["max-tokens"] = validateNumber(rawObj["max-tokens"], "max-tokens", section, {
6205
+ integer: true,
6206
+ min: 1
6207
+ });
6208
+ }
6209
+ return result;
6210
+ }
6211
+ function validateAgentConfig(raw, section) {
6212
+ if (typeof raw !== "object" || raw === null) {
6213
+ throw new ConfigError(`[${section}] must be a table`);
6214
+ }
6215
+ const rawObj = raw;
6216
+ for (const key of Object.keys(rawObj)) {
6217
+ if (!AGENT_CONFIG_KEYS.has(key)) {
6218
+ throw new ConfigError(`[${section}].${key} is not a valid option`);
6219
+ }
6220
+ }
6221
+ const result = { ...validateBaseConfig(rawObj, section) };
6222
+ if ("max-iterations" in rawObj) {
6223
+ result["max-iterations"] = validateNumber(rawObj["max-iterations"], "max-iterations", section, {
6224
+ integer: true,
6225
+ min: 1
6226
+ });
6227
+ }
6228
+ if ("gadget" in rawObj) {
6229
+ result.gadget = validateStringArray(rawObj.gadget, "gadget", section);
6230
+ }
6231
+ if ("parameter-format" in rawObj) {
6232
+ const format = validateString(rawObj["parameter-format"], "parameter-format", section);
6233
+ if (!VALID_PARAMETER_FORMATS.includes(format)) {
6234
+ throw new ConfigError(
6235
+ `[${section}].parameter-format must be one of: ${VALID_PARAMETER_FORMATS.join(", ")}`
6236
+ );
6237
+ }
6238
+ result["parameter-format"] = format;
6239
+ }
6240
+ if ("builtins" in rawObj) {
6241
+ result.builtins = validateBoolean(rawObj.builtins, "builtins", section);
6242
+ }
6243
+ if ("builtin-interaction" in rawObj) {
6244
+ result["builtin-interaction"] = validateBoolean(
6245
+ rawObj["builtin-interaction"],
6246
+ "builtin-interaction",
6247
+ section
6248
+ );
6249
+ }
6250
+ return result;
6251
+ }
6252
+ function validateCustomConfig(raw, section) {
6253
+ if (typeof raw !== "object" || raw === null) {
6254
+ throw new ConfigError(`[${section}] must be a table`);
6255
+ }
6256
+ const rawObj = raw;
6257
+ for (const key of Object.keys(rawObj)) {
6258
+ if (!CUSTOM_CONFIG_KEYS.has(key)) {
6259
+ throw new ConfigError(`[${section}].${key} is not a valid option`);
6260
+ }
6261
+ }
6262
+ let type = "agent";
6263
+ if ("type" in rawObj) {
6264
+ const typeValue = validateString(rawObj.type, "type", section);
6265
+ if (typeValue !== "agent" && typeValue !== "complete") {
6266
+ throw new ConfigError(`[${section}].type must be "agent" or "complete"`);
6267
+ }
6268
+ type = typeValue;
6269
+ }
6270
+ const result = {
6271
+ ...validateBaseConfig(rawObj, section),
6272
+ type
6273
+ };
6274
+ if ("description" in rawObj) {
6275
+ result.description = validateString(rawObj.description, "description", section);
6276
+ }
6277
+ if ("max-iterations" in rawObj) {
6278
+ result["max-iterations"] = validateNumber(rawObj["max-iterations"], "max-iterations", section, {
6279
+ integer: true,
6280
+ min: 1
6281
+ });
6282
+ }
6283
+ if ("gadget" in rawObj) {
6284
+ result.gadget = validateStringArray(rawObj.gadget, "gadget", section);
6285
+ }
6286
+ if ("parameter-format" in rawObj) {
6287
+ const format = validateString(rawObj["parameter-format"], "parameter-format", section);
6288
+ if (!VALID_PARAMETER_FORMATS.includes(format)) {
6289
+ throw new ConfigError(
6290
+ `[${section}].parameter-format must be one of: ${VALID_PARAMETER_FORMATS.join(", ")}`
6291
+ );
6292
+ }
6293
+ result["parameter-format"] = format;
6294
+ }
6295
+ if ("builtins" in rawObj) {
6296
+ result.builtins = validateBoolean(rawObj.builtins, "builtins", section);
6297
+ }
6298
+ if ("builtin-interaction" in rawObj) {
6299
+ result["builtin-interaction"] = validateBoolean(
6300
+ rawObj["builtin-interaction"],
6301
+ "builtin-interaction",
6302
+ section
6303
+ );
6304
+ }
6305
+ if ("max-tokens" in rawObj) {
6306
+ result["max-tokens"] = validateNumber(rawObj["max-tokens"], "max-tokens", section, {
6307
+ integer: true,
6308
+ min: 1
6309
+ });
6310
+ }
6311
+ return result;
6312
+ }
6313
+ function validateConfig(raw, configPath) {
6314
+ if (typeof raw !== "object" || raw === null) {
6315
+ throw new ConfigError("Config must be a TOML table", configPath);
6316
+ }
6317
+ const rawObj = raw;
6318
+ const result = {};
6319
+ for (const [key, value] of Object.entries(rawObj)) {
6320
+ try {
6321
+ if (key === "global") {
6322
+ result.global = validateGlobalConfig(value, key);
6323
+ } else if (key === "complete") {
6324
+ result.complete = validateCompleteConfig(value, key);
6325
+ } else if (key === "agent") {
6326
+ result.agent = validateAgentConfig(value, key);
6327
+ } else {
6328
+ result[key] = validateCustomConfig(value, key);
6329
+ }
6330
+ } catch (error) {
6331
+ if (error instanceof ConfigError) {
6332
+ throw new ConfigError(error.message, configPath);
6333
+ }
6334
+ throw error;
6335
+ }
6336
+ }
6337
+ return result;
6338
+ }
6339
+ function loadConfig() {
6340
+ const configPath = getConfigPath();
6341
+ if (!(0, import_node_fs3.existsSync)(configPath)) {
6342
+ return {};
6343
+ }
6344
+ let content;
6345
+ try {
6346
+ content = (0, import_node_fs3.readFileSync)(configPath, "utf-8");
6347
+ } catch (error) {
6348
+ throw new ConfigError(
6349
+ `Failed to read config file: ${error instanceof Error ? error.message : "Unknown error"}`,
6350
+ configPath
6351
+ );
6352
+ }
6353
+ let raw;
6354
+ try {
6355
+ raw = (0, import_js_toml2.load)(content);
6356
+ } catch (error) {
6357
+ throw new ConfigError(
6358
+ `Invalid TOML syntax: ${error instanceof Error ? error.message : "Unknown error"}`,
6359
+ configPath
6360
+ );
6361
+ }
6362
+ return validateConfig(raw, configPath);
6363
+ }
6364
+ function getCustomCommandNames(config) {
6365
+ const reserved = /* @__PURE__ */ new Set(["global", "complete", "agent"]);
6366
+ return Object.keys(config).filter((key) => !reserved.has(key));
6367
+ }
6368
+
5375
6369
  // src/cli/models-command.ts
5376
- var import_chalk3 = __toESM(require("chalk"), 1);
6370
+ var import_chalk4 = __toESM(require("chalk"), 1);
5377
6371
  init_model_shortcuts();
5378
6372
  async function handleModelsCommand(options, env) {
5379
6373
  const client = env.createClient();
@@ -5393,13 +6387,13 @@ function renderTable(models, verbose, stream2) {
5393
6387
  }
5394
6388
  grouped.get(provider).push(model);
5395
6389
  }
5396
- stream2.write(import_chalk3.default.bold.cyan("\nAvailable Models\n"));
5397
- stream2.write(import_chalk3.default.cyan("=".repeat(80)) + "\n\n");
6390
+ stream2.write(import_chalk4.default.bold.cyan("\nAvailable Models\n"));
6391
+ stream2.write(import_chalk4.default.cyan("=".repeat(80)) + "\n\n");
5398
6392
  const providers = Array.from(grouped.keys()).sort();
5399
6393
  for (const provider of providers) {
5400
6394
  const providerModels = grouped.get(provider);
5401
6395
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
5402
- stream2.write(import_chalk3.default.bold.yellow(`${providerName} Models
6396
+ stream2.write(import_chalk4.default.bold.yellow(`${providerName} Models
5403
6397
  `));
5404
6398
  if (verbose) {
5405
6399
  renderVerboseTable(providerModels, stream2);
@@ -5408,11 +6402,11 @@ function renderTable(models, verbose, stream2) {
5408
6402
  }
5409
6403
  stream2.write("\n");
5410
6404
  }
5411
- stream2.write(import_chalk3.default.bold.magenta("Model Shortcuts\n"));
5412
- stream2.write(import_chalk3.default.dim("\u2500".repeat(80)) + "\n");
6405
+ stream2.write(import_chalk4.default.bold.magenta("Model Shortcuts\n"));
6406
+ stream2.write(import_chalk4.default.dim("\u2500".repeat(80)) + "\n");
5413
6407
  const shortcuts = Object.entries(MODEL_ALIASES).sort((a, b) => a[0].localeCompare(b[0]));
5414
6408
  for (const [shortcut, fullName] of shortcuts) {
5415
- stream2.write(import_chalk3.default.cyan(` ${shortcut.padEnd(15)}`) + import_chalk3.default.dim(" \u2192 ") + import_chalk3.default.white(fullName) + "\n");
6409
+ stream2.write(import_chalk4.default.cyan(` ${shortcut.padEnd(15)}`) + import_chalk4.default.dim(" \u2192 ") + import_chalk4.default.white(fullName) + "\n");
5416
6410
  }
5417
6411
  stream2.write("\n");
5418
6412
  }
@@ -5422,45 +6416,45 @@ function renderCompactTable(models, stream2) {
5422
6416
  const contextWidth = 13;
5423
6417
  const inputWidth = 10;
5424
6418
  const outputWidth = 10;
5425
- stream2.write(import_chalk3.default.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
6419
+ stream2.write(import_chalk4.default.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
5426
6420
  stream2.write(
5427
- import_chalk3.default.bold(
6421
+ import_chalk4.default.bold(
5428
6422
  "Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Context".padEnd(contextWidth) + " " + "Input".padEnd(inputWidth) + " " + "Output".padEnd(outputWidth)
5429
6423
  ) + "\n"
5430
6424
  );
5431
- stream2.write(import_chalk3.default.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
6425
+ stream2.write(import_chalk4.default.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
5432
6426
  for (const model of models) {
5433
6427
  const contextFormatted = formatTokens2(model.contextWindow);
5434
6428
  const inputPrice = `$${model.pricing.input.toFixed(2)}`;
5435
6429
  const outputPrice = `$${model.pricing.output.toFixed(2)}`;
5436
6430
  stream2.write(
5437
- import_chalk3.default.green(model.modelId.padEnd(idWidth)) + " " + import_chalk3.default.white(model.displayName.padEnd(nameWidth)) + " " + import_chalk3.default.yellow(contextFormatted.padEnd(contextWidth)) + " " + import_chalk3.default.cyan(inputPrice.padEnd(inputWidth)) + " " + import_chalk3.default.cyan(outputPrice.padEnd(outputWidth)) + "\n"
6431
+ import_chalk4.default.green(model.modelId.padEnd(idWidth)) + " " + import_chalk4.default.white(model.displayName.padEnd(nameWidth)) + " " + import_chalk4.default.yellow(contextFormatted.padEnd(contextWidth)) + " " + import_chalk4.default.cyan(inputPrice.padEnd(inputWidth)) + " " + import_chalk4.default.cyan(outputPrice.padEnd(outputWidth)) + "\n"
5438
6432
  );
5439
6433
  }
5440
- stream2.write(import_chalk3.default.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
5441
- stream2.write(import_chalk3.default.dim(` * Prices are per 1M tokens
6434
+ stream2.write(import_chalk4.default.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
6435
+ stream2.write(import_chalk4.default.dim(` * Prices are per 1M tokens
5442
6436
  `));
5443
6437
  }
5444
6438
  function renderVerboseTable(models, stream2) {
5445
6439
  for (const model of models) {
5446
- stream2.write(import_chalk3.default.bold.green(`
6440
+ stream2.write(import_chalk4.default.bold.green(`
5447
6441
  ${model.modelId}
5448
6442
  `));
5449
- stream2.write(import_chalk3.default.dim(" " + "\u2500".repeat(60)) + "\n");
5450
- stream2.write(` ${import_chalk3.default.dim("Name:")} ${import_chalk3.default.white(model.displayName)}
6443
+ stream2.write(import_chalk4.default.dim(" " + "\u2500".repeat(60)) + "\n");
6444
+ stream2.write(` ${import_chalk4.default.dim("Name:")} ${import_chalk4.default.white(model.displayName)}
5451
6445
  `);
5452
- stream2.write(` ${import_chalk3.default.dim("Context:")} ${import_chalk3.default.yellow(formatTokens2(model.contextWindow))}
6446
+ stream2.write(` ${import_chalk4.default.dim("Context:")} ${import_chalk4.default.yellow(formatTokens2(model.contextWindow))}
5453
6447
  `);
5454
- stream2.write(` ${import_chalk3.default.dim("Max Output:")} ${import_chalk3.default.yellow(formatTokens2(model.maxOutputTokens))}
6448
+ stream2.write(` ${import_chalk4.default.dim("Max Output:")} ${import_chalk4.default.yellow(formatTokens2(model.maxOutputTokens))}
5455
6449
  `);
5456
- stream2.write(` ${import_chalk3.default.dim("Pricing:")} ${import_chalk3.default.cyan(`$${model.pricing.input.toFixed(2)} input`)} ${import_chalk3.default.dim("/")} ${import_chalk3.default.cyan(`$${model.pricing.output.toFixed(2)} output`)} ${import_chalk3.default.dim("(per 1M tokens)")}
6450
+ stream2.write(` ${import_chalk4.default.dim("Pricing:")} ${import_chalk4.default.cyan(`$${model.pricing.input.toFixed(2)} input`)} ${import_chalk4.default.dim("/")} ${import_chalk4.default.cyan(`$${model.pricing.output.toFixed(2)} output`)} ${import_chalk4.default.dim("(per 1M tokens)")}
5457
6451
  `);
5458
6452
  if (model.pricing.cachedInput !== void 0) {
5459
- stream2.write(` ${import_chalk3.default.dim("Cached Input:")} ${import_chalk3.default.cyan(`$${model.pricing.cachedInput.toFixed(2)} per 1M tokens`)}
6453
+ stream2.write(` ${import_chalk4.default.dim("Cached Input:")} ${import_chalk4.default.cyan(`$${model.pricing.cachedInput.toFixed(2)} per 1M tokens`)}
5460
6454
  `);
5461
6455
  }
5462
6456
  if (model.knowledgeCutoff) {
5463
- stream2.write(` ${import_chalk3.default.dim("Knowledge:")} ${model.knowledgeCutoff}
6457
+ stream2.write(` ${import_chalk4.default.dim("Knowledge:")} ${model.knowledgeCutoff}
5464
6458
  `);
5465
6459
  }
5466
6460
  const features = [];
@@ -5471,20 +6465,20 @@ function renderVerboseTable(models, stream2) {
5471
6465
  if (model.features.structuredOutputs) features.push("structured-outputs");
5472
6466
  if (model.features.fineTuning) features.push("fine-tuning");
5473
6467
  if (features.length > 0) {
5474
- stream2.write(` ${import_chalk3.default.dim("Features:")} ${import_chalk3.default.blue(features.join(", "))}
6468
+ stream2.write(` ${import_chalk4.default.dim("Features:")} ${import_chalk4.default.blue(features.join(", "))}
5475
6469
  `);
5476
6470
  }
5477
6471
  if (model.metadata) {
5478
6472
  if (model.metadata.family) {
5479
- stream2.write(` ${import_chalk3.default.dim("Family:")} ${model.metadata.family}
6473
+ stream2.write(` ${import_chalk4.default.dim("Family:")} ${model.metadata.family}
5480
6474
  `);
5481
6475
  }
5482
6476
  if (model.metadata.releaseDate) {
5483
- stream2.write(` ${import_chalk3.default.dim("Released:")} ${model.metadata.releaseDate}
6477
+ stream2.write(` ${import_chalk4.default.dim("Released:")} ${model.metadata.releaseDate}
5484
6478
  `);
5485
6479
  }
5486
6480
  if (model.metadata.notes) {
5487
- stream2.write(` ${import_chalk3.default.dim("Notes:")} ${import_chalk3.default.italic(model.metadata.notes)}
6481
+ stream2.write(` ${import_chalk4.default.dim("Notes:")} ${import_chalk4.default.italic(model.metadata.notes)}
5488
6482
  `);
5489
6483
  }
5490
6484
  }
@@ -5532,9 +6526,41 @@ function registerModelsCommand(program, env) {
5532
6526
  );
5533
6527
  }
5534
6528
 
6529
+ // src/cli/custom-command.ts
6530
+ function registerCustomCommand(program, name, config, env) {
6531
+ const type = config.type ?? "agent";
6532
+ const description = config.description ?? `Custom ${type} command`;
6533
+ const cmd = program.command(name).description(description).argument("[prompt]", "Prompt for the command. Falls back to stdin when available.");
6534
+ if (type === "complete") {
6535
+ addCompleteOptions(cmd, config);
6536
+ cmd.action(
6537
+ (prompt, cliOptions) => executeAction(async () => {
6538
+ const configDefaults = configToCompleteOptions(config);
6539
+ const options = {
6540
+ ...configDefaults,
6541
+ ...cliOptions
6542
+ };
6543
+ await executeComplete(prompt, options, env);
6544
+ }, env)
6545
+ );
6546
+ } else {
6547
+ addAgentOptions(cmd, config);
6548
+ cmd.action(
6549
+ (prompt, cliOptions) => executeAction(async () => {
6550
+ const configDefaults = configToAgentOptions(config);
6551
+ const options = {
6552
+ ...configDefaults,
6553
+ ...cliOptions
6554
+ };
6555
+ await executeAgent(prompt, options, env);
6556
+ }, env)
6557
+ );
6558
+ }
6559
+ }
6560
+
5535
6561
  // src/cli/environment.ts
5536
6562
  var import_node_readline = __toESM(require("readline"), 1);
5537
- var import_chalk4 = __toESM(require("chalk"), 1);
6563
+ var import_chalk5 = __toESM(require("chalk"), 1);
5538
6564
  init_client();
5539
6565
  init_logger();
5540
6566
  var LOG_LEVEL_MAP = {
@@ -5580,14 +6606,14 @@ function createPromptFunction(stdin, stdout) {
5580
6606
  output: stdout
5581
6607
  });
5582
6608
  stdout.write("\n");
5583
- stdout.write(`${import_chalk4.default.cyan("\u2500".repeat(60))}
6609
+ stdout.write(`${import_chalk5.default.cyan("\u2500".repeat(60))}
5584
6610
  `);
5585
- stdout.write(import_chalk4.default.cyan.bold("\u{1F916} Agent asks:\n"));
6611
+ stdout.write(import_chalk5.default.cyan.bold("\u{1F916} Agent asks:\n"));
5586
6612
  stdout.write(`${question}
5587
6613
  `);
5588
- stdout.write(`${import_chalk4.default.cyan("\u2500".repeat(60))}
6614
+ stdout.write(`${import_chalk5.default.cyan("\u2500".repeat(60))}
5589
6615
  `);
5590
- rl.question(import_chalk4.default.green.bold("You: "), (answer) => {
6616
+ rl.question(import_chalk5.default.green.bold("You: "), (answer) => {
5591
6617
  rl.close();
5592
6618
  resolve(answer);
5593
6619
  });
@@ -5622,29 +6648,39 @@ function parseLogLevel2(value) {
5622
6648
  }
5623
6649
  return normalized;
5624
6650
  }
5625
- function createProgram(env) {
6651
+ function createProgram(env, config) {
5626
6652
  const program = new import_commander3.Command();
5627
6653
  program.name(CLI_NAME).description(CLI_DESCRIPTION).version(package_default.version).option(OPTION_FLAGS.logLevel, OPTION_DESCRIPTIONS.logLevel, parseLogLevel2).option(OPTION_FLAGS.logFile, OPTION_DESCRIPTIONS.logFile).configureOutput({
5628
6654
  writeOut: (str) => env.stdout.write(str),
5629
6655
  writeErr: (str) => env.stderr.write(str)
5630
6656
  });
5631
- registerCompleteCommand(program, env);
5632
- registerAgentCommand(program, env);
6657
+ registerCompleteCommand(program, env, config?.complete);
6658
+ registerAgentCommand(program, env, config?.agent);
5633
6659
  registerModelsCommand(program, env);
6660
+ if (config) {
6661
+ const customNames = getCustomCommandNames(config);
6662
+ for (const name of customNames) {
6663
+ const cmdConfig = config[name];
6664
+ registerCustomCommand(program, name, cmdConfig, env);
6665
+ }
6666
+ }
5634
6667
  return program;
5635
6668
  }
5636
6669
  async function runCLI(overrides = {}) {
6670
+ const opts = "env" in overrides || "config" in overrides ? overrides : { env: overrides };
6671
+ const config = opts.config !== void 0 ? opts.config : loadConfig();
6672
+ const envOverrides = opts.env ?? {};
5637
6673
  const preParser = new import_commander3.Command();
5638
6674
  preParser.option(OPTION_FLAGS.logLevel, OPTION_DESCRIPTIONS.logLevel, parseLogLevel2).option(OPTION_FLAGS.logFile, OPTION_DESCRIPTIONS.logFile).allowUnknownOption().allowExcessArguments().helpOption(false);
5639
6675
  preParser.parse(process.argv);
5640
6676
  const globalOpts = preParser.opts();
5641
6677
  const loggerConfig = {
5642
- logLevel: globalOpts.logLevel,
5643
- logFile: globalOpts.logFile
6678
+ logLevel: globalOpts.logLevel ?? config.global?.["log-level"],
6679
+ logFile: globalOpts.logFile ?? config.global?.["log-file"]
5644
6680
  };
5645
6681
  const defaultEnv = createDefaultEnvironment(loggerConfig);
5646
- const env = { ...defaultEnv, ...overrides };
5647
- const program = createProgram(env);
6682
+ const env = { ...defaultEnv, ...envOverrides };
6683
+ const program = createProgram(env, config);
5648
6684
  await program.parseAsync(env.argv);
5649
6685
  }
5650
6686