llm-cli-gateway 1.1.0 → 1.5.4

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 (57) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +226 -9
  3. package/dist/approval-manager.d.ts +1 -1
  4. package/dist/async-job-manager.d.ts +75 -4
  5. package/dist/async-job-manager.js +303 -19
  6. package/dist/auth.d.ts +15 -0
  7. package/dist/auth.js +46 -0
  8. package/dist/cli-updater.d.ts +55 -0
  9. package/dist/cli-updater.js +248 -0
  10. package/dist/codex-json-parser.d.ts +34 -0
  11. package/dist/codex-json-parser.js +105 -0
  12. package/dist/doctor.d.ts +110 -0
  13. package/dist/doctor.js +280 -0
  14. package/dist/endpoint-exposure.d.ts +22 -0
  15. package/dist/endpoint-exposure.js +231 -0
  16. package/dist/executor.d.ts +2 -0
  17. package/dist/executor.js +2 -2
  18. package/dist/flight-recorder.d.ts +3 -1
  19. package/dist/flight-recorder.js +31 -2
  20. package/dist/gateway-server.d.ts +2 -0
  21. package/dist/gateway-server.js +1 -0
  22. package/dist/gemini-json-parser.d.ts +21 -0
  23. package/dist/gemini-json-parser.js +47 -0
  24. package/dist/health.d.ts +7 -0
  25. package/dist/health.js +22 -0
  26. package/dist/http-transport.d.ts +22 -0
  27. package/dist/http-transport.js +164 -0
  28. package/dist/index.d.ts +210 -2
  29. package/dist/index.js +2880 -1037
  30. package/dist/job-store.d.ts +84 -0
  31. package/dist/job-store.js +251 -0
  32. package/dist/logger.d.ts +9 -0
  33. package/dist/logger.js +14 -0
  34. package/dist/model-registry.d.ts +14 -0
  35. package/dist/model-registry.js +478 -134
  36. package/dist/provider-login-guidance.d.ts +21 -0
  37. package/dist/provider-login-guidance.js +98 -0
  38. package/dist/provider-status.d.ts +41 -0
  39. package/dist/provider-status.js +203 -0
  40. package/dist/request-helpers.d.ts +525 -4
  41. package/dist/request-helpers.js +653 -0
  42. package/dist/resources.js +88 -0
  43. package/dist/session-manager-pg.js +2 -0
  44. package/dist/session-manager.d.ts +1 -1
  45. package/dist/session-manager.js +3 -1
  46. package/dist/validation-normalizer.d.ts +23 -0
  47. package/dist/validation-normalizer.js +79 -0
  48. package/dist/validation-orchestrator.d.ts +47 -0
  49. package/dist/validation-orchestrator.js +145 -0
  50. package/dist/validation-prompts.d.ts +15 -0
  51. package/dist/validation-prompts.js +52 -0
  52. package/dist/validation-report.d.ts +57 -0
  53. package/dist/validation-report.js +129 -0
  54. package/dist/validation-tools.d.ts +7 -0
  55. package/dist/validation-tools.js +198 -0
  56. package/package.json +16 -6
  57. package/setup/status.schema.json +271 -0
@@ -0,0 +1,21 @@
1
+ import type { CliType } from "./session-manager.js";
2
+ export interface ProviderLoginGuidance {
3
+ provider: CliType;
4
+ displayName: string;
5
+ install: {
6
+ summary: string;
7
+ commands: string[];
8
+ documentationUrl?: string;
9
+ };
10
+ login: {
11
+ summary: string;
12
+ commands: string[];
13
+ credentialHandling: string;
14
+ };
15
+ verification: {
16
+ command: string;
17
+ expected: string;
18
+ };
19
+ }
20
+ export declare function getProviderLoginGuidance(provider: CliType): ProviderLoginGuidance;
21
+ export declare function getAllProviderLoginGuidance(): Record<CliType, ProviderLoginGuidance>;
@@ -0,0 +1,98 @@
1
+ const GUIDANCE = {
2
+ claude: {
3
+ provider: "claude",
4
+ displayName: "Claude Code",
5
+ install: {
6
+ summary: "Install Claude Code using Anthropic's current official installer.",
7
+ commands: ["npm install -g @anthropic-ai/claude-code"],
8
+ documentationUrl: "https://docs.anthropic.com/claude-code",
9
+ },
10
+ login: {
11
+ summary: "Sign in through Claude Code's official browser/device flow.",
12
+ commands: ["claude auth login"],
13
+ credentialHandling: "Do not paste Claude passwords, OAuth tokens, or credential files into the gateway or a remote chat.",
14
+ },
15
+ verification: {
16
+ command: "claude auth status --json",
17
+ expected: "loggedIn is true",
18
+ },
19
+ },
20
+ codex: {
21
+ provider: "codex",
22
+ displayName: "Codex CLI",
23
+ install: {
24
+ summary: "Install Codex CLI using OpenAI's npm package or the current official installer.",
25
+ commands: ["npm install -g @openai/codex"],
26
+ documentationUrl: "https://developers.openai.com/codex",
27
+ },
28
+ login: {
29
+ summary: "Sign in through Codex's official login flow.",
30
+ commands: ["codex login", "codex login --device-auth"],
31
+ credentialHandling: "Prefer browser or device-code login. Do not paste API keys or access tokens into assistant prompts.",
32
+ },
33
+ verification: {
34
+ command: "codex login status",
35
+ expected: "command reports that Codex is logged in",
36
+ },
37
+ },
38
+ gemini: {
39
+ provider: "gemini",
40
+ displayName: "Gemini CLI",
41
+ install: {
42
+ summary: "Install Gemini CLI using Google's npm package or current official installer.",
43
+ commands: ["npm install -g @google/gemini-cli"],
44
+ documentationUrl: "https://github.com/google-gemini/gemini-cli",
45
+ },
46
+ login: {
47
+ summary: "Run Gemini CLI and complete Google's official sign-in flow when prompted.",
48
+ commands: ["gemini"],
49
+ credentialHandling: "Let Gemini CLI store credentials in its own local store. Do not paste OAuth files or API keys into chat.",
50
+ },
51
+ verification: {
52
+ command: "gemini --version",
53
+ expected: "CLI is installed; doctor checks the local Gemini credential store for login evidence",
54
+ },
55
+ },
56
+ grok: {
57
+ provider: "grok",
58
+ displayName: "Grok CLI",
59
+ install: {
60
+ summary: "Install Grok CLI using xAI's current official installer or managed update flow.",
61
+ commands: ["npm install -g grok-build"],
62
+ documentationUrl: "https://docs.x.ai/build/cli",
63
+ },
64
+ login: {
65
+ summary: "Sign in through Grok's official OAuth or device-code flow.",
66
+ commands: ["grok login --oauth", "grok login --device-auth"],
67
+ credentialHandling: "Do not paste xAI API keys, OAuth tokens, or Grok auth files into the gateway or a remote chat.",
68
+ },
69
+ verification: {
70
+ command: "grok inspect --json",
71
+ expected: "CLI can inspect local configuration and a local auth store is present",
72
+ },
73
+ },
74
+ mistral: {
75
+ provider: "mistral",
76
+ displayName: "Mistral Vibe CLI",
77
+ install: {
78
+ summary: "Install Mistral Vibe CLI via pip, uv, or Homebrew (Vibe does not self-update; cli_upgrade dispatches to the installer it detects).",
79
+ commands: ["pip install vibe-cli", "uv tool install vibe-cli", "brew install mistral-vibe"],
80
+ documentationUrl: "https://docs.mistral.ai/agents/vibe-cli",
81
+ },
82
+ login: {
83
+ summary: "Sign in through Mistral's official auth flow and enable session_logging in ~/.vibe/config.toml.",
84
+ commands: ["vibe auth login", "vibe config set session_logging.enabled true"],
85
+ credentialHandling: "Do not paste Mistral API keys, OAuth tokens, or ~/.vibe/credentials into the gateway or a remote chat.",
86
+ },
87
+ verification: {
88
+ command: "vibe --version",
89
+ expected: "Vibe CLI is installed; doctor additionally checks ~/.vibe/config.toml for session_logging.enabled=true",
90
+ },
91
+ },
92
+ };
93
+ export function getProviderLoginGuidance(provider) {
94
+ return GUIDANCE[provider];
95
+ }
96
+ export function getAllProviderLoginGuidance() {
97
+ return { ...GUIDANCE };
98
+ }
@@ -0,0 +1,41 @@
1
+ import type { CliType } from "./session-manager.js";
2
+ import { type ProviderLoginGuidance } from "./provider-login-guidance.js";
3
+ export type ProviderLoginStatus = "authenticated" | "not_authenticated" | "unknown" | "not_checked";
4
+ export interface ProviderRuntimeStatus {
5
+ provider: CliType;
6
+ displayName: string;
7
+ command: string;
8
+ installed: boolean;
9
+ version: string | null;
10
+ versionCommand: string[];
11
+ loginStatus: ProviderLoginStatus;
12
+ loginCheck: {
13
+ method: "cli" | "credential_store" | "not_checked";
14
+ command: string[] | null;
15
+ credentialStore: "present" | "not_found" | "not_checked";
16
+ detail: string;
17
+ };
18
+ guidance: ProviderLoginGuidance;
19
+ }
20
+ export declare const PROVIDER_COMMANDS: Record<CliType, string>;
21
+ export declare function listProviderRuntimeStatuses(): Record<CliType, ProviderRuntimeStatus>;
22
+ export declare function getProviderRuntimeStatus(provider: CliType): ProviderRuntimeStatus;
23
+ export interface GeminiAuthMethods {
24
+ oauth: boolean;
25
+ geminiApiKey: boolean;
26
+ googleApiKey: boolean;
27
+ vertexAi: boolean;
28
+ }
29
+ export interface GeminiAuthStatus {
30
+ status: "present" | "not_found";
31
+ methods: GeminiAuthMethods;
32
+ }
33
+ /**
34
+ * U27: Detect Gemini auth across all supported methods.
35
+ * Returns "present" if ANY of:
36
+ * - OAuth credential file present (~/.gemini/oauth_creds.json, etc.)
37
+ * - GEMINI_API_KEY env var set and non-empty
38
+ * - GOOGLE_API_KEY env var set and non-empty
39
+ * - GOOGLE_CLOUD_PROJECT set AND GOOGLE_GENAI_USE_VERTEXAI=true
40
+ */
41
+ export declare function geminiAuthStatus(env?: NodeJS.ProcessEnv, home?: string): GeminiAuthStatus;
@@ -0,0 +1,203 @@
1
+ import { existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { getProviderLoginGuidance } from "./provider-login-guidance.js";
6
+ const PROVIDERS = ["claude", "codex", "gemini", "grok", "mistral"];
7
+ const VERSION_ARGS = {
8
+ claude: ["--version"],
9
+ codex: ["--version"],
10
+ gemini: ["--version"],
11
+ grok: ["--version"],
12
+ mistral: ["--version"],
13
+ };
14
+ // Mistral Vibe ships as the `vibe` binary (PyPI package vibe-cli); the gateway
15
+ // uses `mistral` as the provider key but invokes `vibe` on the shell.
16
+ export const PROVIDER_COMMANDS = {
17
+ claude: "claude",
18
+ codex: "codex",
19
+ gemini: "gemini",
20
+ grok: "grok",
21
+ mistral: "vibe",
22
+ };
23
+ const LOGIN_CHECKS = {
24
+ claude: ["auth", "status", "--json"],
25
+ codex: ["login", "status"],
26
+ grok: ["inspect", "--json"],
27
+ mistral: ["auth", "status"],
28
+ };
29
+ export function listProviderRuntimeStatuses() {
30
+ return Object.fromEntries(PROVIDERS.map(provider => [provider, getProviderRuntimeStatus(provider)]));
31
+ }
32
+ export function getProviderRuntimeStatus(provider) {
33
+ const guidance = getProviderLoginGuidance(provider);
34
+ const command = PROVIDER_COMMANDS[provider];
35
+ const version = runCommand(command, VERSION_ARGS[provider], 5_000);
36
+ const installed = version.exitCode === 0 || Boolean(version.output);
37
+ const versionText = installed ? firstLine(version.output) : null;
38
+ const base = {
39
+ provider,
40
+ displayName: guidance.displayName,
41
+ command,
42
+ installed,
43
+ version: versionText,
44
+ versionCommand: [command, ...VERSION_ARGS[provider]],
45
+ loginStatus: installed ? "unknown" : "not_checked",
46
+ loginCheck: {
47
+ method: installed ? "not_checked" : "not_checked",
48
+ command: null,
49
+ credentialStore: "not_checked",
50
+ detail: installed
51
+ ? "No safe non-interactive login check is available."
52
+ : "Runtime is not installed.",
53
+ },
54
+ guidance,
55
+ };
56
+ if (!installed)
57
+ return base;
58
+ if (provider === "gemini") {
59
+ const auth = geminiAuthStatus();
60
+ const store = auth.status;
61
+ const matchedMethods = Object.entries(auth.methods)
62
+ .filter(([, v]) => v)
63
+ .map(([k]) => k);
64
+ return {
65
+ ...base,
66
+ loginStatus: store === "present" ? "authenticated" : "unknown",
67
+ loginCheck: {
68
+ method: "credential_store",
69
+ command: null,
70
+ credentialStore: store,
71
+ detail: store === "present"
72
+ ? `Gemini auth detected via: ${matchedMethods.join(", ")}; contents were not inspected.`
73
+ : "Gemini CLI is installed, but no credential store or auth env vars were found (oauth_creds.json, GEMINI_API_KEY, GOOGLE_API_KEY, or GOOGLE_CLOUD_PROJECT+GOOGLE_GENAI_USE_VERTEXAI).",
74
+ },
75
+ };
76
+ }
77
+ const args = LOGIN_CHECKS[provider];
78
+ if (!args)
79
+ return base;
80
+ const login = runCommand(command, args, 5_000);
81
+ const status = inferLoginStatus(provider, login.exitCode, login.output);
82
+ const credentialStore = provider === "grok"
83
+ ? grokCredentialStoreStatus()
84
+ : provider === "mistral"
85
+ ? mistralCredentialStoreStatus()
86
+ : "not_checked";
87
+ return {
88
+ ...base,
89
+ loginStatus: status,
90
+ loginCheck: {
91
+ method: "cli",
92
+ command: [command, ...args],
93
+ credentialStore,
94
+ detail: loginCheckDetail(provider, status, login.exitCode),
95
+ },
96
+ };
97
+ }
98
+ function runCommand(command, args, timeoutMs) {
99
+ const result = spawnSync(command, args, {
100
+ encoding: "utf8",
101
+ input: "",
102
+ timeout: timeoutMs,
103
+ windowsHide: true,
104
+ });
105
+ const output = sanitizeOutput(`${result.stdout || ""}\n${result.stderr || ""}`);
106
+ return {
107
+ exitCode: typeof result.status === "number" ? result.status : null,
108
+ output,
109
+ };
110
+ }
111
+ function firstLine(text) {
112
+ return (text
113
+ .split(/\r?\n/)
114
+ .map(line => line.trim())
115
+ .find(Boolean) || null);
116
+ }
117
+ function inferLoginStatus(provider, exitCode, output) {
118
+ if (provider === "claude") {
119
+ try {
120
+ const parsed = JSON.parse(output);
121
+ if (parsed.loggedIn === true)
122
+ return "authenticated";
123
+ if (parsed.loggedIn === false)
124
+ return "not_authenticated";
125
+ }
126
+ catch {
127
+ // Fall through to text heuristics.
128
+ }
129
+ }
130
+ if (/not\s+(logged|signed|authenticated)\s*in|unauthenticated|login required|not authorized/i.test(output)) {
131
+ return "not_authenticated";
132
+ }
133
+ if (/logged\s*in|signed\s*in|authenticated|authorized|using chatgpt|auth store/i.test(output)) {
134
+ return "authenticated";
135
+ }
136
+ if (provider === "grok" && grokCredentialStoreStatus() === "present") {
137
+ return "authenticated";
138
+ }
139
+ if (provider === "mistral" && mistralCredentialStoreStatus() === "present") {
140
+ return "authenticated";
141
+ }
142
+ if (exitCode && exitCode !== 0)
143
+ return "unknown";
144
+ return "unknown";
145
+ }
146
+ function loginCheckDetail(provider, status, exitCode) {
147
+ if (status === "authenticated")
148
+ return `${provider} login check indicates an authenticated local runtime.`;
149
+ if (status === "not_authenticated")
150
+ return `${provider} login check indicates the provider is not authenticated.`;
151
+ if (exitCode && exitCode !== 0)
152
+ return `${provider} login check exited non-zero without exposing credential material.`;
153
+ return `${provider} login check completed, but the output did not clearly indicate login state.`;
154
+ }
155
+ /**
156
+ * U27: Detect Gemini auth across all supported methods.
157
+ * Returns "present" if ANY of:
158
+ * - OAuth credential file present (~/.gemini/oauth_creds.json, etc.)
159
+ * - GEMINI_API_KEY env var set and non-empty
160
+ * - GOOGLE_API_KEY env var set and non-empty
161
+ * - GOOGLE_CLOUD_PROJECT set AND GOOGLE_GENAI_USE_VERTEXAI=true
162
+ */
163
+ export function geminiAuthStatus(env = process.env, home = homedir()) {
164
+ const candidates = [
165
+ join(home, ".gemini", "oauth_creds.json"),
166
+ join(home, ".gemini", "google_accounts.json"),
167
+ join(home, ".config", "gemini", "oauth_creds.json"),
168
+ ];
169
+ const oauth = candidates.some(p => existsSync(p));
170
+ const geminiApiKey = Boolean(env.GEMINI_API_KEY && env.GEMINI_API_KEY.length > 0);
171
+ const googleApiKey = Boolean(env.GOOGLE_API_KEY && env.GOOGLE_API_KEY.length > 0);
172
+ const vertexAi = Boolean(env.GOOGLE_CLOUD_PROJECT &&
173
+ env.GOOGLE_CLOUD_PROJECT.length > 0 &&
174
+ env.GOOGLE_GENAI_USE_VERTEXAI === "true");
175
+ const methods = { oauth, geminiApiKey, googleApiKey, vertexAi };
176
+ const status = oauth || geminiApiKey || googleApiKey || vertexAi ? "present" : "not_found";
177
+ return { status, methods };
178
+ }
179
+ /** Back-compat shim retained for callers that only need the binary store status. */
180
+ function geminiCredentialStoreStatus() {
181
+ return geminiAuthStatus().status;
182
+ }
183
+ function grokCredentialStoreStatus() {
184
+ const home = homedir();
185
+ const candidates = [join(home, ".grok", "auth.json"), join(home, ".config", "grok", "auth.json")];
186
+ return candidates.some(path => existsSync(path)) ? "present" : "not_found";
187
+ }
188
+ function mistralCredentialStoreStatus() {
189
+ const home = homedir();
190
+ const candidates = [
191
+ join(home, ".vibe", "credentials.json"),
192
+ join(home, ".vibe", "auth.json"),
193
+ join(home, ".config", "vibe", "credentials.json"),
194
+ ];
195
+ return candidates.some(path => existsSync(path)) ? "present" : "not_found";
196
+ }
197
+ function sanitizeOutput(output) {
198
+ return output
199
+ .replace(/([A-Z0-9._%+-]+)@([A-Z0-9.-]+\.[A-Z]{2,})/gi, "<redacted-email>")
200
+ .replace(/\b[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\b/gi, "<redacted-id>")
201
+ .replace(/((?:token|secret|credential|password|authorization|api[_-]?key|access[_-]?key)[=:]\s*)\S+/gi, "$1<redacted>")
202
+ .trim();
203
+ }