@llmist/cli 9.7.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.7.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`);
@@ -6308,12 +6438,6 @@ function validateLoggingConfig(raw, section) {
6308
6438
  }
6309
6439
  result["log-level"] = level;
6310
6440
  }
6311
- if ("log-file" in raw) {
6312
- result["log-file"] = validateString(raw["log-file"], "log-file", section);
6313
- }
6314
- if ("log-reset" in raw) {
6315
- result["log-reset"] = validateBoolean(raw["log-reset"], "log-reset", section);
6316
- }
6317
6441
  return result;
6318
6442
  }
6319
6443
  function validateBaseConfig(raw, section) {
@@ -6371,11 +6495,7 @@ function validateCompleteConfig(raw, section) {
6371
6495
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
6372
6496
  }
6373
6497
  if ("log-llm-requests" in rawObj) {
6374
- result["log-llm-requests"] = validateStringOrBoolean(
6375
- rawObj["log-llm-requests"],
6376
- "log-llm-requests",
6377
- section
6378
- );
6498
+ result["log-llm-requests"] = validateBoolean(rawObj["log-llm-requests"], "log-llm-requests", section);
6379
6499
  }
6380
6500
  return result;
6381
6501
  }
@@ -6456,11 +6576,7 @@ function validateAgentConfig(raw, section) {
6456
6576
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
6457
6577
  }
6458
6578
  if ("log-llm-requests" in rawObj) {
6459
- result["log-llm-requests"] = validateStringOrBoolean(
6460
- rawObj["log-llm-requests"],
6461
- "log-llm-requests",
6462
- section
6463
- );
6579
+ result["log-llm-requests"] = validateBoolean(rawObj["log-llm-requests"], "log-llm-requests", section);
6464
6580
  }
6465
6581
  return result;
6466
6582
  }
@@ -6492,7 +6608,7 @@ function validateImageConfig(raw, section) {
6492
6608
  });
6493
6609
  }
6494
6610
  if ("output" in rawObj) {
6495
- result.output = validateString(rawObj.output, "output", section);
6611
+ result.output = validatePathString(rawObj.output, "output", section);
6496
6612
  }
6497
6613
  if ("quiet" in rawObj) {
6498
6614
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
@@ -6526,19 +6642,13 @@ function validateSpeechConfig(raw, section) {
6526
6642
  });
6527
6643
  }
6528
6644
  if ("output" in rawObj) {
6529
- result.output = validateString(rawObj.output, "output", section);
6645
+ result.output = validatePathString(rawObj.output, "output", section);
6530
6646
  }
6531
6647
  if ("quiet" in rawObj) {
6532
6648
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
6533
6649
  }
6534
6650
  return result;
6535
6651
  }
6536
- function validateStringOrBoolean(value, field, section) {
6537
- if (typeof value === "string" || typeof value === "boolean") {
6538
- return value;
6539
- }
6540
- throw new ConfigError(`[${section}].${field} must be a string or boolean`);
6541
- }
6542
6652
  function validateCustomConfig(raw, section) {
6543
6653
  if (typeof raw !== "object" || raw === null) {
6544
6654
  throw new ConfigError(`[${section}] must be a table`);
@@ -6632,6 +6742,9 @@ function validateCustomConfig(raw, section) {
6632
6742
  if ("quiet" in rawObj) {
6633
6743
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
6634
6744
  }
6745
+ if ("log-llm-requests" in rawObj) {
6746
+ result["log-llm-requests"] = validateBoolean(rawObj["log-llm-requests"], "log-llm-requests", section);
6747
+ }
6635
6748
  Object.assign(result, validateLoggingConfig(rawObj, section));
6636
6749
  return result;
6637
6750
  }
@@ -6684,12 +6797,12 @@ function validateConfig(raw, configPath) {
6684
6797
  }
6685
6798
  function loadConfig() {
6686
6799
  const configPath = getConfigPath();
6687
- if (!existsSync(configPath)) {
6800
+ if (!existsSync2(configPath)) {
6688
6801
  return {};
6689
6802
  }
6690
6803
  let content;
6691
6804
  try {
6692
- content = readFileSync(configPath, "utf-8");
6805
+ content = readFileSync2(configPath, "utf-8");
6693
6806
  } catch (error) {
6694
6807
  throw new ConfigError(
6695
6808
  `Failed to read config file: ${error instanceof Error ? error.message : "Unknown error"}`,
@@ -6931,8 +7044,8 @@ var STARTER_CONFIG = `# ~/.llmist/cli.toml
6931
7044
  `;
6932
7045
  async function executeInit(_options, env) {
6933
7046
  const configPath = getConfigPath();
6934
- const configDir = dirname(configPath);
6935
- if (existsSync2(configPath)) {
7047
+ const configDir = dirname2(configPath);
7048
+ if (existsSync3(configPath)) {
6936
7049
  env.stderr.write(`Configuration already exists at ${configPath}
6937
7050
  `);
6938
7051
  env.stderr.write("\n");
@@ -6942,7 +7055,7 @@ async function executeInit(_options, env) {
6942
7055
  `);
6943
7056
  return;
6944
7057
  }
6945
- if (!existsSync2(configDir)) {
7058
+ if (!existsSync3(configDir)) {
6946
7059
  mkdirSync(configDir, { recursive: true });
6947
7060
  }
6948
7061
  writeFileSync(configPath, STARTER_CONFIG, "utf-8");
@@ -6972,6 +7085,7 @@ function registerInitCommand(program, env) {
6972
7085
  }
6973
7086
 
6974
7087
  // src/environment.ts
7088
+ import { join as join3 } from "path";
6975
7089
  import readline from "readline";
6976
7090
  import chalk4 from "chalk";
6977
7091
  import { LLMist, createLogger } from "llmist";
@@ -6984,7 +7098,7 @@ var LOG_LEVEL_MAP = {
6984
7098
  error: 5,
6985
7099
  fatal: 6
6986
7100
  };
6987
- function createLoggerFactory(config) {
7101
+ function createLoggerFactory(config, sessionLogDir) {
6988
7102
  return (name) => {
6989
7103
  const options = { name };
6990
7104
  if (config?.logLevel) {
@@ -6993,12 +7107,10 @@ function createLoggerFactory(config) {
6993
7107
  options.minLevel = LOG_LEVEL_MAP[level];
6994
7108
  }
6995
7109
  }
6996
- if (config?.logReset !== void 0) {
6997
- options.logReset = config.logReset;
6998
- }
6999
- if (config?.logFile) {
7110
+ if (sessionLogDir) {
7111
+ const logFile = join3(sessionLogDir, "session.log.jsonl");
7000
7112
  const originalLogFile = process.env.LLMIST_LOG_FILE;
7001
- process.env.LLMIST_LOG_FILE = config.logFile;
7113
+ process.env.LLMIST_LOG_FILE = logFile;
7002
7114
  const logger = createLogger(options);
7003
7115
  if (originalLogFile === void 0) {
7004
7116
  delete process.env.LLMIST_LOG_FILE;
@@ -7015,7 +7127,7 @@ function createLoggerFactory(config) {
7015
7127
  }
7016
7128
  function createPromptFunction(stdin, stdout) {
7017
7129
  return (question) => {
7018
- return new Promise((resolve2) => {
7130
+ return new Promise((resolve3) => {
7019
7131
  const rl = readline.createInterface({
7020
7132
  input: stdin,
7021
7133
  output: stdout
@@ -7030,12 +7142,12 @@ function createPromptFunction(stdin, stdout) {
7030
7142
  `);
7031
7143
  rl.question(chalk4.green.bold("You: "), (answer) => {
7032
7144
  rl.close();
7033
- resolve2(answer);
7145
+ resolve3(answer);
7034
7146
  });
7035
7147
  });
7036
7148
  };
7037
7149
  }
7038
- function createDefaultEnvironment(loggerConfig) {
7150
+ function createDefaultEnvironment(loggerConfig, sessionLogDir) {
7039
7151
  const isTTY = Boolean(process.stdin.isTTY);
7040
7152
  return {
7041
7153
  argv: process.argv,
@@ -7047,7 +7159,7 @@ function createDefaultEnvironment(loggerConfig) {
7047
7159
  process.exitCode = code;
7048
7160
  },
7049
7161
  loggerConfig,
7050
- createLogger: createLoggerFactory(loggerConfig),
7162
+ createLogger: createLoggerFactory(loggerConfig, sessionLogDir),
7051
7163
  isTTY,
7052
7164
  prompt: isTTY ? createPromptFunction(process.stdin, process.stdout) : async () => {
7053
7165
  throw new Error("Cannot prompt for input: stdin is not a TTY");
@@ -7057,19 +7169,17 @@ function createDefaultEnvironment(loggerConfig) {
7057
7169
 
7058
7170
  // src/custom-command.ts
7059
7171
  function createCommandEnvironment(baseEnv, config) {
7060
- 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;
7061
7173
  if (!hasLoggingConfig) {
7062
7174
  return baseEnv;
7063
7175
  }
7064
7176
  const loggerConfig = {
7065
- logLevel: config["log-level"] ?? baseEnv.loggerConfig?.logLevel,
7066
- logFile: config["log-file"] ?? baseEnv.loggerConfig?.logFile,
7067
- logReset: config["log-reset"] ?? baseEnv.loggerConfig?.logReset
7177
+ logLevel: config["log-level"] ?? baseEnv.loggerConfig?.logLevel
7068
7178
  };
7069
7179
  return {
7070
7180
  ...baseEnv,
7071
7181
  loggerConfig,
7072
- createLogger: createLoggerFactory(loggerConfig)
7182
+ createLogger: createLoggerFactory(loggerConfig, baseEnv.session?.logDir)
7073
7183
  };
7074
7184
  }
7075
7185
  function registerCustomCommand(program, name, config, env, globalSubagents) {
@@ -8005,6 +8115,174 @@ function registerModelsCommand(program, env) {
8005
8115
  );
8006
8116
  }
8007
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
+
8008
8286
  // src/speech-command.ts
8009
8287
  import { writeFileSync as writeFileSync3 } from "fs";
8010
8288
  var DEFAULT_SPEECH_MODEL = "tts-1";
@@ -8098,7 +8376,7 @@ function parseLogLevel(value) {
8098
8376
  }
8099
8377
  function createProgram(env, config) {
8100
8378
  const program = new Command();
8101
- 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({
8102
8380
  writeOut: (str) => env.stdout.write(str),
8103
8381
  writeErr: (str) => env.stderr.write(str)
8104
8382
  });
@@ -8110,6 +8388,7 @@ function createProgram(env, config) {
8110
8388
  registerModelsCommand(program, env);
8111
8389
  registerGadgetCommand(program, env);
8112
8390
  registerInitCommand(program, env);
8391
+ registerConfigCommand(program, env, config);
8113
8392
  if (config) {
8114
8393
  const customNames = getCustomCommandNames(config);
8115
8394
  for (const name of customNames) {
@@ -8123,19 +8402,19 @@ async function runCLI(overrides = {}) {
8123
8402
  const opts = "env" in overrides || "config" in overrides ? overrides : { env: overrides };
8124
8403
  const config = opts.config !== void 0 ? opts.config : loadConfig();
8125
8404
  const envOverrides = opts.env ?? {};
8405
+ const session = await initSession();
8126
8406
  const preParser = new Command();
8127
- 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);
8128
8408
  preParser.parse(process.argv);
8129
8409
  const globalOpts = preParser.opts();
8130
8410
  const loggerConfig = {
8131
- logLevel: globalOpts.logLevel ?? config.global?.["log-level"],
8132
- logFile: globalOpts.logFile ?? config.global?.["log-file"],
8133
- logReset: globalOpts.logReset ?? config.global?.["log-reset"]
8411
+ logLevel: globalOpts.logLevel ?? config.global?.["log-level"]
8134
8412
  };
8135
- const defaultEnv = createDefaultEnvironment(loggerConfig);
8413
+ const defaultEnv = createDefaultEnvironment(loggerConfig, session.logDir);
8136
8414
  const env = {
8137
8415
  ...defaultEnv,
8138
- ...envOverrides
8416
+ ...envOverrides,
8417
+ session
8139
8418
  };
8140
8419
  const program = createProgram(env, config);
8141
8420
  await program.parseAsync(env.argv);