@llmist/cli 16.1.0 → 16.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -110,7 +110,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
110
110
  // package.json
111
111
  var package_default = {
112
112
  name: "@llmist/cli",
113
- version: "16.1.0",
113
+ version: "16.2.1",
114
114
  description: "CLI for llmist - run LLM agents from the command line",
115
115
  type: "module",
116
116
  main: "dist/cli.js",
@@ -167,7 +167,7 @@ var package_default = {
167
167
  node: ">=22.0.0"
168
168
  },
169
169
  dependencies: {
170
- llmist: "^16.1.0",
170
+ llmist: "^16.2.1",
171
171
  "@unblessed/node": "^1.0.0-alpha.23",
172
172
  "diff-match-patch": "^1.0.5",
173
173
  chalk: "^5.6.2",
@@ -182,7 +182,7 @@ var package_default = {
182
182
  zod: "^4.1.12"
183
183
  },
184
184
  devDependencies: {
185
- "@llmist/testing": "^16.1.0",
185
+ "@llmist/testing": "^16.2.1",
186
186
  "@types/diff": "^8.0.0",
187
187
  "@types/diff-match-patch": "^1.0.36",
188
188
  "@types/js-yaml": "^4.0.9",
@@ -211,15 +211,15 @@ var CONVERSION_TIMEOUT_MS = 3e4;
211
211
  var ffmpegCheckPromise = null;
212
212
  async function isFFmpegAvailable() {
213
213
  if (ffmpegCheckPromise !== null) return ffmpegCheckPromise;
214
- ffmpegCheckPromise = new Promise((resolve3) => {
214
+ ffmpegCheckPromise = new Promise((resolve4) => {
215
215
  const proc = spawn("ffmpeg", ["-version"], { stdio: "ignore" });
216
- proc.on("error", () => resolve3(false));
217
- proc.on("close", (code) => resolve3(code === 0));
216
+ proc.on("error", () => resolve4(false));
217
+ proc.on("close", (code) => resolve4(code === 0));
218
218
  });
219
219
  return ffmpegCheckPromise;
220
220
  }
221
221
  async function convertToMp3(input, inputFormat, timeout = CONVERSION_TIMEOUT_MS) {
222
- return new Promise((resolve3) => {
222
+ return new Promise((resolve4) => {
223
223
  let timeoutId;
224
224
  const inputArgs = inputFormat === "pcm16" ? ["-f", "s16le", "-ar", "24000", "-ac", "1"] : ["-f", inputFormat];
225
225
  const proc = spawn(
@@ -244,15 +244,15 @@ async function convertToMp3(input, inputFormat, timeout = CONVERSION_TIMEOUT_MS)
244
244
  proc.stdout.on("data", (chunk) => chunks.push(chunk));
245
245
  proc.on("error", () => {
246
246
  clearTimeout(timeoutId);
247
- resolve3(null);
247
+ resolve4(null);
248
248
  });
249
249
  proc.on("close", (code) => {
250
250
  clearTimeout(timeoutId);
251
- resolve3(code === 0 ? Buffer.concat(chunks) : null);
251
+ resolve4(code === 0 ? Buffer.concat(chunks) : null);
252
252
  });
253
253
  timeoutId = setTimeout(() => {
254
254
  proc.kill();
255
- resolve3(null);
255
+ resolve4(null);
256
256
  }, timeout);
257
257
  proc.stdin.on("error", () => {
258
258
  });
@@ -1413,6 +1413,48 @@ function resolveTemplatesInConfig(config, configPath) {
1413
1413
  return result;
1414
1414
  }
1415
1415
 
1416
+ // src/skills/config-types.ts
1417
+ var SKILLS_CONFIG_KEYS = /* @__PURE__ */ new Set(["sources", "overrides"]);
1418
+ var SKILL_OVERRIDE_KEYS = /* @__PURE__ */ new Set(["model", "enabled"]);
1419
+ function validateSkillsConfig(value, sectionName) {
1420
+ if (typeof value !== "object" || value === null) {
1421
+ throw new Error(`[${sectionName}] must be a table`);
1422
+ }
1423
+ const raw = value;
1424
+ const result = {};
1425
+ for (const [key, val] of Object.entries(raw)) {
1426
+ if (key === "sources") {
1427
+ if (!Array.isArray(val)) {
1428
+ throw new Error(`[${sectionName}].sources must be an array`);
1429
+ }
1430
+ result.sources = val.map(String);
1431
+ } else if (key === "overrides") {
1432
+ if (typeof val !== "object" || val === null) {
1433
+ throw new Error(`[${sectionName}].overrides must be a table`);
1434
+ }
1435
+ result.overrides = {};
1436
+ for (const [skillName, override] of Object.entries(val)) {
1437
+ if (typeof override !== "object" || override === null) {
1438
+ throw new Error(`[${sectionName}].overrides.${skillName} must be a table`);
1439
+ }
1440
+ const overrideObj = override;
1441
+ const skillOverride = {};
1442
+ for (const [oKey, oVal] of Object.entries(overrideObj)) {
1443
+ if (!SKILL_OVERRIDE_KEYS.has(oKey)) {
1444
+ throw new Error(`[${sectionName}].overrides.${skillName}: unknown key "${oKey}"`);
1445
+ }
1446
+ if (oKey === "model") skillOverride.model = String(oVal);
1447
+ if (oKey === "enabled") skillOverride.enabled = Boolean(oVal);
1448
+ }
1449
+ result.overrides[skillName] = skillOverride;
1450
+ }
1451
+ } else if (!SKILLS_CONFIG_KEYS.has(key)) {
1452
+ throw new Error(`[${sectionName}]: unknown key "${key}"`);
1453
+ }
1454
+ }
1455
+ return result;
1456
+ }
1457
+
1416
1458
  // src/config.ts
1417
1459
  function getConfigPath() {
1418
1460
  return join(homedir2(), ".llmist", "cli.toml");
@@ -1443,6 +1485,8 @@ function validateConfig(raw, configPath) {
1443
1485
  result["rate-limits"] = validateRateLimitsConfig(value, key);
1444
1486
  } else if (key === "retry") {
1445
1487
  result.retry = validateRetryConfig(value, key);
1488
+ } else if (key === "skills") {
1489
+ result.skills = validateSkillsConfig(value, key);
1446
1490
  } else {
1447
1491
  result[key] = validateCustomConfig(value, key);
1448
1492
  }
@@ -1607,6 +1651,12 @@ var PathSandboxException = class extends Error {
1607
1651
  function validatePathIsWithinCwd(inputPath) {
1608
1652
  const cwd = process.cwd();
1609
1653
  const resolvedPath = path.resolve(cwd, inputPath);
1654
+ let realCwd;
1655
+ try {
1656
+ realCwd = fs.realpathSync(cwd);
1657
+ } catch {
1658
+ realCwd = cwd;
1659
+ }
1610
1660
  let finalPath;
1611
1661
  try {
1612
1662
  finalPath = fs.realpathSync(resolvedPath);
@@ -1618,8 +1668,8 @@ function validatePathIsWithinCwd(inputPath) {
1618
1668
  throw error;
1619
1669
  }
1620
1670
  }
1621
- const cwdWithSep = cwd + path.sep;
1622
- if (!finalPath.startsWith(cwdWithSep) && finalPath !== cwd) {
1671
+ const cwdWithSep = realCwd + path.sep;
1672
+ if (!finalPath.startsWith(cwdWithSep) && finalPath !== realCwd) {
1623
1673
  throw new PathSandboxException(inputPath, "Path is outside the current working directory");
1624
1674
  }
1625
1675
  return finalPath;
@@ -2670,9 +2720,9 @@ function spawn2(argv, options = {}) {
2670
2720
  options.stderr === "pipe" ? "pipe" : options.stderr ?? "ignore"
2671
2721
  ]
2672
2722
  });
2673
- const exited = new Promise((resolve3, reject) => {
2723
+ const exited = new Promise((resolve4, reject) => {
2674
2724
  proc.on("exit", (code) => {
2675
- resolve3(code ?? 1);
2725
+ resolve4(code ?? 1);
2676
2726
  });
2677
2727
  proc.on("error", (err) => {
2678
2728
  reject(err);
@@ -4858,14 +4908,14 @@ function configToCompleteOptions(config) {
4858
4908
  if (rl["tokens-per-minute"] !== void 0) result.rateLimitTpm = rl["tokens-per-minute"];
4859
4909
  if (rl["tokens-per-day"] !== void 0) result.rateLimitDaily = rl["tokens-per-day"];
4860
4910
  if (rl["safety-margin"] !== void 0) result.rateLimitSafetyMargin = rl["safety-margin"];
4861
- if (rl.enabled === false) result.noRateLimit = true;
4911
+ if (rl.enabled === false) result.rateLimit = false;
4862
4912
  }
4863
4913
  if (config.retry) {
4864
4914
  const r = config.retry;
4865
4915
  if (r.retries !== void 0) result.maxRetries = r.retries;
4866
4916
  if (r["min-timeout"] !== void 0) result.retryMinTimeout = r["min-timeout"];
4867
4917
  if (r["max-timeout"] !== void 0) result.retryMaxTimeout = r["max-timeout"];
4868
- if (r.enabled === false) result.noRetry = true;
4918
+ if (r.enabled === false) result.retry = false;
4869
4919
  }
4870
4920
  if (config.reasoning) {
4871
4921
  result.profileReasoning = config.reasoning;
@@ -4900,14 +4950,14 @@ function configToAgentOptions(config) {
4900
4950
  if (rl["tokens-per-minute"] !== void 0) result.rateLimitTpm = rl["tokens-per-minute"];
4901
4951
  if (rl["tokens-per-day"] !== void 0) result.rateLimitDaily = rl["tokens-per-day"];
4902
4952
  if (rl["safety-margin"] !== void 0) result.rateLimitSafetyMargin = rl["safety-margin"];
4903
- if (rl.enabled === false) result.noRateLimit = true;
4953
+ if (rl.enabled === false) result.rateLimit = false;
4904
4954
  }
4905
4955
  if (config.retry) {
4906
4956
  const r = config.retry;
4907
4957
  if (r.retries !== void 0) result.maxRetries = r.retries;
4908
4958
  if (r["min-timeout"] !== void 0) result.retryMinTimeout = r["min-timeout"];
4909
4959
  if (r["max-timeout"] !== void 0) result.retryMaxTimeout = r["max-timeout"];
4910
- if (r.enabled === false) result.noRetry = true;
4960
+ if (r.enabled === false) result.retry = false;
4911
4961
  }
4912
4962
  if (config.reasoning) {
4913
4963
  result.profileReasoning = config.reasoning;
@@ -4951,7 +5001,7 @@ function detectProvider(model) {
4951
5001
  }
4952
5002
  }
4953
5003
  function resolveRateLimitConfig(options, globalConfig, profileConfig, model) {
4954
- if (options.noRateLimit === true) {
5004
+ if (options.rateLimit === false) {
4955
5005
  return { enabled: false, safetyMargin: 0.8 };
4956
5006
  }
4957
5007
  let resolved;
@@ -5076,12 +5126,82 @@ function resolveRetryConfig(options, globalConfig, profileConfig) {
5076
5126
  if (options.retryMaxTimeout !== void 0) {
5077
5127
  resolved.maxTimeout = options.retryMaxTimeout;
5078
5128
  }
5079
- if (options.noRetry === true) {
5129
+ if (options.retry === false) {
5080
5130
  resolved.enabled = false;
5081
5131
  }
5082
5132
  return resolved;
5083
5133
  }
5084
5134
 
5135
+ // src/skills/skill-manager.ts
5136
+ import { homedir as homedir3 } from "os";
5137
+ import { join as join2, resolve as resolve3 } from "path";
5138
+ import { discoverSkills, loadSkillsFromDirectory } from "llmist";
5139
+ var CLISkillManager = class {
5140
+ /**
5141
+ * Load all skills from standard locations and configured sources.
5142
+ *
5143
+ * @param config - Skills configuration from cli.toml [skills] section
5144
+ * @param projectDir - Project directory (cwd)
5145
+ */
5146
+ async loadAll(config, projectDir) {
5147
+ const registry = discoverSkills({
5148
+ projectDir: projectDir ?? process.cwd()
5149
+ });
5150
+ if (config?.sources) {
5151
+ for (const source of config.sources) {
5152
+ const resolvedSource = this.resolveSource(source);
5153
+ const skills = loadSkillsFromDirectory(resolvedSource, {
5154
+ type: "directory",
5155
+ path: resolvedSource
5156
+ });
5157
+ registry.registerMany(skills);
5158
+ }
5159
+ }
5160
+ if (config?.overrides) {
5161
+ for (const [skillName, override] of Object.entries(config.overrides)) {
5162
+ if (override.enabled === false) {
5163
+ registry.remove(skillName);
5164
+ }
5165
+ }
5166
+ }
5167
+ return registry;
5168
+ }
5169
+ /**
5170
+ * Resolve a source string to an absolute directory path.
5171
+ */
5172
+ resolveSource(source) {
5173
+ if (source.startsWith("~")) {
5174
+ return join2(homedir3(), source.slice(1));
5175
+ }
5176
+ return resolve3(source);
5177
+ }
5178
+ };
5179
+
5180
+ // src/skills/slash-handler.ts
5181
+ function parseSlashCommand(input, registry) {
5182
+ const trimmed = input.trim();
5183
+ if (!trimmed.startsWith("/")) {
5184
+ return { isSkillInvocation: false };
5185
+ }
5186
+ const match = trimmed.match(/^\/(\S+)(?:\s+(.*))?$/);
5187
+ if (!match) {
5188
+ return { isSkillInvocation: false };
5189
+ }
5190
+ const [, commandName, args] = match;
5191
+ if (commandName === "skills") {
5192
+ return { isSkillInvocation: true, isListCommand: true };
5193
+ }
5194
+ const skill = registry.get(commandName);
5195
+ if (!skill || !skill.isUserInvocable) {
5196
+ return { isSkillInvocation: false };
5197
+ }
5198
+ return {
5199
+ isSkillInvocation: true,
5200
+ skillName: commandName,
5201
+ arguments: args?.trim()
5202
+ };
5203
+ }
5204
+
5085
5205
  // src/subagent-config.ts
5086
5206
  var INHERIT_MODEL = "inherit";
5087
5207
  function resolveSubagentConfig(subagentName, parentModel, profileConfig, globalConfig) {
@@ -7448,10 +7568,10 @@ var HintsBar = class {
7448
7568
  import { spawnSync as spawnSync2 } from "child_process";
7449
7569
  import { readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
7450
7570
  import { tmpdir } from "os";
7451
- import { join as join2 } from "path";
7571
+ import { join as join3 } from "path";
7452
7572
  function openEditorSync(initialContent = "") {
7453
7573
  const editor = process.env.VISUAL || process.env.EDITOR || "vi";
7454
- const tmpFile = join2(tmpdir(), `llmist-input-${Date.now()}.txt`);
7574
+ const tmpFile = join3(tmpdir(), `llmist-input-${Date.now()}.txt`);
7455
7575
  writeFileSync2(tmpFile, initialContent, "utf-8");
7456
7576
  try {
7457
7577
  const parts = editor.split(/\s+/);
@@ -7686,11 +7806,11 @@ var InputHandler = class {
7686
7806
  * @returns Promise that resolves with user's response
7687
7807
  */
7688
7808
  async waitForInput(question, gadgetName) {
7689
- return new Promise((resolve3, reject) => {
7809
+ return new Promise((resolve4, reject) => {
7690
7810
  this.pendingInput = {
7691
7811
  question,
7692
7812
  gadgetName,
7693
- resolve: resolve3,
7813
+ resolve: resolve4,
7694
7814
  reject
7695
7815
  };
7696
7816
  this.setActive();
@@ -7705,11 +7825,11 @@ var InputHandler = class {
7705
7825
  * @returns Promise that resolves with user's prompt
7706
7826
  */
7707
7827
  async waitForPrompt() {
7708
- return new Promise((resolve3, reject) => {
7828
+ return new Promise((resolve4, reject) => {
7709
7829
  this.pendingInput = {
7710
7830
  question: "",
7711
7831
  gadgetName: "prompt",
7712
- resolve: resolve3,
7832
+ resolve: resolve4,
7713
7833
  reject
7714
7834
  };
7715
7835
  this.setPendingPrompt();
@@ -7805,10 +7925,10 @@ var InputHandler = class {
7805
7925
  return;
7806
7926
  }
7807
7927
  if (this.pendingInput) {
7808
- const { resolve: resolve3 } = this.pendingInput;
7928
+ const { resolve: resolve4 } = this.pendingInput;
7809
7929
  this.pendingInput = null;
7810
7930
  this.setIdle();
7811
- resolve3(value);
7931
+ resolve4(value);
7812
7932
  } else if (this.midSessionHandler) {
7813
7933
  this.midSessionHandler(value);
7814
7934
  this.setIdle();
@@ -8266,7 +8386,7 @@ import { Box as Box3 } from "@unblessed/node";
8266
8386
  var MAX_PREVIEW_LINES = 10;
8267
8387
  var MAX_PARAM_VALUE_LENGTH = 60;
8268
8388
  function showApprovalDialog(screen, context) {
8269
- return new Promise((resolve3) => {
8389
+ return new Promise((resolve4) => {
8270
8390
  const content = buildDialogContent(context);
8271
8391
  const dialog = new Box3({
8272
8392
  parent: screen,
@@ -8321,7 +8441,7 @@ function showApprovalDialog(screen, context) {
8321
8441
  if (response) {
8322
8442
  dialog.destroy();
8323
8443
  screen.render();
8324
- resolve3(response);
8444
+ resolve4(response);
8325
8445
  }
8326
8446
  };
8327
8447
  dialog.on("keypress", handleKey);
@@ -8391,7 +8511,7 @@ var WHITE = "\x1B[37m";
8391
8511
  function showRawViewer(options) {
8392
8512
  let closeCallback = () => {
8393
8513
  };
8394
- const closed = new Promise((resolve3) => {
8514
+ const closed = new Promise((resolve4) => {
8395
8515
  const {
8396
8516
  screen,
8397
8517
  mode,
@@ -8487,7 +8607,7 @@ ${error}`;
8487
8607
  helpBar.destroy();
8488
8608
  viewer.destroy();
8489
8609
  screen.render();
8490
- resolve3();
8610
+ resolve4();
8491
8611
  };
8492
8612
  closeCallback = close;
8493
8613
  viewer.key(["escape", "q"], close);
@@ -9888,6 +10008,14 @@ async function executeAgent(promptArg, options, env, commandName) {
9888
10008
  registry.registerByClass(gadget);
9889
10009
  }
9890
10010
  }
10011
+ let skillsConfig;
10012
+ try {
10013
+ const fullConfig = loadConfig();
10014
+ skillsConfig = fullConfig.skills;
10015
+ } catch {
10016
+ }
10017
+ const skillManager = new CLISkillManager();
10018
+ const skillRegistry = await skillManager.loadAll(skillsConfig);
9891
10019
  let tui = null;
9892
10020
  if (useTUI) {
9893
10021
  tui = await TUIApp.create({
@@ -10121,6 +10249,9 @@ ${ctx.gadgetName} requires interactive approval. Enable TUI mode or adjust 'gadg
10121
10249
  if (options.temperature !== void 0) {
10122
10250
  builder.withTemperature(options.temperature);
10123
10251
  }
10252
+ if (skillRegistry.size > 0) {
10253
+ builder.withSkills(skillRegistry);
10254
+ }
10124
10255
  if (options.reasoning === false) {
10125
10256
  builder.withoutReasoning();
10126
10257
  } else if (options.reasoning !== void 0 || options.reasoningBudget !== void 0) {
@@ -10197,6 +10328,29 @@ ${ctx.gadgetName} requires interactive approval. Enable TUI mode or adjust 'gadg
10197
10328
  );
10198
10329
  let currentAgent = null;
10199
10330
  const runAgentWithPrompt = async (userPrompt) => {
10331
+ builder.clearPreActivatedSkills();
10332
+ if (skillRegistry.size > 0 && userPrompt.startsWith("/")) {
10333
+ const slashResult = parseSlashCommand(userPrompt, skillRegistry);
10334
+ if (slashResult.isSkillInvocation) {
10335
+ if (slashResult.isListCommand) {
10336
+ const skills = skillRegistry.getUserInvocable();
10337
+ const lines = skills.map((s) => ` /${s.name} \u2014 ${s.description}`);
10338
+ const msg = skills.length > 0 ? `Available skills:
10339
+ ${lines.join("\n")}` : "No skills available.";
10340
+ if (tui) {
10341
+ tui.showUserMessage(`/skills`);
10342
+ tui.showUserMessage(msg);
10343
+ } else {
10344
+ env.stdout.write(`${msg}
10345
+ `);
10346
+ }
10347
+ return;
10348
+ }
10349
+ if (slashResult.skillName) {
10350
+ builder.withSkill(slashResult.skillName, slashResult.arguments);
10351
+ }
10352
+ }
10353
+ }
10200
10354
  if (tui) {
10201
10355
  tui.resetAbort();
10202
10356
  tui.startNewSession();
@@ -10321,11 +10475,11 @@ import {
10321
10475
 
10322
10476
  // src/llm-logging.ts
10323
10477
  import { mkdir, writeFile as writeFile2 } from "fs/promises";
10324
- import { join as join3 } from "path";
10478
+ import { join as join4 } from "path";
10325
10479
  import { formatCallNumber as formatCallNumber2, formatLlmRequest } from "llmist";
10326
10480
  async function writeLogFile(dir, filename, content) {
10327
10481
  await mkdir(dir, { recursive: true });
10328
- await writeFile2(join3(dir, filename), content, "utf-8");
10482
+ await writeFile2(join4(dir, filename), content, "utf-8");
10329
10483
  }
10330
10484
 
10331
10485
  // src/complete-command.ts
@@ -10603,7 +10757,7 @@ System Prompt (${chars.toLocaleString()} chars, ${lines} lines):
10603
10757
  }
10604
10758
 
10605
10759
  // src/environment.ts
10606
- import { join as join4 } from "path";
10760
+ import { join as join5 } from "path";
10607
10761
  import readline from "readline";
10608
10762
  import chalk7 from "chalk";
10609
10763
  import { createLogger, LLMist } from "llmist";
@@ -10626,7 +10780,7 @@ function createLoggerFactory(config, sessionLogDir) {
10626
10780
  }
10627
10781
  }
10628
10782
  if (sessionLogDir) {
10629
- const logFile = join4(sessionLogDir, "session.log.jsonl");
10783
+ const logFile = join5(sessionLogDir, "session.log.jsonl");
10630
10784
  const originalLogFile = process.env.LLMIST_LOG_FILE;
10631
10785
  process.env.LLMIST_LOG_FILE = logFile;
10632
10786
  const logger = createLogger(options);
@@ -10645,7 +10799,7 @@ function createLoggerFactory(config, sessionLogDir) {
10645
10799
  }
10646
10800
  function createPromptFunction(stdin, stdout) {
10647
10801
  return (question) => {
10648
- return new Promise((resolve3) => {
10802
+ return new Promise((resolve4) => {
10649
10803
  const rl = readline.createInterface({
10650
10804
  input: stdin,
10651
10805
  output: stdout
@@ -10660,7 +10814,7 @@ function createPromptFunction(stdin, stdout) {
10660
10814
  `);
10661
10815
  rl.question(chalk7.green.bold("You: "), (answer) => {
10662
10816
  rl.close();
10663
- resolve3(answer);
10817
+ resolve4(answer);
10664
10818
  });
10665
10819
  });
10666
10820
  };
@@ -11729,8 +11883,8 @@ function registerModelsCommand(program, env) {
11729
11883
  // src/session.ts
11730
11884
  import { existsSync as existsSync4 } from "fs";
11731
11885
  import { mkdir as mkdir2 } from "fs/promises";
11732
- import { homedir as homedir3 } from "os";
11733
- import { join as join5 } from "path";
11886
+ import { homedir as homedir4 } from "os";
11887
+ import { join as join6 } from "path";
11734
11888
 
11735
11889
  // src/session-names.ts
11736
11890
  var ADJECTIVES = [
@@ -11865,16 +12019,16 @@ function generateSessionName() {
11865
12019
 
11866
12020
  // src/session.ts
11867
12021
  var currentSession;
11868
- var SESSION_LOGS_BASE = join5(homedir3(), ".llmist", "logs");
12022
+ var SESSION_LOGS_BASE = join6(homedir4(), ".llmist", "logs");
11869
12023
  function findUniqueName(baseName) {
11870
- const baseDir = join5(SESSION_LOGS_BASE, baseName);
12024
+ const baseDir = join6(SESSION_LOGS_BASE, baseName);
11871
12025
  if (!existsSync4(baseDir)) {
11872
12026
  return baseName;
11873
12027
  }
11874
12028
  let suffix = 2;
11875
12029
  while (suffix < 1e3) {
11876
12030
  const name = `${baseName}-${suffix}`;
11877
- const dir = join5(SESSION_LOGS_BASE, name);
12031
+ const dir = join6(SESSION_LOGS_BASE, name);
11878
12032
  if (!existsSync4(dir)) {
11879
12033
  return name;
11880
12034
  }
@@ -11888,12 +12042,101 @@ async function initSession() {
11888
12042
  }
11889
12043
  const baseName = generateSessionName();
11890
12044
  const name = findUniqueName(baseName);
11891
- const logDir = join5(SESSION_LOGS_BASE, name);
12045
+ const logDir = join6(SESSION_LOGS_BASE, name);
11892
12046
  await mkdir2(logDir, { recursive: true });
11893
12047
  currentSession = { name, logDir };
11894
12048
  return currentSession;
11895
12049
  }
11896
12050
 
12051
+ // src/skills/skill-command.ts
12052
+ function registerSkillCommand(program, env) {
12053
+ const skillCmd = program.command("skill").description("Manage Agent Skills (SKILL.md)");
12054
+ skillCmd.command("list").description("List available skills").action(async () => {
12055
+ const config = safeLoadConfig();
12056
+ const manager = new CLISkillManager();
12057
+ const registry = await manager.loadAll(config?.skills);
12058
+ const skills = registry.getAll();
12059
+ if (skills.length === 0) {
12060
+ env.stdout.write("No skills found.\n");
12061
+ env.stdout.write(
12062
+ "Add skills to ~/.llmist/skills/ or configure [skills].sources in ~/.llmist/cli.toml\n"
12063
+ );
12064
+ return;
12065
+ }
12066
+ env.stdout.write(`Found ${skills.length} skill(s):
12067
+
12068
+ `);
12069
+ for (const skill of skills) {
12070
+ const flags = [];
12071
+ if (!skill.isModelInvocable) flags.push("user-only");
12072
+ if (!skill.isUserInvocable) flags.push("background");
12073
+ if (skill.metadata.context === "fork") flags.push("fork");
12074
+ if (skill.metadata.model) flags.push(`model:${skill.metadata.model}`);
12075
+ const flagStr = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
12076
+ const desc = skill.description.length > 80 ? `${skill.description.slice(0, 77)}...` : skill.description;
12077
+ env.stdout.write(` /${skill.name}${flagStr}
12078
+ `);
12079
+ env.stdout.write(` ${desc}
12080
+
12081
+ `);
12082
+ }
12083
+ });
12084
+ skillCmd.command("info <name>").description("Show detailed skill information").action(async (name) => {
12085
+ const config = safeLoadConfig();
12086
+ const manager = new CLISkillManager();
12087
+ const registry = await manager.loadAll(config?.skills);
12088
+ const skill = registry.get(name);
12089
+ if (!skill) {
12090
+ env.stderr.write(`Skill not found: ${name}
12091
+ `);
12092
+ env.stderr.write(`Available skills: ${registry.getNames().join(", ") || "(none)"}
12093
+ `);
12094
+ process.exitCode = 1;
12095
+ return;
12096
+ }
12097
+ env.stdout.write(`Skill: ${skill.name}
12098
+ `);
12099
+ env.stdout.write(`Description: ${skill.description}
12100
+ `);
12101
+ env.stdout.write(`Source: ${skill.sourcePath}
12102
+ `);
12103
+ if (skill.metadata.model) env.stdout.write(`Model: ${skill.metadata.model}
12104
+ `);
12105
+ if (skill.metadata.context) env.stdout.write(`Context: ${skill.metadata.context}
12106
+ `);
12107
+ if (skill.metadata.agent) env.stdout.write(`Agent: ${skill.metadata.agent}
12108
+ `);
12109
+ if (skill.metadata.allowedTools) {
12110
+ env.stdout.write(`Allowed tools: ${skill.metadata.allowedTools.join(", ")}
12111
+ `);
12112
+ }
12113
+ if (skill.metadata.paths) {
12114
+ env.stdout.write(`Paths: ${skill.metadata.paths.join(", ")}
12115
+ `);
12116
+ }
12117
+ const resources = skill.getResources();
12118
+ if (resources.length > 0) {
12119
+ env.stdout.write(`Resources: ${resources.length}
12120
+ `);
12121
+ for (const r of resources) {
12122
+ env.stdout.write(` ${r.category}/${r.relativePath}
12123
+ `);
12124
+ }
12125
+ }
12126
+ env.stdout.write("\n--- Instructions ---\n\n");
12127
+ const instructions = await skill.getInstructions();
12128
+ env.stdout.write(`${instructions}
12129
+ `);
12130
+ });
12131
+ }
12132
+ function safeLoadConfig() {
12133
+ try {
12134
+ return loadConfig();
12135
+ } catch {
12136
+ return void 0;
12137
+ }
12138
+ }
12139
+
11897
12140
  // src/speech-command.ts
11898
12141
  import { writeFileSync as writeFileSync5 } from "fs";
11899
12142
  var DEFAULT_SPEECH_MODEL = "tts-1";
@@ -12005,6 +12248,7 @@ function createProgram(env, config) {
12005
12248
  registerVisionCommand(program, env);
12006
12249
  registerModelsCommand(program, env);
12007
12250
  registerGadgetCommand(program, env);
12251
+ registerSkillCommand(program, env);
12008
12252
  registerInitCommand(program, env);
12009
12253
  registerConfigCommand(program, env, config);
12010
12254
  if (config) {