llm-relay 0.44.0 → 0.45.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.
Files changed (58) hide show
  1. package/README.md +1 -0
  2. package/dist/authEnv.d.ts +20 -12
  3. package/dist/authEnv.js +76 -36
  4. package/dist/authEnv.js.map +1 -1
  5. package/dist/candidates.d.ts +2 -0
  6. package/dist/candidates.js +2 -1
  7. package/dist/candidates.js.map +1 -1
  8. package/dist/circuit-breaker.d.ts +2 -0
  9. package/dist/circuit-breaker.js +17 -0
  10. package/dist/circuit-breaker.js.map +1 -1
  11. package/dist/cli.d.ts +14 -0
  12. package/dist/cli.js +193 -5
  13. package/dist/cli.js.map +1 -1
  14. package/dist/config.d.ts +4 -3
  15. package/dist/config.js +87 -7
  16. package/dist/config.js.map +1 -1
  17. package/dist/cooldown-clear.d.ts +2 -0
  18. package/dist/cooldown-clear.js +20 -4
  19. package/dist/cooldown-clear.js.map +1 -1
  20. package/dist/credential-fleet.d.ts +5 -4
  21. package/dist/credential-fleet.js +9 -9
  22. package/dist/credential-fleet.js.map +1 -1
  23. package/dist/dotenv.d.ts +2 -0
  24. package/dist/dotenv.js +9 -0
  25. package/dist/dotenv.js.map +1 -1
  26. package/dist/key-checker.d.ts +1 -0
  27. package/dist/key-checker.js +3 -2
  28. package/dist/key-checker.js.map +1 -1
  29. package/dist/key-import.d.ts +7 -0
  30. package/dist/key-import.js +52 -0
  31. package/dist/key-import.js.map +1 -1
  32. package/dist/keys-cli.d.ts +48 -0
  33. package/dist/keys-cli.js +693 -0
  34. package/dist/keys-cli.js.map +1 -0
  35. package/dist/keystore.d.ts +130 -0
  36. package/dist/keystore.js +1195 -0
  37. package/dist/keystore.js.map +1 -0
  38. package/dist/os-keyring.d.ts +88 -0
  39. package/dist/os-keyring.js +476 -0
  40. package/dist/os-keyring.js.map +1 -0
  41. package/dist/registry.d.ts +2 -1
  42. package/dist/registry.js +1 -0
  43. package/dist/registry.js.map +1 -1
  44. package/dist/resolved-attempt.d.ts +2 -1
  45. package/dist/resolved-attempt.js +3 -2
  46. package/dist/resolved-attempt.js.map +1 -1
  47. package/dist/routes/admin.js +8 -1
  48. package/dist/routes/admin.js.map +1 -1
  49. package/dist/secret-file-acl.d.ts +47 -12
  50. package/dist/secret-file-acl.js +151 -16
  51. package/dist/secret-file-acl.js.map +1 -1
  52. package/dist/server.d.ts +1 -0
  53. package/dist/server.js +2 -1
  54. package/dist/server.js.map +1 -1
  55. package/dist/target-facts.d.ts +5 -0
  56. package/dist/target-facts.js +10 -3
  57. package/dist/target-facts.js.map +1 -1
  58. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -33,6 +33,7 @@ import { DASHBOARD_MEDIA_TYPE, isDashboardUtcTimestamp } from "./dashboard-contr
33
33
  import { DASHBOARD_BOOTSTRAP_SCHEMA, DASHBOARD_BOOTSTRAP_REQUEST_SCHEMA } from "./dashboard-routes.js";
34
34
  import { flushRuntimeTelemetry } from "./ping/runtime-telemetry.js";
35
35
  import { flushProbeCache } from "./ping/probe-cache.js";
36
+ import { runKeysAddLocal, runKeysDisableLocal, runKeysEnableLocal, runKeysExportLocal, runKeysImportLocal, runKeysListLocal, runKeysRemoveLocal, runKeysRevokeLocal, runKeysRotateLocal, runKeysUnlockLocal, } from "./keys-cli.js";
36
37
  const VALUE_FLAGS = new Set([
37
38
  "--config", "-config", "-c",
38
39
  "--provider", "-provider", "-p",
@@ -63,6 +64,9 @@ const VALUE_FLAGS = new Set([
63
64
  "--reset-field", "-reset-field",
64
65
  "--reset-ms", "-reset-ms",
65
66
  "--import", "-import",
67
+ "--label", "-label",
68
+ "--env-name", "-env-name",
69
+ "--out", "-out",
66
70
  ]);
67
71
  let cachedArgv;
68
72
  let cachedParsedArgs;
@@ -158,7 +162,8 @@ ${formatTextTable([
158
162
  ["llm-relay [options]", "Start proxy."],
159
163
  ["llm-relay onboard [--import <file>] [--force]", "Set up or import provider keys."],
160
164
  ["llm-relay setup [target]", "target: claude-cli | claude-desktop."],
161
- ["llm-relay keys | check-keys", "Check every configured credential slot."],
165
+ ["llm-relay keys | keys check | check-keys", "Check every configured credential slot."],
166
+ ["llm-relay keys <action> ...", "action: add|list|rotate|revoke|remove|disable|enable|export|import|unlock."],
162
167
  ["llm-relay pools [--probe]", "List members; --probe tests each deployment once."],
163
168
  ["llm-relay pools <action> <name> [<spec>...]", "action: set|add|remove|delete."],
164
169
  ["llm-relay routing <action> ...", "action: show|get|default|tier|subagent|sort|benchmark|set|unset."],
@@ -669,6 +674,43 @@ export async function runCheckKeys() {
669
674
  ];
670
675
  process.stdout.write(formatTextTable(rows) + "\n");
671
676
  }
677
+ /** `llm-relay keys add` — CLI-local credential insertion; secret input is never argv. */
678
+ export async function runKeysAdd(provider = getPositionalArgs(process.argv)[2], deps = {}) {
679
+ await runKeysAddLocal(deps.config ?? loadOrExit(), provider, {
680
+ ...(argValue("--label", "-label") === undefined
681
+ ? {} : { label: argValue("--label", "-label") }),
682
+ ...(argValue("--env-name", "-env-name") === undefined
683
+ ? {} : { envName: argValue("--env-name", "-env-name") }),
684
+ check: hasFlag("--check", "-check"),
685
+ }, deps);
686
+ }
687
+ export async function runKeysList(deps = {}) {
688
+ runKeysListLocal(deps.config ?? loadOrExit(), deps);
689
+ }
690
+ export async function runKeysRotate(credential = getPositionalArgs(process.argv)[2], deps = {}) {
691
+ await runKeysRotateLocal(deps.config ?? loadOrExit(), credential, deps);
692
+ }
693
+ export async function runKeysRevoke(credential = getPositionalArgs(process.argv)[2], deps = {}) {
694
+ runKeysRevokeLocal(credential, deps);
695
+ }
696
+ export async function runKeysRemove(credential = getPositionalArgs(process.argv)[2], deps = {}) {
697
+ runKeysRemoveLocal(credential, hasFlag("--purge", "-purge"), deps);
698
+ }
699
+ export async function runKeysDisable(credential = getPositionalArgs(process.argv)[2], deps = {}) {
700
+ runKeysDisableLocal(credential, deps);
701
+ }
702
+ export async function runKeysEnable(credential = getPositionalArgs(process.argv)[2], deps = {}) {
703
+ runKeysEnableLocal(credential, deps);
704
+ }
705
+ export async function runKeysExport(deps = {}) {
706
+ await runKeysExportLocal(argValue("--out", "-out"), deps);
707
+ }
708
+ export async function runKeysImport(importPath = getPositionalArgs(process.argv)[2], deps = {}) {
709
+ await runKeysImportLocal(deps.config ?? loadOrExit(), importPath, deps);
710
+ }
711
+ export async function runKeysUnlock(deps = {}) {
712
+ await runKeysUnlockLocal(deps);
713
+ }
672
714
  /**
673
715
  * Talk to a RUNNING proxy if there is one. The offload switch has to reach the live process to
674
716
  * take effect without a restart, and `candidates` gets better data from it (warm ping history,
@@ -2716,12 +2758,98 @@ import { importKeysFromFile } from "./key-import.js";
2716
2758
  import { setupClaudeCli, setupClaudeDesktop } from "./setup-claude.js";
2717
2759
  import { getTelemetryReport } from "./telemetry.js";
2718
2760
  import { globalCircuitBreaker } from "./circuit-breaker.js";
2761
+ const KEYS_SUBCOMMANDS = [
2762
+ "add", "list", "rotate", "revoke", "remove", "disable", "enable", "export", "import",
2763
+ "unlock", "check",
2764
+ ];
2765
+ const KEYS_GLOBAL_FLAGS = ["--config", "-config", "-c"];
2766
+ const KEYS_FLAGS = {
2767
+ add: ["--label", "-label", "--env-name", "-env-name", "--check", "-check"],
2768
+ list: [],
2769
+ rotate: [],
2770
+ revoke: [],
2771
+ remove: ["--purge", "-purge"],
2772
+ disable: [],
2773
+ enable: [],
2774
+ export: ["--out", "-out"],
2775
+ import: [],
2776
+ unlock: [],
2777
+ check: [],
2778
+ };
2779
+ const KEYS_POSITIONAL_COUNT = {
2780
+ add: 3,
2781
+ list: 2,
2782
+ rotate: 3,
2783
+ revoke: 3,
2784
+ remove: 3,
2785
+ disable: 3,
2786
+ enable: 3,
2787
+ export: 2,
2788
+ import: 3,
2789
+ unlock: 2,
2790
+ check: 2,
2791
+ };
2792
+ /** Strict, secret-safe keys parser. Diagnostics deliberately never echo rejected argv tokens. */
2793
+ export function validateKeysCommandArgs(argv) {
2794
+ const positionals = getPositionalArgs(argv);
2795
+ const candidate = positionals[1];
2796
+ if (candidate !== undefined && !KEYS_SUBCOMMANDS.includes(candidate)) {
2797
+ return `unknown subcommand; valid subcommands: ${KEYS_SUBCOMMANDS.join(", ")}`;
2798
+ }
2799
+ const subcommand = candidate;
2800
+ const allowed = new Set(KEYS_GLOBAL_FLAGS);
2801
+ if (subcommand !== undefined) {
2802
+ for (const flag of KEYS_FLAGS[subcommand])
2803
+ allowed.add(flag);
2804
+ }
2805
+ for (let index = 2; index < argv.length; index += 1) {
2806
+ const arg = argv[index];
2807
+ if (arg === undefined || !arg.startsWith("-"))
2808
+ continue;
2809
+ const equalsAt = arg.indexOf("=");
2810
+ const flag = equalsAt === -1 ? arg : arg.slice(0, equalsAt);
2811
+ if (!allowed.has(flag))
2812
+ return "unsupported option";
2813
+ if (VALUE_FLAGS.has(flag)) {
2814
+ if (equalsAt !== -1) {
2815
+ if (arg.slice(equalsAt + 1).length === 0)
2816
+ return "invalid arguments";
2817
+ }
2818
+ else {
2819
+ const value = argv[index + 1];
2820
+ if (value === undefined || value.startsWith("-"))
2821
+ return "invalid arguments";
2822
+ index += 1;
2823
+ }
2824
+ }
2825
+ else if (equalsAt !== -1) {
2826
+ return "invalid arguments";
2827
+ }
2828
+ }
2829
+ const expected = subcommand === undefined ? 1 : KEYS_POSITIONAL_COUNT[subcommand];
2830
+ return positionals.length === expected ? null : "invalid arguments";
2831
+ }
2832
+ function reportKeysCommandError(error) {
2833
+ process.stderr.write(`llm-relay keys: ${error.message}\n`);
2834
+ process.exit(1);
2835
+ }
2836
+ function runKeysPromise(operation) {
2837
+ operation.catch(reportKeysCommandError);
2838
+ }
2719
2839
  export function main() {
2720
2840
  const rawCommand = rawCliCommand(process.argv);
2721
2841
  const positionals = getPositionalArgs(process.argv);
2722
2842
  const arg2 = positionals[0];
2723
2843
  const arg3 = positionals[1];
2724
2844
  const arg4 = positionals[2];
2845
+ if (arg2 === "keys") {
2846
+ const validationError = validateKeysCommandArgs(process.argv);
2847
+ if (validationError !== null) {
2848
+ process.stderr.write(`llm-relay keys: ${validationError}\n`);
2849
+ process.exit(1);
2850
+ return;
2851
+ }
2852
+ }
2725
2853
  // Mutation parsing owns its raw argv so help/version-shaped typos cannot bypass fail-closed
2726
2854
  // validation and turn a scoped clear into a wider request.
2727
2855
  if (rawCommand === "cooldowns" || arg2 === "cooldowns") {
@@ -2779,13 +2907,57 @@ export function main() {
2779
2907
  process.stdout.write(JSON.stringify(report, null, 2) + "\n");
2780
2908
  return;
2781
2909
  }
2782
- if (arg2 === "keys" || arg2 === "check-keys") {
2910
+ if (arg2 === "check-keys") {
2783
2911
  runCheckKeys().catch((e) => {
2784
2912
  process.stderr.write(`llm-relay check-keys: ${e.message}\n`);
2785
2913
  process.exit(1);
2786
2914
  });
2787
2915
  return;
2788
2916
  }
2917
+ if (arg2 === "keys") {
2918
+ switch (arg3) {
2919
+ case undefined:
2920
+ case "check":
2921
+ // Bare `keys` remains byte-for-byte the same status command as `check-keys`.
2922
+ runCheckKeys().catch((e) => {
2923
+ process.stderr.write(`llm-relay check-keys: ${e.message}\n`);
2924
+ process.exit(1);
2925
+ });
2926
+ return;
2927
+ case "add":
2928
+ runKeysPromise(runKeysAdd(arg4));
2929
+ return;
2930
+ case "list":
2931
+ runKeysPromise(runKeysList());
2932
+ return;
2933
+ case "rotate":
2934
+ runKeysPromise(runKeysRotate(arg4));
2935
+ return;
2936
+ case "revoke":
2937
+ runKeysPromise(runKeysRevoke(arg4));
2938
+ return;
2939
+ case "remove":
2940
+ runKeysPromise(runKeysRemove(arg4));
2941
+ return;
2942
+ case "disable":
2943
+ runKeysPromise(runKeysDisable(arg4));
2944
+ return;
2945
+ case "enable":
2946
+ runKeysPromise(runKeysEnable(arg4));
2947
+ return;
2948
+ case "export":
2949
+ runKeysPromise(runKeysExport());
2950
+ return;
2951
+ case "import":
2952
+ runKeysPromise(runKeysImport(arg4));
2953
+ return;
2954
+ case "unlock":
2955
+ runKeysPromise(runKeysUnlock());
2956
+ return;
2957
+ default:
2958
+ throw new Error("unreachable keys subcommand after validation");
2959
+ }
2960
+ }
2789
2961
  if (arg2 === "models") {
2790
2962
  runModels().catch((e) => {
2791
2963
  process.stderr.write(`llm-relay: ${e.message}\n`);
@@ -2934,6 +3106,12 @@ export function classifyCommand(argv) {
2934
3106
  // Clears process-local relay state through the authenticated mutation plane.
2935
3107
  case "cooldowns":
2936
3108
  return arg3 === "clear" ? "mutating" : "read-only";
3109
+ case "keys":
3110
+ return arg3 === "add" || arg3 === "rotate" || arg3 === "revoke" ||
3111
+ arg3 === "remove" || arg3 === "disable" || arg3 === "enable" ||
3112
+ arg3 === "export" || arg3 === "import"
3113
+ ? "mutating"
3114
+ : "read-only";
2937
3115
  case "config":
2938
3116
  return arg3 === "set" || arg3 === "unset" ? "mutating" : "read-only";
2939
3117
  case "routing":
@@ -2952,7 +3130,7 @@ export function classifyCommand(argv) {
2952
3130
  return arg3 === "accept" || arg3 === "reject" || arg3 === "propose" ? "mutating" : "read-only";
2953
3131
  case "dashboard":
2954
3132
  return "read-only";
2955
- // keys, check-keys, models, telemetry, dispatch, candidates, pools, ping, help, version —
3133
+ // check-keys, models, telemetry, dispatch, candidates, pools, ping, help, version —
2956
3134
  // and anything not yet listed. `dispatch -x` is included on purpose: it reports spend to a
2957
3135
  // running proxy's in-memory cooldowns and changes nothing on this machine.
2958
3136
  default:
@@ -2964,9 +3142,19 @@ export function classifyCommand(argv) {
2964
3142
  * and re-exec), then the command itself. `main` stays synchronous so its exit paths are direct.
2965
3143
  */
2966
3144
  export async function run() {
2967
- // Reject malformed mutation argv before the self-update gate can perform any network request.
2968
- if (rawCliCommand(process.argv) === "cooldowns")
3145
+ // Reject malformed mutation argv before the self-update gate can perform any network request
3146
+ // or replace the installed CLI. Keys diagnostics stay generic so rejected argv is never echoed.
3147
+ const command = rawCliCommand(process.argv);
3148
+ if (command === "cooldowns")
2969
3149
  parseCooldownClearArgs(process.argv);
3150
+ if (command === "keys") {
3151
+ const validationError = validateKeysCommandArgs(process.argv);
3152
+ if (validationError !== null) {
3153
+ process.stderr.write(`llm-relay keys: ${validationError}\n`);
3154
+ process.exit(1);
3155
+ return;
3156
+ }
3157
+ }
2970
3158
  if (shouldCheckUpdates(process.argv, process.env, classifyCommand(process.argv))) {
2971
3159
  try {
2972
3160
  await ensureUpToDate();