@bjorntech/key-ferry 0.0.0-stage → 0.1.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/LICENSE +21 -0
  2. package/README.md +209 -2
  3. package/SECURITY.md +87 -0
  4. package/dist/contracts.d.ts +133 -0
  5. package/dist/contracts.d.ts.map +1 -0
  6. package/dist/contracts.js +29 -0
  7. package/dist/contracts.js.map +1 -0
  8. package/dist/index.d.ts +7 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +12 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/options.d.ts +44 -0
  13. package/dist/options.d.ts.map +1 -0
  14. package/dist/options.js +260 -0
  15. package/dist/options.js.map +1 -0
  16. package/dist/plugin.d.ts +24 -0
  17. package/dist/plugin.d.ts.map +1 -0
  18. package/dist/plugin.js +215 -0
  19. package/dist/plugin.js.map +1 -0
  20. package/dist/relay/index.d.ts +37 -0
  21. package/dist/relay/index.d.ts.map +1 -0
  22. package/dist/relay/index.js +480 -0
  23. package/dist/relay/index.js.map +1 -0
  24. package/dist/remote.d.ts +62 -0
  25. package/dist/remote.d.ts.map +1 -0
  26. package/dist/remote.js +227 -0
  27. package/dist/remote.js.map +1 -0
  28. package/dist/rpc.d.ts +112 -0
  29. package/dist/rpc.d.ts.map +1 -0
  30. package/dist/rpc.js +117 -0
  31. package/dist/rpc.js.map +1 -0
  32. package/dist/services/process.d.ts +56 -0
  33. package/dist/services/process.d.ts.map +1 -0
  34. package/dist/services/process.js +208 -0
  35. package/dist/services/process.js.map +1 -0
  36. package/dist/tui.d.ts +59 -0
  37. package/dist/tui.d.ts.map +1 -0
  38. package/dist/tui.js +93 -0
  39. package/dist/tui.js.map +1 -0
  40. package/dist/varlock/manager.d.ts +6 -0
  41. package/dist/varlock/manager.d.ts.map +1 -0
  42. package/dist/varlock/manager.js +425 -0
  43. package/dist/varlock/manager.js.map +1 -0
  44. package/dist/varlock/protocol.d.ts +67 -0
  45. package/dist/varlock/protocol.d.ts.map +1 -0
  46. package/dist/varlock/protocol.js +248 -0
  47. package/dist/varlock/protocol.js.map +1 -0
  48. package/docs/architecture.md +179 -0
  49. package/docs/quality.md +102 -0
  50. package/docs/releasing.md +149 -0
  51. package/docs/troubleshooting.md +214 -0
  52. package/examples/.env.schema +17 -0
  53. package/examples/opencode.jsonc +44 -0
  54. package/package.json +96 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 key-ferry 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 CHANGED
@@ -1,3 +1,210 @@
1
- # Temporary Holding Version
1
+ # @bjorntech/key-ferry
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Project-scoped Key Ferry credential proxy integration for OpenCode V2.
4
+
5
+ ## What it protects
6
+
7
+ The native Effect 4 plugin owns a project-scoped Varlock proxy process and
8
+ protects explicitly selected MCP servers:
9
+
10
+ - Local MCP servers receive placeholder-backed environment mappings.
11
+ - Remote API-key MCP servers use an authenticated, scoped loopback relay.
12
+ The relay forwards Streamable HTTP and legacy SSE through Varlock's HTTP
13
+ CONNECT proxy to the configured HTTPS endpoint.
14
+ - Startup, refresh, missing-placeholder, relay, and sidecar failures fail
15
+ closed. There is no direct-transport fallback.
16
+ - Unselected remote servers, including native OAuth servers, remain unchanged.
17
+
18
+ The plugin never wraps OpenCode, mutates global `process.env`, or stores real
19
+ secret values in OpenCode configuration. Public status, RPC, and TUI output
20
+ never expose relay tokens, environment values, CA contents, or raw daemon
21
+ output.
22
+
23
+ This is scoped credential-proxy routing, not a full sandbox. Same-user
24
+ processes can still access normal files, stores, and daemons. The remote relay
25
+ controls its configured outbound path rather than relying on the MCP client to
26
+ honor proxy environment variables, but response payloads are not guaranteed
27
+ to have every secret scrubbed.
28
+ Direct provider/API transport protection is not included.
29
+
30
+ ## Requirements
31
+
32
+ - OpenCode V2 with native `@opencode/plugin/effect`
33
+ - `@opencode/plugin` 2.0.30-compatible runtime
34
+ - Effect 4.0.1-compatible runtime
35
+ - Node.js 22 or newer
36
+ - Varlock CLI 1.23.0 installed separately as `varlock`
37
+ - An installed Varlock CLI and a configured Varlock environment/schema with the
38
+ secrets and proxy rules your MCP servers need
39
+ - Configure secret sources and authentication following the
40
+ [Varlock documentation](https://varlock.dev/)
41
+
42
+ Verification has been exercised with Node 24 and Bun 1.4.2, alongside the
43
+ native OpenCode V2 package types and Effect 4.0.1.
44
+
45
+ ## Installation
46
+
47
+ OpenCode installs configured npm plugins automatically. Add the package to the
48
+ global OpenCode plugin configuration (normally `~/.config/opencode/opencode.jsonc`)
49
+ and preserve any unrelated entries already there:
50
+
51
+ ```jsonc
52
+ {
53
+ "plugins": [
54
+ {
55
+ "package": "@bjorntech/key-ferry@0.1.4",
56
+ "options": {
57
+ "enabled": true,
58
+ "requireStrictEgress": true,
59
+ "projectDirectory": ".",
60
+ "servers": {
61
+ "example-local-mcp": { "env": ["FAKE_API_KEY"] },
62
+ "example-remote-mcp": {
63
+ "remote": {
64
+ "headers": {
65
+ "Authorization": { "env": "REMOTE_API_KEY", "prefix": "Bearer " }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ ]
73
+ }
74
+ ```
75
+
76
+ The package is configured globally, while `projectDirectory` and the selected
77
+ server mappings determine which project context is protected. Unmapped MCP
78
+ servers remain under native OpenCode behavior. A selected remote API-key server
79
+ must explicitly set `oauth: false`; native OAuth remains unchanged for
80
+ unselected servers.
81
+
82
+ Install the Varlock CLI separately. A shared CLI installation is preferred over
83
+ installing it in every project:
84
+
85
+ ```sh
86
+ brew install dmno-dev/tap/varlock
87
+ varlock --version
88
+ ```
89
+
90
+ If Homebrew is unavailable, use the pinned Varlock 1.23.0 installer from the
91
+ official Varlock source and ensure the resulting `varlock` executable is on
92
+ `PATH`. The plugin does not install Varlock or unlock a secret store.
93
+
94
+ The package's sibling `./tui` export is discovered automatically when the
95
+ server plugin is loaded. Do not add a duplicate `cli.json` entry for an
96
+ ordinary server-plugin installation. A global `cli.json` entry is only useful
97
+ for a CLI-only deployment or a CLI connected to a remote OpenCode server that
98
+ already loaded the server plugin.
99
+
100
+ Set `projectDirectory` to the project root containing the schema, or to a
101
+ central Varlock configuration directory such as `~/.config/key-ferry`. It does
102
+ not need to be a Git repository. The plugin starts and owns its Varlock sidecar
103
+ itself; do not wrap OpenCode or manually run `varlock proxy`.
104
+
105
+ ## Configuration
106
+
107
+ Local mappings preserve the MCP command, cwd, and unrelated environment:
108
+
109
+ ```jsonc
110
+ "example-local-mcp": {
111
+ "env": { "UPSTREAM_API_KEY": "FAKE_API_KEY" }
112
+ }
113
+ ```
114
+
115
+ Remote mappings map target HTTP headers to exact Varlock environment names.
116
+ The string form is an environment reference, never a raw header value:
117
+
118
+ ```jsonc
119
+ "example-remote-mcp": {
120
+ "remote": {
121
+ "headers": {
122
+ "Authorization": { "env": "REMOTE_API_KEY", "prefix": "Bearer " },
123
+ "X-Tenant": { "env": "REMOTE_TENANT", "suffix": "-synthetic" }
124
+ }
125
+ }
126
+ }
127
+ ```
128
+
129
+ Header names are validated for control, hop-by-hop, protocol, duplicate, and
130
+ credential-confusion cases. The target URL comes from the native MCP remote
131
+ configuration, not from the relay mapping. HTTPS credentials and fragments
132
+ are rejected. Literal local/private IP targets are rejected unless the
133
+ explicit `allowPrivateNetwork: true` policy is set. Normal corporate DNS
134
+ names are not rejected merely because they may resolve internally.
135
+
136
+ Use `examples/.env.schema` for synthetic strict-egress values only. Mark each
137
+ remote upstream host with `@proxy(domain="...")` and keep
138
+ `@proxyConfig={egress="strict"}`. Configure any secret source through Varlock;
139
+ Key Ferry does not install Varlock, create its schema, or configure provider
140
+ authentication. Never commit a real secret.
141
+
142
+ ## TUI commands
143
+
144
+ | Palette command | Slash command | RPC |
145
+ | --- | --- | --- |
146
+ | `key-ferry.status` | `/key-ferry-status` | `status` |
147
+ | `key-ferry.refresh` | `/key-ferry-refresh` | `refresh` |
148
+ | `key-ferry.doctor` | `/key-ferry-doctor` | `doctor` |
149
+
150
+ The rename is intentionally breaking for the 0.1.0 release: no `varlock-*`
151
+ command aliases are retained.
152
+
153
+ Refresh and doctor report safe lifecycle and remote-configuration diagnostics.
154
+ They do not provide credential prompts or collect credentials. Doctor can
155
+ reconcile or recreate unhealthy selected `oauth:false` remote relays after
156
+ Varlock readiness succeeds, so authentication still depends on the configured
157
+ Varlock context.
158
+
159
+ ## Transport and lifecycle limits
160
+
161
+ The relay supports Streamable HTTP POST/GET/DELETE, streaming SSE, scoped
162
+ loopback authentication, bounded request handling, and session URL rewriting
163
+ for legacy SSE endpoints. Modern Streamable HTTP and legacy SSE responses are
164
+ streamed through bounded framing; compressed or oversized traffic fails closed
165
+ within the configured limits. A relay maintains at most 256 registered session
166
+ routes; duplicate registrations do not consume capacity, and new routes are
167
+ rejected after the cap. Native MCP session state is reset when Varlock refresh
168
+ changes the manager generation or when the relay is replaced. Existing remote
169
+ sessions must reconnect.
170
+
171
+ Varlock's public CA is read at the IO boundary and trusted by the relay. The
172
+ CA is not a secret, while raw secret values remain outside OpenCode. This is
173
+ not a complete response-content scrubber and does not replace native OAuth.
174
+ The public CA uses Varlock's required `0644` trust-material permissions.
175
+ Cleanup is scoped to this project instance and never performs a global
176
+ Varlock shutdown.
177
+
178
+ The packed root, TUI, and relay import paths are verified together. No wrapper
179
+ process is needed.
180
+
181
+ ## Verification
182
+
183
+ ```sh
184
+ bun typecheck
185
+ bun test
186
+ bun run build
187
+ ```
188
+
189
+ Coverage, formatter, and per-function CRAP-gate details are documented in
190
+ [Quality checks](docs/quality.md).
191
+
192
+ The two live Varlock tests are gated and skipped unless
193
+ `VARLOCK_LIVE_BINARY` points to an absolute CLI path. It uses synthetic
194
+ fixtures and isolated configuration. No per-test host credential or provider
195
+ authentication is required. Run them explicitly with:
196
+
197
+ ```sh
198
+ VARLOCK_LIVE_BINARY=/absolute/path/to/varlock bun test tests/varlock-live.test.ts
199
+ VARLOCK_LIVE_BINARY=/absolute/path/to/varlock bun test tests/remote-varlock-live.test.ts
200
+ ```
201
+
202
+ The normal suite and gated live fixtures are separate verification layers. Run
203
+ the commands above as needed; do not treat the normal suite as
204
+ certification of every external MCP provider, production deployment, or live
205
+ OpenCode TUI session.
206
+
207
+ See [Architecture](docs/architecture.md),
208
+ [Troubleshooting](docs/troubleshooting.md), and
209
+ [Quality checks](docs/quality.md),
210
+ [the example configuration](examples/opencode.jsonc).
package/SECURITY.md ADDED
@@ -0,0 +1,87 @@
1
+ # Security
2
+
3
+ ## Scope and security model
4
+
5
+ This project provides a native OpenCode V2 Effect plugin with a project-scoped,
6
+ owned Varlock CLI proxy sidecar. It is not a wrapper around all of OpenCode.
7
+ Local MCP servers are mapped explicitly to proxy placeholders and are disabled
8
+ until the sidecar is ready. Selected remote API-key MCP servers are instead
9
+ rewritten at runtime to an authenticated, project-scoped loopback relay. The
10
+ relay forwards Streamable HTTP and legacy SSE through Varlock's HTTP CONNECT
11
+ proxy to the fixed HTTPS target. Both modes fail closed when startup, refresh,
12
+ placeholder, relay, or sidecar health checks fail. Connections are scoped to
13
+ the OpenCode location and project, not to individual sessions.
14
+
15
+ The protocol exposes only an allowlisted, sanitized subset of Varlock status
16
+ JSON through public RPC. Do not assume that Varlock CLI output is safe to
17
+ publish: public CLI status may contain tokens. Full proxy environments may
18
+ contain raw passthrough values, including transient values held in process
19
+ memory, but this plugin permits only exact, placeholder-matched
20
+ `placeholderOverrides` entries to cross its adapter boundary. Never log or
21
+ publish a full environment map.
22
+
23
+ Strict egress is required by the manager default and is schema-controlled. The
24
+ local MCP path depends on the client honoring the supplied proxy environment;
25
+ the plugin cannot force an arbitrary local client to use that route.
26
+ The remote relay controls its configured outbound dispatcher instead of relying
27
+ on that client behavior, but this is not a sandbox or a claim of universal
28
+ network isolation. A same-user process may access configured secret stores,
29
+ files, configuration, or daemons. Direct provider/API transports are not
30
+ implemented. Unselected remote servers and native MCP OAuth handling remain
31
+ unchanged; this project does not replace OAuth.
32
+
33
+ Remote relay selection requires an explicit native `oauth: false` and exact
34
+ header-to-environment mappings. OAuth-enabled or OAuth-default remote servers
35
+ are disabled when selected for Varlock protection rather than silently
36
+ downgraded or sent directly. The relay binds to loopback, authenticates every
37
+ request with a scoped token, rejects unregistered downstream paths, and
38
+ invalidates old capabilities on manager generation changes.
39
+
40
+ Doctor rechecks Varlock readiness and can reconcile or recreate unhealthy
41
+ selected `oauth:false` relays. It does not provide a credential prompt or
42
+ replace native OAuth. Each relay caps registered session routes at 256;
43
+ duplicates do not consume capacity and new routes are rejected after the cap.
44
+ Relay TLS uses CONNECT and one verified `tls.connect` with the manager CA,
45
+ target `servername`, and `rejectUnauthorized: true`; the custom HTTP agent
46
+ returns that socket for HTTP framing. No global TLS flags are changed and
47
+ certificate verification is not weakened. Compressed or oversized framing
48
+ fails closed within configured limits.
49
+
50
+ Remote target URLs must be HTTPS without credentials or fragments. Literal
51
+ private/local targets require explicit `allowPrivateNetwork: true`; normal
52
+ corporate DNS names are not rejected solely because they may resolve
53
+ internally. The relay uses the configured target origin/path and never accepts
54
+ an arbitrary destination from downstream MCP traffic.
55
+
56
+ Varlock's public CA is trusted by the relay at the IO boundary, while raw
57
+ secret values remain outside OpenCode. CA MITM support and response scrubbing
58
+ have limitations; not every response payload is guaranteed scrubbed. Do not
59
+ place secret values in logs, errors, RPC payloads, issue reports, or support output.
60
+ The public CA file uses Varlock's required `0644` trust-material permissions;
61
+ it is not a secret. Cleanup is scoped to resources acquired by this project
62
+ instance and never invokes a global Varlock shutdown.
63
+ The plugin does not mutate `process.env`.
64
+
65
+ Native V2 types, mocks, package imports, and synthetic HTTPS/Varlock fixtures
66
+ are covered by the project verification commands. Opt-in fixture results do not
67
+ constitute production certification, compatibility with every external MCP
68
+ provider, or a fully live OpenCode host/TUI session.
69
+
70
+ ## Reporting a vulnerability
71
+
72
+ Report vulnerabilities through the repository's GitHub security reporting
73
+ mechanism for `bjorntech/key-ferry`. Do not open a public issue for an
74
+ unfixed vulnerability. Include reproduction steps and impact, but never
75
+ include secret values, tokens, credentials, or complete sensitive output.
76
+
77
+ The report should identify the affected version or commit and whether the
78
+ issue involves the plugin, the Varlock CLI dependency, OpenCode, or an MCP
79
+ server. Do not assume that a Varlock or OpenCode issue is fixed by this plugin.
80
+
81
+ ## Dependency and deployment guidance
82
+
83
+ Use supported, pinned dependency versions and review changes to the OpenCode
84
+ V2 plugin API, Effect 4, Bun, and the Varlock CLI before upgrading. Run the
85
+ sidecar with the least privilege available to the project and user. Do not
86
+ enable real secret-manager or network integration in automated tests unless a
87
+ separate, explicitly controlled environment is being used.
@@ -0,0 +1,133 @@
1
+ import { Context, Effect, Layer, Schema, Scope } from "effect";
2
+ /**
3
+ * Effect-native contracts shared by the Key Ferry sidecar manager and OpenCode
4
+ * V2 adapters.
5
+ *
6
+ * Resolved environment values are an internal capability. They must never be
7
+ * returned by status, RPC, logs, or model-visible tool output.
8
+ */
9
+ export type ProjectDirectory = string & {
10
+ readonly __projectDirectory: unique symbol;
11
+ };
12
+ export type ManagerPhase = "stopped" | "starting" | "ready" | "degraded" | "failed" | "stopping";
13
+ export type VarlockErrorCode = "invalid_config" | "unavailable" | "start_failed" | "refresh_failed" | "not_ready" | "disposed" | "protocol_error" | "timeout";
14
+ declare const VarlockError_base: Schema.Class<VarlockError, Schema.TaggedStruct<"Varlock.Error", {
15
+ readonly code: Schema.Literals<readonly ["invalid_config", "unavailable", "start_failed", "refresh_failed", "not_ready", "disposed", "protocol_error", "timeout"]>;
16
+ readonly message: Schema.String;
17
+ readonly retryable: Schema.Boolean;
18
+ }>, import("effect/Cause").YieldableError>;
19
+ /** Tagged and already-sanitized error safe for public diagnostics. */
20
+ export declare class VarlockError extends VarlockError_base {
21
+ }
22
+ /** Safe to return from status RPCs or display to a user. */
23
+ export interface SafeStatus {
24
+ readonly phase: ManagerPhase;
25
+ readonly projectDirectory: ProjectDirectory;
26
+ readonly generation: number;
27
+ readonly pid?: number;
28
+ readonly endpoint?: string;
29
+ readonly lastRefreshAt?: string;
30
+ readonly error?: VarlockError;
31
+ readonly diagnostics?: readonly SafeDiagnostic[];
32
+ }
33
+ /** Secret-free configuration diagnostics surfaced to RPC/TUI consumers. */
34
+ export interface SafeDiagnostic {
35
+ readonly code: string;
36
+ readonly server: string;
37
+ readonly message: string;
38
+ }
39
+ /**
40
+ * Secret-bearing values are deliberately separate from SafeStatus. This type
41
+ * is for internal process/proxy wiring only and must not cross an RPC boundary.
42
+ */
43
+ export type ResolvedEnvironment = Readonly<Record<string, string>>;
44
+ /**
45
+ * A manager is location/project scoped. Every mutating operation is required
46
+ * to serialize against the manager's internal semaphore and to honor Effect
47
+ * interruption. Implementations must never mutate global process.env.
48
+ */
49
+ export interface ProjectKeyFerryManagerService {
50
+ readonly status: Effect.Effect<SafeStatus>;
51
+ /** Start or reuse the manager-owned process, failing closed if not ready. */
52
+ readonly ensureStarted: Effect.Effect<SafeStatus, VarlockError>;
53
+ /** Internal-only environment capability for a child process or proxy. */
54
+ readonly environment: Effect.Effect<ResolvedEnvironment, VarlockError>;
55
+ /** Re-resolve and atomically apply values; failures leave the connection closed. */
56
+ readonly refresh: Effect.Effect<SafeStatus, VarlockError>;
57
+ /** Idempotent teardown of only the process and resources this manager owns. */
58
+ readonly dispose: Effect.Effect<void>;
59
+ /**
60
+ * Serializes lifecycle and refresh operations. Implementations should back
61
+ * this with an internal Semaphore and reject work after disposal.
62
+ */
63
+ readonly withExclusive: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
64
+ }
65
+ declare const ProjectKeyFerryManager_base: Context.ServiceClass<ProjectKeyFerryManager, "key-ferry/ProjectKeyFerryManager", ProjectKeyFerryManagerService>;
66
+ /** Native Effect service tag used by the manager and plugin layers. */
67
+ export declare class ProjectKeyFerryManager extends ProjectKeyFerryManager_base {
68
+ }
69
+ export interface ProjectKeyFerryManagerOptions {
70
+ readonly projectDirectory: ProjectDirectory;
71
+ /** Stable diagnostic label only, never a credential or secret. */
72
+ readonly owner?: string;
73
+ /** Adapter-specific non-secret options, pending Varlock protocol finalization. */
74
+ readonly adapter?: Readonly<Record<string, unknown>>;
75
+ /** Optional explicit owned session identifier; omitted for owned-only MVP. */
76
+ readonly sessionID?: string;
77
+ }
78
+ /**
79
+ * A factory supplies a scoped Layer. The implementation should use
80
+ * Effect.acquireRelease for the owned process and register finalization in the
81
+ * caller's Scope. No daemon adoption or global stop operation is implied.
82
+ */
83
+ export interface ProjectKeyFerryManagerFactory<R = never> {
84
+ readonly layer: (options: ProjectKeyFerryManagerOptions) => Layer.Layer<ProjectKeyFerryManager, VarlockError, R>;
85
+ }
86
+ /** Public plugin options. Values are configuration, never resolved secrets. */
87
+ export interface KeyFerryPluginOptions {
88
+ readonly enabled?: boolean;
89
+ readonly projectDirectory?: string;
90
+ readonly adapter?: Readonly<Record<string, unknown>>;
91
+ }
92
+ /** Runtime service dependencies for an injectable process implementation. */
93
+ export interface VarlockProcessService {
94
+ readonly spawn: (input: VarlockProcessSpawn) => Effect.Effect<VarlockProcessHandle, VarlockError>;
95
+ readonly send: (handle: VarlockProcessHandle, input: VarlockProcessCommand) => Effect.Effect<VarlockProcessResult, VarlockError>;
96
+ readonly stop: (handle: VarlockProcessHandle) => Effect.Effect<void, VarlockError>;
97
+ }
98
+ export interface VarlockProcessSpawn {
99
+ readonly command: readonly string[];
100
+ readonly cwd: ProjectDirectory;
101
+ readonly sessionID: string;
102
+ readonly allowReload: true;
103
+ }
104
+ export interface VarlockProcessHandle {
105
+ readonly pid: number;
106
+ readonly sessionID: string;
107
+ readonly ownershipID: string;
108
+ }
109
+ export type VarlockProcessCommand = {
110
+ readonly type: "status";
111
+ } | {
112
+ readonly type: "environment";
113
+ readonly names: readonly string[];
114
+ } | {
115
+ readonly type: "refresh";
116
+ };
117
+ export interface VarlockProcessResult {
118
+ /** Raw protocol data stays inside the Varlock adapter. */
119
+ readonly value: unknown;
120
+ }
121
+ declare const VarlockProcess_base: Context.ServiceClass<VarlockProcess, "key-ferry/VarlockProcess", VarlockProcessService>;
122
+ export declare class VarlockProcess extends VarlockProcess_base {
123
+ }
124
+ /** Explicit brand constructor used at configuration boundaries. */
125
+ export declare function projectDirectory(value: string): ProjectDirectory;
126
+ /**
127
+ * A scoped layer must be provided under an Effect Scope. This alias documents
128
+ * the intended lifetime without forcing callers to expose implementation
129
+ * details or Promise wrappers.
130
+ */
131
+ export type ScopedManagerLayer<R = never> = Layer.Layer<ProjectKeyFerryManager, VarlockError, R | Scope.Scope>;
132
+ export {};
133
+ //# sourceMappingURL=contracts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE/D;;;;;;GAMG;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,kBAAkB,EAAE,OAAO,MAAM,CAAA;CAAE,CAAC;AAEvF,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEjG,MAAM,MAAM,gBAAgB,GACxB,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,gBAAgB,GAChB,WAAW,GACX,UAAU,GACV,gBAAgB,GAChB,SAAS,CAAC;;;;;;AAEd,sEAAsE;AACtE,qBAAa,YAAa,SAAQ,iBAahC;CAAG;AAEL,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CAClD;AAED,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEnE;;;;GAIG;AACH,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAE3C,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAEhE,yEAAyE;IACzE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,mBAAmB,EAAE,YAAY,CAAC,CAAC;IAEvE,oFAAoF;IACpF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAE1D,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAEtC;;;OAGG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;CAC7F;;AAED,uEAAuE;AACvE,qBAAa,sBAAuB,SAAQ,2BAGL;CAAG;AAE1C,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,kEAAkE;IAClE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACrD,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,6BAA6B,CAAC,CAAC,GAAG,KAAK;IACtD,QAAQ,CAAC,KAAK,EAAE,CACd,OAAO,EAAE,6BAA6B,KACnC,KAAK,CAAC,KAAK,CAAC,sBAAsB,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;CAC3D;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACtD;AAED,6EAA6E;AAC7E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE,YAAY,CAAC,CAAC;IAClG,QAAQ,CAAC,IAAI,EAAE,CACb,MAAM,EAAE,oBAAoB,EAC5B,KAAK,EAAE,qBAAqB,KACzB,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE,YAAY,CAAC,CAAC;IACvD,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;CACpF;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,GAAG,EAAE,gBAAgB,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACnE;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAEjC,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;;AAED,qBAAa,cAAe,SAAQ,mBAEnC;CAAG;AAEJ,mEAAmE;AACnE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAGhE;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,CAAC,CAAC,GAAG,KAAK,IAAI,KAAK,CAAC,KAAK,CACrD,sBAAsB,EACtB,YAAY,EACZ,CAAC,GAAG,KAAK,CAAC,KAAK,CAChB,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { Context, Effect, Layer, Schema, Scope } from "effect";
2
+ /** Tagged and already-sanitized error safe for public diagnostics. */
3
+ export class VarlockError extends Schema.TaggedError()("Varlock.Error", {
4
+ code: Schema.Literals([
5
+ "invalid_config",
6
+ "unavailable",
7
+ "start_failed",
8
+ "refresh_failed",
9
+ "not_ready",
10
+ "disposed",
11
+ "protocol_error",
12
+ "timeout",
13
+ ]),
14
+ message: Schema.String,
15
+ retryable: Schema.Boolean,
16
+ }) {
17
+ }
18
+ /** Native Effect service tag used by the manager and plugin layers. */
19
+ export class ProjectKeyFerryManager extends Context.Service()("key-ferry/ProjectKeyFerryManager") {
20
+ }
21
+ export class VarlockProcess extends Context.Service()("key-ferry/VarlockProcess") {
22
+ }
23
+ /** Explicit brand constructor used at configuration boundaries. */
24
+ export function projectDirectory(value) {
25
+ if (value.length === 0)
26
+ throw new TypeError("project directory must not be empty");
27
+ return value;
28
+ }
29
+ //# sourceMappingURL=contracts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAwB/D,sEAAsE;AACtE,MAAM,OAAO,YAAa,SAAQ,MAAM,CAAC,WAAW,EAAgB,CAAC,eAAe,EAAE;IACpF,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC;QACpB,gBAAgB;QAChB,aAAa;QACb,cAAc;QACd,gBAAgB;QAChB,WAAW;QACX,UAAU;QACV,gBAAgB;QAChB,SAAS;KACD,CAAC;IACX,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,SAAS,EAAE,MAAM,CAAC,OAAO;CAC1B,CAAC;CAAG;AAsDL,uEAAuE;AACvE,MAAM,OAAO,sBAAuB,SAAQ,OAAO,CAAC,OAAO,EAGxD,CAAC,kCAAkC,CAAC;CAAG;AA+D1C,MAAM,OAAO,cAAe,SAAQ,OAAO,CAAC,OAAO,EAAyC,CAC1F,0BAA0B,CAC3B;CAAG;AAEJ,mEAAmE;AACnE,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,qCAAqC,CAAC,CAAC;IACnF,OAAO,KAAyB,CAAC;AACnC,CAAC"}
@@ -0,0 +1,7 @@
1
+ /** The native OpenCode V2 server plugin entrypoint. */
2
+ declare const plugin: import("@opencode/plugin/effect/plugin").Plugin<import("effect/Scope").Scope>;
3
+ export default plugin;
4
+ export { makePlugin } from "./plugin.js";
5
+ export { startRelay, RELAY_AUTH_HEADER, RelayError } from "./relay/index.js";
6
+ export type { RelayConfig, RelayHandle, RelayHealth, RelayErrorCode } from "./relay/index.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,uDAAuD;AAIvD,QAAA,MAAM,MAAM,+EAAwD,CAAC;AAErE,eAAe,MAAM,CAAC;AAEtB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC7E,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { makePlugin } from "./plugin.js";
2
+ import { startRelay } from "./relay/index.js";
3
+ import { projectKeyFerryManagerFactory } from "./varlock/manager.js";
4
+ /** The native OpenCode V2 server plugin entrypoint. */
5
+ // The production entrypoint supplies the real CONNECT relay. Keeping the
6
+ // factory injectable in makePlugin is useful for tests, but omitting it here
7
+ // would silently disable every selected remote MCP server.
8
+ const plugin = makePlugin(projectKeyFerryManagerFactory, startRelay);
9
+ export default plugin;
10
+ export { makePlugin } from "./plugin.js";
11
+ export { startRelay, RELAY_AUTH_HEADER, RelayError } from "./relay/index.js";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAC;AAErE,uDAAuD;AACvD,yEAAyE;AACzE,6EAA6E;AAC7E,2DAA2D;AAC3D,MAAM,MAAM,GAAG,UAAU,CAAC,6BAA6B,EAAE,UAAU,CAAC,CAAC;AAErE,eAAe,MAAM,CAAC;AAEtB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { ProjectDirectory, ProjectKeyFerryManagerOptions, KeyFerryPluginOptions } from "./contracts.js";
2
+ export interface ServerProtectionInput {
3
+ /** Output environment name -> Varlock placeholder/environment key. */
4
+ readonly env: readonly string[] | Readonly<Record<string, string>>;
5
+ }
6
+ export interface RemoteHeaderMapping {
7
+ readonly env: string;
8
+ readonly prefix?: string;
9
+ readonly suffix?: string;
10
+ }
11
+ export interface RemoteServerProtectionInput {
12
+ readonly remote: {
13
+ readonly headers: Readonly<Record<string, string | RemoteHeaderMapping>>;
14
+ };
15
+ }
16
+ export type ServerProtection = Readonly<Record<string, string>> | RemoteServerProtectionInput;
17
+ export interface VarlockAdapterOptions {
18
+ readonly command: readonly string[];
19
+ readonly startupTimeoutMs: number;
20
+ readonly commandTimeoutMs: number;
21
+ readonly reloadTimeoutMs: number;
22
+ readonly pollIntervalMs: number;
23
+ readonly requireStrictEgress: boolean;
24
+ readonly allowPrivateNetwork: boolean;
25
+ readonly servers: Readonly<Record<string, ServerProtection>>;
26
+ /** @deprecated Use reloadTimeoutMs. Kept as a non-enumerable compatibility view. */
27
+ readonly refreshTimeoutMs: number;
28
+ }
29
+ export interface ValidatedOptions {
30
+ readonly enabled: boolean;
31
+ readonly projectDirectory?: ProjectDirectory;
32
+ readonly adapter: VarlockAdapterOptions;
33
+ readonly manager: Omit<KeyFerryPluginOptions, "adapter"> & {
34
+ readonly adapter: VarlockAdapterOptions;
35
+ };
36
+ }
37
+ /**
38
+ * Decode untrusted OpenCode plugin options without touching process.env.
39
+ * `enabled: false` explicitly means no manager layer, MCP transform, or RPC
40
+ * is installed and existing MCP configuration is left untouched.
41
+ */
42
+ export declare function parseOptions(raw: unknown, directory?: string): ValidatedOptions;
43
+ export declare function managerOptions(options: ValidatedOptions, directory: string): ProjectKeyFerryManagerOptions;
44
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,6BAA6B,EAC7B,qBAAqB,EACtB,MAAM,gBAAgB,CAAC;AAGxB,MAAM,WAAW,qBAAqB;IACpC,sEAAsE;IACtE,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACpE;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE;QACf,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAAC,CAAC,CAAC;KAC1E,CAAC;CACH;AAED,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,2BAA2B,CAAC;AAE9F,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC7D,oFAAoF;IACpF,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,SAAS,CAAC,GAAG;QACzD,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;KACzC,CAAC;CACH;AA6JD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAoH/E;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,gBAAgB,EACzB,SAAS,EAAE,MAAM,GAChB,6BAA6B,CA6B/B"}