llm-cli-gateway 1.15.3 → 1.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +50 -10
- package/dist/cli-updater.js +16 -8
- package/dist/config.d.ts +2 -17
- package/dist/config.js +6 -24
- package/dist/db.d.ts +3 -13
- package/dist/db.js +6 -78
- package/dist/doctor.d.ts +4 -4
- package/dist/doctor.js +23 -13
- package/dist/health.d.ts +1 -8
- package/dist/health.js +2 -18
- package/dist/index.d.ts +5 -1
- package/dist/index.js +19 -11
- package/dist/migrate-sessions.js +3 -5
- package/dist/provider-login-guidance.js +10 -5
- package/dist/provider-status.js +1 -1
- package/dist/request-helpers.d.ts +10 -3
- package/dist/request-helpers.js +16 -4
- package/dist/session-manager-pg.d.ts +13 -42
- package/dist/session-manager-pg.js +25 -277
- package/dist/session-manager.js +3 -3
- package/dist/upstream-contracts.js +24 -2
- package/package.json +1 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,55 @@ All notable changes to the llm-cli-gateway project.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## [1.16.1] - 2026-05-29 — align Mistral Vibe CLI contract
|
|
8
|
+
|
|
9
|
+
Patch release for the current `mistral-vibe` CLI surface.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Updated Mistral Vibe requests to emit `--output text|json|streaming` instead
|
|
14
|
+
of the removed `--output-format` flag.
|
|
15
|
+
- Kept legacy MCP aliases working by mapping `plain` to `text` and
|
|
16
|
+
`stream-json` to `streaming`.
|
|
17
|
+
- Added `maxTokens` support for Mistral Vibe via `--max-tokens`.
|
|
18
|
+
- Updated Vibe install, upgrade, and doctor guidance for the current
|
|
19
|
+
`mistral-vibe` package and default-on session logging.
|
|
20
|
+
|
|
21
|
+
## [1.16.0] - 2026-05-29 — remove Redis session dependency
|
|
22
|
+
|
|
23
|
+
Feature release that removes the optional Redis/ioredis layer from the
|
|
24
|
+
PostgreSQL-backed session manager and tightens the public README around the
|
|
25
|
+
project's current demand and quality signals.
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
|
|
29
|
+
- Removed the optional `ioredis` peer/dev dependency and its transitive
|
|
30
|
+
packages from the install graph.
|
|
31
|
+
- Removed `REDIS_URL` as a requirement for PostgreSQL-backed sessions.
|
|
32
|
+
- Removed Redis from the PostgreSQL test Docker Compose stack and PG test
|
|
33
|
+
harness.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- PostgreSQL-backed sessions now require only `DATABASE_URL` plus the optional
|
|
38
|
+
`pg` peer dependency. PostgreSQL remains the source of truth for session
|
|
39
|
+
records and active-session state.
|
|
40
|
+
- Simplified database health reporting to PostgreSQL connectivity only.
|
|
41
|
+
- Simplified the PG session manager by removing Redis cache-aside reads/writes
|
|
42
|
+
and Redis lock handling.
|
|
43
|
+
- Updated migration and testing docs to describe the Postgres-only backend.
|
|
44
|
+
- Updated release-readiness and Socket-alert documentation now that the Redis
|
|
45
|
+
client dependency is no longer present.
|
|
46
|
+
- Refocused the README first screen around the strongest current trust and
|
|
47
|
+
demand signals: npm monthly downloads, passing CI/security workflows,
|
|
48
|
+
OpenSSF status, Sigstore-signed releases, and MIT licensing.
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- Added `docs/plans/provider-workflow-assets.dag.toml`, a machine-readable
|
|
53
|
+
implementation plan for provider-specific skill and DAG-TOML pairs for
|
|
54
|
+
Claude, Codex, Gemini, Grok, and Mistral Vibe.
|
|
55
|
+
|
|
7
56
|
## [1.15.3] - 2026-05-29 — remove retired PyPI plugin
|
|
8
57
|
|
|
9
58
|
Patch release removing the retired Python `llm` plugin integration so the
|
package/README.md
CHANGED
|
@@ -3,19 +3,38 @@
|
|
|
3
3
|
[](https://github.com/verivus-oss/llm-cli-gateway/actions/workflows/ci.yml)
|
|
4
4
|
[](https://github.com/verivus-oss/llm-cli-gateway/actions/workflows/security.yml)
|
|
5
5
|
[](https://scorecard.dev/viewer/?uri=github.com/verivus-oss/llm-cli-gateway)
|
|
6
|
-
[](https://www.bestpractices.dev/projects/13025)
|
|
7
6
|
[](https://www.npmjs.com/package/llm-cli-gateway)
|
|
8
|
-
[](https://www.npmjs.com/package/llm-cli-gateway)
|
|
9
7
|
[](https://www.npmjs.com/package/llm-cli-gateway)
|
|
10
|
-
[](https://github.com/verivus-oss/llm-cli-gateway/releases)
|
|
11
8
|
[](LICENSE)
|
|
12
|
-
[](SECURITY.md#release-signing)
|
|
13
9
|
|
|
14
10
|
> _"Without consultation, plans are frustrated, but with many counselors they succeed."_
|
|
15
11
|
> — Proverbs 15:22 (LSB)
|
|
16
12
|
|
|
17
13
|
A Model Context Protocol (MCP) gateway for running Claude Code, Codex, Gemini, Grok, and Mistral (Vibe) CLIs from one MCP endpoint, with durable async jobs, session continuity, cache-aware prompting, observability, and personal-appliance setup tooling.
|
|
18
14
|
|
|
15
|
+
**Why developers try it:** one local MCP endpoint for cross-LLM validation, multi-agent coding workflows, and repeatable assistant-led setup across five provider CLIs.
|
|
16
|
+
|
|
17
|
+
**Current signals:** crossed 5k monthly npm downloads in May 2026; live npm downloads are shown above. CI and security workflows pass on `main`, OpenSSF Scorecard is published, OpenSSF Best Practices is passing, releases use Sigstore signing, and the package is MIT licensed.
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g llm-cli-gateway
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or use directly with `npx` from an MCP client:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"llm-gateway": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["-y", "llm-cli-gateway"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
19
38
|
## What It Provides Today
|
|
20
39
|
|
|
21
40
|
`llm-cli-gateway` is a single-user MCP gateway for cross-LLM validation and multi-agent coding workflows. It is more than a thin CLI wrapper:
|
|
@@ -27,6 +46,24 @@ A Model Context Protocol (MCP) gateway for running Claude Code, Codex, Gemini, G
|
|
|
27
46
|
- Can run requests inside gateway-managed git worktrees for isolated multi-agent review and implementation loops.
|
|
28
47
|
- Ships personal-appliance setup surfaces: HTTP transport with bearer-token auth, `doctor --json`, setup UI artifacts, provider setup snippets, Docker fallback, and checked release bundles.
|
|
29
48
|
|
|
49
|
+
## Workflow Assets
|
|
50
|
+
|
|
51
|
+
The repo ships agent-ready workflow skills under [`.agents/skills`](.agents/skills) for async orchestration, session continuity, multi-LLM review, implement-review-fix loops, and secure approval-gated dispatch. Machine-readable DAG-TOML plans live under [`docs/plans`](docs/plans) and [`setup/install-plan.dag.toml`](setup/install-plan.dag.toml) for workflows that need deterministic sequencing and verification gates.
|
|
52
|
+
|
|
53
|
+
The next documentation focus is provider-specific skill and DAG-TOML pairs for each outbound CLI: Claude, Codex, Gemini, Grok, and Mistral Vibe. The implementation plan is tracked in [`docs/plans/provider-workflow-assets.dag.toml`](docs/plans/provider-workflow-assets.dag.toml), with each provider asset expected to cover install/login checks, session behavior, approval modes, cache/telemetry surfaces, failure modes, and a smoke-test gate.
|
|
54
|
+
|
|
55
|
+
## Trust & Supply Chain
|
|
56
|
+
|
|
57
|
+
[](https://www.bestpractices.dev/projects/13025)
|
|
58
|
+
[](https://www.npmjs.com/package/llm-cli-gateway)
|
|
59
|
+
[](https://github.com/verivus-oss/llm-cli-gateway/releases)
|
|
60
|
+
[](SECURITY.md#release-signing)
|
|
61
|
+
|
|
62
|
+
- CI runs build, lint, format, tests, package checks, and npm audit.
|
|
63
|
+
- Security CI runs actionlint, zizmor, shellcheck, typos, osv-scanner, gitleaks, and lychee.
|
|
64
|
+
- GitHub release installer artifacts are checksummed and signed with Sigstore keyless signing.
|
|
65
|
+
- npm releases use provenance through OIDC trusted publishing.
|
|
66
|
+
|
|
30
67
|
## Personal MCP Appliance
|
|
31
68
|
|
|
32
69
|
The personal-appliance contract keeps that surface intentionally narrow: one trusted user runs the gateway on a machine or volume they own, connects one MCP endpoint, and asks any connected client for cross-LLM validation.
|
|
@@ -216,13 +253,16 @@ grok login # OAuth flow, or set GROK_CODE_XAI_API_KEY
|
|
|
216
253
|
|
|
217
254
|
```bash
|
|
218
255
|
# Pick one — the gateway's cli_upgrade auto-detects which one you used.
|
|
219
|
-
|
|
220
|
-
|
|
256
|
+
curl -LsSf https://mistral.ai/vibe/install.sh | bash
|
|
257
|
+
pip install mistral-vibe
|
|
258
|
+
uv tool install mistral-vibe
|
|
221
259
|
brew install mistral-vibe
|
|
222
260
|
|
|
223
261
|
vibe auth login
|
|
224
|
-
#
|
|
225
|
-
|
|
262
|
+
# Current Vibe defaults session logging to enabled. If an older config disabled it,
|
|
263
|
+
# edit ~/.vibe/config.toml and set:
|
|
264
|
+
# [session_logging]
|
|
265
|
+
# enabled = true
|
|
226
266
|
```
|
|
227
267
|
|
|
228
268
|
Vibe-specific notes:
|
|
@@ -699,7 +739,8 @@ Run a Mistral Vibe agentic coding request. Like `grok_request` in shape, but wit
|
|
|
699
739
|
- `permissionMode`: `default | plan | accept-edits | auto-approve | chat | explore | lean` — emitted as `--agent <mode>`. Defaults to `auto-approve` in programmatic mode.
|
|
700
740
|
- `allowedTools` (string[], optional): One `--enabled-tools <tool>` flag per entry (allow-list only).
|
|
701
741
|
- `disallowedTools` (string[], optional): Accepted for parity with the other providers; ignored at the CLI boundary with a logged warning.
|
|
702
|
-
- `
|
|
742
|
+
- `outputFormat` (string, optional): Vibe 2.x values are `"text"`, `"json"`, or `"streaming"`; legacy aliases `"plain"` and `"stream-json"` are accepted and normalized before spawn.
|
|
743
|
+
- `sessionId` / `resumeLatest` / `createNewSession`: standard session controls. Current Vibe defaults session logging to enabled; if an older config has `[session_logging] enabled = false`, `doctor --json` surfaces an actionable next-action.
|
|
703
744
|
|
|
704
745
|
##### `claude_request_async` / `codex_request_async` / `gemini_request_async` / `grok_request_async` / `mistral_request_async`
|
|
705
746
|
|
|
@@ -1143,7 +1184,6 @@ If you're vetting `llm-cli-gateway` through [Socket](https://socket.dev/npm/pack
|
|
|
1143
1184
|
| **Shell access** | `src/executor.ts` uses `child_process.spawn(cmd, args, …)` to invoke the underlying LLM CLIs. | `spawn` is called with an argument array and **never** `shell: true`, so there is no shell interpolation path for caller input. The command name is restricted to an allow-list of known CLI binaries (`claude`, `codex`, `gemini`, `grok`, `vibe`). |
|
|
1144
1185
|
| **Uses eval** | None in our source. Transitive: `@modelcontextprotocol/sdk` → `ajv@8` uses `new Function(...)` in `ajv/dist/compile/index.js` to compile JSON Schema validators. | This is ajv's standard codegen path. Only known schemas (defined in our source and the MCP SDK) flow into it; no caller-supplied data ever reaches the compiled function body. |
|
|
1145
1186
|
| **better-sqlite3 PRAGMA helper** | Transitive: `better-sqlite3/lib/methods/pragma.js` interpolates its caller-provided `source` into a `PRAGMA ${source}` statement. | We do not call `db.pragma()` from production source. Internal SQLite setup uses fixed literal `db.exec("PRAGMA ...")` statements, and `npm run security:audit` fails the release if production code reintroduces `.pragma()` calls. |
|
|
1146
|
-
| **ioredis obfuscated code** | Optional peer/dev dependency: `ioredis@5.10.1` may be flagged at `built/constants/TLSProfiles.js` for base64-looking strings. | Reviewed as a false positive. The file is a Redis Cloud TLS CA certificate bundle in PEM format, which is base64 by design. It contains no decoder loop, dynamic evaluation, network call, or hidden execution path. The same file is byte-for-byte identical in `ioredis@5.9.2`; our default production install does not install `ioredis`, and our code does not pass ioredis TLS profile options. |
|
|
1147
1187
|
| **Dependency ownership** | A handful of small transitive packages (e.g. `bindings` via `better-sqlite3`, `media-typer` via `@modelcontextprotocol/sdk`) trip Socket's "unstable ownership" or "obfuscated code" heuristics. | These are pinned, well-known micro-deps in the Node ecosystem with no known issues. We pin direct override versions of `content-type` and `type-is` in `package.json#overrides`. Our previous direct dependency on `toml@3.0.0` (also single-maintainer, last released 2020) was replaced with the actively-maintained `smol-toml` to reduce inherited risk. |
|
|
1148
1188
|
|
|
1149
1189
|
See [`socket.yml`](./socket.yml) for the same context in machine-readable form.
|
package/dist/cli-updater.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { executeCli } from "./executor.js";
|
|
3
3
|
import { getProviderRuntimeStatus } from "./provider-status.js";
|
|
4
|
+
const MISTRAL_VIBE_PACKAGE = "mistral-vibe";
|
|
5
|
+
const LEGACY_VIBE_PACKAGE = "vibe-cli";
|
|
4
6
|
/**
|
|
5
7
|
* Detect how Vibe was installed on this machine. Vibe does not self-update, so
|
|
6
8
|
* cli_upgrade has to dispatch to the package manager that owns the binary.
|
|
@@ -16,15 +18,19 @@ export function detectMistralInstallMethod(exec = (cmd, args) => {
|
|
|
16
18
|
stdout: result.stdout || "",
|
|
17
19
|
};
|
|
18
20
|
}) {
|
|
19
|
-
const pip = exec("pip", ["show",
|
|
20
|
-
if (pip.exitCode === 0 && /Name:\s*vibe
|
|
21
|
+
const pip = exec("pip", ["show", MISTRAL_VIBE_PACKAGE]);
|
|
22
|
+
if (pip.exitCode === 0 && /Name:\s*mistral-vibe/i.test(pip.stdout)) {
|
|
23
|
+
return "pip";
|
|
24
|
+
}
|
|
25
|
+
const legacyPip = exec("pip", ["show", LEGACY_VIBE_PACKAGE]);
|
|
26
|
+
if (legacyPip.exitCode === 0 && /Name:\s*vibe-cli/i.test(legacyPip.stdout)) {
|
|
21
27
|
return "pip";
|
|
22
28
|
}
|
|
23
29
|
const uv = exec("uv", ["tool", "list"]);
|
|
24
|
-
if (uv.exitCode === 0 && /\
|
|
30
|
+
if (uv.exitCode === 0 && /\b(?:mistral-vibe|vibe-cli|vibe)\b/i.test(uv.stdout)) {
|
|
25
31
|
return "uv";
|
|
26
32
|
}
|
|
27
|
-
const brew = exec("brew", ["list",
|
|
33
|
+
const brew = exec("brew", ["list", MISTRAL_VIBE_PACKAGE]);
|
|
28
34
|
if (brew.exitCode === 0) {
|
|
29
35
|
return "brew";
|
|
30
36
|
}
|
|
@@ -154,7 +160,9 @@ function buildMistralUpgradePlan(normalizedTarget, detectMistral) {
|
|
|
154
160
|
// (we surface it as a no-op plan with `command: ""` so runCliUpgrade can
|
|
155
161
|
// throw before spawning anything).
|
|
156
162
|
if (method === "pip") {
|
|
157
|
-
const pkg = normalizedTarget === "latest"
|
|
163
|
+
const pkg = normalizedTarget === "latest"
|
|
164
|
+
? MISTRAL_VIBE_PACKAGE
|
|
165
|
+
: `${MISTRAL_VIBE_PACKAGE}==${normalizedTarget}`;
|
|
158
166
|
return {
|
|
159
167
|
cli: "mistral",
|
|
160
168
|
target: normalizedTarget,
|
|
@@ -170,7 +178,7 @@ function buildMistralUpgradePlan(normalizedTarget, detectMistral) {
|
|
|
170
178
|
cli: "mistral",
|
|
171
179
|
target: normalizedTarget,
|
|
172
180
|
command: "uv",
|
|
173
|
-
args: ["tool", "upgrade",
|
|
181
|
+
args: ["tool", "upgrade", MISTRAL_VIBE_PACKAGE],
|
|
174
182
|
strategy: "uv-tool-upgrade",
|
|
175
183
|
requiresNetwork: true,
|
|
176
184
|
note: normalizedTarget === "latest"
|
|
@@ -183,7 +191,7 @@ function buildMistralUpgradePlan(normalizedTarget, detectMistral) {
|
|
|
183
191
|
cli: "mistral",
|
|
184
192
|
target: normalizedTarget,
|
|
185
193
|
command: "brew",
|
|
186
|
-
args: ["upgrade",
|
|
194
|
+
args: ["upgrade", MISTRAL_VIBE_PACKAGE],
|
|
187
195
|
strategy: "brew-upgrade",
|
|
188
196
|
requiresNetwork: true,
|
|
189
197
|
note: normalizedTarget === "latest"
|
|
@@ -191,7 +199,7 @@ function buildMistralUpgradePlan(normalizedTarget, detectMistral) {
|
|
|
191
199
|
: "brew upgrade does not honour explicit version targets; running upgrade to latest.",
|
|
192
200
|
};
|
|
193
201
|
}
|
|
194
|
-
throw new Error("Could not detect how Mistral Vibe was installed. Install it via pip (`pip install vibe
|
|
202
|
+
throw new Error("Could not detect how Mistral Vibe was installed. Install it via pip (`pip install mistral-vibe`), uv (`uv tool install mistral-vibe`), or Homebrew (`brew install mistral-vibe`) before running cli_upgrade.");
|
|
195
203
|
}
|
|
196
204
|
async function fallbackCliVersion(cli, args) {
|
|
197
205
|
try {
|
package/dist/config.d.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import type { Logger } from "./logger.js";
|
|
2
|
-
export interface CacheTtl {
|
|
3
|
-
session: number;
|
|
4
|
-
activeSession: number;
|
|
5
|
-
sessionList: number;
|
|
6
|
-
}
|
|
7
2
|
export interface DatabaseConfig {
|
|
8
3
|
connectionString: string;
|
|
9
4
|
pool: {
|
|
@@ -13,25 +8,15 @@ export interface DatabaseConfig {
|
|
|
13
8
|
statementTimeout: number;
|
|
14
9
|
};
|
|
15
10
|
}
|
|
16
|
-
export interface RedisConfig {
|
|
17
|
-
url: string;
|
|
18
|
-
retryStrategy: {
|
|
19
|
-
maxRetries: number;
|
|
20
|
-
initialDelay: number;
|
|
21
|
-
maxDelay: number;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
11
|
export declare const DEFAULT_SESSION_TTL_SECONDS = 2592000;
|
|
25
12
|
export interface Config {
|
|
26
13
|
database?: DatabaseConfig;
|
|
27
|
-
redis?: RedisConfig;
|
|
28
|
-
cacheTtl: CacheTtl;
|
|
29
14
|
sessionTtl: number;
|
|
30
15
|
}
|
|
31
16
|
/**
|
|
32
17
|
* Load configuration from environment variables.
|
|
33
|
-
* Always returns a Config object with base fields
|
|
34
|
-
* Database
|
|
18
|
+
* Always returns a Config object with base fields.
|
|
19
|
+
* Database fields are populated when DATABASE_URL is set.
|
|
35
20
|
*/
|
|
36
21
|
export declare function loadConfig(): Config;
|
|
37
22
|
export declare const PERSISTENCE_BACKENDS: readonly ["sqlite", "postgres", "memory", "none"];
|
package/dist/config.js
CHANGED
|
@@ -11,37 +11,28 @@ const DatabaseUrlSchema = z
|
|
|
11
11
|
.refine(url => url.startsWith("postgresql://") || url.startsWith("postgres://"), {
|
|
12
12
|
message: "Database URL must start with postgresql:// or postgres://",
|
|
13
13
|
});
|
|
14
|
-
const RedisUrlSchema = z.string().url().startsWith("redis://");
|
|
15
14
|
export const DEFAULT_SESSION_TTL_SECONDS = 2592000; // 30 days
|
|
16
15
|
/**
|
|
17
16
|
* Load configuration from environment variables.
|
|
18
|
-
* Always returns a Config object with base fields
|
|
19
|
-
* Database
|
|
17
|
+
* Always returns a Config object with base fields.
|
|
18
|
+
* Database fields are populated when DATABASE_URL is set.
|
|
20
19
|
*/
|
|
21
20
|
export function loadConfig() {
|
|
22
21
|
const databaseUrl = process.env.DATABASE_URL;
|
|
23
|
-
const redisUrl = process.env.REDIS_URL;
|
|
24
|
-
// Default cache TTLs
|
|
25
|
-
const cacheTtl = {
|
|
26
|
-
session: 3600, // 1 hour
|
|
27
|
-
activeSession: 1800, // 30 minutes
|
|
28
|
-
sessionList: 120, // 2 minutes
|
|
29
|
-
};
|
|
30
22
|
const rawSessionTtl = parseInt(process.env.SESSION_TTL || String(DEFAULT_SESSION_TTL_SECONDS), 10);
|
|
31
23
|
const sessionTtl = Number.isFinite(rawSessionTtl) && rawSessionTtl > 0
|
|
32
24
|
? rawSessionTtl
|
|
33
25
|
: DEFAULT_SESSION_TTL_SECONDS;
|
|
34
26
|
// If no database config, return base config (file-based storage)
|
|
35
|
-
if (!databaseUrl
|
|
36
|
-
return {
|
|
27
|
+
if (!databaseUrl) {
|
|
28
|
+
return { sessionTtl };
|
|
37
29
|
}
|
|
38
|
-
// Validate
|
|
30
|
+
// Validate URL
|
|
39
31
|
try {
|
|
40
32
|
DatabaseUrlSchema.parse(databaseUrl);
|
|
41
|
-
RedisUrlSchema.parse(redisUrl);
|
|
42
33
|
}
|
|
43
34
|
catch (error) {
|
|
44
|
-
throw new Error(`Invalid database
|
|
35
|
+
throw new Error(`Invalid database URL: ${error instanceof Error ? error.message : String(error)}`);
|
|
45
36
|
}
|
|
46
37
|
return {
|
|
47
38
|
database: {
|
|
@@ -53,15 +44,6 @@ export function loadConfig() {
|
|
|
53
44
|
statementTimeout: 10000,
|
|
54
45
|
},
|
|
55
46
|
},
|
|
56
|
-
redis: {
|
|
57
|
-
url: redisUrl,
|
|
58
|
-
retryStrategy: {
|
|
59
|
-
maxRetries: 3,
|
|
60
|
-
initialDelay: 50,
|
|
61
|
-
maxDelay: 2000,
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
cacheTtl,
|
|
65
47
|
sessionTtl,
|
|
66
48
|
};
|
|
67
49
|
}
|
package/dist/db.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Pool } from "pg";
|
|
2
|
-
import type { Redis } from "ioredis";
|
|
3
2
|
import { Config } from "./config.js";
|
|
4
3
|
import type { Logger } from "./logger.js";
|
|
5
4
|
export interface HealthCheckResult {
|
|
@@ -7,22 +6,17 @@ export interface HealthCheckResult {
|
|
|
7
6
|
connected: boolean;
|
|
8
7
|
latency: number;
|
|
9
8
|
};
|
|
10
|
-
redis: {
|
|
11
|
-
connected: boolean;
|
|
12
|
-
latency: number;
|
|
13
|
-
};
|
|
14
9
|
}
|
|
15
10
|
/**
|
|
16
|
-
* Database connection manager for PostgreSQL
|
|
11
|
+
* Database connection manager for PostgreSQL-backed sessions.
|
|
17
12
|
*/
|
|
18
13
|
export declare class DatabaseConnection {
|
|
19
14
|
private logger;
|
|
20
15
|
private pool;
|
|
21
|
-
private redis;
|
|
22
16
|
private config;
|
|
23
17
|
constructor(config: Config, logger?: Logger);
|
|
24
18
|
/**
|
|
25
|
-
* Initialize
|
|
19
|
+
* Initialize connection to PostgreSQL.
|
|
26
20
|
*/
|
|
27
21
|
connect(): Promise<void>;
|
|
28
22
|
/**
|
|
@@ -30,17 +24,13 @@ export declare class DatabaseConnection {
|
|
|
30
24
|
*/
|
|
31
25
|
disconnect(): Promise<void>;
|
|
32
26
|
/**
|
|
33
|
-
* Health check for PostgreSQL
|
|
27
|
+
* Health check for PostgreSQL.
|
|
34
28
|
*/
|
|
35
29
|
healthCheck(): Promise<HealthCheckResult>;
|
|
36
30
|
/**
|
|
37
31
|
* Get PostgreSQL pool
|
|
38
32
|
*/
|
|
39
33
|
getPool(): Pool;
|
|
40
|
-
/**
|
|
41
|
-
* Get Redis client
|
|
42
|
-
*/
|
|
43
|
-
getRedis(): Redis;
|
|
44
34
|
}
|
|
45
35
|
/**
|
|
46
36
|
* Factory function to create and connect DatabaseConnection
|
package/dist/db.js
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
import { noopLogger } from "./logger.js";
|
|
2
2
|
/**
|
|
3
|
-
* Database connection manager for PostgreSQL
|
|
3
|
+
* Database connection manager for PostgreSQL-backed sessions.
|
|
4
4
|
*/
|
|
5
5
|
export class DatabaseConnection {
|
|
6
6
|
logger;
|
|
7
7
|
pool = null;
|
|
8
|
-
redis = null;
|
|
9
8
|
config;
|
|
10
9
|
constructor(config, logger = noopLogger) {
|
|
11
10
|
this.logger = logger;
|
|
12
|
-
if (!config.database
|
|
13
|
-
throw new Error("Database
|
|
11
|
+
if (!config.database) {
|
|
12
|
+
throw new Error("Database configuration required");
|
|
14
13
|
}
|
|
15
14
|
this.config = config;
|
|
16
15
|
}
|
|
17
16
|
/**
|
|
18
|
-
* Initialize
|
|
17
|
+
* Initialize connection to PostgreSQL.
|
|
19
18
|
*/
|
|
20
19
|
async connect() {
|
|
21
20
|
const { Pool } = await importOptionalPg();
|
|
22
|
-
const Redis = await importOptionalRedis();
|
|
23
21
|
// Initialize PostgreSQL pool
|
|
24
22
|
const poolConfig = {
|
|
25
23
|
connectionString: this.config.database.connectionString,
|
|
@@ -40,32 +38,6 @@ export class DatabaseConnection {
|
|
|
40
38
|
this.logger.error("Failed to connect to PostgreSQL", { error });
|
|
41
39
|
throw new Error(`Failed to connect to PostgreSQL: ${error instanceof Error ? error.message : String(error)}`);
|
|
42
40
|
}
|
|
43
|
-
// Initialize Redis client
|
|
44
|
-
const redisOptions = {
|
|
45
|
-
retryStrategy: (times) => {
|
|
46
|
-
const { maxRetries, initialDelay, maxDelay } = this.config.redis.retryStrategy;
|
|
47
|
-
if (times > maxRetries) {
|
|
48
|
-
return null; // Stop retrying
|
|
49
|
-
}
|
|
50
|
-
return Math.min(initialDelay * times, maxDelay);
|
|
51
|
-
},
|
|
52
|
-
lazyConnect: false,
|
|
53
|
-
reconnectOnError: (err) => {
|
|
54
|
-
// Reconnect on READONLY and ECONNRESET errors
|
|
55
|
-
const targetErrors = ["READONLY", "ECONNRESET"];
|
|
56
|
-
return targetErrors.some(targetError => err.message.includes(targetError));
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
this.redis = new Redis(this.config.redis.url, redisOptions);
|
|
60
|
-
// Test Redis connection
|
|
61
|
-
try {
|
|
62
|
-
await this.redis.ping();
|
|
63
|
-
this.logger.info("Redis connection established");
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
this.logger.error("Failed to connect to Redis", { error });
|
|
67
|
-
throw new Error(`Failed to connect to Redis: ${error instanceof Error ? error.message : String(error)}`);
|
|
68
|
-
}
|
|
69
41
|
}
|
|
70
42
|
/**
|
|
71
43
|
* Graceful shutdown - close all connections
|
|
@@ -82,26 +54,16 @@ export class DatabaseConnection {
|
|
|
82
54
|
errors.push(new Error(`PostgreSQL disconnect error: ${error instanceof Error ? error.message : String(error)}`));
|
|
83
55
|
}
|
|
84
56
|
}
|
|
85
|
-
if (this.redis) {
|
|
86
|
-
try {
|
|
87
|
-
this.redis.disconnect();
|
|
88
|
-
this.redis = null;
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
errors.push(new Error(`Redis disconnect error: ${error instanceof Error ? error.message : String(error)}`));
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
57
|
if (errors.length > 0) {
|
|
95
58
|
throw new Error(`Disconnect errors: ${errors.map(e => e.message).join("; ")}`);
|
|
96
59
|
}
|
|
97
60
|
}
|
|
98
61
|
/**
|
|
99
|
-
* Health check for PostgreSQL
|
|
62
|
+
* Health check for PostgreSQL.
|
|
100
63
|
*/
|
|
101
64
|
async healthCheck() {
|
|
102
65
|
const result = {
|
|
103
66
|
postgres: { connected: false, latency: 0 },
|
|
104
|
-
redis: { connected: false, latency: 0 },
|
|
105
67
|
};
|
|
106
68
|
// Check PostgreSQL
|
|
107
69
|
if (this.pool) {
|
|
@@ -123,21 +85,8 @@ export class DatabaseConnection {
|
|
|
123
85
|
}
|
|
124
86
|
}
|
|
125
87
|
}
|
|
126
|
-
// Check Redis
|
|
127
|
-
if (this.redis) {
|
|
128
|
-
const redisStart = Date.now();
|
|
129
|
-
try {
|
|
130
|
-
await this.redis.ping();
|
|
131
|
-
result.redis.connected = true;
|
|
132
|
-
result.redis.latency = Date.now() - redisStart;
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
135
|
-
result.redis.connected = false;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
88
|
this.logger.debug("Health check completed", {
|
|
139
89
|
postgres: result.postgres.connected,
|
|
140
|
-
redis: result.redis.connected,
|
|
141
90
|
});
|
|
142
91
|
return result;
|
|
143
92
|
}
|
|
@@ -150,15 +99,6 @@ export class DatabaseConnection {
|
|
|
150
99
|
}
|
|
151
100
|
return this.pool;
|
|
152
101
|
}
|
|
153
|
-
/**
|
|
154
|
-
* Get Redis client
|
|
155
|
-
*/
|
|
156
|
-
getRedis() {
|
|
157
|
-
if (!this.redis) {
|
|
158
|
-
throw new Error("Redis client not initialized");
|
|
159
|
-
}
|
|
160
|
-
return this.redis;
|
|
161
|
-
}
|
|
162
102
|
}
|
|
163
103
|
async function importOptionalPg() {
|
|
164
104
|
try {
|
|
@@ -166,19 +106,7 @@ async function importOptionalPg() {
|
|
|
166
106
|
}
|
|
167
107
|
catch (error) {
|
|
168
108
|
if (error?.code === "ERR_MODULE_NOT_FOUND" || error?.code === "MODULE_NOT_FOUND") {
|
|
169
|
-
throw new Error("PostgreSQL sessions require optional peer dependency 'pg'. Install it alongside llm-cli-gateway to use DATABASE_URL
|
|
170
|
-
}
|
|
171
|
-
throw error;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
async function importOptionalRedis() {
|
|
175
|
-
try {
|
|
176
|
-
const mod = await import("ioredis");
|
|
177
|
-
return mod.Redis ?? mod.default;
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
if (error?.code === "ERR_MODULE_NOT_FOUND" || error?.code === "MODULE_NOT_FOUND") {
|
|
181
|
-
throw new Error("PostgreSQL sessions require optional peer dependency 'ioredis'. Install it alongside llm-cli-gateway to use DATABASE_URL/REDIS_URL-backed sessions.");
|
|
109
|
+
throw new Error("PostgreSQL sessions require optional peer dependency 'pg'. Install it alongside llm-cli-gateway to use DATABASE_URL-backed sessions.");
|
|
182
110
|
}
|
|
183
111
|
throw error;
|
|
184
112
|
}
|
package/dist/doctor.d.ts
CHANGED
|
@@ -51,10 +51,10 @@ export interface GeminiConfigStatus {
|
|
|
51
51
|
next_actions: string[];
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
|
-
* Probe ~/.vibe/config.toml to see whether session_logging is enabled.
|
|
55
|
-
*
|
|
56
|
-
* `[session_logging] enabled =
|
|
57
|
-
* gateway never mutates this file.
|
|
54
|
+
* Probe ~/.vibe/config.toml to see whether session_logging is enabled. Current
|
|
55
|
+
* Mistral Vibe defaults session logging to enabled; an explicit
|
|
56
|
+
* `[session_logging] enabled = false` disables `--continue` / `--resume`.
|
|
57
|
+
* The probe is read-only: the gateway never mutates this file.
|
|
58
58
|
*/
|
|
59
59
|
export declare function checkVibeSessionLogging(home?: string): VibeSessionLoggingStatus;
|
|
60
60
|
/**
|
package/dist/doctor.js
CHANGED
|
@@ -10,10 +10,10 @@ import { loadCacheAwarenessConfig } from "./config.js";
|
|
|
10
10
|
import { computeGlobalCacheStats } from "./cache-stats.js";
|
|
11
11
|
import { FlightRecorder, resolveFlightRecorderDbPath } from "./flight-recorder.js";
|
|
12
12
|
/**
|
|
13
|
-
* Probe ~/.vibe/config.toml to see whether session_logging is enabled.
|
|
14
|
-
*
|
|
15
|
-
* `[session_logging] enabled =
|
|
16
|
-
* gateway never mutates this file.
|
|
13
|
+
* Probe ~/.vibe/config.toml to see whether session_logging is enabled. Current
|
|
14
|
+
* Mistral Vibe defaults session logging to enabled; an explicit
|
|
15
|
+
* `[session_logging] enabled = false` disables `--continue` / `--resume`.
|
|
16
|
+
* The probe is read-only: the gateway never mutates this file.
|
|
17
17
|
*/
|
|
18
18
|
export function checkVibeSessionLogging(home = homedir()) {
|
|
19
19
|
const configPath = join(home, ".vibe", "config.toml");
|
|
@@ -21,26 +21,28 @@ export function checkVibeSessionLogging(home = homedir()) {
|
|
|
21
21
|
return {
|
|
22
22
|
config_path: configPath,
|
|
23
23
|
config_present: false,
|
|
24
|
-
session_logging_enabled:
|
|
25
|
-
note: "~/.vibe/config.toml not found
|
|
24
|
+
session_logging_enabled: true,
|
|
25
|
+
note: "~/.vibe/config.toml not found; current Vibe defaults session_logging.enabled to true. If resume fails, create ~/.vibe/config.toml with [session_logging]\\nenabled = true.",
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
try {
|
|
29
29
|
const text = readFileSync(configPath, "utf8");
|
|
30
30
|
const enabled = parseVibeSessionLoggingEnabled(text);
|
|
31
|
-
if (enabled) {
|
|
31
|
+
if (enabled !== false) {
|
|
32
32
|
return {
|
|
33
33
|
config_path: configPath,
|
|
34
34
|
config_present: true,
|
|
35
35
|
session_logging_enabled: true,
|
|
36
|
-
note:
|
|
36
|
+
note: enabled === true
|
|
37
|
+
? "session_logging.enabled is true; --continue/--resume will work for mistral_request."
|
|
38
|
+
: "session_logging.enabled is not set; current Vibe defaults it to true.",
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
41
|
return {
|
|
40
42
|
config_path: configPath,
|
|
41
43
|
config_present: true,
|
|
42
44
|
session_logging_enabled: false,
|
|
43
|
-
note: "[session_logging] enabled = false
|
|
45
|
+
note: "[session_logging] enabled = false. Edit ~/.vibe/config.toml so the [session_logging] block sets enabled = true before using mistral_request --resume / --continue.",
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
48
|
catch (err) {
|
|
@@ -54,7 +56,7 @@ export function checkVibeSessionLogging(home = homedir()) {
|
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
/**
|
|
57
|
-
* Tiny TOML probe focused on `[session_logging] enabled =
|
|
59
|
+
* Tiny TOML probe focused on `[session_logging] enabled = ...`. Avoids pulling
|
|
58
60
|
* in the full `toml` parser when only one boolean is needed.
|
|
59
61
|
*/
|
|
60
62
|
function parseVibeSessionLoggingEnabled(text) {
|
|
@@ -73,7 +75,11 @@ function parseVibeSessionLoggingEnabled(text) {
|
|
|
73
75
|
const kv = line.match(/^enabled\s*=\s*(.+)$/);
|
|
74
76
|
if (kv) {
|
|
75
77
|
const value = kv[1].trim().toLowerCase();
|
|
76
|
-
|
|
78
|
+
if (value === "true")
|
|
79
|
+
return true;
|
|
80
|
+
if (value === "false")
|
|
81
|
+
return false;
|
|
82
|
+
return undefined;
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
85
|
else {
|
|
@@ -81,11 +87,15 @@ function parseVibeSessionLoggingEnabled(text) {
|
|
|
81
87
|
const dotted = line.match(/^session_logging\.enabled\s*=\s*(.+)$/);
|
|
82
88
|
if (dotted) {
|
|
83
89
|
const value = dotted[1].trim().toLowerCase();
|
|
84
|
-
|
|
90
|
+
if (value === "true")
|
|
91
|
+
return true;
|
|
92
|
+
if (value === "false")
|
|
93
|
+
return false;
|
|
94
|
+
return undefined;
|
|
85
95
|
}
|
|
86
96
|
}
|
|
87
97
|
}
|
|
88
|
-
return
|
|
98
|
+
return undefined;
|
|
89
99
|
}
|
|
90
100
|
/**
|
|
91
101
|
* U27: Probe Gemini's project/user config locations.
|
package/dist/health.d.ts
CHANGED
|
@@ -6,10 +6,6 @@ export interface HealthStatus {
|
|
|
6
6
|
status: "up" | "down";
|
|
7
7
|
latency: number;
|
|
8
8
|
};
|
|
9
|
-
redis: {
|
|
10
|
-
status: "up" | "down";
|
|
11
|
-
latency: number;
|
|
12
|
-
};
|
|
13
9
|
timestamp: string;
|
|
14
10
|
}
|
|
15
11
|
export interface ProviderRuntimeHealth {
|
|
@@ -18,10 +14,7 @@ export interface ProviderRuntimeHealth {
|
|
|
18
14
|
timestamp: string;
|
|
19
15
|
}
|
|
20
16
|
/**
|
|
21
|
-
* Check health status of PostgreSQL
|
|
22
|
-
* - Both up → healthy
|
|
23
|
-
* - Only PostgreSQL up → degraded (Redis down but DB works)
|
|
24
|
-
* - PostgreSQL down → unhealthy (critical failure)
|
|
17
|
+
* Check health status of PostgreSQL.
|
|
25
18
|
*/
|
|
26
19
|
export declare function checkHealth(db: DatabaseConnection): Promise<HealthStatus>;
|
|
27
20
|
export declare function checkProviderRuntimeHealth(): ProviderRuntimeHealth;
|