@llmist/cli 9.6.0 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -11,7 +11,8 @@ var COMMANDS = {
11
11
  image: "image",
12
12
  speech: "speech",
13
13
  vision: "vision",
14
- init: "init"
14
+ init: "init",
15
+ config: "config"
15
16
  };
16
17
  var LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
17
18
  var DEFAULT_MODEL = "openai:gpt-5-nano";
@@ -23,9 +24,7 @@ var OPTION_FLAGS = {
23
24
  maxIterations: "-i, --max-iterations <count>",
24
25
  gadgetModule: "-g, --gadget <module>",
25
26
  logLevel: "--log-level <level>",
26
- logFile: "--log-file <path>",
27
- logReset: "--log-reset",
28
- logLlmRequests: "--log-llm-requests [dir]",
27
+ logLlmRequests: "--log-llm-requests",
29
28
  noBuiltins: "--no-builtins",
30
29
  noBuiltinInteraction: "--no-builtin-interaction",
31
30
  quiet: "-q, --quiet",
@@ -51,9 +50,7 @@ var OPTION_DESCRIPTIONS = {
51
50
  maxIterations: "Maximum number of agent loop iterations before exiting.",
52
51
  gadgetModule: "Path or module specifier for a gadget export. Repeat to register multiple gadgets.",
53
52
  logLevel: "Log level: silly, trace, debug, info, warn, error, fatal.",
54
- logFile: "Path to log file. When set, logs are written to file instead of stderr.",
55
- logReset: "Reset (truncate) the log file at session start instead of appending.",
56
- logLlmRequests: "Save LLM requests/responses to session directories. Optional dir, defaults to ~/.llmist/logs/requests/",
53
+ logLlmRequests: "Save LLM requests/responses to the session log directory.",
57
54
  noBuiltins: "Disable built-in gadgets (AskUser, TellUser).",
58
55
  noBuiltinInteraction: "Disable interactive gadgets (AskUser) while keeping TellUser.",
59
56
  quiet: "Suppress all output except content (text and TellUser messages).",
@@ -79,7 +76,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
79
76
  // package.json
80
77
  var package_default = {
81
78
  name: "@llmist/cli",
82
- version: "9.6.0",
79
+ version: "10.0.0",
83
80
  description: "CLI for llmist - run LLM agents from the command line",
84
81
  type: "module",
85
82
  main: "dist/cli.js",
@@ -412,9 +409,9 @@ function spawn(argv, options = {}) {
412
409
  options.stderr === "pipe" ? "pipe" : options.stderr ?? "ignore"
413
410
  ]
414
411
  });
415
- const exited = new Promise((resolve2, reject) => {
412
+ const exited = new Promise((resolve3, reject) => {
416
413
  proc.on("exit", (code) => {
417
- resolve2(code ?? 1);
414
+ resolve3(code ?? 1);
418
415
  });
419
416
  proc.on("error", (err) => {
420
417
  reject(err);
@@ -1262,6 +1259,15 @@ async function loadExternalGadgets(specifier, forceInstall = false) {
1262
1259
  return gadgets;
1263
1260
  }
1264
1261
 
1262
+ // src/paths.ts
1263
+ import { homedir } from "os";
1264
+ function expandTildePath(path6) {
1265
+ if (!path6.startsWith("~")) {
1266
+ return path6;
1267
+ }
1268
+ return path6.replace(/^~/, homedir());
1269
+ }
1270
+
1265
1271
  // src/gadgets.ts
1266
1272
  var PATH_PREFIXES = [".", "/", "~"];
1267
1273
  var BUILTIN_PREFIX = "builtin:";
@@ -1289,16 +1295,6 @@ function isGadgetConstructor(value) {
1289
1295
  }
1290
1296
  return isGadgetLike(prototype);
1291
1297
  }
1292
- function expandHomePath(input) {
1293
- if (!input.startsWith("~")) {
1294
- return input;
1295
- }
1296
- const home = process.env.HOME;
1297
- if (!home) {
1298
- return input;
1299
- }
1300
- return path5.join(home, input.slice(1));
1301
- }
1302
1298
  function parseLocalSpecifier(specifier) {
1303
1299
  const match = specifier.match(/^(.+):([A-Z][a-zA-Z0-9]*)$/);
1304
1300
  if (match) {
@@ -1330,7 +1326,7 @@ function resolveGadgetSpecifier(specifier, cwd) {
1330
1326
  if (!isFileLikeSpecifier(specifier)) {
1331
1327
  return specifier;
1332
1328
  }
1333
- const expanded = expandHomePath(specifier);
1329
+ const expanded = expandTildePath(specifier);
1334
1330
  const resolvedPath = path5.resolve(cwd, expanded);
1335
1331
  if (!fs6.existsSync(resolvedPath)) {
1336
1332
  throw new Error(`Gadget module not found at ${resolvedPath}`);
@@ -1429,19 +1425,8 @@ async function loadGadgets(specifiers, cwd, importer = (specifier) => import(spe
1429
1425
 
1430
1426
  // src/llm-logging.ts
1431
1427
  import { mkdir, writeFile as writeFile2 } from "fs/promises";
1432
- import { homedir } from "os";
1433
1428
  import { join } from "path";
1434
1429
  import { extractMessageText } from "llmist";
1435
- var DEFAULT_LLM_LOG_DIR = join(homedir(), ".llmist", "logs");
1436
- function resolveLogDir(option, subdir) {
1437
- if (option === true) {
1438
- return join(DEFAULT_LLM_LOG_DIR, subdir);
1439
- }
1440
- if (typeof option === "string") {
1441
- return option;
1442
- }
1443
- return void 0;
1444
- }
1445
1430
  function formatLlmRequest(messages) {
1446
1431
  const lines = [];
1447
1432
  for (const msg of messages) {
@@ -1455,27 +1440,6 @@ async function writeLogFile(dir, filename, content) {
1455
1440
  await mkdir(dir, { recursive: true });
1456
1441
  await writeFile2(join(dir, filename), content, "utf-8");
1457
1442
  }
1458
- function formatSessionTimestamp(date = /* @__PURE__ */ new Date()) {
1459
- const pad = (n) => n.toString().padStart(2, "0");
1460
- const year = date.getFullYear();
1461
- const month = pad(date.getMonth() + 1);
1462
- const day = pad(date.getDate());
1463
- const hours = pad(date.getHours());
1464
- const minutes = pad(date.getMinutes());
1465
- const seconds = pad(date.getSeconds());
1466
- return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
1467
- }
1468
- async function createSessionDir(baseDir) {
1469
- const timestamp = formatSessionTimestamp();
1470
- const sessionDir = join(baseDir, timestamp);
1471
- try {
1472
- await mkdir(sessionDir, { recursive: true });
1473
- return sessionDir;
1474
- } catch (error) {
1475
- console.warn(`[llmist] Failed to create log session directory: ${sessionDir}`, error);
1476
- return void 0;
1477
- }
1478
- }
1479
1443
  function formatCallNumber(n) {
1480
1444
  return n.toString().padStart(4, "0");
1481
1445
  }
@@ -3274,11 +3238,11 @@ var InputHandler = class {
3274
3238
  * @returns Promise that resolves with user's response
3275
3239
  */
3276
3240
  async waitForInput(question, gadgetName) {
3277
- return new Promise((resolve2, reject) => {
3241
+ return new Promise((resolve3, reject) => {
3278
3242
  this.pendingInput = {
3279
3243
  question,
3280
3244
  gadgetName,
3281
- resolve: resolve2,
3245
+ resolve: resolve3,
3282
3246
  reject
3283
3247
  };
3284
3248
  this.appendQuestionToBody(question);
@@ -3294,11 +3258,11 @@ var InputHandler = class {
3294
3258
  * @returns Promise that resolves with user's prompt
3295
3259
  */
3296
3260
  async waitForPrompt() {
3297
- return new Promise((resolve2, reject) => {
3261
+ return new Promise((resolve3, reject) => {
3298
3262
  this.pendingInput = {
3299
3263
  question: "",
3300
3264
  gadgetName: "prompt",
3301
- resolve: resolve2,
3265
+ resolve: resolve3,
3302
3266
  reject
3303
3267
  };
3304
3268
  this.setPendingPrompt();
@@ -3370,10 +3334,10 @@ var InputHandler = class {
3370
3334
  return;
3371
3335
  }
3372
3336
  if (this.pendingInput) {
3373
- const { resolve: resolve2 } = this.pendingInput;
3337
+ const { resolve: resolve3 } = this.pendingInput;
3374
3338
  this.pendingInput = null;
3375
3339
  this.setIdle();
3376
- resolve2(value);
3340
+ resolve3(value);
3377
3341
  } else if (this.midSessionHandler) {
3378
3342
  this.midSessionHandler(value);
3379
3343
  this.setIdle();
@@ -4569,7 +4533,7 @@ import { Box as Box3 } from "@unblessed/node";
4569
4533
  var MAX_PREVIEW_LINES = 10;
4570
4534
  var MAX_PARAM_VALUE_LENGTH = 60;
4571
4535
  function showApprovalDialog(screen, context) {
4572
- return new Promise((resolve2) => {
4536
+ return new Promise((resolve3) => {
4573
4537
  const content = buildDialogContent(context);
4574
4538
  const dialog = new Box3({
4575
4539
  parent: screen,
@@ -4624,7 +4588,7 @@ function showApprovalDialog(screen, context) {
4624
4588
  if (response) {
4625
4589
  dialog.destroy();
4626
4590
  screen.render();
4627
- resolve2(response);
4591
+ resolve3(response);
4628
4592
  }
4629
4593
  };
4630
4594
  dialog.on("keypress", handleKey);
@@ -4694,7 +4658,7 @@ var WHITE = "\x1B[37m";
4694
4658
  function showRawViewer(options) {
4695
4659
  let closeCallback = () => {
4696
4660
  };
4697
- const closed = new Promise((resolve2) => {
4661
+ const closed = new Promise((resolve3) => {
4698
4662
  const {
4699
4663
  screen,
4700
4664
  mode,
@@ -4790,7 +4754,7 @@ ${error}`;
4790
4754
  helpBar.destroy();
4791
4755
  viewer.destroy();
4792
4756
  screen.render();
4793
- resolve2();
4757
+ resolve3();
4794
4758
  };
4795
4759
  closeCallback = close;
4796
4760
  viewer.key(["escape", "q"], close);
@@ -5588,8 +5552,8 @@ async function executeAgent(promptArg, options, env) {
5588
5552
  };
5589
5553
  let usage;
5590
5554
  let iterations = 0;
5591
- const llmLogsBaseDir = resolveLogDir(options.logLlmRequests, "requests");
5592
- let llmSessionDir;
5555
+ const llmLogsEnabled = options.logLlmRequests === true;
5556
+ const llmLogDir = llmLogsEnabled ? env.session?.logDir : void 0;
5593
5557
  let llmCallCounter = 0;
5594
5558
  const countMessagesTokens = async (model, messages) => {
5595
5559
  try {
@@ -5636,15 +5600,10 @@ async function executeAgent(promptArg, options, env) {
5636
5600
  if (tui) {
5637
5601
  tui.setLLMCallRequest(context.options.messages);
5638
5602
  }
5639
- if (llmLogsBaseDir) {
5640
- if (!llmSessionDir) {
5641
- llmSessionDir = await createSessionDir(llmLogsBaseDir);
5642
- }
5643
- if (llmSessionDir) {
5644
- const filename = `${formatCallNumber(llmCallCounter)}.request`;
5645
- const content = formatLlmRequest(context.options.messages);
5646
- await writeLogFile(llmSessionDir, filename, content);
5647
- }
5603
+ if (llmLogDir) {
5604
+ const filename = `${formatCallNumber(llmCallCounter)}.request`;
5605
+ const content = formatLlmRequest(context.options.messages);
5606
+ await writeLogFile(llmLogDir, filename, content);
5648
5607
  }
5649
5608
  },
5650
5609
  // onStreamChunk: Update status bar with real-time output token estimate
@@ -5687,9 +5646,9 @@ async function executeAgent(promptArg, options, env) {
5687
5646
  rawResponse: context.rawResponse
5688
5647
  });
5689
5648
  }
5690
- if (llmSessionDir) {
5649
+ if (llmLogDir) {
5691
5650
  const filename = `${formatCallNumber(llmCallCounter)}.response`;
5692
- await writeLogFile(llmSessionDir, filename, context.rawResponse);
5651
+ await writeLogFile(llmLogDir, filename, context.rawResponse);
5693
5652
  }
5694
5653
  }
5695
5654
  },
@@ -5937,15 +5896,12 @@ async function executeComplete(promptArg, options, env) {
5937
5896
  builder.addUser(prompt);
5938
5897
  }
5939
5898
  const messages = builder.build();
5940
- const llmLogsBaseDir = resolveLogDir(options.logLlmRequests, "requests");
5941
- let llmSessionDir;
5942
- if (llmLogsBaseDir) {
5943
- llmSessionDir = await createSessionDir(llmLogsBaseDir);
5944
- if (llmSessionDir) {
5945
- const filename = "0001.request";
5946
- const content = formatLlmRequest(messages);
5947
- await writeLogFile(llmSessionDir, filename, content);
5948
- }
5899
+ const llmLogsEnabled = options.logLlmRequests === true;
5900
+ const llmLogDir = llmLogsEnabled ? env.session?.logDir : void 0;
5901
+ if (llmLogDir) {
5902
+ const filename = "0001.request";
5903
+ const content = formatLlmRequest(messages);
5904
+ await writeLogFile(llmLogDir, filename, content);
5949
5905
  }
5950
5906
  const stream = client.stream({
5951
5907
  model,
@@ -5984,9 +5940,9 @@ async function executeComplete(promptArg, options, env) {
5984
5940
  progress.endCall(usage);
5985
5941
  progress.complete();
5986
5942
  printer.ensureNewline();
5987
- if (llmSessionDir) {
5943
+ if (llmLogDir) {
5988
5944
  const filename = "0001.response";
5989
- await writeLogFile(llmSessionDir, filename, accumulatedResponse);
5945
+ await writeLogFile(llmLogDir, filename, accumulatedResponse);
5990
5946
  }
5991
5947
  if (stderrTTY && !options.quiet) {
5992
5948
  const summary = renderSummary({ finishReason, usage, cost: progress.getTotalCost() });
@@ -6004,18 +5960,163 @@ function registerCompleteCommand(program, env, config) {
6004
5960
  );
6005
5961
  }
6006
5962
 
5963
+ // src/config-command.ts
5964
+ function registerConfigCommand(program, env, config) {
5965
+ program.command(`${COMMANDS.config} [profile]`).description("Display resolved configuration for a profile or list all profiles").action(
5966
+ (profileName) => executeAction(() => executeConfigCommand(profileName, config, env), env)
5967
+ );
5968
+ }
5969
+ async function executeConfigCommand(profileName, config, env) {
5970
+ if (!config || Object.keys(config).length === 0) {
5971
+ env.stdout.write("No configuration file found at ~/.llmist/cli.toml\n");
5972
+ env.stdout.write("Run 'llmist init' to create one.\n");
5973
+ return;
5974
+ }
5975
+ if (!profileName) {
5976
+ listProfiles(config, env);
5977
+ } else {
5978
+ showProfile(profileName, config, env);
5979
+ }
5980
+ }
5981
+ function listProfiles(config, env) {
5982
+ const reserved = /* @__PURE__ */ new Set(["global", "prompts", "subagents"]);
5983
+ const profiles = Object.keys(config).filter((k) => !reserved.has(k));
5984
+ if (profiles.length === 0) {
5985
+ env.stdout.write("No profiles defined in configuration.\n");
5986
+ return;
5987
+ }
5988
+ env.stdout.write("Available Profiles:\n\n");
5989
+ for (const name of profiles) {
5990
+ const section = config[name];
5991
+ const inherits = section?.inherits;
5992
+ const desc = section?.description;
5993
+ let line = ` ${name.padEnd(22)}`;
5994
+ if (desc) {
5995
+ line += desc;
5996
+ } else if (inherits) {
5997
+ line += `inherits: ${Array.isArray(inherits) ? inherits.join(", ") : inherits}`;
5998
+ }
5999
+ env.stdout.write(line + "\n");
6000
+ }
6001
+ }
6002
+ function showProfile(name, config, env) {
6003
+ const reserved = /* @__PURE__ */ new Set(["global", "prompts", "subagents"]);
6004
+ if (reserved.has(name)) {
6005
+ env.stderr.write(`"${name}" is a reserved section, not a profile.
6006
+ `);
6007
+ return;
6008
+ }
6009
+ const section = config[name];
6010
+ if (!section) {
6011
+ env.stderr.write(`Profile "${name}" not found.
6012
+ `);
6013
+ env.stderr.write("Run 'llmist config' to see available profiles.\n");
6014
+ return;
6015
+ }
6016
+ const separator = "\u2500".repeat(60);
6017
+ env.stdout.write(`
6018
+ Profile: ${name}
6019
+ `);
6020
+ if (section.inherits) {
6021
+ const chain = Array.isArray(section.inherits) ? section.inherits.join(" \u2192 ") : section.inherits;
6022
+ env.stdout.write(`Inherits: ${chain}
6023
+ `);
6024
+ }
6025
+ if (section.description) {
6026
+ env.stdout.write(`Description: ${section.description}
6027
+ `);
6028
+ }
6029
+ env.stdout.write(separator + "\n\n");
6030
+ env.stdout.write("Options:\n");
6031
+ env.stdout.write(` Model: ${section.model ?? "(default)"}
6032
+ `);
6033
+ env.stdout.write(` Max Iterations: ${section["max-iterations"] ?? "(default)"}
6034
+ `);
6035
+ env.stdout.write(
6036
+ ` Temperature: ${section.temperature !== void 0 ? section.temperature : "(default)"}
6037
+ `
6038
+ );
6039
+ const gadgets = section.gadgets ?? section.gadget;
6040
+ if (gadgets && gadgets.length > 0) {
6041
+ env.stdout.write("\nGadgets:\n");
6042
+ for (const g of gadgets) {
6043
+ env.stdout.write(` \u2022 ${g}
6044
+ `);
6045
+ }
6046
+ }
6047
+ const approval = section["gadget-approval"];
6048
+ if (approval && Object.keys(approval).length > 0) {
6049
+ env.stdout.write("\nGadget Approval:\n");
6050
+ for (const [gadget, policy] of Object.entries(approval)) {
6051
+ env.stdout.write(` \u2022 ${gadget}: ${policy}
6052
+ `);
6053
+ }
6054
+ }
6055
+ if (section.subagents && Object.keys(section.subagents).length > 0) {
6056
+ env.stdout.write("\nSubagents:\n");
6057
+ for (const [subagent, subConfig] of Object.entries(section.subagents)) {
6058
+ env.stdout.write(` ${subagent}:
6059
+ `);
6060
+ if (subConfig.model) env.stdout.write(` model: ${subConfig.model}
6061
+ `);
6062
+ if (subConfig.maxIterations)
6063
+ env.stdout.write(` maxIterations: ${subConfig.maxIterations}
6064
+ `);
6065
+ if (subConfig.timeoutMs) env.stdout.write(` timeoutMs: ${subConfig.timeoutMs}
6066
+ `);
6067
+ }
6068
+ }
6069
+ if (section.system) {
6070
+ const chars = section.system.length;
6071
+ const lines = section.system.split("\n").length;
6072
+ env.stdout.write(`
6073
+ System Prompt (${chars.toLocaleString()} chars, ${lines} lines):
6074
+ `);
6075
+ env.stdout.write(separator + "\n");
6076
+ env.stdout.write(section.system);
6077
+ if (!section.system.endsWith("\n")) {
6078
+ env.stdout.write("\n");
6079
+ }
6080
+ env.stdout.write(separator + "\n");
6081
+ } else {
6082
+ env.stdout.write("\nSystem Prompt: (none)\n");
6083
+ }
6084
+ }
6085
+
6007
6086
  // src/init-command.ts
6008
- import { existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
6009
- import { dirname } from "path";
6087
+ import { existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
6088
+ import { dirname as dirname2 } from "path";
6010
6089
 
6011
6090
  // src/config.ts
6012
- import { existsSync, readFileSync } from "fs";
6091
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
6013
6092
  import { homedir as homedir2 } from "os";
6014
6093
  import { join as join2 } from "path";
6015
6094
  import { load as parseToml } from "js-toml";
6016
6095
 
6017
6096
  // src/templates.ts
6018
6097
  import { Eta } from "eta";
6098
+ import { existsSync, readFileSync, statSync } from "fs";
6099
+ import { dirname, resolve as resolve2 } from "path";
6100
+ var MAX_PROMPT_FILE_SIZE = 1024 * 1024;
6101
+ function resolvePromptFilePath(filePath, configDir) {
6102
+ const expanded = expandTildePath(filePath);
6103
+ if (expanded.startsWith("/")) {
6104
+ return expanded;
6105
+ }
6106
+ return resolve2(configDir ?? process.cwd(), expanded);
6107
+ }
6108
+ function loadFileContents(filePath, configDir) {
6109
+ const absolutePath = resolvePromptFilePath(filePath, configDir);
6110
+ if (!existsSync(absolutePath)) {
6111
+ throw new Error(`File not found: ${filePath}`);
6112
+ }
6113
+ const stats = statSync(absolutePath);
6114
+ if (stats.size > MAX_PROMPT_FILE_SIZE) {
6115
+ const sizeMB = (stats.size / (1024 * 1024)).toFixed(1);
6116
+ throw new Error(`File too large: ${filePath} (${sizeMB}MB, max 1MB)`);
6117
+ }
6118
+ return readFileSync(absolutePath, "utf-8");
6119
+ }
6019
6120
  var TemplateError = class extends Error {
6020
6121
  constructor(message, promptName, configPath) {
6021
6122
  super(promptName ? `[prompts.${promptName}]: ${message}` : message);
@@ -6025,14 +6126,40 @@ var TemplateError = class extends Error {
6025
6126
  }
6026
6127
  };
6027
6128
  function createTemplateEngine(prompts, configPath) {
6129
+ const configDir = configPath ? dirname(configPath) : void 0;
6028
6130
  const eta = new Eta({
6029
6131
  views: "/",
6030
6132
  // Required but we use named templates
6031
6133
  autoEscape: false,
6032
6134
  // Don't escape - these are prompts, not HTML
6033
- autoTrim: false
6135
+ autoTrim: false,
6034
6136
  // Preserve whitespace in prompts
6137
+ // Inject includeFile function into compiled templates
6138
+ functionHeader: "const includeFile = (path) => it.__includeFile(path);"
6035
6139
  });
6140
+ const includeStack = [];
6141
+ eta.__includeFileImpl = (path6) => {
6142
+ if (includeStack.includes(path6)) {
6143
+ throw new Error(
6144
+ `Circular include detected: ${[...includeStack, path6].join(" -> ")}`
6145
+ );
6146
+ }
6147
+ includeStack.push(path6);
6148
+ try {
6149
+ const content = loadFileContents(path6, configDir);
6150
+ if (hasTemplateSyntax(content)) {
6151
+ const context = {
6152
+ env: process.env,
6153
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
6154
+ __includeFile: eta.__includeFileImpl
6155
+ };
6156
+ return eta.renderString(content, context);
6157
+ }
6158
+ return content;
6159
+ } finally {
6160
+ includeStack.pop();
6161
+ }
6162
+ };
6036
6163
  for (const [name, template] of Object.entries(prompts)) {
6037
6164
  try {
6038
6165
  eta.loadTemplate(`@${name}`, template);
@@ -6051,8 +6178,9 @@ function resolveTemplate(eta, template, context = {}, configPath) {
6051
6178
  const fullContext = {
6052
6179
  ...context,
6053
6180
  env: process.env,
6054
- date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
6181
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
6055
6182
  // "2025-12-01"
6183
+ __includeFile: eta.__includeFileImpl ?? (() => "")
6056
6184
  };
6057
6185
  return eta.renderString(template, fullContext);
6058
6186
  } catch (error) {
@@ -6067,7 +6195,10 @@ function validatePrompts(prompts, configPath) {
6067
6195
  const eta = createTemplateEngine(prompts, configPath);
6068
6196
  for (const [name, template] of Object.entries(prompts)) {
6069
6197
  try {
6070
- eta.renderString(template, { env: {} });
6198
+ eta.renderString(template, {
6199
+ env: {},
6200
+ __includeFile: eta.__includeFileImpl ?? (() => "")
6201
+ });
6071
6202
  } catch (error) {
6072
6203
  throw new TemplateError(
6073
6204
  error instanceof Error ? error.message : String(error),
@@ -6097,7 +6228,7 @@ function hasTemplateSyntax(str) {
6097
6228
 
6098
6229
  // src/config.ts
6099
6230
  var VALID_PERMISSION_LEVELS = ["allowed", "denied", "approval-required"];
6100
- var GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set(["log-level", "log-file", "log-reset"]);
6231
+ var GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set(["log-level"]);
6101
6232
  var VALID_LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
6102
6233
  var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
6103
6234
  "model",
@@ -6107,8 +6238,6 @@ var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
6107
6238
  "quiet",
6108
6239
  "inherits",
6109
6240
  "log-level",
6110
- "log-file",
6111
- "log-reset",
6112
6241
  "log-llm-requests",
6113
6242
  "type"
6114
6243
  // Allowed for inheritance compatibility, ignored for built-in commands
@@ -6137,8 +6266,6 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
6137
6266
  "quiet",
6138
6267
  "inherits",
6139
6268
  "log-level",
6140
- "log-file",
6141
- "log-reset",
6142
6269
  "log-llm-requests",
6143
6270
  "type"
6144
6271
  // Allowed for inheritance compatibility, ignored for built-in commands
@@ -6167,6 +6294,9 @@ function validateString(value, key, section) {
6167
6294
  }
6168
6295
  return value;
6169
6296
  }
6297
+ function validatePathString(value, key, section) {
6298
+ return expandTildePath(validateString(value, key, section));
6299
+ }
6170
6300
  function validateNumber(value, key, section, opts) {
6171
6301
  if (typeof value !== "number") {
6172
6302
  throw new ConfigError(`[${section}].${key} must be a number`);
@@ -6234,6 +6364,13 @@ function validateSingleSubagentConfig(value, subagentName, section) {
6234
6364
  );
6235
6365
  }
6236
6366
  result.maxIterations = val;
6367
+ } else if (key === "timeoutMs") {
6368
+ if (typeof val !== "number" || !Number.isInteger(val) || val < 0) {
6369
+ throw new ConfigError(
6370
+ `[${section}].${subagentName}.timeoutMs must be a non-negative integer`
6371
+ );
6372
+ }
6373
+ result.timeoutMs = val;
6237
6374
  } else {
6238
6375
  result[key] = val;
6239
6376
  }
@@ -6301,12 +6438,6 @@ function validateLoggingConfig(raw, section) {
6301
6438
  }
6302
6439
  result["log-level"] = level;
6303
6440
  }
6304
- if ("log-file" in raw) {
6305
- result["log-file"] = validateString(raw["log-file"], "log-file", section);
6306
- }
6307
- if ("log-reset" in raw) {
6308
- result["log-reset"] = validateBoolean(raw["log-reset"], "log-reset", section);
6309
- }
6310
6441
  return result;
6311
6442
  }
6312
6443
  function validateBaseConfig(raw, section) {
@@ -6364,11 +6495,7 @@ function validateCompleteConfig(raw, section) {
6364
6495
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
6365
6496
  }
6366
6497
  if ("log-llm-requests" in rawObj) {
6367
- result["log-llm-requests"] = validateStringOrBoolean(
6368
- rawObj["log-llm-requests"],
6369
- "log-llm-requests",
6370
- section
6371
- );
6498
+ result["log-llm-requests"] = validateBoolean(rawObj["log-llm-requests"], "log-llm-requests", section);
6372
6499
  }
6373
6500
  return result;
6374
6501
  }
@@ -6449,11 +6576,7 @@ function validateAgentConfig(raw, section) {
6449
6576
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
6450
6577
  }
6451
6578
  if ("log-llm-requests" in rawObj) {
6452
- result["log-llm-requests"] = validateStringOrBoolean(
6453
- rawObj["log-llm-requests"],
6454
- "log-llm-requests",
6455
- section
6456
- );
6579
+ result["log-llm-requests"] = validateBoolean(rawObj["log-llm-requests"], "log-llm-requests", section);
6457
6580
  }
6458
6581
  return result;
6459
6582
  }
@@ -6485,7 +6608,7 @@ function validateImageConfig(raw, section) {
6485
6608
  });
6486
6609
  }
6487
6610
  if ("output" in rawObj) {
6488
- result.output = validateString(rawObj.output, "output", section);
6611
+ result.output = validatePathString(rawObj.output, "output", section);
6489
6612
  }
6490
6613
  if ("quiet" in rawObj) {
6491
6614
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
@@ -6519,19 +6642,13 @@ function validateSpeechConfig(raw, section) {
6519
6642
  });
6520
6643
  }
6521
6644
  if ("output" in rawObj) {
6522
- result.output = validateString(rawObj.output, "output", section);
6645
+ result.output = validatePathString(rawObj.output, "output", section);
6523
6646
  }
6524
6647
  if ("quiet" in rawObj) {
6525
6648
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
6526
6649
  }
6527
6650
  return result;
6528
6651
  }
6529
- function validateStringOrBoolean(value, field, section) {
6530
- if (typeof value === "string" || typeof value === "boolean") {
6531
- return value;
6532
- }
6533
- throw new ConfigError(`[${section}].${field} must be a string or boolean`);
6534
- }
6535
6652
  function validateCustomConfig(raw, section) {
6536
6653
  if (typeof raw !== "object" || raw === null) {
6537
6654
  throw new ConfigError(`[${section}] must be a table`);
@@ -6625,6 +6742,9 @@ function validateCustomConfig(raw, section) {
6625
6742
  if ("quiet" in rawObj) {
6626
6743
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
6627
6744
  }
6745
+ if ("log-llm-requests" in rawObj) {
6746
+ result["log-llm-requests"] = validateBoolean(rawObj["log-llm-requests"], "log-llm-requests", section);
6747
+ }
6628
6748
  Object.assign(result, validateLoggingConfig(rawObj, section));
6629
6749
  return result;
6630
6750
  }
@@ -6677,12 +6797,12 @@ function validateConfig(raw, configPath) {
6677
6797
  }
6678
6798
  function loadConfig() {
6679
6799
  const configPath = getConfigPath();
6680
- if (!existsSync(configPath)) {
6800
+ if (!existsSync2(configPath)) {
6681
6801
  return {};
6682
6802
  }
6683
6803
  let content;
6684
6804
  try {
6685
- content = readFileSync(configPath, "utf-8");
6805
+ content = readFileSync2(configPath, "utf-8");
6686
6806
  } catch (error) {
6687
6807
  throw new ConfigError(
6688
6808
  `Failed to read config file: ${error instanceof Error ? error.message : "Unknown error"}`,
@@ -6924,8 +7044,8 @@ var STARTER_CONFIG = `# ~/.llmist/cli.toml
6924
7044
  `;
6925
7045
  async function executeInit(_options, env) {
6926
7046
  const configPath = getConfigPath();
6927
- const configDir = dirname(configPath);
6928
- if (existsSync2(configPath)) {
7047
+ const configDir = dirname2(configPath);
7048
+ if (existsSync3(configPath)) {
6929
7049
  env.stderr.write(`Configuration already exists at ${configPath}
6930
7050
  `);
6931
7051
  env.stderr.write("\n");
@@ -6935,7 +7055,7 @@ async function executeInit(_options, env) {
6935
7055
  `);
6936
7056
  return;
6937
7057
  }
6938
- if (!existsSync2(configDir)) {
7058
+ if (!existsSync3(configDir)) {
6939
7059
  mkdirSync(configDir, { recursive: true });
6940
7060
  }
6941
7061
  writeFileSync(configPath, STARTER_CONFIG, "utf-8");
@@ -6965,6 +7085,7 @@ function registerInitCommand(program, env) {
6965
7085
  }
6966
7086
 
6967
7087
  // src/environment.ts
7088
+ import { join as join3 } from "path";
6968
7089
  import readline from "readline";
6969
7090
  import chalk4 from "chalk";
6970
7091
  import { LLMist, createLogger } from "llmist";
@@ -6977,7 +7098,7 @@ var LOG_LEVEL_MAP = {
6977
7098
  error: 5,
6978
7099
  fatal: 6
6979
7100
  };
6980
- function createLoggerFactory(config) {
7101
+ function createLoggerFactory(config, sessionLogDir) {
6981
7102
  return (name) => {
6982
7103
  const options = { name };
6983
7104
  if (config?.logLevel) {
@@ -6986,12 +7107,10 @@ function createLoggerFactory(config) {
6986
7107
  options.minLevel = LOG_LEVEL_MAP[level];
6987
7108
  }
6988
7109
  }
6989
- if (config?.logReset !== void 0) {
6990
- options.logReset = config.logReset;
6991
- }
6992
- if (config?.logFile) {
7110
+ if (sessionLogDir) {
7111
+ const logFile = join3(sessionLogDir, "session.log.jsonl");
6993
7112
  const originalLogFile = process.env.LLMIST_LOG_FILE;
6994
- process.env.LLMIST_LOG_FILE = config.logFile;
7113
+ process.env.LLMIST_LOG_FILE = logFile;
6995
7114
  const logger = createLogger(options);
6996
7115
  if (originalLogFile === void 0) {
6997
7116
  delete process.env.LLMIST_LOG_FILE;
@@ -7008,7 +7127,7 @@ function createLoggerFactory(config) {
7008
7127
  }
7009
7128
  function createPromptFunction(stdin, stdout) {
7010
7129
  return (question) => {
7011
- return new Promise((resolve2) => {
7130
+ return new Promise((resolve3) => {
7012
7131
  const rl = readline.createInterface({
7013
7132
  input: stdin,
7014
7133
  output: stdout
@@ -7023,12 +7142,12 @@ function createPromptFunction(stdin, stdout) {
7023
7142
  `);
7024
7143
  rl.question(chalk4.green.bold("You: "), (answer) => {
7025
7144
  rl.close();
7026
- resolve2(answer);
7145
+ resolve3(answer);
7027
7146
  });
7028
7147
  });
7029
7148
  };
7030
7149
  }
7031
- function createDefaultEnvironment(loggerConfig) {
7150
+ function createDefaultEnvironment(loggerConfig, sessionLogDir) {
7032
7151
  const isTTY = Boolean(process.stdin.isTTY);
7033
7152
  return {
7034
7153
  argv: process.argv,
@@ -7040,7 +7159,7 @@ function createDefaultEnvironment(loggerConfig) {
7040
7159
  process.exitCode = code;
7041
7160
  },
7042
7161
  loggerConfig,
7043
- createLogger: createLoggerFactory(loggerConfig),
7162
+ createLogger: createLoggerFactory(loggerConfig, sessionLogDir),
7044
7163
  isTTY,
7045
7164
  prompt: isTTY ? createPromptFunction(process.stdin, process.stdout) : async () => {
7046
7165
  throw new Error("Cannot prompt for input: stdin is not a TTY");
@@ -7050,19 +7169,17 @@ function createDefaultEnvironment(loggerConfig) {
7050
7169
 
7051
7170
  // src/custom-command.ts
7052
7171
  function createCommandEnvironment(baseEnv, config) {
7053
- const hasLoggingConfig = config["log-level"] !== void 0 || config["log-file"] !== void 0 || config["log-reset"] !== void 0;
7172
+ const hasLoggingConfig = config["log-level"] !== void 0;
7054
7173
  if (!hasLoggingConfig) {
7055
7174
  return baseEnv;
7056
7175
  }
7057
7176
  const loggerConfig = {
7058
- logLevel: config["log-level"] ?? baseEnv.loggerConfig?.logLevel,
7059
- logFile: config["log-file"] ?? baseEnv.loggerConfig?.logFile,
7060
- logReset: config["log-reset"] ?? baseEnv.loggerConfig?.logReset
7177
+ logLevel: config["log-level"] ?? baseEnv.loggerConfig?.logLevel
7061
7178
  };
7062
7179
  return {
7063
7180
  ...baseEnv,
7064
7181
  loggerConfig,
7065
- createLogger: createLoggerFactory(loggerConfig)
7182
+ createLogger: createLoggerFactory(loggerConfig, baseEnv.session?.logDir)
7066
7183
  };
7067
7184
  }
7068
7185
  function registerCustomCommand(program, name, config, env, globalSubagents) {
@@ -7998,6 +8115,174 @@ function registerModelsCommand(program, env) {
7998
8115
  );
7999
8116
  }
8000
8117
 
8118
+ // src/session.ts
8119
+ import { existsSync as existsSync4 } from "fs";
8120
+ import { mkdir as mkdir2 } from "fs/promises";
8121
+ import { homedir as homedir3 } from "os";
8122
+ import { join as join4 } from "path";
8123
+
8124
+ // src/session-names.ts
8125
+ var ADJECTIVES = [
8126
+ "bright",
8127
+ "calm",
8128
+ "clever",
8129
+ "cool",
8130
+ "crisp",
8131
+ "eager",
8132
+ "fair",
8133
+ "fancy",
8134
+ "fast",
8135
+ "fresh",
8136
+ "gentle",
8137
+ "glad",
8138
+ "golden",
8139
+ "green",
8140
+ "happy",
8141
+ "jolly",
8142
+ "keen",
8143
+ "kind",
8144
+ "lively",
8145
+ "lucky",
8146
+ "merry",
8147
+ "neat",
8148
+ "nice",
8149
+ "noble",
8150
+ "peppy",
8151
+ "perky",
8152
+ "pink",
8153
+ "proud",
8154
+ "quick",
8155
+ "quiet",
8156
+ "rapid",
8157
+ "ready",
8158
+ "red",
8159
+ "rich",
8160
+ "rosy",
8161
+ "royal",
8162
+ "sharp",
8163
+ "shiny",
8164
+ "silver",
8165
+ "sleek",
8166
+ "smart",
8167
+ "smooth",
8168
+ "snappy",
8169
+ "solar",
8170
+ "steady",
8171
+ "sunny",
8172
+ "super",
8173
+ "sweet",
8174
+ "swift",
8175
+ "tidy",
8176
+ "vivid",
8177
+ "warm",
8178
+ "wild",
8179
+ "wise",
8180
+ "witty",
8181
+ "young"
8182
+ ];
8183
+ var NOUNS = [
8184
+ "arrow",
8185
+ "badge",
8186
+ "bear",
8187
+ "bird",
8188
+ "blade",
8189
+ "bolt",
8190
+ "brook",
8191
+ "cloud",
8192
+ "coral",
8193
+ "crane",
8194
+ "creek",
8195
+ "crown",
8196
+ "delta",
8197
+ "dove",
8198
+ "eagle",
8199
+ "ember",
8200
+ "falcon",
8201
+ "flame",
8202
+ "flash",
8203
+ "frost",
8204
+ "gem",
8205
+ "grove",
8206
+ "hawk",
8207
+ "heart",
8208
+ "heron",
8209
+ "hill",
8210
+ "jade",
8211
+ "lake",
8212
+ "leaf",
8213
+ "lion",
8214
+ "lotus",
8215
+ "maple",
8216
+ "moon",
8217
+ "oak",
8218
+ "ocean",
8219
+ "olive",
8220
+ "otter",
8221
+ "owl",
8222
+ "panda",
8223
+ "pearl",
8224
+ "pine",
8225
+ "plum",
8226
+ "raven",
8227
+ "reef",
8228
+ "ridge",
8229
+ "river",
8230
+ "robin",
8231
+ "rose",
8232
+ "sage",
8233
+ "shell",
8234
+ "snow",
8235
+ "spark",
8236
+ "star",
8237
+ "stone",
8238
+ "storm",
8239
+ "stream",
8240
+ "sun",
8241
+ "swan",
8242
+ "tiger",
8243
+ "tree",
8244
+ "wave",
8245
+ "willow",
8246
+ "wind",
8247
+ "wolf"
8248
+ ];
8249
+ function generateSessionName() {
8250
+ const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
8251
+ const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
8252
+ return `${adjective}-${noun}`;
8253
+ }
8254
+
8255
+ // src/session.ts
8256
+ var currentSession;
8257
+ var SESSION_LOGS_BASE = join4(homedir3(), ".llmist", "logs");
8258
+ function findUniqueName(baseName) {
8259
+ const baseDir = join4(SESSION_LOGS_BASE, baseName);
8260
+ if (!existsSync4(baseDir)) {
8261
+ return baseName;
8262
+ }
8263
+ let suffix = 2;
8264
+ while (suffix < 1e3) {
8265
+ const name = `${baseName}-${suffix}`;
8266
+ const dir = join4(SESSION_LOGS_BASE, name);
8267
+ if (!existsSync4(dir)) {
8268
+ return name;
8269
+ }
8270
+ suffix++;
8271
+ }
8272
+ return `${baseName}-${Date.now()}`;
8273
+ }
8274
+ async function initSession() {
8275
+ if (currentSession) {
8276
+ return currentSession;
8277
+ }
8278
+ const baseName = generateSessionName();
8279
+ const name = findUniqueName(baseName);
8280
+ const logDir = join4(SESSION_LOGS_BASE, name);
8281
+ await mkdir2(logDir, { recursive: true });
8282
+ currentSession = { name, logDir };
8283
+ return currentSession;
8284
+ }
8285
+
8001
8286
  // src/speech-command.ts
8002
8287
  import { writeFileSync as writeFileSync3 } from "fs";
8003
8288
  var DEFAULT_SPEECH_MODEL = "tts-1";
@@ -8091,7 +8376,7 @@ function parseLogLevel(value) {
8091
8376
  }
8092
8377
  function createProgram(env, config) {
8093
8378
  const program = new Command();
8094
- 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).option(OPTION_FLAGS.logReset, OPTION_DESCRIPTIONS.logReset).configureOutput({
8379
+ program.name(CLI_NAME).description(CLI_DESCRIPTION).version(package_default.version).option(OPTION_FLAGS.logLevel, OPTION_DESCRIPTIONS.logLevel, parseLogLevel).configureOutput({
8095
8380
  writeOut: (str) => env.stdout.write(str),
8096
8381
  writeErr: (str) => env.stderr.write(str)
8097
8382
  });
@@ -8103,6 +8388,7 @@ function createProgram(env, config) {
8103
8388
  registerModelsCommand(program, env);
8104
8389
  registerGadgetCommand(program, env);
8105
8390
  registerInitCommand(program, env);
8391
+ registerConfigCommand(program, env, config);
8106
8392
  if (config) {
8107
8393
  const customNames = getCustomCommandNames(config);
8108
8394
  for (const name of customNames) {
@@ -8116,19 +8402,19 @@ async function runCLI(overrides = {}) {
8116
8402
  const opts = "env" in overrides || "config" in overrides ? overrides : { env: overrides };
8117
8403
  const config = opts.config !== void 0 ? opts.config : loadConfig();
8118
8404
  const envOverrides = opts.env ?? {};
8405
+ const session = await initSession();
8119
8406
  const preParser = new Command();
8120
- preParser.option(OPTION_FLAGS.logLevel, OPTION_DESCRIPTIONS.logLevel, parseLogLevel).option(OPTION_FLAGS.logFile, OPTION_DESCRIPTIONS.logFile).option(OPTION_FLAGS.logReset, OPTION_DESCRIPTIONS.logReset).allowUnknownOption().allowExcessArguments().helpOption(false);
8407
+ preParser.option(OPTION_FLAGS.logLevel, OPTION_DESCRIPTIONS.logLevel, parseLogLevel).allowUnknownOption().allowExcessArguments().helpOption(false);
8121
8408
  preParser.parse(process.argv);
8122
8409
  const globalOpts = preParser.opts();
8123
8410
  const loggerConfig = {
8124
- logLevel: globalOpts.logLevel ?? config.global?.["log-level"],
8125
- logFile: globalOpts.logFile ?? config.global?.["log-file"],
8126
- logReset: globalOpts.logReset ?? config.global?.["log-reset"]
8411
+ logLevel: globalOpts.logLevel ?? config.global?.["log-level"]
8127
8412
  };
8128
- const defaultEnv = createDefaultEnvironment(loggerConfig);
8413
+ const defaultEnv = createDefaultEnvironment(loggerConfig, session.logDir);
8129
8414
  const env = {
8130
8415
  ...defaultEnv,
8131
- ...envOverrides
8416
+ ...envOverrides,
8417
+ session
8132
8418
  };
8133
8419
  const program = createProgram(env, config);
8134
8420
  await program.parseAsync(env.argv);