@bothat-io/molenkopf 0.1.2
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/.env.example +2 -0
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/SECURITY.md +36 -0
- package/bin/launcher.js +76 -0
- package/bin/molenkopf.js +4 -0
- package/docs/DEPLOYMENT.md +104 -0
- package/docs/MOLENKOPF_PLUGIN_API.md +113 -0
- package/docs/MOLENKOPF_PROVIDER_ENV.md +123 -0
- package/docs/MOLENKOPF_USAGE.md +195 -0
- package/docs/PRODUCT_INTENT.md +36 -0
- package/docs/THREAT_MODEL.md +94 -0
- package/molenkopf.config.example.json +68 -0
- package/package.json +98 -0
- package/packages/core/src/auth/password.ts +47 -0
- package/packages/core/src/auth/session.ts +64 -0
- package/packages/core/src/ci/ci-mode.ts +71 -0
- package/packages/core/src/compression/content-classifier.ts +25 -0
- package/packages/core/src/compression/context-compressor.ts +48 -0
- package/packages/core/src/compression/json-compressor.ts +54 -0
- package/packages/core/src/compression/log-compressor.ts +32 -0
- package/packages/core/src/compression/operational-block-compressor.ts +43 -0
- package/packages/core/src/compression/stacktrace-compressor.ts +23 -0
- package/packages/core/src/config/config-policies.ts +146 -0
- package/packages/core/src/config/molenkopf-config.ts +137 -0
- package/packages/core/src/config/provider-config.ts +139 -0
- package/packages/core/src/events/event-bus.ts +88 -0
- package/packages/core/src/identity/api-keys.ts +149 -0
- package/packages/core/src/identity/budget.ts +51 -0
- package/packages/core/src/identity/db.ts +68 -0
- package/packages/core/src/identity/identity-store.ts +175 -0
- package/packages/core/src/identity/identity-validation.ts +102 -0
- package/packages/core/src/identity/key-permissions.ts +18 -0
- package/packages/core/src/identity/pricing.ts +11 -0
- package/packages/core/src/identity/types.ts +87 -0
- package/packages/core/src/identity/usage-snapshot.ts +116 -0
- package/packages/core/src/manifest/audit-activity.ts +74 -0
- package/packages/core/src/manifest/audit-metrics.ts +7 -0
- package/packages/core/src/manifest/audit-safety.ts +113 -0
- package/packages/core/src/manifest/audit-store.ts +189 -0
- package/packages/core/src/manifest/audit-summary.ts +184 -0
- package/packages/core/src/manifest/usage-meter.ts +105 -0
- package/packages/core/src/memory/memory-extractor.ts +57 -0
- package/packages/core/src/memory/memory-graph.ts +55 -0
- package/packages/core/src/pipeline/json-string-spans.ts +143 -0
- package/packages/core/src/pipeline/openai-request-rewriter.ts +66 -0
- package/packages/core/src/plugins/builtin-plugin-descriptors.ts +10 -0
- package/packages/core/src/plugins/builtin-plugin-modules.ts +9 -0
- package/packages/core/src/plugins/plugin-api.ts +96 -0
- package/packages/core/src/plugins/plugin-catalog.ts +42 -0
- package/packages/core/src/plugins/plugin-descriptor.ts +51 -0
- package/packages/core/src/plugins/plugin-sdk.ts +47 -0
- package/packages/core/src/plugins/static-pipeline.ts +5 -0
- package/packages/core/src/profiles/profile-router.ts +45 -0
- package/packages/core/src/providers/provider-catalog.ts +186 -0
- package/packages/core/src/routing/distribution.ts +31 -0
- package/packages/core/src/security/secret-redactor.ts +139 -0
- package/packages/core/src/security/target-policy.ts +61 -0
- package/packages/core/src/storage/local-paths.ts +6 -0
- package/packages/core/src/storage/private-state.ts +30 -0
- package/packages/core/src/storage/purge-dir.ts +10 -0
- package/packages/core/src/store/retrieval-store.ts +114 -0
- package/packages/core/src/utils/hash.ts +9 -0
- package/packages/core/src/utils/text.ts +18 -0
- package/packages/core/src/utils/tokens.ts +3 -0
- package/packages/dashboard/dist/assets/index-B_aSPgHx.js +11 -0
- package/packages/dashboard/dist/assets/index-D6z2TEL2.css +1 -0
- package/packages/dashboard/dist/favicon.png +0 -0
- package/packages/dashboard/dist/index.html +15 -0
- package/packages/dashboard/dist/molenkopf-logo.png +0 -0
- package/packages/dashboard/public/favicon.png +0 -0
- package/packages/dashboard/public/molenkopf-logo.png +0 -0
- package/packages/plugins/context-compressor-plugin/descriptor.ts +19 -0
- package/packages/plugins/context-compressor-plugin/page.html +191 -0
- package/packages/plugins/context-compressor-plugin/plugin.ts +40 -0
- package/packages/plugins/obsidian-graph-plugin/descriptor.ts +19 -0
- package/packages/plugins/obsidian-graph-plugin/page.html +68 -0
- package/packages/plugins/obsidian-graph-plugin/plugin.ts +27 -0
- package/packages/plugins/shared/audit-projects.ts +32 -0
- package/packages/proxy/src/cli/args.ts +34 -0
- package/packages/proxy/src/cli/config-loader.ts +43 -0
- package/packages/proxy/src/cli/env-file.ts +43 -0
- package/packages/proxy/src/cli/main.ts +132 -0
- package/packages/proxy/src/cli/profile-server.ts +176 -0
- package/packages/proxy/src/cli/target.ts +7 -0
- package/packages/proxy/src/http/agent-drafts.ts +103 -0
- package/packages/proxy/src/http/agent-router.ts +69 -0
- package/packages/proxy/src/http/audit-view.ts +15 -0
- package/packages/proxy/src/http/auth-state.ts +44 -0
- package/packages/proxy/src/http/budget-gate.ts +45 -0
- package/packages/proxy/src/http/budget-warnings.ts +7 -0
- package/packages/proxy/src/http/cli-stream-response.ts +51 -0
- package/packages/proxy/src/http/client-identity.ts +51 -0
- package/packages/proxy/src/http/communication-graph.ts +139 -0
- package/packages/proxy/src/http/control-plane-guard.ts +56 -0
- package/packages/proxy/src/http/dashboard-assets.ts +115 -0
- package/packages/proxy/src/http/encoded-usage-meter.ts +32 -0
- package/packages/proxy/src/http/header-utils.ts +65 -0
- package/packages/proxy/src/http/identity-id.ts +11 -0
- package/packages/proxy/src/http/local-api-agent-actions.ts +17 -0
- package/packages/proxy/src/http/local-api-auth.ts +120 -0
- package/packages/proxy/src/http/local-api-consumer-actions.ts +20 -0
- package/packages/proxy/src/http/local-api-identity.ts +194 -0
- package/packages/proxy/src/http/local-api-io.ts +82 -0
- package/packages/proxy/src/http/local-api-keys.ts +126 -0
- package/packages/proxy/src/http/local-api-pipeline.ts +41 -0
- package/packages/proxy/src/http/local-api-plugin-actions.ts +31 -0
- package/packages/proxy/src/http/local-api-provider-actions.ts +181 -0
- package/packages/proxy/src/http/local-api-retention.ts +28 -0
- package/packages/proxy/src/http/local-api-runtime-auth.ts +119 -0
- package/packages/proxy/src/http/local-api-scope.ts +47 -0
- package/packages/proxy/src/http/local-api-state.ts +180 -0
- package/packages/proxy/src/http/local-api.ts +166 -0
- package/packages/proxy/src/http/password-policy.ts +5 -0
- package/packages/proxy/src/http/plugin-data.ts +38 -0
- package/packages/proxy/src/http/plugin-host.ts +87 -0
- package/packages/proxy/src/http/plugin-modules.ts +1 -0
- package/packages/proxy/src/http/plugin-page-loader.ts +24 -0
- package/packages/proxy/src/http/plugin-pipeline.ts +125 -0
- package/packages/proxy/src/http/provider-access.ts +33 -0
- package/packages/proxy/src/http/provider-http-test.ts +133 -0
- package/packages/proxy/src/http/provider-input.ts +39 -0
- package/packages/proxy/src/http/provider-routing-snapshot.ts +28 -0
- package/packages/proxy/src/http/provider-test.ts +149 -0
- package/packages/proxy/src/http/proxy-identity.ts +78 -0
- package/packages/proxy/src/http/public-bind.ts +8 -0
- package/packages/proxy/src/http/request-finish.ts +62 -0
- package/packages/proxy/src/http/request-path.ts +8 -0
- package/packages/proxy/src/http/request-policy.ts +46 -0
- package/packages/proxy/src/http/runtime-auth-proof.ts +55 -0
- package/packages/proxy/src/http/runtime-auth-registry.ts +105 -0
- package/packages/proxy/src/http/runtime-settings.ts +199 -0
- package/packages/proxy/src/http/runtime-state.ts +198 -0
- package/packages/proxy/src/http/server-io.ts +80 -0
- package/packages/proxy/src/http/server-types.ts +17 -0
- package/packages/proxy/src/http/server.ts +190 -0
- package/packages/proxy/src/http/session-secret.ts +19 -0
- package/packages/proxy/src/http/streaming-proxy.ts +88 -0
- package/packages/proxy/src/http/usage-accounting.ts +100 -0
- package/packages/proxy/src/http/usage-restore.ts +15 -0
- package/packages/proxy/src/runtime/cli-diagnostics.ts +64 -0
- package/packages/proxy/src/runtime/cli-env.ts +22 -0
- package/packages/proxy/src/runtime/cli-executor.ts +134 -0
- package/packages/proxy/src/runtime/cli-provider.ts +162 -0
- package/packages/proxy/src/runtime/cli-request.ts +79 -0
- package/packages/proxy/src/runtime/codex-runtime-config.ts +37 -0
- package/packages/proxy/src/runtime/runtime-profile.ts +170 -0
package/.env.example
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 bothat.io and Molenkopf contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="packages/dashboard/public/molenkopf-logo.png" alt="Molenkopf logo" width="72">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# Molenkopf
|
|
6
|
+
|
|
7
|
+
Molenkopf is a local gateway for API and CLI based coding agents. It is not
|
|
8
|
+
only an OpenAI-compatible API proxy: it also supports Anthropic/Claude API
|
|
9
|
+
traffic and local CLI runtimes such as Claude CLI and Codex CLI. It should
|
|
10
|
+
reduce real transferred context, redact secrets, build local derived memory,
|
|
11
|
+
and write audit manifests without full prompts or responses.
|
|
12
|
+
|
|
13
|
+
Molenkopf has a fixed core safety pipeline for secret redaction, content classification, safe compression for logs, JSON and stacktraces, local retrieval storage, audit manifests, and redacted SSE events. Optional plugins extend this pipeline; core safety behavior is not toggleable.
|
|
14
|
+
|
|
15
|
+
Product intent and non-negotiable plugin semantics live in
|
|
16
|
+
`docs/PRODUCT_INTENT.md` and `docs/MOLENKOPF_PLUGIN_API.md`.
|
|
17
|
+
|
|
18
|
+
## Safety Notice
|
|
19
|
+
|
|
20
|
+
Do not run Molenkopf blindly with real provider accounts, private repositories,
|
|
21
|
+
browser sessions, or imported runtime credentials. Review `SECURITY.md` first,
|
|
22
|
+
keep source runs on the default loopback bind, and treat `.molenkopf/`, audit
|
|
23
|
+
files, retrieval stores, databases, env files, and runtime-auth profiles as
|
|
24
|
+
sensitive local state.
|
|
25
|
+
|
|
26
|
+
## Implemented Baseline
|
|
27
|
+
|
|
28
|
+
Built now:
|
|
29
|
+
|
|
30
|
+
- Local HTTP proxy bound to `127.0.0.1` by default.
|
|
31
|
+
- Transparent streaming gateway for OpenAI- and Anthropic-compatible traffic via
|
|
32
|
+
base-URL interception (`OPENAI_BASE_URL` / `ANTHROPIC_BASE_URL`). Responses
|
|
33
|
+
are streamed through byte-for-byte where possible (SSE, gzip, headers
|
|
34
|
+
preserved); the agent runs normally and its context stream flows through
|
|
35
|
+
Molenkopf.
|
|
36
|
+
- Transparent by default: the request body is never altered unless a transform plugin is explicitly enabled. Compression is opt-in and only reduces structured/operational content (json/log/stacktrace/shell); prose, markdown, source code, and diffs pass through untouched.
|
|
37
|
+
- Per-agent multi-account routing: each agent (`x-molenkopf-agent`) routes to its bound provider/account; config `profiles`/`agents` resolve to providers.
|
|
38
|
+
- Real token accounting: upstream `usage` is read from the provider response and recorded as
|
|
39
|
+
real input/output tokens (not only a chars/4 estimate).
|
|
40
|
+
- Text-derived memory graph: concepts (files, symbols, error types) are extracted from the
|
|
41
|
+
real redacted transferred text into a bounded co-occurrence graph.
|
|
42
|
+
- Static pipeline with request IDs, redaction, classification, compression, retrieval, audit, SSE events, and upstream routing.
|
|
43
|
+
- Local API under `/__molenkopf/*`: bootstrap endpoints for health, session
|
|
44
|
+
status, and first-run admin creation, user-scoped usage/key endpoints, and admin-only
|
|
45
|
+
provider/plugin/agent/stats/events/config metadata plus retention purge.
|
|
46
|
+
- Dashboard shell served at `/__molenkopf/dashboard` with Overview and Admin
|
|
47
|
+
views backed by an isolated React/Vite dashboard package. Dedicated Providers,
|
|
48
|
+
Plugins, Requests, Audit, Agents, and Settings views are planned.
|
|
49
|
+
- Plugin pages are local HTML surfaces under `/__molenkopf/plugins/:id/page`; context compression and memory graph pages read scoped plugin data endpoints and show explicit load errors instead of fake empty workspaces.
|
|
50
|
+
- The context compression plugin must expose safe token accounting for real transferred context only. Empty states must not show placeholder pressure or fake savings. Usage notes: `docs/CONTEXT_COMPRESSION_PLUGIN_README.md`.
|
|
51
|
+
- Practical dashboard and proxy connection guide: `docs/MOLENKOPF_USAGE.md`.
|
|
52
|
+
- JSON config startup target for providers, agents, and plugin policies:
|
|
53
|
+
`docs/MOLENKOPF_JSON_CONFIG_PLAN.md`.
|
|
54
|
+
- Multi-provider env setup: `docs/MOLENKOPF_PROVIDER_ENV.md`.
|
|
55
|
+
- Root `/` redirects to the local dashboard; upstream API traffic stays on `/v1/...`.
|
|
56
|
+
- Claude/Anthropic-compatible target resolution through `ANTHROPIC_BASE_URL`; Molenkopf-owned CLI runtime bridging exists for local `claude` and `codex` child processes.
|
|
57
|
+
- Profile routing primitives for fixed, manual, and failover routing with env credentials, budgets, and health summaries.
|
|
58
|
+
- Local plugin SDK with explicit permissions and remote plugin loading disabled.
|
|
59
|
+
- CI mode primitives for PR context packing and audit artifacts.
|
|
60
|
+
|
|
61
|
+
Explicitly not connected:
|
|
62
|
+
|
|
63
|
+
- Remote issue-tracker integration.
|
|
64
|
+
- Remote plugin installation.
|
|
65
|
+
- Credential-file scanning or config-file credential storage. Runtime auth import
|
|
66
|
+
stores credentials only when the operator explicitly imports a local profile.
|
|
67
|
+
|
|
68
|
+
## Security Model
|
|
69
|
+
|
|
70
|
+
Core and proxy use Node.js built-ins only. Molenkopf API keys are stored as hashes, imported runtime auth is kept in isolated local profile directories, and Local API responses hide provider credentials. Proxy traffic on `/v1/...` requires a valid Molenkopf API key. The default upstream route can forward incoming auth headers only when the selected profile requires it; configured provider profiles strip incoming client auth and inject the server-side credential at the forwarding boundary. Prompts and responses are not logged in full. Source code and diffs pass through in safe mode.
|
|
71
|
+
|
|
72
|
+
Before the first admin exists, only health, session status, and browser first-run
|
|
73
|
+
admin creation are usable. After setup, Local API metadata is role-gated: normal
|
|
74
|
+
users get scoped usage and key data, while provider, plugin, agent, routing,
|
|
75
|
+
stats, events, and retention endpoints are admin-only.
|
|
76
|
+
|
|
77
|
+
Use `--allow-public-bind` only when you intentionally want to bind outside
|
|
78
|
+
localhost. If Docker is published publicly before an admin exists, the first-run
|
|
79
|
+
screen is reachable there until the first admin is created.
|
|
80
|
+
|
|
81
|
+
## Control Plane Usage
|
|
82
|
+
|
|
83
|
+
Open `http://127.0.0.1:8787/__molenkopf/dashboard` after starting Molenkopf.
|
|
84
|
+
Root `/` redirects there, while upstream agent traffic still uses `/v1/...`.
|
|
85
|
+
|
|
86
|
+
Every `/v1/...` proxy request must present a valid Molenkopf API key. Use
|
|
87
|
+
`Authorization: Bearer mk_...` when Molenkopf supplies provider credentials. If
|
|
88
|
+
the client must also forward an upstream `Authorization` header, put the
|
|
89
|
+
Molenkopf key in `x-molenkopf-token: mk_...`; Molenkopf strips that header
|
|
90
|
+
before forwarding upstream.
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
Authorization: Bearer mk_...
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`x-molenkopf-agent` is optional local request metadata. It may select an
|
|
97
|
+
explicit agent binding only when that binding is already allowed for the
|
|
98
|
+
authenticated key, team, and provider policy; otherwise the request fails
|
|
99
|
+
closed.
|
|
100
|
+
|
|
101
|
+
Manual provider switching happens in the Admin provider section or by posting `{ "id": "openai-env" }` to `/__molenkopf/providers/select`. Explicit provider profiles, API-key scopes, team allowlists, and routing mode are enforced before forwarding; manual selection remains the default route when no explicit profile is attached.
|
|
102
|
+
|
|
103
|
+
Multiple provider profiles can be declared with `molenkopf.config.json` or
|
|
104
|
+
`MOLENKOPF_PROVIDER_IDS`. JSON profiles must reference credentials with
|
|
105
|
+
`auth.credentialRef` such as `env:OPENAI_API_KEY`; inline credentials are
|
|
106
|
+
rejected in file config. Local API responses hide credential values and show
|
|
107
|
+
only `credentialRef` plus configured state. Selected configured profiles inject
|
|
108
|
+
their credential at the forwarding boundary.
|
|
109
|
+
|
|
110
|
+
Users, teams, projects, and Molenkopf API keys are managed in the Dashboard.
|
|
111
|
+
Overview is the signed-in user's status, usage, teams, members, and project-key
|
|
112
|
+
surface. Admin contains provider, plugin, user, team, and system controls.
|
|
113
|
+
Projects are required key/workload labels; teams carry provider policy and usage
|
|
114
|
+
scope. See `docs/MOLENKOPF_USAGE.md` for the workflow and Local API endpoints.
|
|
115
|
+
|
|
116
|
+
Plugin toggles happen in the Admin plugin section or by posting `{ "id": "context-compressor-plugin", "enabled": false }` to `/__molenkopf/plugins/toggle`. Plugins are optional extensions; core safety, storage, audit, event, and routing code is not exposed as plugins and cannot be disabled through plugin controls. Remote plugin loading is disabled.
|
|
117
|
+
|
|
118
|
+
Agent drafts are stored as local proxy metadata through `/__molenkopf/agents/draft`; raw token fields are rejected and only `tokenHash` is accepted. The dashboard keeps a `localStorage` fallback if the local API is unavailable. These drafts are routing metadata, not provider credentials.
|
|
119
|
+
|
|
120
|
+
For a step-by-step local setup and test flow, read `docs/MOLENKOPF_USAGE.md`.
|
|
121
|
+
|
|
122
|
+
Plugin pages open in standalone windows from `/__molenkopf/plugins/context-compressor-plugin/page` and `/__molenkopf/plugins/obsidian-graph-plugin/page`. The graph workspace is derived from safe request metadata and redacted transferred text. Context and graph pages group by project/key where available and surface plugin-data failures explicitly.
|
|
123
|
+
|
|
124
|
+
## Commands
|
|
125
|
+
Install from npm with Node.js 24 or newer:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npm install -g @bothat-io/molenkopf
|
|
129
|
+
node -e "require('node:fs').writeFileSync('.env','MOLENKOPF_SESSION_SECRET='+require('node:crypto').randomBytes(32).toString('hex')+'\n')"
|
|
130
|
+
molenkopf proxy
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Quick Docker start on the Docker host:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
cp .env.example .env
|
|
137
|
+
# Edit .env and set a unique MOLENKOPF_SESSION_SECRET.
|
|
138
|
+
docker pull ghcr.io/bothat-io/molenkopf:latest
|
|
139
|
+
docker run --rm \
|
|
140
|
+
--env-file .env \
|
|
141
|
+
-p 127.0.0.1:8787:8787 \
|
|
142
|
+
-v molenkopf-data:/data \
|
|
143
|
+
ghcr.io/bothat-io/molenkopf:latest
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Open `http://127.0.0.1:8787/` and create the first admin user. The Docker
|
|
147
|
+
quickstart binds Molenkopf to `127.0.0.1` on the host for local use; do not
|
|
148
|
+
publish the port publicly before admin setup and deployment security are done.
|
|
149
|
+
Docker requires `--env-file .env`; admin users are created only in the browser.
|
|
150
|
+
|
|
151
|
+
Use `docs/DEPLOYMENT.md` when you need a different port or non-loopback access.
|
|
152
|
+
|
|
153
|
+
For local development, use a source checkout:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npm run bootstrap
|
|
157
|
+
cp .env.example .env
|
|
158
|
+
# Edit .env and set a unique MOLENKOPF_SESSION_SECRET.
|
|
159
|
+
npm run dev
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Connect A Client
|
|
163
|
+
|
|
164
|
+
Use Molenkopf as the OpenAI-compatible base URL:
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
http://127.0.0.1:8787/v1
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Authenticate proxy traffic with a Molenkopf API key:
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
Authorization: Bearer mk_...
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
If your client also sends an upstream provider credential in `Authorization`,
|
|
177
|
+
send the Molenkopf key separately:
|
|
178
|
+
|
|
179
|
+
```text
|
|
180
|
+
x-molenkopf-token: mk_...
|
|
181
|
+
x-molenkopf-agent: codex-local
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
These local headers are stripped before upstream forwarding. See
|
|
185
|
+
`docs/MOLENKOPF_USAGE.md` for a concrete `curl` request, provider setup, and
|
|
186
|
+
dashboard checks.
|
|
187
|
+
|
|
188
|
+
## Limitations
|
|
189
|
+
|
|
190
|
+
Provider token accounting uses upstream `usage` when present and falls back to bounded estimates for local compression pressure. JSON summaries are readable summaries, not guaranteed valid JSON. v0.1 does not retry provider failures automatically and request-side safe compression remains opt-in.
|
|
191
|
+
|
|
192
|
+
Be careful with secrets and private repositories. Molenkopf reduces accidental
|
|
193
|
+
exposure, but local retrieval stores still contain bounded redacted excerpts.
|
|
194
|
+
They do not support full-original recovery and should be treated as sensitive
|
|
195
|
+
local artifacts.
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT License. Copyright (c) 2026 bothat.io and Molenkopf contributors.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
Molenkopf is a local gateway for coding-agent traffic. Do not run it blindly on
|
|
4
|
+
a workstation with real provider accounts, private repositories, browser
|
|
5
|
+
sessions, or imported runtime credentials. Review the configuration and runtime
|
|
6
|
+
flags first.
|
|
7
|
+
|
|
8
|
+
## Safe Use
|
|
9
|
+
|
|
10
|
+
- Start on loopback only unless you intentionally need network access.
|
|
11
|
+
- Set a unique `MOLENKOPF_SESSION_SECRET` before starting Molenkopf. Do not
|
|
12
|
+
commit `.env` files or bake secrets into Docker images.
|
|
13
|
+
- Do not use `--allow-public-bind` unless you intentionally need network access.
|
|
14
|
+
- First-run bootstrap is intentionally narrow. Before an admin exists, only
|
|
15
|
+
health, session status, and first-run admin creation are usable.
|
|
16
|
+
- After setup, provider, plugin, routing, agent, stats, event, config metadata,
|
|
17
|
+
and retention purge endpoints are admin-only. Normal users receive scoped
|
|
18
|
+
usage and key data.
|
|
19
|
+
- Treat `.molenkopf/`, audit manifests, retrieval stores, SQLite
|
|
20
|
+
files, runtime-auth profiles, and env files as sensitive local state.
|
|
21
|
+
- Do not commit provider keys, imported `auth.json`, Claude credentials,
|
|
22
|
+
cookies, database files, audit files, retrieval stores, or screenshots with
|
|
23
|
+
secrets.
|
|
24
|
+
- Prefer environment credential references or a private local config file over
|
|
25
|
+
UI-entered provider credentials. File config rejects inline raw credentials.
|
|
26
|
+
- Read Docker and deployment settings before exposing a container port.
|
|
27
|
+
|
|
28
|
+
## Reports
|
|
29
|
+
|
|
30
|
+
When the repository is hosted on GitHub, prefer GitHub private vulnerability
|
|
31
|
+
reporting if it is enabled. If only public issues are available, post a
|
|
32
|
+
sanitized reproduction only and keep sensitive material private.
|
|
33
|
+
|
|
34
|
+
Do not open a public issue that contains tokens, prompts, responses, auth files,
|
|
35
|
+
provider credentials, cookies, or private repository content. Redact the
|
|
36
|
+
material first and include only the minimal reproduction details.
|
package/bin/launcher.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { chmodSync, cpSync, existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const sourceRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
9
|
+
const signals = ["SIGINT", "SIGTERM"];
|
|
10
|
+
|
|
11
|
+
export function runLauncher(args = process.argv.slice(2), options = {}) {
|
|
12
|
+
const root = options.sourceRoot ?? sourceRoot;
|
|
13
|
+
const exit = options.exit ?? process.exit;
|
|
14
|
+
const signalSelf = options.signalSelf ?? ((name) => process.kill(process.pid, name));
|
|
15
|
+
const runtimeRoot = runtimeRootFor(root);
|
|
16
|
+
const entry = join(runtimeRoot, "packages", "proxy", "src", "cli", "main.ts");
|
|
17
|
+
if (!existsSync(entry)) throw new Error(`missing CLI entrypoint: ${entry}`);
|
|
18
|
+
const child = spawn(process.execPath, ["--experimental-strip-types", "--experimental-sqlite", "--disable-warning=ExperimentalWarning", entry, ...args], { stdio: options.stdio ?? "inherit" });
|
|
19
|
+
let closeRequested = false;
|
|
20
|
+
let finished = false;
|
|
21
|
+
let forced = false;
|
|
22
|
+
let requestedSignal = "";
|
|
23
|
+
let graceTimer;
|
|
24
|
+
|
|
25
|
+
const cleanup = () => cleanupRuntime(runtimeRoot, root);
|
|
26
|
+
const finish = (code, signal) => {
|
|
27
|
+
if (finished) return;
|
|
28
|
+
finished = true;
|
|
29
|
+
if (graceTimer) clearTimeout(graceTimer);
|
|
30
|
+
for (const name of signals) process.removeListener(name, handlers[name]);
|
|
31
|
+
cleanup();
|
|
32
|
+
const finalSignal = requestedSignal || signal;
|
|
33
|
+
if (finalSignal && process.platform !== "win32") signalSelf(finalSignal);
|
|
34
|
+
exit(code ?? (forced ? 1 : 1));
|
|
35
|
+
};
|
|
36
|
+
const handlers = Object.fromEntries(signals.map((name) => [name, () => {
|
|
37
|
+
if (closeRequested) return;
|
|
38
|
+
closeRequested = true;
|
|
39
|
+
requestedSignal = name;
|
|
40
|
+
child.kill(name);
|
|
41
|
+
graceTimer = setTimeout(() => {
|
|
42
|
+
forced = true;
|
|
43
|
+
child.kill("SIGKILL");
|
|
44
|
+
}, Number(process.env.MOLENKOPF_LAUNCHER_GRACE_MS ?? 5000)).unref();
|
|
45
|
+
}]));
|
|
46
|
+
|
|
47
|
+
child.on("close", finish);
|
|
48
|
+
child.on("error", (error) => {
|
|
49
|
+
console.error(error.message);
|
|
50
|
+
finish(1, null);
|
|
51
|
+
});
|
|
52
|
+
for (const name of signals) process.once(name, handlers[name]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function runtimeRootFor(root) {
|
|
56
|
+
if (!root.split(/[\\/]/).includes("node_modules")) return root;
|
|
57
|
+
const prefix = `molenkopf-cli-${createHash("sha256").update(root).digest("hex").slice(0, 12)}-`;
|
|
58
|
+
const runtimeRoot = mkdtempSync(join(tmpdir(), prefix));
|
|
59
|
+
try {
|
|
60
|
+
try { chmodSync(runtimeRoot, 0o700); } catch { /* Windows ACLs are inherited. */ }
|
|
61
|
+
for (const name of ["package.json", "packages", "bin"]) cpSync(join(root, name), join(runtimeRoot, name), { recursive: true });
|
|
62
|
+
return runtimeRoot;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
cleanupRuntime(runtimeRoot, root);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function cleanupRuntime(path, root = sourceRoot) {
|
|
70
|
+
if (path === root) return;
|
|
71
|
+
try {
|
|
72
|
+
rmSync(path, { recursive: true, force: true, maxRetries: 10, retryDelay: 50 });
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error(`cleanup warning: ${error instanceof Error ? error.message : String(error)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
package/bin/molenkopf.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Deployment
|
|
2
|
+
|
|
3
|
+
This document defines the current production deployment target for Molenkopf.
|
|
4
|
+
Routes, flags, and environment variables use Molenkopf names.
|
|
5
|
+
|
|
6
|
+
## Local Modes
|
|
7
|
+
|
|
8
|
+
- Development: `npm run dev`
|
|
9
|
+
- Binds `127.0.0.1:8787`
|
|
10
|
+
- Uses `.molenkopf/dev`
|
|
11
|
+
- Starts the Vite dashboard unless `MOLENKOPF_DASHBOARD_DEV=0`
|
|
12
|
+
- Test server: `npm run serve:test`
|
|
13
|
+
- Binds `127.0.0.1:8798`
|
|
14
|
+
- Uses `.molenkopf/test`
|
|
15
|
+
- Local prod smoke: `npm run prod`
|
|
16
|
+
- Builds the dashboard
|
|
17
|
+
- Binds `127.0.0.1:8787`
|
|
18
|
+
- Uses `.molenkopf/prod`
|
|
19
|
+
|
|
20
|
+
`npm run prod` is a local production profile with durable local state. Override
|
|
21
|
+
it with `MOLENKOPF_PROD_PORT`, `MOLENKOPF_PROD_HOST`, `MOLENKOPF_PROD_TARGET`,
|
|
22
|
+
and `MOLENKOPF_PROD_DATA_DIR` when needed.
|
|
23
|
+
|
|
24
|
+
## Docker Build
|
|
25
|
+
|
|
26
|
+
Build after the release source tree is clean:
|
|
27
|
+
|
|
28
|
+
```powershell
|
|
29
|
+
docker build --pull -t molenkopf:local .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The Docker image builds dashboard assets in a separate stage and copies only the
|
|
33
|
+
runtime source, plugin pages, root manifests, and built dashboard assets into the
|
|
34
|
+
final image. The final image also carries the MIT license notice. Core and Proxy
|
|
35
|
+
keep Node built-ins only.
|
|
36
|
+
|
|
37
|
+
## Docker Run
|
|
38
|
+
|
|
39
|
+
The published image listens on `0.0.0.0:8787` inside the container so Docker port
|
|
40
|
+
publishing works. For local use, publish it only to host loopback:
|
|
41
|
+
|
|
42
|
+
```powershell
|
|
43
|
+
Copy-Item .env.example .env
|
|
44
|
+
# Edit .env and set a unique MOLENKOPF_SESSION_SECRET.
|
|
45
|
+
|
|
46
|
+
docker run --rm `
|
|
47
|
+
--env-file .env `
|
|
48
|
+
-p 127.0.0.1:8787:8787 `
|
|
49
|
+
-v molenkopf-data:/data `
|
|
50
|
+
molenkopf:local
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Open `http://127.0.0.1:8787/` and create the first admin in the browser. Do not
|
|
54
|
+
seed admin usernames or passwords through environment variables. Docker starts
|
|
55
|
+
require a valid `MOLENKOPF_SESSION_SECRET`; copy `.env.example` to `.env`,
|
|
56
|
+
change the value, and pass it with `--env-file .env`. Docker does not
|
|
57
|
+
automatically read a host `.env` file. Configure provider credentials through
|
|
58
|
+
environment variables or a mounted config file that uses credential references.
|
|
59
|
+
Do not bake provider keys, imported runtime auth, `.env`, or local data into the
|
|
60
|
+
image.
|
|
61
|
+
|
|
62
|
+
## Data Volume
|
|
63
|
+
|
|
64
|
+
Mount one durable volume at `/data`. It contains SQLite files, audit manifests,
|
|
65
|
+
retrieval store files, runtime settings, and imported runtime profiles. Treat the
|
|
66
|
+
volume as sensitive local state.
|
|
67
|
+
|
|
68
|
+
On POSIX filesystems Molenkopf creates and repairs sensitive state directories as
|
|
69
|
+
`0700` and sensitive files as `0600`, including SQLite, audit, retrieval, runtime
|
|
70
|
+
settings, and imported runtime auth/profile files. Windows does not expose full
|
|
71
|
+
POSIX permission bits through Node, so protect the data directory with Windows
|
|
72
|
+
ACLs or a private user profile.
|
|
73
|
+
|
|
74
|
+
Admins can manually purge audit and retrieval data with
|
|
75
|
+
`POST /__molenkopf/retention/purge` and an explicit `scope` of `audit`,
|
|
76
|
+
`retrieval`, or `all`. TTLs, quotas, pagination, and project-scoped retention
|
|
77
|
+
policies are still future work.
|
|
78
|
+
|
|
79
|
+
Current constraint: one writer only. Do not run multiple containers against the
|
|
80
|
+
same SQLite volume.
|
|
81
|
+
|
|
82
|
+
## Security Gates
|
|
83
|
+
|
|
84
|
+
- Non-loopback source binds require `--allow-public-bind`.
|
|
85
|
+
- `MOLENKOPF_SESSION_SECRET` is required for every server start.
|
|
86
|
+
- `/v1/...` proxy APIs require a valid Molenkopf API key.
|
|
87
|
+
- `x-molenkopf-token` carries Molenkopf auth when a client must keep
|
|
88
|
+
`Authorization` for the upstream provider; it is stripped before forwarding.
|
|
89
|
+
- `/__molenkopf/health` is public.
|
|
90
|
+
- Before first admin setup, only health, session status, and first-run admin
|
|
91
|
+
creation are usable.
|
|
92
|
+
- Control-plane APIs require auth after an admin exists; provider, plugin,
|
|
93
|
+
routing, agent, stats, event, config metadata, and retention endpoints are
|
|
94
|
+
admin-only.
|
|
95
|
+
- Full prompts, full responses, provider credentials, cookies, auth headers, and
|
|
96
|
+
imported auth JSON must never appear in local API responses, plugin data, logs,
|
|
97
|
+
audit files, or docs.
|
|
98
|
+
|
|
99
|
+
## Not Supported Yet
|
|
100
|
+
|
|
101
|
+
- Dockerized host Claude/Codex CLI runtimes by default.
|
|
102
|
+
- Multi-replica deployment with one SQLite volume.
|
|
103
|
+
- Remote plugin installation.
|
|
104
|
+
- Obsidian vault writes without dry-run and path guards.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Molenkopf Plugin API
|
|
2
|
+
|
|
3
|
+
Molenkopf plugins are optional local extensions. Core safety, credential
|
|
4
|
+
handling, routing enforcement, audit invariants, and request redaction are core
|
|
5
|
+
responsibilities and are not exposed as plugins.
|
|
6
|
+
|
|
7
|
+
Each plugin is a TypeScript module at
|
|
8
|
+
`packages/plugins/<plugin-id>/plugin.ts`. It exports a static `descriptor` and
|
|
9
|
+
an executable `plugin` module.
|
|
10
|
+
|
|
11
|
+
The TypeScript descriptor remains the source of truth for permissions,
|
|
12
|
+
readable scopes, traffic mutations, workspace data, and default enabled state.
|
|
13
|
+
Runtime hook results are accepted only when the descriptor allows the matching
|
|
14
|
+
mutation.
|
|
15
|
+
Built-in local plugin modules are imported by the proxy from a static module
|
|
16
|
+
registry. Remote or downloaded plugin code is disabled.
|
|
17
|
+
|
|
18
|
+
## Descriptor
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import type { PluginDescriptor } from "../../core/src/plugins/plugin-descriptor.ts";
|
|
22
|
+
|
|
23
|
+
export const descriptor: PluginDescriptor = {
|
|
24
|
+
id: "context-compressor-plugin",
|
|
25
|
+
name: "context-compressor-plugin",
|
|
26
|
+
type: "transformer",
|
|
27
|
+
category: "compression",
|
|
28
|
+
description: "Compresses large safe context and keeps bounded redacted excerpts locally.",
|
|
29
|
+
traffic: { reads: ["redacted-body", "audit"], mutates: ["transform"] },
|
|
30
|
+
permissions: ["body:read", "body:write", "audit:read", "audit:write"],
|
|
31
|
+
hooks: ["request:body:rewrite", "audit:manifest", "workspace:local-page"],
|
|
32
|
+
toggle: { defaultEnabled: false, canDisable: true },
|
|
33
|
+
modulePath: "plugin.ts"
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`canDisable` is always `true` for visible plugins. If a feature is required for
|
|
38
|
+
Molenkopf to run safely, it belongs in Core instead of the plugin catalog.
|
|
39
|
+
|
|
40
|
+
## TypeScript Module
|
|
41
|
+
|
|
42
|
+
The public hook shape lives in `packages/core/src/plugins/plugin-api.ts`.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import type { MolenkopfPluginModule } from "../../core/src/plugins/plugin-api.ts";
|
|
46
|
+
|
|
47
|
+
export const plugin: MolenkopfPluginModule = {
|
|
48
|
+
onBoot(ctx) {
|
|
49
|
+
ctx.note("plugin booted");
|
|
50
|
+
},
|
|
51
|
+
onStart(ctx) {
|
|
52
|
+
ctx.note("plugin started");
|
|
53
|
+
},
|
|
54
|
+
onRequest(ctx) {
|
|
55
|
+
return { body: ctx.body.toUpperCase(), notes: ["rewrote request"] };
|
|
56
|
+
},
|
|
57
|
+
getData(ctx) {
|
|
58
|
+
return { plugin: ctx.plugin, requests: ctx.manifests.length };
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Available hooks:
|
|
64
|
+
|
|
65
|
+
| Hook | Purpose |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `onBoot` | Prepare local plugin state before the proxy listens. |
|
|
68
|
+
| `onStart` | Initialize enabled plugin work after the proxy listens. |
|
|
69
|
+
| `onEnable` | React to an admin enabling the plugin. |
|
|
70
|
+
| `onDisable` | React to an admin disabling the plugin. |
|
|
71
|
+
| `onRequest` | Inspect or transform a redacted request. |
|
|
72
|
+
| `onAudit` | Observe redacted audit manifests. |
|
|
73
|
+
| `onEvent` | Observe or emit local lifecycle events. |
|
|
74
|
+
| `getData` | Serve scoped workspace data. |
|
|
75
|
+
| `onStop` | Flush local state before shutdown. |
|
|
76
|
+
|
|
77
|
+
`getData` receives already-scoped, already-redacted audit manifests plus safe
|
|
78
|
+
workspace context such as the plugin view, declared scopes, and the memory graph
|
|
79
|
+
when available. The central proxy does not contain plugin-specific metrics.
|
|
80
|
+
|
|
81
|
+
## Request Results
|
|
82
|
+
|
|
83
|
+
`onRequest` returns a result object instead of mutating global state directly.
|
|
84
|
+
The runner applies only the allowed fields:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
type PluginRequestResult = {
|
|
88
|
+
body?: string;
|
|
89
|
+
providerId?: string;
|
|
90
|
+
block?: { status: number; error: string };
|
|
91
|
+
notes?: string[];
|
|
92
|
+
redactedSecrets?: number;
|
|
93
|
+
compressedItems?: number;
|
|
94
|
+
savedTokens?: number;
|
|
95
|
+
retrievalIds?: string[];
|
|
96
|
+
compressorsUsed?: string[];
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If a plugin returns a body rewrite without `traffic.mutates` containing
|
|
101
|
+
`transform`, `mask`, or `augment-context`, the runner restores the previous
|
|
102
|
+
body and blocks with `plugin_capability_violation`.
|
|
103
|
+
|
|
104
|
+
## Invariants
|
|
105
|
+
|
|
106
|
+
- Remote plugin loading is disabled.
|
|
107
|
+
- Core redaction runs before optional request plugins.
|
|
108
|
+
- Full prompts, full responses, credential values, cookies, and authorization
|
|
109
|
+
headers must not appear in plugin data, audit data, dashboard data, events, or
|
|
110
|
+
logs.
|
|
111
|
+
- Provider reroutes still pass provider, team, and key policy checks. Project is
|
|
112
|
+
key-level attribution, not a provider access layer.
|
|
113
|
+
- A disabled plugin must leave Molenkopf usable.
|