llm-cli-gateway 1.4.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 (54) hide show
  1. package/CHANGELOG.md +67 -1
  2. package/README.md +111 -8
  3. package/dist/approval-manager.d.ts +1 -1
  4. package/dist/async-job-manager.d.ts +24 -2
  5. package/dist/async-job-manager.js +71 -7
  6. package/dist/auth.d.ts +15 -0
  7. package/dist/auth.js +46 -0
  8. package/dist/cli-updater.d.ts +19 -2
  9. package/dist/cli-updater.js +110 -7
  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 +183 -2
  29. package/dist/index.js +2629 -1411
  30. package/dist/logger.d.ts +9 -0
  31. package/dist/logger.js +14 -0
  32. package/dist/model-registry.js +40 -6
  33. package/dist/provider-login-guidance.d.ts +21 -0
  34. package/dist/provider-login-guidance.js +98 -0
  35. package/dist/provider-status.d.ts +41 -0
  36. package/dist/provider-status.js +203 -0
  37. package/dist/request-helpers.d.ts +484 -4
  38. package/dist/request-helpers.js +613 -0
  39. package/dist/resources.js +44 -0
  40. package/dist/session-manager-pg.js +1 -0
  41. package/dist/session-manager.d.ts +1 -1
  42. package/dist/session-manager.js +2 -1
  43. package/dist/validation-normalizer.d.ts +23 -0
  44. package/dist/validation-normalizer.js +79 -0
  45. package/dist/validation-orchestrator.d.ts +47 -0
  46. package/dist/validation-orchestrator.js +145 -0
  47. package/dist/validation-prompts.d.ts +15 -0
  48. package/dist/validation-prompts.js +52 -0
  49. package/dist/validation-report.d.ts +57 -0
  50. package/dist/validation-report.js +129 -0
  51. package/dist/validation-tools.d.ts +7 -0
  52. package/dist/validation-tools.js +198 -0
  53. package/package.json +15 -5
  54. package/setup/status.schema.json +271 -0
@@ -1,11 +1,15 @@
1
1
  import type { Logger } from "./logger.js";
2
2
  import type { CliType } from "./session-manager.js";
3
+ import { type ProviderLoginStatus } from "./provider-status.js";
4
+ import type { ProviderLoginGuidance } from "./provider-login-guidance.js";
3
5
  export interface CliVersionInfo {
4
6
  cli: CliType;
5
7
  command: string;
6
8
  args: string[];
7
9
  installed: boolean;
8
10
  version?: string;
11
+ loginStatus?: ProviderLoginStatus;
12
+ loginGuidance?: ProviderLoginGuidance["login"];
9
13
  stdout: string;
10
14
  stderr: string;
11
15
  error?: string;
@@ -15,10 +19,23 @@ export interface CliUpgradePlan {
15
19
  target: string;
16
20
  command: string;
17
21
  args: string[];
18
- strategy: "self-update" | "npm-global-install";
22
+ strategy: "self-update" | "npm-global-install" | "pip-install" | "uv-tool-upgrade" | "brew-upgrade";
19
23
  requiresNetwork: boolean;
20
24
  note?: string;
21
25
  }
26
+ export type MistralInstallMethod = "pip" | "uv" | "brew" | "unknown";
27
+ /**
28
+ * Detect how Vibe was installed on this machine. Vibe does not self-update, so
29
+ * cli_upgrade has to dispatch to the package manager that owns the binary.
30
+ *
31
+ * Probe order: pip → uv → brew. The first one that returns a positive signal
32
+ * wins; if none do, callers should surface an actionable error rather than
33
+ * blindly running `vibe update` (a command that does not exist).
34
+ */
35
+ export declare function detectMistralInstallMethod(exec?: (cmd: string, args: string[]) => {
36
+ exitCode: number | null;
37
+ stdout: string;
38
+ }): MistralInstallMethod;
22
39
  export interface CliUpgradeResult {
23
40
  dryRun: boolean;
24
41
  plan: CliUpgradePlan;
@@ -26,7 +43,7 @@ export interface CliUpgradeResult {
26
43
  stderr?: string;
27
44
  exitCode?: number;
28
45
  }
29
- export declare function buildCliUpgradePlan(cli: CliType, target?: string): CliUpgradePlan;
46
+ export declare function buildCliUpgradePlan(cli: CliType, target?: string, detectMistral?: () => MistralInstallMethod): CliUpgradePlan;
30
47
  export declare function getCliVersion(cli: CliType): Promise<CliVersionInfo>;
31
48
  export declare function getCliVersions(cli?: CliType): Promise<CliVersionInfo[]>;
32
49
  export declare function runCliUpgrade(params: {
@@ -1,16 +1,51 @@
1
+ import { spawnSync } from "node:child_process";
1
2
  import { executeCli } from "./executor.js";
3
+ import { getProviderRuntimeStatus } from "./provider-status.js";
4
+ /**
5
+ * Detect how Vibe was installed on this machine. Vibe does not self-update, so
6
+ * cli_upgrade has to dispatch to the package manager that owns the binary.
7
+ *
8
+ * Probe order: pip → uv → brew. The first one that returns a positive signal
9
+ * wins; if none do, callers should surface an actionable error rather than
10
+ * blindly running `vibe update` (a command that does not exist).
11
+ */
12
+ export function detectMistralInstallMethod(exec = (cmd, args) => {
13
+ const result = spawnSync(cmd, args, { encoding: "utf8", timeout: 5_000, windowsHide: true });
14
+ return {
15
+ exitCode: typeof result.status === "number" ? result.status : null,
16
+ stdout: result.stdout || "",
17
+ };
18
+ }) {
19
+ const pip = exec("pip", ["show", "vibe-cli"]);
20
+ if (pip.exitCode === 0 && /Name:\s*vibe-cli/i.test(pip.stdout)) {
21
+ return "pip";
22
+ }
23
+ const uv = exec("uv", ["tool", "list"]);
24
+ if (uv.exitCode === 0 && /\bvibe(?:-cli)?\b/i.test(uv.stdout)) {
25
+ return "uv";
26
+ }
27
+ const brew = exec("brew", ["list", "mistral-vibe"]);
28
+ if (brew.exitCode === 0) {
29
+ return "brew";
30
+ }
31
+ return "unknown";
32
+ }
2
33
  const VERSION_ARGS = {
3
34
  claude: ["--version"],
4
35
  codex: ["--version"],
5
36
  gemini: ["--version"],
6
37
  grok: ["--version"],
38
+ mistral: ["--version"],
7
39
  };
8
40
  const NPM_PACKAGES = {
9
41
  codex: "@openai/codex",
10
42
  gemini: "@google/gemini-cli",
11
43
  };
12
- export function buildCliUpgradePlan(cli, target = "latest") {
44
+ export function buildCliUpgradePlan(cli, target = "latest", detectMistral = detectMistralInstallMethod) {
13
45
  const normalizedTarget = normalizeTarget(target);
46
+ if (cli === "mistral") {
47
+ return buildMistralUpgradePlan(normalizedTarget, detectMistral);
48
+ }
14
49
  if (cli === "claude") {
15
50
  if (normalizedTarget === "latest") {
16
51
  return {
@@ -79,18 +114,23 @@ export function buildCliUpgradePlan(cli, target = "latest") {
79
114
  export async function getCliVersion(cli) {
80
115
  const args = VERSION_ARGS[cli];
81
116
  try {
82
- const result = await executeCli(cli, args, { timeout: 15_000 });
117
+ const status = getProviderRuntimeStatus(cli);
83
118
  return {
84
119
  cli,
85
120
  command: cli,
86
121
  args,
87
- installed: true,
88
- version: extractVersion(result.stdout, result.stderr),
89
- stdout: result.stdout,
90
- stderr: result.stderr,
122
+ installed: status.installed,
123
+ version: status.version || undefined,
124
+ loginStatus: status.loginStatus,
125
+ loginGuidance: status.guidance.login,
126
+ stdout: status.version || "",
127
+ stderr: "",
91
128
  };
92
129
  }
93
130
  catch (error) {
131
+ const result = await fallbackCliVersion(cli, args);
132
+ if (result)
133
+ return result;
94
134
  const message = error instanceof Error ? error.message : String(error);
95
135
  return {
96
136
  cli,
@@ -104,9 +144,72 @@ export async function getCliVersion(cli) {
104
144
  }
105
145
  }
106
146
  export async function getCliVersions(cli) {
107
- const clis = cli ? [cli] : ["claude", "codex", "gemini", "grok"];
147
+ const clis = cli ? [cli] : ["claude", "codex", "gemini", "grok", "mistral"];
108
148
  return Promise.all(clis.map(item => getCliVersion(item)));
109
149
  }
150
+ function buildMistralUpgradePlan(normalizedTarget, detectMistral) {
151
+ const method = detectMistral();
152
+ // Vibe ships no self-update command. cli_upgrade dispatches to the installer
153
+ // it detects; if none can be detected the caller gets an actionable error
154
+ // (we surface it as a no-op plan with `command: ""` so runCliUpgrade can
155
+ // throw before spawning anything).
156
+ if (method === "pip") {
157
+ const pkg = normalizedTarget === "latest" ? "vibe-cli" : `vibe-cli==${normalizedTarget}`;
158
+ return {
159
+ cli: "mistral",
160
+ target: normalizedTarget,
161
+ command: "pip",
162
+ args: ["install", "-U", pkg],
163
+ strategy: "pip-install",
164
+ requiresNetwork: true,
165
+ note: "Mistral Vibe has no self-update command; gateway detected a pip install.",
166
+ };
167
+ }
168
+ if (method === "uv") {
169
+ return {
170
+ cli: "mistral",
171
+ target: normalizedTarget,
172
+ command: "uv",
173
+ args: ["tool", "upgrade", "vibe-cli"],
174
+ strategy: "uv-tool-upgrade",
175
+ requiresNetwork: true,
176
+ note: normalizedTarget === "latest"
177
+ ? "Mistral Vibe has no self-update command; gateway detected a uv tool install."
178
+ : "uv tool upgrade does not honour explicit version targets; running upgrade to latest.",
179
+ };
180
+ }
181
+ if (method === "brew") {
182
+ return {
183
+ cli: "mistral",
184
+ target: normalizedTarget,
185
+ command: "brew",
186
+ args: ["upgrade", "mistral-vibe"],
187
+ strategy: "brew-upgrade",
188
+ requiresNetwork: true,
189
+ note: normalizedTarget === "latest"
190
+ ? "Mistral Vibe has no self-update command; gateway detected a Homebrew install."
191
+ : "brew upgrade does not honour explicit version targets; running upgrade to latest.",
192
+ };
193
+ }
194
+ throw new Error("Could not detect how Mistral Vibe was installed. Install it via pip (`pip install vibe-cli`), uv (`uv tool install vibe-cli`), or Homebrew (`brew install mistral-vibe`) before running cli_upgrade.");
195
+ }
196
+ async function fallbackCliVersion(cli, args) {
197
+ try {
198
+ const result = await executeCli(cli, args, { timeout: 15_000 });
199
+ return {
200
+ cli,
201
+ command: cli,
202
+ args,
203
+ installed: true,
204
+ version: extractVersion(result.stdout, result.stderr),
205
+ stdout: result.stdout,
206
+ stderr: result.stderr,
207
+ };
208
+ }
209
+ catch {
210
+ return null;
211
+ }
212
+ }
110
213
  export async function runCliUpgrade(params) {
111
214
  const plan = buildCliUpgradePlan(params.cli, params.target);
112
215
  if (params.dryRun) {
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Parser for Codex CLI `--json` JSONL event stream.
3
+ *
4
+ * Codex emits one JSON object per line, e.g.:
5
+ * {"type":"thread.started","thread_id":"t-abc"}
6
+ * {"type":"turn.started","turn_id":"u-001"}
7
+ * {"type":"item.started","item":{...}}
8
+ * {"type":"item.completed","item":{"type":"agent_message","text":"..."}}
9
+ * {"type":"turn.completed","usage":{"input_tokens":...,"output_tokens":...,...}}
10
+ * {"type":"turn.failed","error":{...}}
11
+ * {"type":"error","message":"..."}
12
+ *
13
+ * This parser is lenient: malformed lines are skipped, partial streams are
14
+ * tolerated (usage is `undefined` if no turn.completed event arrived), and
15
+ * error events are surfaced.
16
+ *
17
+ * Cost is intentionally NOT computed here — Codex does not price client-side
18
+ * and U23 only plumbs tokens. A future unit can compute cost from the model
19
+ * registry.
20
+ */
21
+ export interface CodexUsage {
22
+ input_tokens: number;
23
+ output_tokens: number;
24
+ cache_read_tokens?: number;
25
+ cache_creation_tokens?: number;
26
+ cost_usd?: number;
27
+ }
28
+ export interface CodexJsonParseResult {
29
+ usage?: CodexUsage;
30
+ error?: string;
31
+ threadId?: string;
32
+ finalMessage?: string;
33
+ }
34
+ export declare function parseCodexJsonStream(stdout: string): CodexJsonParseResult;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Parser for Codex CLI `--json` JSONL event stream.
3
+ *
4
+ * Codex emits one JSON object per line, e.g.:
5
+ * {"type":"thread.started","thread_id":"t-abc"}
6
+ * {"type":"turn.started","turn_id":"u-001"}
7
+ * {"type":"item.started","item":{...}}
8
+ * {"type":"item.completed","item":{"type":"agent_message","text":"..."}}
9
+ * {"type":"turn.completed","usage":{"input_tokens":...,"output_tokens":...,...}}
10
+ * {"type":"turn.failed","error":{...}}
11
+ * {"type":"error","message":"..."}
12
+ *
13
+ * This parser is lenient: malformed lines are skipped, partial streams are
14
+ * tolerated (usage is `undefined` if no turn.completed event arrived), and
15
+ * error events are surfaced.
16
+ *
17
+ * Cost is intentionally NOT computed here — Codex does not price client-side
18
+ * and U23 only plumbs tokens. A future unit can compute cost from the model
19
+ * registry.
20
+ */
21
+ export function parseCodexJsonStream(stdout) {
22
+ const lines = stdout.split("\n").filter(line => line.trim().length > 0);
23
+ const result = {};
24
+ let lastAgentMessage;
25
+ for (const line of lines) {
26
+ let parsed;
27
+ try {
28
+ parsed = JSON.parse(line);
29
+ }
30
+ catch {
31
+ // Skip preamble/garbage lines that aren't valid JSON.
32
+ continue;
33
+ }
34
+ if (!parsed || typeof parsed !== "object") {
35
+ continue;
36
+ }
37
+ switch (parsed.type) {
38
+ case "thread.started":
39
+ if (typeof parsed.thread_id === "string") {
40
+ result.threadId = parsed.thread_id;
41
+ }
42
+ break;
43
+ case "turn.completed": {
44
+ const u = parsed.usage;
45
+ if (u && typeof u === "object") {
46
+ const usage = {
47
+ input_tokens: typeof u.input_tokens === "number" ? u.input_tokens : 0,
48
+ output_tokens: typeof u.output_tokens === "number" ? u.output_tokens : 0,
49
+ };
50
+ if (typeof u.cache_read_input_tokens === "number") {
51
+ usage.cache_read_tokens = u.cache_read_input_tokens;
52
+ }
53
+ else if (typeof u.cache_read_tokens === "number") {
54
+ usage.cache_read_tokens = u.cache_read_tokens;
55
+ }
56
+ if (typeof u.cache_creation_input_tokens === "number") {
57
+ usage.cache_creation_tokens = u.cache_creation_input_tokens;
58
+ }
59
+ else if (typeof u.cache_creation_tokens === "number") {
60
+ usage.cache_creation_tokens = u.cache_creation_tokens;
61
+ }
62
+ if (typeof u.cost_usd === "number") {
63
+ usage.cost_usd = u.cost_usd;
64
+ }
65
+ result.usage = usage;
66
+ }
67
+ break;
68
+ }
69
+ case "turn.failed": {
70
+ const err = parsed.error;
71
+ if (typeof err === "string") {
72
+ result.error = err;
73
+ }
74
+ else if (err && typeof err === "object" && typeof err.message === "string") {
75
+ result.error = err.message;
76
+ }
77
+ else {
78
+ result.error = "turn failed";
79
+ }
80
+ break;
81
+ }
82
+ case "error":
83
+ if (typeof parsed.message === "string") {
84
+ result.error = parsed.message;
85
+ }
86
+ break;
87
+ case "item.completed": {
88
+ const item = parsed.item;
89
+ if (item &&
90
+ typeof item === "object" &&
91
+ item.type === "agent_message" &&
92
+ typeof item.text === "string") {
93
+ lastAgentMessage = item.text;
94
+ }
95
+ break;
96
+ }
97
+ default:
98
+ break;
99
+ }
100
+ }
101
+ if (lastAgentMessage !== undefined) {
102
+ result.finalMessage = lastAgentMessage;
103
+ }
104
+ return result;
105
+ }
@@ -0,0 +1,110 @@
1
+ import { type EndpointExposureReport } from "./endpoint-exposure.js";
2
+ import { type ProviderLoginStatus } from "./provider-status.js";
3
+ export interface VibeSessionLoggingStatus {
4
+ config_path: string;
5
+ config_present: boolean;
6
+ session_logging_enabled: boolean;
7
+ note: string;
8
+ }
9
+ export interface GeminiConfigStatus {
10
+ /** Presence of a project-local `GEMINI.md` in the gateway's cwd. */
11
+ project_gemini_md_present: boolean;
12
+ project_gemini_md_path: string;
13
+ /** Presence of `~/.gemini/GEMINI.md`. */
14
+ user_gemini_md_present: boolean;
15
+ user_gemini_md_path: string;
16
+ /** Presence and contents of `~/.gemini/settings.json` `mcpServers` block. */
17
+ settings_json_present: boolean;
18
+ settings_json_path: string;
19
+ mcp_servers_registered: string[];
20
+ /** Per-server reconciliation against the gateway's `--allowed-mcp-server-names` whitelist. */
21
+ mcp_reconciliation: {
22
+ whitelisted: string[];
23
+ missing_from_settings: string[];
24
+ };
25
+ next_actions: string[];
26
+ }
27
+ /**
28
+ * Probe ~/.vibe/config.toml to see whether session_logging is enabled. Vibe
29
+ * persists session logs (which sessionId/--continue depends on) only when
30
+ * `[session_logging] enabled = true` is set. The probe is read-only: the
31
+ * gateway never mutates this file.
32
+ */
33
+ export declare function checkVibeSessionLogging(home?: string): VibeSessionLoggingStatus;
34
+ /**
35
+ * U27: Probe Gemini's project/user config locations.
36
+ *
37
+ * - `./GEMINI.md` (gateway cwd) and `~/.gemini/GEMINI.md` are documented
38
+ * "context" surfaces. Missing both means Gemini has no project-specific
39
+ * guidance.
40
+ * - `~/.gemini/settings.json` defines registered MCP servers (`mcpServers`
41
+ * block). The gateway tracks its own whitelist (`CLAUDE_MCP_SERVER_NAMES`)
42
+ * and surfaces a reconciliation warning for each whitelisted server not
43
+ * present in settings.json so callers don't ship requests for unregistered
44
+ * servers.
45
+ */
46
+ export declare function checkGeminiConfig(cwd?: string, home?: string, whitelist?: readonly string[]): GeminiConfigStatus;
47
+ export interface DoctorReport {
48
+ schema_version: "1.0";
49
+ ok: boolean;
50
+ generated_at: string;
51
+ system: {
52
+ os: NodeJS.Platform;
53
+ arch: string;
54
+ release: string;
55
+ node_version: string;
56
+ };
57
+ gateway: {
58
+ name: string;
59
+ version: string;
60
+ };
61
+ transport: {
62
+ default: "stdio" | "http";
63
+ http: {
64
+ enabled: boolean;
65
+ host: string;
66
+ port: number;
67
+ path: string;
68
+ public_url_configured: boolean;
69
+ public_url: string | null;
70
+ };
71
+ };
72
+ auth: {
73
+ required: boolean;
74
+ token_configured: boolean;
75
+ source: string;
76
+ };
77
+ providers: Record<"claude" | "codex" | "gemini" | "grok" | "mistral", {
78
+ cli_available: boolean;
79
+ version: string | null;
80
+ login_status: ProviderLoginStatus;
81
+ version_command: string[];
82
+ login_check: {
83
+ method: "cli" | "credential_store" | "not_checked";
84
+ command: string[] | null;
85
+ credential_store: "present" | "not_found" | "not_checked";
86
+ detail: string;
87
+ };
88
+ install_guidance: {
89
+ summary: string;
90
+ commands: string[];
91
+ documentation_url?: string;
92
+ };
93
+ login_guidance: {
94
+ summary: string;
95
+ commands: string[];
96
+ credential_handling: string;
97
+ };
98
+ }>;
99
+ endpoint_exposure: EndpointExposureReport;
100
+ client_config: {
101
+ claude_desktop_config_present: boolean;
102
+ codex_config_present: boolean;
103
+ gemini_settings_present: boolean;
104
+ gemini_config: GeminiConfigStatus;
105
+ vibe_session_logging: VibeSessionLoggingStatus;
106
+ };
107
+ next_actions: string[];
108
+ }
109
+ export declare function createDoctorReport(env?: NodeJS.ProcessEnv): DoctorReport;
110
+ export declare function printDoctorJson(): void;