jev-layer 0.1.0 → 0.2.0

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 (83) hide show
  1. package/CHANGELOG.md +45 -34
  2. package/CONTRIBUTING.md +146 -146
  3. package/LICENSE +21 -21
  4. package/README.md +123 -126
  5. package/README.ru.md +138 -117
  6. package/README.zh-CN.md +137 -117
  7. package/RELEASE.md +53 -53
  8. package/SECURITY.md +56 -56
  9. package/bin/jev.mjs +213 -213
  10. package/config/codex.mcp.toml +8 -8
  11. package/config/generic-mcp.json +13 -13
  12. package/config/hermes.mcp.yaml +9 -9
  13. package/config/jev.example.json +22 -22
  14. package/config/omp.mcp.json +14 -14
  15. package/config/providers.env.example +13 -13
  16. package/docs/AGENT-IMPLEMENTATION.md +250 -0
  17. package/docs/COMPATIBILITY.md +15 -15
  18. package/docs/HERMES-LOCAL-AGENT-HANDOFF.ru.md +177 -0
  19. package/docs/PROVIDERS.md +67 -0
  20. package/docs/SCHEMA-VERSIONING.md +96 -94
  21. package/docs/architecture.svg +77 -0
  22. package/docs/jev-layer-banner.svg +38 -0
  23. package/examples/capabilities.json +39 -39
  24. package/examples/route-request.json +28 -28
  25. package/integrations/codex/.codex-plugin/plugin.json +19 -19
  26. package/integrations/codex/.mcp.json +11 -11
  27. package/integrations/codex/AGENTS.md +1 -1
  28. package/integrations/codex/run-mcp.mjs +9 -9
  29. package/integrations/codex/skills/jev-route/SKILL.md +17 -17
  30. package/integrations/hermes/__init__.py +150 -51
  31. package/integrations/hermes/plugin.yaml +10 -5
  32. package/integrations/hermes/schemas.py +89 -12
  33. package/integrations/omp/extension.js +105 -105
  34. package/integrations/template/README.md +10 -10
  35. package/integrations/template/adapter.mjs +87 -87
  36. package/package.json +60 -59
  37. package/scripts/benchmark.mjs +35 -35
  38. package/scripts/browser-benchmark.mjs +92 -92
  39. package/scripts/browser-e2e.mjs +117 -117
  40. package/scripts/capability-e2e.mjs +31 -31
  41. package/scripts/clean-install-smoke.mjs +162 -162
  42. package/scripts/codex-mcp-smoke.mjs +119 -119
  43. package/scripts/context-filter-e2e.mjs +45 -45
  44. package/scripts/fail-open-smoke.mjs +120 -120
  45. package/scripts/feature-flags-smoke.mjs +46 -46
  46. package/scripts/mcp-receipt-smoke.mjs +113 -98
  47. package/scripts/model-route-report.mjs +15 -0
  48. package/scripts/openrouter-choice.mjs +59 -59
  49. package/scripts/replay-eval.mjs +124 -124
  50. package/scripts/smoke.mjs +34 -34
  51. package/scripts/supervision-e2e.mjs +138 -109
  52. package/src/browser.mjs +571 -569
  53. package/src/cli.mjs +33 -33
  54. package/src/config.mjs +85 -75
  55. package/src/context-filter.mjs +56 -56
  56. package/src/contract.mjs +72 -72
  57. package/src/discovery.mjs +65 -65
  58. package/src/hermes-adapter.mjs +123 -0
  59. package/src/mcp-server.mjs +267 -210
  60. package/src/model-route-metrics.mjs +89 -0
  61. package/src/model-routing.mjs +87 -0
  62. package/src/providers/demo.mjs +52 -52
  63. package/src/providers/index.mjs +12 -0
  64. package/src/providers/typesafe.mjs +126 -126
  65. package/src/receipts.mjs +227 -226
  66. package/src/registry.mjs +109 -109
  67. package/src/relevance-filter.mjs +99 -99
  68. package/src/route.mjs +219 -221
  69. package/src/shadow-compaction.mjs +170 -0
  70. package/src/supervision.mjs +268 -244
  71. package/test/browser.test.mjs +199 -199
  72. package/test/capability.test.mjs +54 -54
  73. package/test/context-filter.test.mjs +65 -65
  74. package/test/hermes-adapter.test.mjs +62 -0
  75. package/test/model-route-metrics.test.mjs +61 -0
  76. package/test/model-routing.test.mjs +59 -0
  77. package/test/openrouter-provider.test.mjs +55 -55
  78. package/test/provider-transports.test.mjs +205 -0
  79. package/test/providers.test.mjs +126 -0
  80. package/test/receipts.test.mjs +71 -71
  81. package/test/route.test.mjs +74 -74
  82. package/test/shadow-compaction.test.mjs +67 -0
  83. package/test/supervision.test.mjs +117 -99
package/bin/jev.mjs CHANGED
@@ -1,214 +1,214 @@
1
- #!/usr/bin/env node
2
- import { mkdir, readFile, writeFile } from "node:fs/promises";
3
- import { dirname, join, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { configuredProvider, loadConfig, pathExists } from "../src/config.mjs";
6
-
7
- const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
8
- const [command = "help", ...argv] = process.argv.slice(2);
9
-
10
- try {
11
- if (command === "mcp") {
12
- await import("../src/mcp-server.mjs");
13
- } else if (command === "cli") {
14
- await import("../src/cli.mjs");
15
- } else if (command === "install") {
16
- await install(argv);
17
- } else if (command === "init") {
18
- await init(argv);
19
- } else if (command === "add") {
20
- await add(argv);
21
- } else if (command === "doctor") {
22
- await doctor(argv);
23
- } else {
24
- printHelp();
25
- if (command !== "help" && command !== "--help" && command !== "-h") process.exitCode = 1;
26
- }
27
- } catch (error) {
28
- console.error(`jev: ${error instanceof Error ? error.message : String(error)}`);
29
- process.exitCode = 1;
30
- }
31
-
32
- async function install(argv) {
33
- const options = parseOptions(argv);
34
- const project = projectPath(options);
35
- const result = await writeInitialConfig(project, options.force);
36
- console.log(`jev-layer ${result.created ? "initialized" : "already initialized"} at ${project}`);
37
- console.log("Runtime dependencies: none. Node.js >= 20 is required.");
38
- console.log("Next: jev add <omp|hermes|codex|generic> && jev doctor");
39
- }
40
-
41
- async function init(argv) {
42
- const options = parseOptions(argv);
43
- const project = projectPath(options);
44
- const result = await writeInitialConfig(project, options.force);
45
- console.log(JSON.stringify({ ok: true, project, created: result.created, config: result.configPath, env_example: result.envPath }, null, 2));
46
- }
47
-
48
- async function add(argv) {
49
- const harness = argv[0];
50
- if (!harness || ["--help", "-h"].includes(harness)) {
51
- console.error("Usage: jev add <omp|hermes|codex|generic> [--project DIR] [--command CMD]");
52
- process.exitCode = 1;
53
- return;
54
- }
55
- const options = parseOptions(argv.slice(1));
56
- const project = projectPath(options);
57
- const { config } = await loadConfig({ cwd: project });
58
- const provider = configuredProvider(config, options.provider);
59
- const commandName = options.command ?? process.env.JEV_COMMAND ?? "jev";
60
- const server = {
61
- type: "stdio",
62
- command: commandName,
63
- args: ["mcp"],
64
- cwd: ".",
65
- env: { JEV_LAYER_PROVIDER: provider },
66
- };
67
-
68
- let destination;
69
- let mode = "snippet";
70
- if (harness === "omp") {
71
- destination = await addOmp(project, server);
72
- mode = destination.endsWith(".omp/mcp.json") ? "config" : "snippet";
73
- } else if (harness === "codex") {
74
- destination = await addCodex(project, server);
75
- mode = destination.endsWith(".codex/config.toml") ? "config" : "snippet";
76
- } else if (harness === "hermes") {
77
- destination = await writeSnippet(project, "hermes", hermesSnippet(server));
78
- } else if (harness === "generic" || harness === "mcp") {
79
- destination = await writeSnippet(project, "generic", JSON.stringify({ mcpServers: { jev: server } }, null, 2) + "\n", ".json");
80
- } else {
81
- throw new Error(`unsupported harness: ${harness}`);
82
- }
83
-
84
- console.log(JSON.stringify({ ok: true, harness, provider, destination, mode, command: commandName }, null, 2));
85
- if (mode === "snippet") console.log("Merge this adapter snippet into the harness configuration; existing files were not overwritten.");
86
- }
87
-
88
- async function doctor(argv) {
89
- const options = parseOptions(argv);
90
- const project = projectPath(options);
91
- const { config, path: configPath, error: configError } = await loadConfig({ cwd: project });
92
- const provider = configuredProvider(config, options.provider);
93
- const checks = [];
94
- const fail = (name, detail) => checks.push({ name, status: "error", detail });
95
- const pass = (name, detail) => checks.push({ name, status: "ok", detail });
96
- const warn = (name, detail) => checks.push({ name, status: "warning", detail });
97
- const nodeMajor = Number(process.versions.node.split(".")[0]);
98
-
99
- if (nodeMajor >= 20) pass("node", process.versions.node);
100
- else fail("node", `Node.js >= 20 required; found ${process.versions.node}`);
101
- for (const relative of ["src/route.mjs", "src/cli.mjs", "src/mcp-server.mjs", "bin/jev.mjs"]) {
102
- if (await pathExists(join(PACKAGE_ROOT, relative))) pass(`package:${relative}`, "present");
103
- else fail(`package:${relative}`, "missing from installation");
104
- }
105
- if (configError) fail("config", configError);
106
- else if (await pathExists(configPath)) pass("config", configPath);
107
- else warn("config", `not initialized at ${configPath}; run jev init`);
108
-
109
- if (!["demo", "openrouter", "typesafe"].includes(provider)) fail("provider", `unsupported provider ${provider}`);
110
- else pass("provider", provider);
111
- if (provider === "openrouter" || provider === "typesafe") {
112
- const keyEnv = config.providers?.[provider]?.api_key_env ?? (provider === "openrouter" ? "OPENROUTER_API_KEY" : "TYPESAFE_API_KEY");
113
- if (process.env[keyEnv]) pass(`secret:${keyEnv}`, "configured through environment");
114
- else fail(`secret:${keyEnv}`, `missing; export ${keyEnv} before using ${provider}`);
115
- } else {
116
- pass("provider-secret", "not required for demo provider");
117
- }
118
- if (process.env.JEV_LAYER_ENABLED === "0") warn("fail-open", "JEV_LAYER_ENABLED=0; routing will return fallback decisions");
119
-
120
- const errors = checks.filter((check) => check.status === "error");
121
- console.log(JSON.stringify({ ok: errors.length === 0, project, package_root: PACKAGE_ROOT, checks }, null, 2));
122
- if (errors.length) process.exitCode = 1;
123
- }
124
-
125
- async function writeInitialConfig(project, force = false) {
126
- const state = join(project, ".jev");
127
- await mkdir(state, { recursive: true });
128
- const configPath = join(state, "config.json");
129
- const envPath = join(state, "providers.env.example");
130
- const config = await readPackageFile("config/jev.example.json");
131
- const envExample = await readPackageFile("config/providers.env.example");
132
- const configCreated = await writeIfAllowed(configPath, config, force);
133
- const envCreated = await writeIfAllowed(envPath, envExample, force);
134
- return { created: configCreated || envCreated, configPath, envPath };
135
- }
136
-
137
- async function addOmp(project, server) {
138
- const target = join(project, ".omp", "mcp.json");
139
- if (!(await pathExists(target))) {
140
- await writeJson(target, { "$schema": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json", mcpServers: { jev: server } });
141
- return target;
142
- }
143
- try {
144
- const parsed = JSON.parse(await readFile(target, "utf8"));
145
- parsed.mcpServers = { ...(parsed.mcpServers ?? {}), jev: server };
146
- await writeFile(target, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
147
- return target;
148
- } catch {
149
- return writeSnippet(project, "omp", JSON.stringify({ mcpServers: { jev: server } }, null, 2) + "\n", ".json");
150
- }
151
- }
152
-
153
- async function addCodex(project, server) {
154
- const target = join(project, ".codex", "config.toml");
155
- if (!(await pathExists(target))) {
156
- await mkdir(dirname(target), { recursive: true });
157
- await writeFile(target, codexSnippet(server), "utf8");
158
- return target;
159
- }
160
- return writeSnippet(project, "codex", codexSnippet(server), ".toml");
161
- }
162
-
163
- async function writeSnippet(project, name, content, extension = ".yaml") {
164
- const destination = join(project, ".jev", "adapters", `${name}${extension}`);
165
- await mkdir(dirname(destination), { recursive: true });
166
- await writeFile(destination, content, "utf8");
167
- return destination;
168
- }
169
-
170
- function codexSnippet(server) {
171
- return `[mcp_servers.jev]\ncommand = ${JSON.stringify(server.command)}\nargs = ["mcp"]\ncwd = "."\n\n[mcp_servers.jev.env]\nJEV_LAYER_PROVIDER = ${JSON.stringify(server.env.JEV_LAYER_PROVIDER)}\n`;
172
- }
173
-
174
- function hermesSnippet(server) {
175
- return `mcp_servers:\n jev:\n command: ${server.command}\n args:\n - mcp\n env:\n JEV_LAYER_PROVIDER: ${server.env.JEV_LAYER_PROVIDER}\n # Export OPENROUTER_API_KEY or TYPESAFE_API_KEY in the Hermes process environment.\n`;
176
- }
177
-
178
- async function writeJson(path, value) {
179
- await mkdir(dirname(path), { recursive: true });
180
- await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
181
- }
182
-
183
- async function writeIfAllowed(path, content, force) {
184
- if (!force && await pathExists(path)) return false;
185
- await mkdir(dirname(path), { recursive: true });
186
- await writeFile(path, content, "utf8");
187
- return true;
188
- }
189
-
190
- async function readPackageFile(relative) {
191
- return readFile(join(PACKAGE_ROOT, relative), "utf8");
192
- }
193
-
194
- function projectPath(options) {
195
- return resolve(options.project ?? process.cwd());
196
- }
197
-
198
- function parseOptions(argv) {
199
- const options = {};
200
- for (let index = 0; index < argv.length; index += 1) {
201
- const value = argv[index];
202
- if (value === "--project") options.project = argv[++index];
203
- else if (value === "--command") options.command = argv[++index];
204
- else if (value === "--provider") options.provider = argv[++index];
205
- else if (value === "--force") options.force = true;
206
- else if (value === "--help" || value === "-h") options.help = true;
207
- else throw new Error(`unknown option ${value}`);
208
- }
209
- return options;
210
- }
211
-
212
- function printHelp() {
213
- console.log(`jev-layer portable setup\n\nCommands:\n jev install [--project DIR] initialize local state\n jev init [--project DIR] write .jev/config.json and env example\n jev add <harness> [options] add omp, hermes, codex, or generic MCP adapter\n jev doctor [--project DIR] check runtime, config, provider and secrets\n jev mcp run the stdio MCP server\n jev cli run the JSONL CLI adapter\n\nOptions:\n --project DIR target workspace; defaults to cwd\n --command CMD MCP command; defaults to jev\n --provider NAME override provider for generated config\n --force replace generated init files\n`);
1
+ #!/usr/bin/env node
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { configuredProvider, loadConfig, pathExists } from "../src/config.mjs";
6
+
7
+ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
8
+ const [command = "help", ...argv] = process.argv.slice(2);
9
+
10
+ try {
11
+ if (command === "mcp") {
12
+ await import("../src/mcp-server.mjs");
13
+ } else if (command === "cli") {
14
+ await import("../src/cli.mjs");
15
+ } else if (command === "install") {
16
+ await install(argv);
17
+ } else if (command === "init") {
18
+ await init(argv);
19
+ } else if (command === "add") {
20
+ await add(argv);
21
+ } else if (command === "doctor") {
22
+ await doctor(argv);
23
+ } else {
24
+ printHelp();
25
+ if (command !== "help" && command !== "--help" && command !== "-h") process.exitCode = 1;
26
+ }
27
+ } catch (error) {
28
+ console.error(`jev: ${error instanceof Error ? error.message : String(error)}`);
29
+ process.exitCode = 1;
30
+ }
31
+
32
+ async function install(argv) {
33
+ const options = parseOptions(argv);
34
+ const project = projectPath(options);
35
+ const result = await writeInitialConfig(project, options.force);
36
+ console.log(`jev-layer ${result.created ? "initialized" : "already initialized"} at ${project}`);
37
+ console.log("Runtime dependencies: none. Node.js >= 20 is required.");
38
+ console.log("Next: jev add <omp|hermes|codex|generic> && jev doctor");
39
+ }
40
+
41
+ async function init(argv) {
42
+ const options = parseOptions(argv);
43
+ const project = projectPath(options);
44
+ const result = await writeInitialConfig(project, options.force);
45
+ console.log(JSON.stringify({ ok: true, project, created: result.created, config: result.configPath, env_example: result.envPath }, null, 2));
46
+ }
47
+
48
+ async function add(argv) {
49
+ const harness = argv[0];
50
+ if (!harness || ["--help", "-h"].includes(harness)) {
51
+ console.error("Usage: jev add <omp|hermes|codex|generic> [--project DIR] [--command CMD]");
52
+ process.exitCode = 1;
53
+ return;
54
+ }
55
+ const options = parseOptions(argv.slice(1));
56
+ const project = projectPath(options);
57
+ const { config } = await loadConfig({ cwd: project });
58
+ const provider = configuredProvider(config, options.provider);
59
+ const commandName = options.command ?? process.env.JEV_COMMAND ?? "jev";
60
+ const server = {
61
+ type: "stdio",
62
+ command: commandName,
63
+ args: ["mcp"],
64
+ cwd: ".",
65
+ env: { JEV_LAYER_PROVIDER: provider },
66
+ };
67
+
68
+ let destination;
69
+ let mode = "snippet";
70
+ if (harness === "omp") {
71
+ destination = await addOmp(project, server);
72
+ mode = destination.endsWith(".omp/mcp.json") ? "config" : "snippet";
73
+ } else if (harness === "codex") {
74
+ destination = await addCodex(project, server);
75
+ mode = destination.endsWith(".codex/config.toml") ? "config" : "snippet";
76
+ } else if (harness === "hermes") {
77
+ destination = await writeSnippet(project, "hermes", hermesSnippet(server));
78
+ } else if (harness === "generic" || harness === "mcp") {
79
+ destination = await writeSnippet(project, "generic", JSON.stringify({ mcpServers: { jev: server } }, null, 2) + "\n", ".json");
80
+ } else {
81
+ throw new Error(`unsupported harness: ${harness}`);
82
+ }
83
+
84
+ console.log(JSON.stringify({ ok: true, harness, provider, destination, mode, command: commandName }, null, 2));
85
+ if (mode === "snippet") console.log("Merge this adapter snippet into the harness configuration; existing files were not overwritten.");
86
+ }
87
+
88
+ async function doctor(argv) {
89
+ const options = parseOptions(argv);
90
+ const project = projectPath(options);
91
+ const { config, path: configPath, error: configError } = await loadConfig({ cwd: project });
92
+ const provider = configuredProvider(config, options.provider);
93
+ const checks = [];
94
+ const fail = (name, detail) => checks.push({ name, status: "error", detail });
95
+ const pass = (name, detail) => checks.push({ name, status: "ok", detail });
96
+ const warn = (name, detail) => checks.push({ name, status: "warning", detail });
97
+ const nodeMajor = Number(process.versions.node.split(".")[0]);
98
+
99
+ if (nodeMajor >= 20) pass("node", process.versions.node);
100
+ else fail("node", `Node.js >= 20 required; found ${process.versions.node}`);
101
+ for (const relative of ["src/route.mjs", "src/cli.mjs", "src/mcp-server.mjs", "bin/jev.mjs"]) {
102
+ if (await pathExists(join(PACKAGE_ROOT, relative))) pass(`package:${relative}`, "present");
103
+ else fail(`package:${relative}`, "missing from installation");
104
+ }
105
+ if (configError) fail("config", configError);
106
+ else if (await pathExists(configPath)) pass("config", configPath);
107
+ else warn("config", `not initialized at ${configPath}; run jev init`);
108
+
109
+ if (!["demo", "openrouter", "typesafe"].includes(provider)) fail("provider", `unsupported provider ${provider}`);
110
+ else pass("provider", provider);
111
+ if (provider === "openrouter" || provider === "typesafe") {
112
+ const keyEnv = config.providers?.[provider]?.api_key_env ?? (provider === "openrouter" ? "OPENROUTER_API_KEY" : "TYPESAFE_API_KEY");
113
+ if (process.env[keyEnv]) pass(`secret:${keyEnv}`, "configured through environment");
114
+ else fail(`secret:${keyEnv}`, `missing; export ${keyEnv} before using ${provider}`);
115
+ } else {
116
+ pass("provider-secret", "not required for demo provider");
117
+ }
118
+ if (process.env.JEV_LAYER_ENABLED === "0") warn("fail-open", "JEV_LAYER_ENABLED=0; routing will return fallback decisions");
119
+
120
+ const errors = checks.filter((check) => check.status === "error");
121
+ console.log(JSON.stringify({ ok: errors.length === 0, project, package_root: PACKAGE_ROOT, checks }, null, 2));
122
+ if (errors.length) process.exitCode = 1;
123
+ }
124
+
125
+ async function writeInitialConfig(project, force = false) {
126
+ const state = join(project, ".jev");
127
+ await mkdir(state, { recursive: true });
128
+ const configPath = join(state, "config.json");
129
+ const envPath = join(state, "providers.env.example");
130
+ const config = await readPackageFile("config/jev.example.json");
131
+ const envExample = await readPackageFile("config/providers.env.example");
132
+ const configCreated = await writeIfAllowed(configPath, config, force);
133
+ const envCreated = await writeIfAllowed(envPath, envExample, force);
134
+ return { created: configCreated || envCreated, configPath, envPath };
135
+ }
136
+
137
+ async function addOmp(project, server) {
138
+ const target = join(project, ".omp", "mcp.json");
139
+ if (!(await pathExists(target))) {
140
+ await writeJson(target, { "$schema": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json", mcpServers: { jev: server } });
141
+ return target;
142
+ }
143
+ try {
144
+ const parsed = JSON.parse(await readFile(target, "utf8"));
145
+ parsed.mcpServers = { ...(parsed.mcpServers ?? {}), jev: server };
146
+ await writeFile(target, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
147
+ return target;
148
+ } catch {
149
+ return writeSnippet(project, "omp", JSON.stringify({ mcpServers: { jev: server } }, null, 2) + "\n", ".json");
150
+ }
151
+ }
152
+
153
+ async function addCodex(project, server) {
154
+ const target = join(project, ".codex", "config.toml");
155
+ if (!(await pathExists(target))) {
156
+ await mkdir(dirname(target), { recursive: true });
157
+ await writeFile(target, codexSnippet(server), "utf8");
158
+ return target;
159
+ }
160
+ return writeSnippet(project, "codex", codexSnippet(server), ".toml");
161
+ }
162
+
163
+ async function writeSnippet(project, name, content, extension = ".yaml") {
164
+ const destination = join(project, ".jev", "adapters", `${name}${extension}`);
165
+ await mkdir(dirname(destination), { recursive: true });
166
+ await writeFile(destination, content, "utf8");
167
+ return destination;
168
+ }
169
+
170
+ function codexSnippet(server) {
171
+ return `[mcp_servers.jev]\ncommand = ${JSON.stringify(server.command)}\nargs = ["mcp"]\ncwd = "."\n\n[mcp_servers.jev.env]\nJEV_LAYER_PROVIDER = ${JSON.stringify(server.env.JEV_LAYER_PROVIDER)}\n`;
172
+ }
173
+
174
+ function hermesSnippet(server) {
175
+ return `mcp_servers:\n jev:\n command: ${server.command}\n args:\n - mcp\n env:\n JEV_LAYER_PROVIDER: ${server.env.JEV_LAYER_PROVIDER}\n # Export OPENROUTER_API_KEY or TYPESAFE_API_KEY in the Hermes process environment.\n`;
176
+ }
177
+
178
+ async function writeJson(path, value) {
179
+ await mkdir(dirname(path), { recursive: true });
180
+ await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
181
+ }
182
+
183
+ async function writeIfAllowed(path, content, force) {
184
+ if (!force && await pathExists(path)) return false;
185
+ await mkdir(dirname(path), { recursive: true });
186
+ await writeFile(path, content, "utf8");
187
+ return true;
188
+ }
189
+
190
+ async function readPackageFile(relative) {
191
+ return readFile(join(PACKAGE_ROOT, relative), "utf8");
192
+ }
193
+
194
+ function projectPath(options) {
195
+ return resolve(options.project ?? process.cwd());
196
+ }
197
+
198
+ function parseOptions(argv) {
199
+ const options = {};
200
+ for (let index = 0; index < argv.length; index += 1) {
201
+ const value = argv[index];
202
+ if (value === "--project") options.project = argv[++index];
203
+ else if (value === "--command") options.command = argv[++index];
204
+ else if (value === "--provider") options.provider = argv[++index];
205
+ else if (value === "--force") options.force = true;
206
+ else if (value === "--help" || value === "-h") options.help = true;
207
+ else throw new Error(`unknown option ${value}`);
208
+ }
209
+ return options;
210
+ }
211
+
212
+ function printHelp() {
213
+ console.log(`jev-layer portable setup\n\nCommands:\n jev install [--project DIR] initialize local state\n jev init [--project DIR] write .jev/config.json and env example\n jev add <harness> [options] add omp, hermes, codex, or generic MCP adapter\n jev doctor [--project DIR] check runtime, config, provider and secrets\n jev mcp run the stdio MCP server\n jev cli run the JSONL CLI adapter\n\nOptions:\n --project DIR target workspace; defaults to cwd\n --command CMD MCP command; defaults to jev\n --provider NAME override provider for generated config\n --force replace generated init files\n`);
214
214
  }
@@ -1,8 +1,8 @@
1
- [mcp_servers.jev]
2
- command = "jev"
3
- args = ["mcp"]
4
- cwd = "."
5
-
6
- [mcp_servers.jev.env]
7
- JEV_LAYER_PROVIDER = "openrouter"
8
- # Export OPENROUTER_API_KEY in the Codex process environment.
1
+ [mcp_servers.jev]
2
+ command = "jev"
3
+ args = ["mcp"]
4
+ cwd = "."
5
+
6
+ [mcp_servers.jev.env]
7
+ JEV_LAYER_PROVIDER = "openrouter"
8
+ # Export OPENROUTER_API_KEY in the Codex process environment.
@@ -1,13 +1,13 @@
1
- {
2
- "mcpServers": {
3
- "jev": {
4
- "type": "stdio",
5
- "command": "jev",
6
- "args": ["mcp"],
7
- "cwd": ".",
8
- "env": {
9
- "JEV_LAYER_PROVIDER": "demo"
10
- }
11
- }
12
- }
13
- }
1
+ {
2
+ "mcpServers": {
3
+ "jev": {
4
+ "type": "stdio",
5
+ "command": "jev",
6
+ "args": ["mcp"],
7
+ "cwd": ".",
8
+ "env": {
9
+ "JEV_LAYER_PROVIDER": "demo"
10
+ }
11
+ }
12
+ }
13
+ }
@@ -1,9 +1,9 @@
1
- # Merge this block into the Hermes config under mcp_servers.
2
- mcp_servers:
3
- jev:
4
- command: jev
5
- args:
6
- - mcp
7
- env:
8
- JEV_LAYER_PROVIDER: openrouter
9
- # Export OPENROUTER_API_KEY in the Hermes process environment.
1
+ # Merge this block into the Hermes config under mcp_servers.
2
+ mcp_servers:
3
+ jev:
4
+ command: jev
5
+ args:
6
+ - mcp
7
+ env:
8
+ JEV_LAYER_PROVIDER: openrouter
9
+ # Export OPENROUTER_API_KEY in the Hermes process environment.
@@ -1,22 +1,22 @@
1
- {
2
- "schema_version": 1,
3
- "provider": "demo",
4
- "replay_cases": ".jev/replay/cases.jsonl",
5
- "features": {
6
- "browser_fast_path": false,
7
- "supervision": false,
8
- "context_filter": null
9
- },
10
- "providers": {
11
- "openrouter": {
12
- "api_key_env": "OPENROUTER_API_KEY",
13
- "endpoint_env": "OPENROUTER_DECISIONS_ENDPOINT",
14
- "model_env": "OPENROUTER_DECISIONS_MODEL"
15
- },
16
- "typesafe": {
17
- "api_key_env": "TYPESAFE_API_KEY",
18
- "endpoint_env": "TYPESAFE_ENDPOINT",
19
- "model_env": "TYPESAFE_MODEL"
20
- }
21
- }
22
- }
1
+ {
2
+ "schema_version": 1,
3
+ "provider": "demo",
4
+ "replay_cases": ".jev/replay/cases.jsonl",
5
+ "features": {
6
+ "browser_fast_path": false,
7
+ "supervision": false,
8
+ "context_filter": null
9
+ },
10
+ "providers": {
11
+ "openrouter": {
12
+ "api_key_env": "OPENROUTER_API_KEY",
13
+ "endpoint_env": "OPENROUTER_DECISIONS_ENDPOINT",
14
+ "model_env": "OPENROUTER_DECISIONS_MODEL"
15
+ },
16
+ "typesafe": {
17
+ "api_key_env": "TYPESAFE_API_KEY",
18
+ "endpoint_env": "TYPESAFE_ENDPOINT",
19
+ "model_env": "TYPESAFE_MODEL"
20
+ }
21
+ }
22
+ }
@@ -1,14 +1,14 @@
1
- {
2
- "$schema": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json",
3
- "mcpServers": {
4
- "jev": {
5
- "type": "stdio",
6
- "command": "jev",
7
- "args": ["mcp"],
8
- "cwd": ".",
9
- "env": {
10
- "JEV_LAYER_PROVIDER": "openrouter"
11
- }
12
- }
13
- }
14
- }
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json",
3
+ "mcpServers": {
4
+ "jev": {
5
+ "type": "stdio",
6
+ "command": "jev",
7
+ "args": ["mcp"],
8
+ "cwd": ".",
9
+ "env": {
10
+ "JEV_LAYER_PROVIDER": "openrouter"
11
+ }
12
+ }
13
+ }
14
+ }
@@ -1,13 +1,13 @@
1
- # Copy the variables needed by the selected provider into the harness environment.
2
- # Do not commit a file containing real values.
3
-
4
- JEV_LAYER_PROVIDER=demo
5
- # JEV_LAYER_PROVIDER=openrouter
6
- # OPENROUTER_API_KEY=replace-in-your-shell-only
7
- # OPENROUTER_DECISIONS_ENDPOINT=https://openrouter.ai/api/alpha/decisions
8
- # OPENROUTER_DECISIONS_MODEL=typesafe/jev-1.13
9
-
10
- # JEV_LAYER_PROVIDER=typesafe
11
- # TYPESAFE_API_KEY=replace-in-your-shell-only
12
- # TYPESAFE_ENDPOINT=https://api.typesafe.ai/v1/systemone
13
- # TYPESAFE_MODEL=jev-latest
1
+ # Copy the variables needed by the selected provider into the harness environment.
2
+ # Do not commit a file containing real values.
3
+
4
+ JEV_LAYER_PROVIDER=demo
5
+ # JEV_LAYER_PROVIDER=openrouter
6
+ # OPENROUTER_API_KEY=replace-in-your-shell-only
7
+ # OPENROUTER_DECISIONS_ENDPOINT=https://openrouter.ai/api/alpha/decisions
8
+ # OPENROUTER_DECISIONS_MODEL=typesafe/jev-1.13
9
+
10
+ # JEV_LAYER_PROVIDER=typesafe
11
+ # TYPESAFE_API_KEY=replace-in-your-shell-only
12
+ # TYPESAFE_ENDPOINT=https://api.typesafe.ai/v1/systemone
13
+ # TYPESAFE_MODEL=jev-latest