@llmist/cli 12.3.6 → 13.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
@@ -40,7 +40,18 @@ var OPTION_FLAGS = {
40
40
  voice: "--voice <name>",
41
41
  speechFormat: "--format <format>",
42
42
  speechSpeed: "--speed <value>",
43
- speechOutput: "-o, --output <path>"
43
+ speechOutput: "-o, --output <path>",
44
+ // Rate limiting options
45
+ rateLimitRpm: "--rate-limit-rpm <count>",
46
+ rateLimitTpm: "--rate-limit-tpm <count>",
47
+ rateLimitDaily: "--rate-limit-daily <count>",
48
+ rateLimitSafetyMargin: "--rate-limit-safety-margin <value>",
49
+ noRateLimit: "--no-rate-limit",
50
+ // Retry options
51
+ maxRetries: "--max-retries <count>",
52
+ retryMinTimeout: "--retry-min-timeout <ms>",
53
+ retryMaxTimeout: "--retry-max-timeout <ms>",
54
+ noRetry: "--no-retry"
44
55
  };
45
56
  var OPTION_DESCRIPTIONS = {
46
57
  model: "Model identifier, e.g. openai:gpt-5-nano or anthropic:claude-sonnet-4-5.",
@@ -66,7 +77,18 @@ var OPTION_DESCRIPTIONS = {
66
77
  voice: "Voice name for speech generation, e.g. 'nova', 'alloy', 'Zephyr'.",
67
78
  speechFormat: "Audio format: 'mp3', 'opus', 'aac', 'flac', 'wav', 'pcm'.",
68
79
  speechSpeed: "Speech speed multiplier (0.25 to 4.0, default 1.0).",
69
- speechOutput: "Output path for audio file. Defaults to stdout if not specified."
80
+ speechOutput: "Output path for audio file. Defaults to stdout if not specified.",
81
+ // Rate limiting descriptions
82
+ rateLimitRpm: "Maximum requests per minute (RPM). Overrides config and defaults.",
83
+ rateLimitTpm: "Maximum tokens per minute (TPM). Overrides config and defaults.",
84
+ rateLimitDaily: "Maximum tokens per day. Useful for Gemini free tier.",
85
+ rateLimitSafetyMargin: "Safety margin (0-1). Start throttling at this percentage of limit.",
86
+ noRateLimit: "Disable rate limiting entirely.",
87
+ // Retry descriptions
88
+ maxRetries: "Maximum retry attempts for failed API calls.",
89
+ retryMinTimeout: "Initial retry delay in milliseconds.",
90
+ retryMaxTimeout: "Maximum retry delay in milliseconds.",
91
+ noRetry: "Disable retry logic for API calls."
70
92
  };
71
93
  var SUMMARY_PREFIX = "[llmist]";
72
94
 
@@ -76,7 +98,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
76
98
  // package.json
77
99
  var package_default = {
78
100
  name: "@llmist/cli",
79
- version: "12.3.6",
101
+ version: "13.0.0",
80
102
  description: "CLI for llmist - run LLM agents from the command line",
81
103
  type: "module",
82
104
  main: "dist/cli.js",
@@ -132,7 +154,7 @@ var package_default = {
132
154
  node: ">=22.0.0"
133
155
  },
134
156
  dependencies: {
135
- llmist: "^12.3.6",
157
+ llmist: "^13.0.0",
136
158
  "@unblessed/node": "^1.0.0-alpha.23",
137
159
  chalk: "^5.6.2",
138
160
  commander: "^12.1.0",
@@ -146,7 +168,7 @@ var package_default = {
146
168
  zod: "^4.1.12"
147
169
  },
148
170
  devDependencies: {
149
- "@llmist/testing": "^12.3.6",
171
+ "@llmist/testing": "^13.0.0",
150
172
  "@types/diff": "^8.0.0",
151
173
  "@types/js-yaml": "^4.0.9",
152
174
  "@types/marked-terminal": "^6.1.1",
@@ -399,6 +421,8 @@ var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
399
421
  "inherits",
400
422
  "log-level",
401
423
  "log-llm-requests",
424
+ "rate-limits",
425
+ "retry",
402
426
  "type"
403
427
  // Allowed for inheritance compatibility, ignored for built-in commands
404
428
  ]);
@@ -429,6 +453,8 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
429
453
  "inherits",
430
454
  "log-level",
431
455
  "log-llm-requests",
456
+ "rate-limits",
457
+ "retry",
432
458
  "type"
433
459
  // Allowed for inheritance compatibility, ignored for built-in commands
434
460
  ]);
@@ -554,7 +580,110 @@ function validateSubagentConfigMap(value, section) {
554
580
  }
555
581
  const result = {};
556
582
  for (const [subagentName, config] of Object.entries(value)) {
557
- result[subagentName] = validateSingleSubagentConfig(config, subagentName, `${section}.subagents`);
583
+ result[subagentName] = validateSingleSubagentConfig(
584
+ config,
585
+ subagentName,
586
+ `${section}.subagents`
587
+ );
588
+ }
589
+ return result;
590
+ }
591
+ var RATE_LIMITS_CONFIG_KEYS = /* @__PURE__ */ new Set([
592
+ "requests-per-minute",
593
+ "tokens-per-minute",
594
+ "tokens-per-day",
595
+ "safety-margin",
596
+ "enabled"
597
+ ]);
598
+ function validateRateLimitsConfig(value, section) {
599
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
600
+ throw new ConfigError(`[${section}] must be a table`);
601
+ }
602
+ const raw = value;
603
+ const result = {};
604
+ for (const [key, val] of Object.entries(raw)) {
605
+ if (!RATE_LIMITS_CONFIG_KEYS.has(key)) {
606
+ throw new ConfigError(`[${section}] has unknown key: ${key}`);
607
+ }
608
+ switch (key) {
609
+ case "requests-per-minute":
610
+ result["requests-per-minute"] = validateNumber(val, key, section, {
611
+ integer: true,
612
+ min: 1
613
+ });
614
+ break;
615
+ case "tokens-per-minute":
616
+ result["tokens-per-minute"] = validateNumber(val, key, section, { integer: true, min: 1 });
617
+ break;
618
+ case "tokens-per-day":
619
+ result["tokens-per-day"] = validateNumber(val, key, section, { integer: true, min: 1 });
620
+ break;
621
+ case "safety-margin":
622
+ result["safety-margin"] = validateNumber(val, key, section, { min: 0, max: 1 });
623
+ break;
624
+ case "enabled":
625
+ result.enabled = validateBoolean(val, key, section);
626
+ break;
627
+ }
628
+ }
629
+ if (result["requests-per-minute"] && result["requests-per-minute"] > 1e4) {
630
+ console.warn(
631
+ `\u26A0\uFE0F Warning: [${section}].requests-per-minute is very high (${result["requests-per-minute"]}). Make sure your API tier supports this rate.`
632
+ );
633
+ }
634
+ if (result["tokens-per-minute"] && result["tokens-per-minute"] > 5e6) {
635
+ console.warn(
636
+ `\u26A0\uFE0F Warning: [${section}].tokens-per-minute is very high (${result["tokens-per-minute"]}). Make sure your API tier supports this rate.`
637
+ );
638
+ }
639
+ return result;
640
+ }
641
+ var RETRY_CONFIG_KEYS = /* @__PURE__ */ new Set([
642
+ "enabled",
643
+ "retries",
644
+ "min-timeout",
645
+ "max-timeout",
646
+ "factor",
647
+ "randomize",
648
+ "respect-retry-after",
649
+ "max-retry-after-ms"
650
+ ]);
651
+ function validateRetryConfig(value, section) {
652
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
653
+ throw new ConfigError(`[${section}] must be a table`);
654
+ }
655
+ const raw = value;
656
+ const result = {};
657
+ for (const [key, val] of Object.entries(raw)) {
658
+ if (!RETRY_CONFIG_KEYS.has(key)) {
659
+ throw new ConfigError(`[${section}] has unknown key: ${key}`);
660
+ }
661
+ switch (key) {
662
+ case "enabled":
663
+ result.enabled = validateBoolean(val, key, section);
664
+ break;
665
+ case "retries":
666
+ result.retries = validateNumber(val, key, section, { integer: true, min: 0 });
667
+ break;
668
+ case "min-timeout":
669
+ result["min-timeout"] = validateNumber(val, key, section, { integer: true, min: 0 });
670
+ break;
671
+ case "max-timeout":
672
+ result["max-timeout"] = validateNumber(val, key, section, { integer: true, min: 0 });
673
+ break;
674
+ case "factor":
675
+ result.factor = validateNumber(val, key, section, { min: 1 });
676
+ break;
677
+ case "randomize":
678
+ result.randomize = validateBoolean(val, key, section);
679
+ break;
680
+ case "respect-retry-after":
681
+ result["respect-retry-after"] = validateBoolean(val, key, section);
682
+ break;
683
+ case "max-retry-after-ms":
684
+ result["max-retry-after-ms"] = validateNumber(val, key, section, { integer: true, min: 0 });
685
+ break;
686
+ }
558
687
  }
559
688
  return result;
560
689
  }
@@ -610,7 +739,9 @@ function validateInitialGadgets(value, section) {
610
739
  }
611
740
  const entryObj = entry;
612
741
  if (!("gadget" in entryObj)) {
613
- throw new ConfigError(`[${section}].initial-gadgets[${i}] is missing required field 'gadget'`);
742
+ throw new ConfigError(
743
+ `[${section}].initial-gadgets[${i}] is missing required field 'gadget'`
744
+ );
614
745
  }
615
746
  if (typeof entryObj.gadget !== "string") {
616
747
  throw new ConfigError(`[${section}].initial-gadgets[${i}].gadget must be a string`);
@@ -624,7 +755,9 @@ function validateInitialGadgets(value, section) {
624
755
  throw new ConfigError(`[${section}].initial-gadgets[${i}].parameters must be a table`);
625
756
  }
626
757
  if (!("result" in entryObj)) {
627
- throw new ConfigError(`[${section}].initial-gadgets[${i}] is missing required field 'result'`);
758
+ throw new ConfigError(
759
+ `[${section}].initial-gadgets[${i}] is missing required field 'result'`
760
+ );
628
761
  }
629
762
  if (typeof entryObj.result !== "string") {
630
763
  throw new ConfigError(`[${section}].initial-gadgets[${i}].result must be a string`);
@@ -705,7 +838,20 @@ function validateCompleteConfig(raw, section) {
705
838
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
706
839
  }
707
840
  if ("log-llm-requests" in rawObj) {
708
- result["log-llm-requests"] = validateBoolean(rawObj["log-llm-requests"], "log-llm-requests", section);
841
+ result["log-llm-requests"] = validateBoolean(
842
+ rawObj["log-llm-requests"],
843
+ "log-llm-requests",
844
+ section
845
+ );
846
+ }
847
+ if ("rate-limits" in rawObj) {
848
+ result["rate-limits"] = validateRateLimitsConfig(
849
+ rawObj["rate-limits"],
850
+ `${section}.rate-limits`
851
+ );
852
+ }
853
+ if ("retry" in rawObj) {
854
+ result.retry = validateRetryConfig(rawObj.retry, `${section}.retry`);
709
855
  }
710
856
  return result;
711
857
  }
@@ -789,7 +935,20 @@ function validateAgentConfig(raw, section) {
789
935
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
790
936
  }
791
937
  if ("log-llm-requests" in rawObj) {
792
- result["log-llm-requests"] = validateBoolean(rawObj["log-llm-requests"], "log-llm-requests", section);
938
+ result["log-llm-requests"] = validateBoolean(
939
+ rawObj["log-llm-requests"],
940
+ "log-llm-requests",
941
+ section
942
+ );
943
+ }
944
+ if ("rate-limits" in rawObj) {
945
+ result["rate-limits"] = validateRateLimitsConfig(
946
+ rawObj["rate-limits"],
947
+ `${section}.rate-limits`
948
+ );
949
+ }
950
+ if ("retry" in rawObj) {
951
+ result.retry = validateRetryConfig(rawObj.retry, `${section}.retry`);
793
952
  }
794
953
  return result;
795
954
  }
@@ -959,7 +1118,11 @@ function validateCustomConfig(raw, section) {
959
1118
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
960
1119
  }
961
1120
  if ("log-llm-requests" in rawObj) {
962
- result["log-llm-requests"] = validateBoolean(rawObj["log-llm-requests"], "log-llm-requests", section);
1121
+ result["log-llm-requests"] = validateBoolean(
1122
+ rawObj["log-llm-requests"],
1123
+ "log-llm-requests",
1124
+ section
1125
+ );
963
1126
  }
964
1127
  Object.assign(result, validateLoggingConfig(rawObj, section));
965
1128
  return result;
@@ -999,6 +1162,10 @@ function validateConfig(raw, configPath) {
999
1162
  result.prompts = validatePromptsConfig(value, key);
1000
1163
  } else if (key === "subagents") {
1001
1164
  result.subagents = validateGlobalSubagentConfig(value, key);
1165
+ } else if (key === "rate-limits") {
1166
+ result["rate-limits"] = validateRateLimitsConfig(value, key);
1167
+ } else if (key === "retry") {
1168
+ result.retry = validateRetryConfig(value, key);
1002
1169
  } else {
1003
1170
  result[key] = validateCustomConfig(value, key);
1004
1171
  }
@@ -1046,7 +1213,9 @@ function getCustomCommandNames(config) {
1046
1213
  "image",
1047
1214
  "speech",
1048
1215
  "prompts",
1049
- "subagents"
1216
+ "subagents",
1217
+ "rate-limits",
1218
+ "retry"
1050
1219
  ]);
1051
1220
  return Object.keys(config).filter((key) => !reserved.has(key));
1052
1221
  }
@@ -1191,7 +1360,7 @@ function resolveInheritance(config, configPath) {
1191
1360
  if (resolvedGadgets.length > 0) {
1192
1361
  merged.gadgets = resolvedGadgets;
1193
1362
  }
1194
- delete merged["gadget"];
1363
+ delete merged.gadget;
1195
1364
  delete merged["gadget-add"];
1196
1365
  delete merged["gadget-remove"];
1197
1366
  resolving.delete(name);
@@ -3483,7 +3652,42 @@ function addCompleteOptions(cmd, defaults) {
3483
3652
  OPTION_FLAGS.logLlmRequests,
3484
3653
  OPTION_DESCRIPTIONS.logLlmRequests,
3485
3654
  defaults?.["log-llm-requests"]
3486
- ).option(OPTION_FLAGS.inputImage, OPTION_DESCRIPTIONS.inputImage).option(OPTION_FLAGS.inputAudio, OPTION_DESCRIPTIONS.inputAudio);
3655
+ ).option(OPTION_FLAGS.inputImage, OPTION_DESCRIPTIONS.inputImage).option(OPTION_FLAGS.inputAudio, OPTION_DESCRIPTIONS.inputAudio).option(
3656
+ OPTION_FLAGS.rateLimitRpm,
3657
+ OPTION_DESCRIPTIONS.rateLimitRpm,
3658
+ createNumericParser({ label: "RPM", integer: true, min: 1 }),
3659
+ defaults?.["rate-limits"]?.["requests-per-minute"]
3660
+ ).option(
3661
+ OPTION_FLAGS.rateLimitTpm,
3662
+ OPTION_DESCRIPTIONS.rateLimitTpm,
3663
+ createNumericParser({ label: "TPM", integer: true, min: 1 }),
3664
+ defaults?.["rate-limits"]?.["tokens-per-minute"]
3665
+ ).option(
3666
+ OPTION_FLAGS.rateLimitDaily,
3667
+ OPTION_DESCRIPTIONS.rateLimitDaily,
3668
+ createNumericParser({ label: "Daily tokens", integer: true, min: 1 }),
3669
+ defaults?.["rate-limits"]?.["tokens-per-day"]
3670
+ ).option(
3671
+ OPTION_FLAGS.rateLimitSafetyMargin,
3672
+ OPTION_DESCRIPTIONS.rateLimitSafetyMargin,
3673
+ createNumericParser({ label: "Safety margin", min: 0, max: 1 }),
3674
+ defaults?.["rate-limits"]?.["safety-margin"]
3675
+ ).option(OPTION_FLAGS.noRateLimit, OPTION_DESCRIPTIONS.noRateLimit).option(
3676
+ OPTION_FLAGS.maxRetries,
3677
+ OPTION_DESCRIPTIONS.maxRetries,
3678
+ createNumericParser({ label: "Max retries", integer: true, min: 0 }),
3679
+ defaults?.retry?.retries
3680
+ ).option(
3681
+ OPTION_FLAGS.retryMinTimeout,
3682
+ OPTION_DESCRIPTIONS.retryMinTimeout,
3683
+ createNumericParser({ label: "Min timeout", integer: true, min: 0 }),
3684
+ defaults?.retry?.["min-timeout"]
3685
+ ).option(
3686
+ OPTION_FLAGS.retryMaxTimeout,
3687
+ OPTION_DESCRIPTIONS.retryMaxTimeout,
3688
+ createNumericParser({ label: "Max timeout", integer: true, min: 0 }),
3689
+ defaults?.retry?.["max-timeout"]
3690
+ ).option(OPTION_FLAGS.noRetry, OPTION_DESCRIPTIONS.noRetry);
3487
3691
  }
3488
3692
  function addAgentOptions(cmd, defaults) {
3489
3693
  const gadgetAccumulator = (value, previous = []) => [
@@ -3511,7 +3715,42 @@ function addAgentOptions(cmd, defaults) {
3511
3715
  OPTION_FLAGS.logLlmRequests,
3512
3716
  OPTION_DESCRIPTIONS.logLlmRequests,
3513
3717
  defaults?.["log-llm-requests"]
3514
- ).option(OPTION_FLAGS.inputImage, OPTION_DESCRIPTIONS.inputImage).option(OPTION_FLAGS.inputAudio, OPTION_DESCRIPTIONS.inputAudio);
3718
+ ).option(OPTION_FLAGS.inputImage, OPTION_DESCRIPTIONS.inputImage).option(OPTION_FLAGS.inputAudio, OPTION_DESCRIPTIONS.inputAudio).option(
3719
+ OPTION_FLAGS.rateLimitRpm,
3720
+ OPTION_DESCRIPTIONS.rateLimitRpm,
3721
+ createNumericParser({ label: "RPM", integer: true, min: 1 }),
3722
+ defaults?.["rate-limits"]?.["requests-per-minute"]
3723
+ ).option(
3724
+ OPTION_FLAGS.rateLimitTpm,
3725
+ OPTION_DESCRIPTIONS.rateLimitTpm,
3726
+ createNumericParser({ label: "TPM", integer: true, min: 1 }),
3727
+ defaults?.["rate-limits"]?.["tokens-per-minute"]
3728
+ ).option(
3729
+ OPTION_FLAGS.rateLimitDaily,
3730
+ OPTION_DESCRIPTIONS.rateLimitDaily,
3731
+ createNumericParser({ label: "Daily tokens", integer: true, min: 1 }),
3732
+ defaults?.["rate-limits"]?.["tokens-per-day"]
3733
+ ).option(
3734
+ OPTION_FLAGS.rateLimitSafetyMargin,
3735
+ OPTION_DESCRIPTIONS.rateLimitSafetyMargin,
3736
+ createNumericParser({ label: "Safety margin", min: 0, max: 1 }),
3737
+ defaults?.["rate-limits"]?.["safety-margin"]
3738
+ ).option(OPTION_FLAGS.noRateLimit, OPTION_DESCRIPTIONS.noRateLimit).option(
3739
+ OPTION_FLAGS.maxRetries,
3740
+ OPTION_DESCRIPTIONS.maxRetries,
3741
+ createNumericParser({ label: "Max retries", integer: true, min: 0 }),
3742
+ defaults?.retry?.retries
3743
+ ).option(
3744
+ OPTION_FLAGS.retryMinTimeout,
3745
+ OPTION_DESCRIPTIONS.retryMinTimeout,
3746
+ createNumericParser({ label: "Min timeout", integer: true, min: 0 }),
3747
+ defaults?.retry?.["min-timeout"]
3748
+ ).option(
3749
+ OPTION_FLAGS.retryMaxTimeout,
3750
+ OPTION_DESCRIPTIONS.retryMaxTimeout,
3751
+ createNumericParser({ label: "Max timeout", integer: true, min: 0 }),
3752
+ defaults?.retry?.["max-timeout"]
3753
+ ).option(OPTION_FLAGS.noRetry, OPTION_DESCRIPTIONS.noRetry);
3515
3754
  }
3516
3755
  function configToCompleteOptions(config) {
3517
3756
  const result = {};
@@ -3521,6 +3760,21 @@ function configToCompleteOptions(config) {
3521
3760
  if (config["max-tokens"] !== void 0) result.maxTokens = config["max-tokens"];
3522
3761
  if (config.quiet !== void 0) result.quiet = config.quiet;
3523
3762
  if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
3763
+ if (config["rate-limits"]) {
3764
+ const rl = config["rate-limits"];
3765
+ if (rl["requests-per-minute"] !== void 0) result.rateLimitRpm = rl["requests-per-minute"];
3766
+ if (rl["tokens-per-minute"] !== void 0) result.rateLimitTpm = rl["tokens-per-minute"];
3767
+ if (rl["tokens-per-day"] !== void 0) result.rateLimitDaily = rl["tokens-per-day"];
3768
+ if (rl["safety-margin"] !== void 0) result.rateLimitSafetyMargin = rl["safety-margin"];
3769
+ if (rl.enabled === false) result.noRateLimit = true;
3770
+ }
3771
+ if (config.retry) {
3772
+ const r = config.retry;
3773
+ if (r.retries !== void 0) result.maxRetries = r.retries;
3774
+ if (r["min-timeout"] !== void 0) result.retryMinTimeout = r["min-timeout"];
3775
+ if (r["max-timeout"] !== void 0) result.retryMaxTimeout = r["max-timeout"];
3776
+ if (r.enabled === false) result.noRetry = true;
3777
+ }
3524
3778
  return result;
3525
3779
  }
3526
3780
  function configToAgentOptions(config) {
@@ -3545,9 +3799,190 @@ function configToAgentOptions(config) {
3545
3799
  if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
3546
3800
  if (config.subagents !== void 0) result.subagents = config.subagents;
3547
3801
  if (config["initial-gadgets"] !== void 0) result.initialGadgets = config["initial-gadgets"];
3802
+ if (config["rate-limits"]) {
3803
+ const rl = config["rate-limits"];
3804
+ if (rl["requests-per-minute"] !== void 0) result.rateLimitRpm = rl["requests-per-minute"];
3805
+ if (rl["tokens-per-minute"] !== void 0) result.rateLimitTpm = rl["tokens-per-minute"];
3806
+ if (rl["tokens-per-day"] !== void 0) result.rateLimitDaily = rl["tokens-per-day"];
3807
+ if (rl["safety-margin"] !== void 0) result.rateLimitSafetyMargin = rl["safety-margin"];
3808
+ if (rl.enabled === false) result.noRateLimit = true;
3809
+ }
3810
+ if (config.retry) {
3811
+ const r = config.retry;
3812
+ if (r.retries !== void 0) result.maxRetries = r.retries;
3813
+ if (r["min-timeout"] !== void 0) result.retryMinTimeout = r["min-timeout"];
3814
+ if (r["max-timeout"] !== void 0) result.retryMaxTimeout = r["max-timeout"];
3815
+ if (r.enabled === false) result.noRetry = true;
3816
+ }
3548
3817
  return result;
3549
3818
  }
3550
3819
 
3820
+ // src/rate-limit-resolver.ts
3821
+ import { getProvider, resolveModel } from "llmist";
3822
+ var PROVIDER_DEFAULTS = {
3823
+ anthropic: {
3824
+ requestsPerMinute: 50,
3825
+ // Tier 1 safe
3826
+ tokensPerMinute: 4e4,
3827
+ // Tier 1 for claude-3-5-sonnet
3828
+ safetyMargin: 0.8
3829
+ },
3830
+ openai: {
3831
+ requestsPerMinute: 3,
3832
+ // Free tier minimum (very conservative)
3833
+ tokensPerMinute: 4e4,
3834
+ safetyMargin: 0.8
3835
+ },
3836
+ gemini: {
3837
+ requestsPerMinute: 15,
3838
+ // Free tier documented
3839
+ tokensPerMinute: 1e6,
3840
+ // Free tier documented
3841
+ tokensPerDay: 15e5,
3842
+ // Free tier daily limit
3843
+ safetyMargin: 0.8
3844
+ }
3845
+ };
3846
+ function detectProvider(model) {
3847
+ try {
3848
+ const resolved = resolveModel(model, { silent: true });
3849
+ return getProvider(resolved) ?? null;
3850
+ } catch {
3851
+ return null;
3852
+ }
3853
+ }
3854
+ function resolveRateLimitConfig(options, globalConfig, profileConfig, model) {
3855
+ if (options.noRateLimit === true) {
3856
+ return { enabled: false, safetyMargin: 0.8 };
3857
+ }
3858
+ let resolved;
3859
+ if (model) {
3860
+ const provider = detectProvider(model);
3861
+ if (provider && PROVIDER_DEFAULTS[provider]) {
3862
+ resolved = { ...PROVIDER_DEFAULTS[provider] };
3863
+ }
3864
+ }
3865
+ if (!resolved) {
3866
+ resolved = {};
3867
+ }
3868
+ if (globalConfig) {
3869
+ if (globalConfig["requests-per-minute"] !== void 0) {
3870
+ resolved.requestsPerMinute = globalConfig["requests-per-minute"];
3871
+ }
3872
+ if (globalConfig["tokens-per-minute"] !== void 0) {
3873
+ resolved.tokensPerMinute = globalConfig["tokens-per-minute"];
3874
+ }
3875
+ if (globalConfig["tokens-per-day"] !== void 0) {
3876
+ resolved.tokensPerDay = globalConfig["tokens-per-day"];
3877
+ }
3878
+ if (globalConfig["safety-margin"] !== void 0) {
3879
+ resolved.safetyMargin = globalConfig["safety-margin"];
3880
+ }
3881
+ if (globalConfig.enabled !== void 0) {
3882
+ resolved.enabled = globalConfig.enabled;
3883
+ }
3884
+ }
3885
+ if (profileConfig) {
3886
+ if (profileConfig["requests-per-minute"] !== void 0) {
3887
+ resolved.requestsPerMinute = profileConfig["requests-per-minute"];
3888
+ }
3889
+ if (profileConfig["tokens-per-minute"] !== void 0) {
3890
+ resolved.tokensPerMinute = profileConfig["tokens-per-minute"];
3891
+ }
3892
+ if (profileConfig["tokens-per-day"] !== void 0) {
3893
+ resolved.tokensPerDay = profileConfig["tokens-per-day"];
3894
+ }
3895
+ if (profileConfig["safety-margin"] !== void 0) {
3896
+ resolved.safetyMargin = profileConfig["safety-margin"];
3897
+ }
3898
+ if (profileConfig.enabled !== void 0) {
3899
+ resolved.enabled = profileConfig.enabled;
3900
+ }
3901
+ }
3902
+ if (options.rateLimitRpm !== void 0) {
3903
+ resolved.requestsPerMinute = options.rateLimitRpm;
3904
+ }
3905
+ if (options.rateLimitTpm !== void 0) {
3906
+ resolved.tokensPerMinute = options.rateLimitTpm;
3907
+ }
3908
+ if (options.rateLimitDaily !== void 0) {
3909
+ resolved.tokensPerDay = options.rateLimitDaily;
3910
+ }
3911
+ if (options.rateLimitSafetyMargin !== void 0) {
3912
+ resolved.safetyMargin = options.rateLimitSafetyMargin;
3913
+ }
3914
+ const hasLimits = resolved.requestsPerMinute !== void 0 || resolved.tokensPerMinute !== void 0 || resolved.tokensPerDay !== void 0;
3915
+ if (resolved.enabled === false) {
3916
+ return { enabled: false, safetyMargin: resolved.safetyMargin ?? 0.8 };
3917
+ }
3918
+ if (!hasLimits) {
3919
+ return void 0;
3920
+ }
3921
+ return {
3922
+ ...resolved,
3923
+ enabled: true,
3924
+ safetyMargin: resolved.safetyMargin ?? 0.8
3925
+ };
3926
+ }
3927
+ var DEFAULT_RETRY_CONFIG = {
3928
+ enabled: true,
3929
+ retries: 3,
3930
+ minTimeout: 1e3,
3931
+ maxTimeout: 3e4,
3932
+ factor: 2,
3933
+ randomize: true,
3934
+ respectRetryAfter: true,
3935
+ maxRetryAfterMs: 12e4
3936
+ };
3937
+ function resolveRetryConfig(options, globalConfig, profileConfig) {
3938
+ const resolved = { ...DEFAULT_RETRY_CONFIG };
3939
+ if (globalConfig) {
3940
+ if (globalConfig.enabled !== void 0) resolved.enabled = globalConfig.enabled;
3941
+ if (globalConfig.retries !== void 0) resolved.retries = globalConfig.retries;
3942
+ if (globalConfig["min-timeout"] !== void 0)
3943
+ resolved.minTimeout = globalConfig["min-timeout"];
3944
+ if (globalConfig["max-timeout"] !== void 0)
3945
+ resolved.maxTimeout = globalConfig["max-timeout"];
3946
+ if (globalConfig.factor !== void 0) resolved.factor = globalConfig.factor;
3947
+ if (globalConfig.randomize !== void 0) resolved.randomize = globalConfig.randomize;
3948
+ if (globalConfig["respect-retry-after"] !== void 0) {
3949
+ resolved.respectRetryAfter = globalConfig["respect-retry-after"];
3950
+ }
3951
+ if (globalConfig["max-retry-after-ms"] !== void 0) {
3952
+ resolved.maxRetryAfterMs = globalConfig["max-retry-after-ms"];
3953
+ }
3954
+ }
3955
+ if (profileConfig) {
3956
+ if (profileConfig.enabled !== void 0) resolved.enabled = profileConfig.enabled;
3957
+ if (profileConfig.retries !== void 0) resolved.retries = profileConfig.retries;
3958
+ if (profileConfig["min-timeout"] !== void 0)
3959
+ resolved.minTimeout = profileConfig["min-timeout"];
3960
+ if (profileConfig["max-timeout"] !== void 0)
3961
+ resolved.maxTimeout = profileConfig["max-timeout"];
3962
+ if (profileConfig.factor !== void 0) resolved.factor = profileConfig.factor;
3963
+ if (profileConfig.randomize !== void 0) resolved.randomize = profileConfig.randomize;
3964
+ if (profileConfig["respect-retry-after"] !== void 0) {
3965
+ resolved.respectRetryAfter = profileConfig["respect-retry-after"];
3966
+ }
3967
+ if (profileConfig["max-retry-after-ms"] !== void 0) {
3968
+ resolved.maxRetryAfterMs = profileConfig["max-retry-after-ms"];
3969
+ }
3970
+ }
3971
+ if (options.maxRetries !== void 0) {
3972
+ resolved.retries = options.maxRetries;
3973
+ }
3974
+ if (options.retryMinTimeout !== void 0) {
3975
+ resolved.minTimeout = options.retryMinTimeout;
3976
+ }
3977
+ if (options.retryMaxTimeout !== void 0) {
3978
+ resolved.maxTimeout = options.retryMaxTimeout;
3979
+ }
3980
+ if (options.noRetry === true) {
3981
+ resolved.enabled = false;
3982
+ }
3983
+ return resolved;
3984
+ }
3985
+
3551
3986
  // src/subagent-config.ts
3552
3987
  var INHERIT_MODEL = "inherit";
3553
3988
  function resolveSubagentConfig(subagentName, parentModel, profileConfig, globalConfig) {
@@ -4142,6 +4577,33 @@ var BlockRenderer = class _BlockRenderer {
4142
4577
  this.rebuildBlocks();
4143
4578
  return id;
4144
4579
  }
4580
+ /**
4581
+ * Add a system message block (for rate limiting, retry notifications, etc.).
4582
+ *
4583
+ * Displays immediately with an icon and color based on category.
4584
+ * Non-selectable like text blocks.
4585
+ *
4586
+ * @param message - The system message text
4587
+ * @param category - Message category for styling
4588
+ * @returns The block ID
4589
+ */
4590
+ addSystemMessage(message, category) {
4591
+ const id = this.generateId("system");
4592
+ const node = {
4593
+ id,
4594
+ type: "system_message",
4595
+ depth: 0,
4596
+ parentId: null,
4597
+ sessionId: this.currentSessionId,
4598
+ message,
4599
+ category,
4600
+ children: []
4601
+ };
4602
+ this.nodes.set(id, node);
4603
+ this.rootIds.push(id);
4604
+ this.rebuildBlocks();
4605
+ return id;
4606
+ }
4145
4607
  /**
4146
4608
  * Add a user message block (for REPL mid-session input).
4147
4609
  *
@@ -4524,6 +4986,50 @@ ${fullContent}
4524
4986
  }
4525
4987
  return this.abbreviateToLines(fullContent, 2, selected);
4526
4988
  }
4989
+ case "system_message": {
4990
+ const icon = this.getSystemMessageIcon(node.category);
4991
+ const color = this.getSystemMessageColor(node.category);
4992
+ const RESET2 = "\x1B[0m";
4993
+ return `${indent}${color}${icon} ${node.message}${RESET2}`;
4994
+ }
4995
+ }
4996
+ }
4997
+ /**
4998
+ * Get icon for system message category.
4999
+ */
5000
+ getSystemMessageIcon(category) {
5001
+ switch (category) {
5002
+ case "throttle":
5003
+ return "\u23F8";
5004
+ case "retry":
5005
+ return "\u{1F504}";
5006
+ case "info":
5007
+ return "\u2139\uFE0F";
5008
+ case "warning":
5009
+ return "\u26A0\uFE0F";
5010
+ case "error":
5011
+ return "\u274C";
5012
+ }
5013
+ }
5014
+ /**
5015
+ * Get ANSI color code for system message category.
5016
+ */
5017
+ getSystemMessageColor(category) {
5018
+ const YELLOW2 = "\x1B[33m";
5019
+ const BLUE = "\x1B[34m";
5020
+ const GRAY = "\x1B[90m";
5021
+ const RED2 = "\x1B[31m";
5022
+ switch (category) {
5023
+ case "throttle":
5024
+ return YELLOW2;
5025
+ case "retry":
5026
+ return BLUE;
5027
+ case "info":
5028
+ return GRAY;
5029
+ case "warning":
5030
+ return YELLOW2;
5031
+ case "error":
5032
+ return RED2;
4527
5033
  }
4528
5034
  }
4529
5035
  /**
@@ -6268,6 +6774,10 @@ var StatusBar = class {
6268
6774
  nodeIdToGadgetName = /* @__PURE__ */ new Map();
6269
6775
  /** Tree subscription unsubscribe function */
6270
6776
  treeUnsubscribe = null;
6777
+ /** Rate limiting state */
6778
+ rateLimitState = null;
6779
+ /** Retry state */
6780
+ retryState = null;
6271
6781
  constructor(statusBox, model, renderCallback, renderNowCallback) {
6272
6782
  this.statusBox = statusBox;
6273
6783
  this.renderCallback = renderCallback;
@@ -6407,6 +6917,37 @@ var StatusBar = class {
6407
6917
  this.stopSpinner();
6408
6918
  this.render();
6409
6919
  }
6920
+ /**
6921
+ * Show rate limiting throttle indicator.
6922
+ * @param delayMs - Delay in milliseconds before next request
6923
+ */
6924
+ showThrottling(delayMs) {
6925
+ this.rateLimitState = { isThrottling: true, delayMs };
6926
+ this.render(true);
6927
+ }
6928
+ /**
6929
+ * Clear rate limiting throttle indicator.
6930
+ */
6931
+ clearThrottling() {
6932
+ this.rateLimitState = null;
6933
+ this.render(true);
6934
+ }
6935
+ /**
6936
+ * Show retry attempt indicator.
6937
+ * @param attemptNumber - Current attempt number (1-based)
6938
+ * @param retriesLeft - Number of retries remaining after this attempt
6939
+ */
6940
+ showRetry(attemptNumber, retriesLeft) {
6941
+ this.retryState = { attemptNumber, retriesLeft };
6942
+ this.render(true);
6943
+ }
6944
+ /**
6945
+ * Clear retry attempt indicator.
6946
+ */
6947
+ clearRetry() {
6948
+ this.retryState = null;
6949
+ this.render(true);
6950
+ }
6410
6951
  // ─────────────────────────────────────────────────────────────────────────────
6411
6952
  // Tree Subscription (for tree-only block creation)
6412
6953
  // ─────────────────────────────────────────────────────────────────────────────
@@ -6634,6 +7175,15 @@ var StatusBar = class {
6634
7175
  const typeStr = debug.nodeType ? ` [${debug.nodeType}]` : "";
6635
7176
  parts.push(`${GRAY}${debugStr}${typeStr}${RESET2}`);
6636
7177
  }
7178
+ if (this.rateLimitState?.isThrottling) {
7179
+ const seconds = Math.ceil(this.rateLimitState.delayMs / 1e3);
7180
+ parts.push(`${YELLOW2}\u23F8 Throttled ${seconds}s${RESET2}`);
7181
+ }
7182
+ if (this.retryState) {
7183
+ const { attemptNumber, retriesLeft } = this.retryState;
7184
+ const totalAttempts = attemptNumber + retriesLeft;
7185
+ parts.push(`${BLUE}\u{1F504} Retry ${attemptNumber}/${totalAttempts}${RESET2}`);
7186
+ }
6637
7187
  if (this.activeLLMCalls.size > 0 || this.activeGadgets.size > 0) {
6638
7188
  const spinner = SPINNER_FRAMES2[this.spinnerFrame];
6639
7189
  if (this.activeLLMCalls.size > 0) {
@@ -7018,6 +7568,42 @@ var TUIApp = class _TUIApp {
7018
7568
  flushText() {
7019
7569
  this.statusBar.clearActivity();
7020
7570
  }
7571
+ /**
7572
+ * Show rate limiting throttle indicator in status bar.
7573
+ * @param delayMs - Delay in milliseconds before next request
7574
+ */
7575
+ showThrottling(delayMs) {
7576
+ this.statusBar.showThrottling(delayMs);
7577
+ }
7578
+ /**
7579
+ * Clear rate limiting throttle indicator from status bar.
7580
+ */
7581
+ clearThrottling() {
7582
+ this.statusBar.clearThrottling();
7583
+ }
7584
+ /**
7585
+ * Show retry attempt indicator in status bar.
7586
+ * @param attemptNumber - Current attempt number (1-based)
7587
+ * @param retriesLeft - Number of retries remaining after this attempt
7588
+ */
7589
+ showRetry(attemptNumber, retriesLeft) {
7590
+ this.statusBar.showRetry(attemptNumber, retriesLeft);
7591
+ }
7592
+ /**
7593
+ * Clear retry attempt indicator from status bar.
7594
+ */
7595
+ clearRetry() {
7596
+ this.statusBar.clearRetry();
7597
+ }
7598
+ /**
7599
+ * Add a system message to the conversation (for rate limiting, retry notifications, etc.).
7600
+ * @param message - The system message text
7601
+ * @param category - Message category for styling
7602
+ * @returns The block ID
7603
+ */
7604
+ addSystemMessage(message, category) {
7605
+ return this.blockRenderer.addSystemMessage(message, category);
7606
+ }
7021
7607
  // ─────────────────────────────────────────────────────────────────────────────
7022
7608
  // Profile Management
7023
7609
  // ─────────────────────────────────────────────────────────────────────────────
@@ -7223,7 +7809,7 @@ async function executeAgent(promptArg, options, env, commandName) {
7223
7809
  }
7224
7810
  }
7225
7811
  const abortController = new AbortController();
7226
- let wasCancelled = false;
7812
+ let _wasCancelled = false;
7227
7813
  const handleQuit = () => {
7228
7814
  if (tui) {
7229
7815
  tui.destroy();
@@ -7233,7 +7819,7 @@ async function executeAgent(promptArg, options, env, commandName) {
7233
7819
  if (tui) {
7234
7820
  tui.onQuit(handleQuit);
7235
7821
  tui.onCancel(() => {
7236
- wasCancelled = true;
7822
+ _wasCancelled = true;
7237
7823
  abortController.abort();
7238
7824
  });
7239
7825
  }
@@ -7258,12 +7844,12 @@ async function executeAgent(promptArg, options, env, commandName) {
7258
7844
  gadgetApprovals,
7259
7845
  defaultMode: "allowed"
7260
7846
  };
7261
- let usage;
7847
+ let _usage;
7262
7848
  let iterations = 0;
7263
7849
  const llmLogsEnabled = options.logLlmRequests === true;
7264
7850
  const llmLogDir = llmLogsEnabled ? env.session?.logDir : void 0;
7265
7851
  let llmCallCounter = 0;
7266
- const countGadgetOutputTokens = async (output) => {
7852
+ const _countGadgetOutputTokens = async (output) => {
7267
7853
  if (!output) return void 0;
7268
7854
  try {
7269
7855
  const messages = [{ role: "assistant", content: output }];
@@ -7306,12 +7892,46 @@ async function executeAgent(promptArg, options, env, commandName) {
7306
7892
  // onLLMCallComplete: Capture metadata for final summary and file logging
7307
7893
  onLLMCallComplete: async (context) => {
7308
7894
  if (context.subagentContext) return;
7309
- usage = context.usage;
7895
+ _usage = context.usage;
7310
7896
  iterations = Math.max(iterations, context.iteration + 1);
7311
7897
  if (llmLogDir) {
7312
7898
  const filename = `${formatCallNumber(llmCallCounter)}.response`;
7313
7899
  await writeLogFile(llmLogDir, filename, context.rawResponse);
7314
7900
  }
7901
+ if (tui) {
7902
+ tui.clearRetry();
7903
+ }
7904
+ },
7905
+ // onRateLimitThrottle: Show throttling delay in status bar and conversation
7906
+ onRateLimitThrottle: async (context) => {
7907
+ if (context.subagentContext) return;
7908
+ if (tui) {
7909
+ const seconds = Math.ceil(context.delayMs / 1e3);
7910
+ tui.showThrottling(context.delayMs);
7911
+ const statsMsg = [];
7912
+ if (context.stats.rpm > 0) statsMsg.push(`${context.stats.rpm} RPM`);
7913
+ if (context.stats.tpm > 0)
7914
+ statsMsg.push(`${Math.round(context.stats.tpm / 1e3)}K TPM`);
7915
+ const statsStr = statsMsg.length > 0 ? ` (${statsMsg.join(", ")})` : "";
7916
+ tui.addSystemMessage(
7917
+ `Rate limit approaching${statsStr}, waiting ${seconds}s...`,
7918
+ "throttle"
7919
+ );
7920
+ setTimeout(() => tui.clearThrottling(), context.delayMs);
7921
+ }
7922
+ },
7923
+ // onRetryAttempt: Show retry attempt in status bar and conversation
7924
+ onRetryAttempt: async (context) => {
7925
+ if (context.subagentContext) return;
7926
+ if (tui) {
7927
+ const totalAttempts = context.attemptNumber + context.retriesLeft;
7928
+ tui.showRetry(context.attemptNumber, context.retriesLeft);
7929
+ const retryAfterInfo = context.retryAfterMs ? ` (server requested ${Math.ceil(context.retryAfterMs / 1e3)}s wait)` : "";
7930
+ tui.addSystemMessage(
7931
+ `Request failed (attempt ${context.attemptNumber}/${totalAttempts}), retrying...${retryAfterInfo}`,
7932
+ "retry"
7933
+ );
7934
+ }
7315
7935
  }
7316
7936
  },
7317
7937
  // SHOWCASE: Controller-based approval gating for gadgets
@@ -7380,6 +8000,19 @@ ${ctx.gadgetName} requires interactive approval. Run in a terminal to approve.`
7380
8000
  }
7381
8001
  }
7382
8002
  });
8003
+ const rateLimitConfig = resolveRateLimitConfig(
8004
+ options,
8005
+ options.globalRateLimits,
8006
+ options.profileRateLimits,
8007
+ options.model
8008
+ );
8009
+ const retryConfig = resolveRetryConfig(options, options.globalRetry, options.profileRetry);
8010
+ if (rateLimitConfig) {
8011
+ builder.withRateLimits(rateLimitConfig);
8012
+ }
8013
+ if (retryConfig) {
8014
+ builder.withRetry(retryConfig);
8015
+ }
7383
8016
  if (options.system) {
7384
8017
  builder.withSystem(options.system);
7385
8018
  }
@@ -7530,7 +8163,7 @@ ${ctx.gadgetName} requires interactive approval. Run in a terminal to approve.`
7530
8163
  }
7531
8164
  }
7532
8165
  }
7533
- function registerAgentCommand(program, env, config, globalSubagents) {
8166
+ function registerAgentCommand(program, env, config, globalSubagents, globalRateLimits, globalRetry) {
7534
8167
  const cmd = program.command(COMMANDS.agent).description("Run the llmist agent loop with optional gadgets.").argument("[prompt]", "Prompt for the agent loop. Falls back to stdin when available.");
7535
8168
  addAgentOptions(cmd, config);
7536
8169
  cmd.action(
@@ -7540,7 +8173,11 @@ function registerAgentCommand(program, env, config, globalSubagents) {
7540
8173
  gadgetApproval: config?.["gadget-approval"],
7541
8174
  subagents: config?.subagents,
7542
8175
  globalSubagents,
7543
- initialGadgets: config?.["initial-gadgets"]
8176
+ initialGadgets: config?.["initial-gadgets"],
8177
+ globalRateLimits,
8178
+ globalRetry,
8179
+ profileRateLimits: config?.["rate-limits"],
8180
+ profileRetry: config?.retry
7544
8181
  };
7545
8182
  return executeAgent(prompt, mergedOptions, env, "agent");
7546
8183
  }, env)
@@ -7548,14 +8185,11 @@ function registerAgentCommand(program, env, config, globalSubagents) {
7548
8185
  }
7549
8186
 
7550
8187
  // src/complete-command.ts
7551
- import { text as text2 } from "llmist";
7552
- import { LLMMessageBuilder } from "llmist";
7553
- import { resolveModel } from "llmist";
7554
- import { FALLBACK_CHARS_PER_TOKEN as FALLBACK_CHARS_PER_TOKEN2 } from "llmist";
8188
+ import { FALLBACK_CHARS_PER_TOKEN as FALLBACK_CHARS_PER_TOKEN2, LLMMessageBuilder, resolveModel as resolveModel2, text as text2 } from "llmist";
7555
8189
  async function executeComplete(promptArg, options, env) {
7556
8190
  const prompt = await resolvePrompt(promptArg, env);
7557
8191
  const client = env.createClient();
7558
- const model = resolveModel(options.model);
8192
+ const model = resolveModel2(options.model);
7559
8193
  const builder = new LLMMessageBuilder();
7560
8194
  if (options.system) {
7561
8195
  builder.addSystem(options.system);
@@ -7629,11 +8263,18 @@ async function executeComplete(promptArg, options, env) {
7629
8263
  }
7630
8264
  }
7631
8265
  }
7632
- function registerCompleteCommand(program, env, config) {
8266
+ function registerCompleteCommand(program, env, config, globalRateLimits, globalRetry) {
7633
8267
  const cmd = program.command(COMMANDS.complete).description("Stream a single completion from a specified model.").argument("[prompt]", "Prompt to send to the LLM. If omitted, stdin is used when available.");
7634
8268
  addCompleteOptions(cmd, config);
7635
8269
  cmd.action(
7636
- (prompt, options) => executeAction(() => executeComplete(prompt, options, env), env)
8270
+ (prompt, options) => executeAction(() => {
8271
+ const mergedOptions = {
8272
+ ...options,
8273
+ globalRateLimits,
8274
+ globalRetry
8275
+ };
8276
+ return executeComplete(prompt, mergedOptions, env);
8277
+ }, env)
7637
8278
  );
7638
8279
  }
7639
8280
 
@@ -7760,104 +8401,6 @@ System Prompt (${chars.toLocaleString()} chars, ${lines} lines):
7760
8401
  }
7761
8402
  }
7762
8403
 
7763
- // src/init-command.ts
7764
- import { existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
7765
- import { dirname as dirname2 } from "path";
7766
- var STARTER_CONFIG = `# ~/.llmist/cli.toml
7767
- # llmist CLI configuration file
7768
- #
7769
- # This is a minimal starter config. For a comprehensive example with all options:
7770
- # https://github.com/zbigniewsobiecki/llmist/blob/main/examples/cli.example.toml
7771
- #
7772
- # Key concepts:
7773
- # - Any section can inherit from others using: inherits = "section-name"
7774
- # - Prompts can use templates with Eta syntax: <%~ include("@prompt-name") %>
7775
- # - Custom sections become CLI commands: [my-command] -> llmist my-command
7776
-
7777
- #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7778
- # GLOBAL OPTIONS
7779
- # These apply to all commands. CLI flags override these settings.
7780
- #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7781
- [global]
7782
- # log-level = "info" # silly, trace, debug, info, warn, error, fatal
7783
- # log-file = "/tmp/llmist.log" # Enable file logging (JSON format)
7784
-
7785
- #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7786
- # COMPLETE COMMAND DEFAULTS
7787
- # For single LLM responses: llmist complete "prompt"
7788
- # Model format: provider:model (e.g., openai:gpt-4o, anthropic:claude-sonnet-4-5)
7789
- #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7790
- [complete]
7791
- # model = "openai:gpt-4o"
7792
- # temperature = 0.7 # 0-2, higher = more creative
7793
- # max-tokens = 4096 # Maximum response length
7794
-
7795
- #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7796
- # AGENT COMMAND DEFAULTS
7797
- # For tool-using agents: llmist agent "prompt"
7798
- #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7799
- [agent]
7800
- # model = "anthropic:claude-sonnet-4-5"
7801
- # max-iterations = 15 # Max tool-use loops before stopping
7802
- # gadgets = [ # Tools the agent can use
7803
- # "ListDirectory",
7804
- # "ReadFile",
7805
- # "WriteFile",
7806
- # ]
7807
-
7808
- #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7809
- # CUSTOM COMMANDS
7810
- # Any other section becomes a new CLI command!
7811
- # Uncomment below to create: llmist summarize "your text"
7812
- #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7813
- # [summarize]
7814
- # type = "complete" # "complete" or "agent"
7815
- # description = "Summarize text concisely."
7816
- # system = "Summarize the following text in 2-3 bullet points."
7817
- # temperature = 0.3
7818
- `;
7819
- async function executeInit(_options, env) {
7820
- const configPath = getConfigPath();
7821
- const configDir = dirname2(configPath);
7822
- if (existsSync3(configPath)) {
7823
- env.stderr.write(`Configuration already exists at ${configPath}
7824
- `);
7825
- env.stderr.write("\n");
7826
- env.stderr.write(`To view it: cat ${configPath}
7827
- `);
7828
- env.stderr.write(`To reset: rm ${configPath} && llmist init
7829
- `);
7830
- return;
7831
- }
7832
- if (!existsSync3(configDir)) {
7833
- mkdirSync(configDir, { recursive: true });
7834
- }
7835
- writeFileSync(configPath, STARTER_CONFIG, "utf-8");
7836
- env.stderr.write(`Created ${configPath}
7837
- `);
7838
- env.stderr.write("\n");
7839
- env.stderr.write("Next steps:\n");
7840
- env.stderr.write(" 1. Set your API key:\n");
7841
- env.stderr.write(" export OPENAI_API_KEY=sk-...\n");
7842
- env.stderr.write(" export ANTHROPIC_API_KEY=sk-...\n");
7843
- env.stderr.write(" export GEMINI_API_KEY=...\n");
7844
- env.stderr.write("\n");
7845
- env.stderr.write(` 2. Customize your config:
7846
- `);
7847
- env.stderr.write(` $EDITOR ${configPath}
7848
- `);
7849
- env.stderr.write("\n");
7850
- env.stderr.write(" 3. See all options:\n");
7851
- env.stderr.write(
7852
- " https://github.com/zbigniewsobiecki/llmist/blob/main/examples/cli.example.toml\n"
7853
- );
7854
- env.stderr.write("\n");
7855
- env.stderr.write('Try it: llmist complete "Hello, world!"\n');
7856
- }
7857
- function registerInitCommand(program, env) {
7858
- program.command(COMMANDS.init).description("Initialize llmist configuration at ~/.llmist/cli.toml").action((options) => executeAction(() => executeInit(options, env), env));
7859
- }
7860
-
7861
8404
  // src/environment.ts
7862
8405
  import { join as join3 } from "path";
7863
8406
  import readline from "readline";
@@ -7956,7 +8499,7 @@ function createCommandEnvironment(baseEnv, config) {
7956
8499
  createLogger: createLoggerFactory(loggerConfig, baseEnv.session?.logDir)
7957
8500
  };
7958
8501
  }
7959
- function registerCustomCommand(program, name, config, env, globalSubagents) {
8502
+ function registerCustomCommand(program, name, config, env, globalSubagents, globalRateLimits, globalRetry) {
7960
8503
  const type = config.type ?? "agent";
7961
8504
  const description = config.description ?? `Custom ${type} command`;
7962
8505
  const cmd = program.command(name).description(description).argument("[prompt]", "Prompt for the command. Falls back to stdin when available.");
@@ -7968,7 +8511,9 @@ function registerCustomCommand(program, name, config, env, globalSubagents) {
7968
8511
  const configDefaults = configToCompleteOptions(config);
7969
8512
  const options = {
7970
8513
  ...configDefaults,
7971
- ...cliOptions
8514
+ ...cliOptions,
8515
+ globalRateLimits,
8516
+ globalRetry
7972
8517
  };
7973
8518
  await executeComplete(prompt, options, cmdEnv);
7974
8519
  }, cmdEnv);
@@ -7982,7 +8527,9 @@ function registerCustomCommand(program, name, config, env, globalSubagents) {
7982
8527
  const options = {
7983
8528
  ...configDefaults,
7984
8529
  ...cliOptions,
7985
- globalSubagents
8530
+ globalSubagents,
8531
+ globalRateLimits,
8532
+ globalRetry
7986
8533
  };
7987
8534
  await executeAgent(prompt, options, cmdEnv, name);
7988
8535
  }, cmdEnv);
@@ -8425,7 +8972,7 @@ function registerGadgetCommand(program, env) {
8425
8972
  }
8426
8973
 
8427
8974
  // src/image-command.ts
8428
- import { writeFileSync as writeFileSync2 } from "fs";
8975
+ import { writeFileSync } from "fs";
8429
8976
  var DEFAULT_IMAGE_MODEL = "dall-e-3";
8430
8977
  async function executeImage(promptArg, options, env) {
8431
8978
  const prompt = await resolvePrompt(promptArg, env);
@@ -8449,7 +8996,7 @@ async function executeImage(promptArg, options, env) {
8449
8996
  const imageData = result.images[0];
8450
8997
  if (imageData.b64Json) {
8451
8998
  const buffer = Buffer.from(imageData.b64Json, "base64");
8452
- writeFileSync2(options.output, buffer);
8999
+ writeFileSync(options.output, buffer);
8453
9000
  if (!options.quiet) {
8454
9001
  env.stderr.write(`${SUMMARY_PREFIX} Image saved to ${options.output}
8455
9002
  `);
@@ -8487,6 +9034,104 @@ function registerImageCommand(program, env, config) {
8487
9034
  );
8488
9035
  }
8489
9036
 
9037
+ // src/init-command.ts
9038
+ import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
9039
+ import { dirname as dirname2 } from "path";
9040
+ var STARTER_CONFIG = `# ~/.llmist/cli.toml
9041
+ # llmist CLI configuration file
9042
+ #
9043
+ # This is a minimal starter config. For a comprehensive example with all options:
9044
+ # https://github.com/zbigniewsobiecki/llmist/blob/main/examples/cli.example.toml
9045
+ #
9046
+ # Key concepts:
9047
+ # - Any section can inherit from others using: inherits = "section-name"
9048
+ # - Prompts can use templates with Eta syntax: <%~ include("@prompt-name") %>
9049
+ # - Custom sections become CLI commands: [my-command] -> llmist my-command
9050
+
9051
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
9052
+ # GLOBAL OPTIONS
9053
+ # These apply to all commands. CLI flags override these settings.
9054
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
9055
+ [global]
9056
+ # log-level = "info" # silly, trace, debug, info, warn, error, fatal
9057
+ # log-file = "/tmp/llmist.log" # Enable file logging (JSON format)
9058
+
9059
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
9060
+ # COMPLETE COMMAND DEFAULTS
9061
+ # For single LLM responses: llmist complete "prompt"
9062
+ # Model format: provider:model (e.g., openai:gpt-4o, anthropic:claude-sonnet-4-5)
9063
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
9064
+ [complete]
9065
+ # model = "openai:gpt-4o"
9066
+ # temperature = 0.7 # 0-2, higher = more creative
9067
+ # max-tokens = 4096 # Maximum response length
9068
+
9069
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
9070
+ # AGENT COMMAND DEFAULTS
9071
+ # For tool-using agents: llmist agent "prompt"
9072
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
9073
+ [agent]
9074
+ # model = "anthropic:claude-sonnet-4-5"
9075
+ # max-iterations = 15 # Max tool-use loops before stopping
9076
+ # gadgets = [ # Tools the agent can use
9077
+ # "ListDirectory",
9078
+ # "ReadFile",
9079
+ # "WriteFile",
9080
+ # ]
9081
+
9082
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
9083
+ # CUSTOM COMMANDS
9084
+ # Any other section becomes a new CLI command!
9085
+ # Uncomment below to create: llmist summarize "your text"
9086
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
9087
+ # [summarize]
9088
+ # type = "complete" # "complete" or "agent"
9089
+ # description = "Summarize text concisely."
9090
+ # system = "Summarize the following text in 2-3 bullet points."
9091
+ # temperature = 0.3
9092
+ `;
9093
+ async function executeInit(_options, env) {
9094
+ const configPath = getConfigPath();
9095
+ const configDir = dirname2(configPath);
9096
+ if (existsSync3(configPath)) {
9097
+ env.stderr.write(`Configuration already exists at ${configPath}
9098
+ `);
9099
+ env.stderr.write("\n");
9100
+ env.stderr.write(`To view it: cat ${configPath}
9101
+ `);
9102
+ env.stderr.write(`To reset: rm ${configPath} && llmist init
9103
+ `);
9104
+ return;
9105
+ }
9106
+ if (!existsSync3(configDir)) {
9107
+ mkdirSync(configDir, { recursive: true });
9108
+ }
9109
+ writeFileSync2(configPath, STARTER_CONFIG, "utf-8");
9110
+ env.stderr.write(`Created ${configPath}
9111
+ `);
9112
+ env.stderr.write("\n");
9113
+ env.stderr.write("Next steps:\n");
9114
+ env.stderr.write(" 1. Set your API key:\n");
9115
+ env.stderr.write(" export OPENAI_API_KEY=sk-...\n");
9116
+ env.stderr.write(" export ANTHROPIC_API_KEY=sk-...\n");
9117
+ env.stderr.write(" export GEMINI_API_KEY=...\n");
9118
+ env.stderr.write("\n");
9119
+ env.stderr.write(` 2. Customize your config:
9120
+ `);
9121
+ env.stderr.write(` $EDITOR ${configPath}
9122
+ `);
9123
+ env.stderr.write("\n");
9124
+ env.stderr.write(" 3. See all options:\n");
9125
+ env.stderr.write(
9126
+ " https://github.com/zbigniewsobiecki/llmist/blob/main/examples/cli.example.toml\n"
9127
+ );
9128
+ env.stderr.write("\n");
9129
+ env.stderr.write('Try it: llmist complete "Hello, world!"\n');
9130
+ }
9131
+ function registerInitCommand(program, env) {
9132
+ program.command(COMMANDS.init).description("Initialize llmist configuration at ~/.llmist/cli.toml").action((options) => executeAction(() => executeInit(options, env), env));
9133
+ }
9134
+
8490
9135
  // src/models-command.ts
8491
9136
  import chalk7 from "chalk";
8492
9137
  import { MODEL_ALIASES } from "llmist";
@@ -9105,10 +9750,10 @@ function registerSpeechCommand(program, env, config) {
9105
9750
  }
9106
9751
 
9107
9752
  // src/vision-command.ts
9108
- import { resolveModel as resolveModel2 } from "llmist";
9753
+ import { resolveModel as resolveModel3 } from "llmist";
9109
9754
  async function executeVision(imagePath, options, env) {
9110
9755
  const client = env.createClient();
9111
- const model = resolveModel2(options.model);
9756
+ const model = resolveModel3(options.model);
9112
9757
  const imageBuffer = await readFileBuffer(imagePath);
9113
9758
  const prompt = options.prompt ?? "Describe this image in detail.";
9114
9759
  const stderrTTY = env.stderr.isTTY === true;
@@ -9154,8 +9799,15 @@ function createProgram(env, config) {
9154
9799
  writeOut: (str) => env.stdout.write(str),
9155
9800
  writeErr: (str) => env.stderr.write(str)
9156
9801
  });
9157
- registerCompleteCommand(program, env, config?.complete);
9158
- registerAgentCommand(program, env, config?.agent, config?.subagents);
9802
+ registerCompleteCommand(program, env, config?.complete, config?.["rate-limits"], config?.retry);
9803
+ registerAgentCommand(
9804
+ program,
9805
+ env,
9806
+ config?.agent,
9807
+ config?.subagents,
9808
+ config?.["rate-limits"],
9809
+ config?.retry
9810
+ );
9159
9811
  registerImageCommand(program, env, config?.image);
9160
9812
  registerSpeechCommand(program, env, config?.speech);
9161
9813
  registerVisionCommand(program, env);
@@ -9167,7 +9819,15 @@ function createProgram(env, config) {
9167
9819
  const customNames = getCustomCommandNames(config);
9168
9820
  for (const name of customNames) {
9169
9821
  const cmdConfig = config[name];
9170
- registerCustomCommand(program, name, cmdConfig, env, config.subagents);
9822
+ registerCustomCommand(
9823
+ program,
9824
+ name,
9825
+ cmdConfig,
9826
+ env,
9827
+ config.subagents,
9828
+ config["rate-limits"],
9829
+ config.retry
9830
+ );
9171
9831
  }
9172
9832
  }
9173
9833
  return program;