@jacobbd/relay-ai 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.5] - 2026-06-26
4
+
5
+ ### Fixed
6
+
7
+ - **Windows: Claude Desktop 3P config now writes to the correct path** — relay-ai was writing the `configLibrary` to `%APPDATA%\Claude-3p` (Roaming) but Claude Desktop reads from `%LOCALAPPDATA%\Claude-3p` (Local). The config is now written to the correct location. Reported by Trojan28A ([#11](https://github.com/jacob-bd/relay-ai/issues/11)).
8
+
9
+ - **Windows: Claude Desktop and Codex App now launch correctly from MSIX installs** — `Start-Process 'shell:AppsFolder\...'` failed silently due to PowerShell backslash double-escaping via `JSON.stringify`. The launcher now uses `cmd /c start` with an argument array, which bypasses PowerShell string parsing entirely and correctly opens MSIX-packaged apps. ([#11](https://github.com/jacob-bd/relay-ai/issues/11)).
10
+
11
+ - **Windows: OpenCode CLI now discovered correctly when `where.exe` returns multiple results** — `where.exe opencode` returns both a bare script and a `.cmd` wrapper. relay-ai was taking the first result (the bare script), which Node's `spawn()` cannot execute directly. relay-ai now prefers the `.cmd` entry. The same fix applies to the `claude`, `codex`, and `gemini` binary lookups. The OpenCode `serve` subprocess also now uses `cmd.exe /c` on Windows to avoid Node 22's DEP0190 deprecation warning. ([#11](https://github.com/jacob-bd/relay-ai/issues/11)).
12
+
13
+ ## [0.3.4] - 2026-06-23
14
+
15
+ ### Fixed
16
+
17
+ - **Go models no longer mislabeled as Anthropic format** — OpenCode Go models (e.g. `minimax-m3`, `qwen3.7-plus`, `minimax-m2.7`, `qwen3.7-max`, `qwen3.6-plus`) were incorrectly classified as `modelFormat: 'anthropic'` due to stale `@ai-sdk/anthropic` npm entries written by the OpenCode cache. The Go backend is an OpenAI-compatible gateway only; relay-ai now clamps any `anthropic` format classification to `openai` for all Go models regardless of cache data. Reported by Philip2050 ([#10](https://github.com/jacob-bd/relay-ai/issues/10)).
18
+
3
19
  ## [0.3.3] - 2026-06-22
4
20
 
5
21
  ### Fixed
package/README.md CHANGED
@@ -13,11 +13,16 @@
13
13
 
14
14
  <p align="center">
15
15
  <a href="https://youtu.be/IvsUPHLhX0o">
16
- <img src="assets/demo-part1-thumbnail.png" alt="relay-ai demo — installation, configuration, Claude Code, Claude Cowork & Claude Code Desktop (Part 1)" width="100%">
16
+ <img src="https://img.youtube.com/vi/IvsUPHLhX0o/maxresdefault.jpg" alt="relay-ai demo — installation, configuration, Claude Code, Claude Cowork & Claude Code Desktop (Part 1)" width="49%">
17
+ </a>
18
+ <a href="https://youtu.be/42oiOB8IAu4">
19
+ <img src="https://img.youtube.com/vi/42oiOB8IAu4/maxresdefault.jpg" alt="relay-ai demo — OpenAI Codex CLI & Codex Desktop App (Part 2)" width="49%">
17
20
  </a>
18
21
  </p>
19
22
 
20
- > **Demo (Part 1):** Installation · Configuration · Claude Code · Claude Cowork · Claude Code Desktop — [watch on YouTube](https://youtu.be/IvsUPHLhX0o)
23
+ > 🎥 **Watch the Demos:**
24
+ > - **Demo (Part 1):** Installation · Configuration · Claude Code · Claude Cowork & Claude Code Desktop — [watch on YouTube](https://youtu.be/IvsUPHLhX0o)
25
+ > - **Demo (Part 2):** OpenAI Codex CLI & Codex Desktop App — [watch on YouTube](https://youtu.be/42oiOB8IAu4)
21
26
 
22
27
  **relay-ai** is an interactive CLI that launches AI coding tools and runs local API gateways on your machine. Currently, it supports **Claude Code**, **Claude Desktop (Cowork + Code)**, the **OpenAI Codex CLI**, the **Codex desktop app (macOS + Windows)**, and the **Google Gemini CLI**.
23
28
 
@@ -44,8 +49,6 @@ Pick your backend:
44
49
  | `relay-ai gemini` | Launch Google Gemini CLI with registry providers |
45
50
  | `relay-ai --ai` | Full agent reference for scripts and alef-agent ([guide](docs/AI-AGENTS.md)) |
46
51
 
47
- Bare `relay-ai` prints help and migration guidance. Use `relay-ai claude` for the wizard.
48
-
49
52
  ## Features
50
53
 
51
54
  - **Native provider registry:** `relay-ai providers` stores config in `~/.relay-ai/providers.json` and secrets in the OS keychain — no OpenCode binary required at launch. See **[docs/PROVIDERS.md](docs/PROVIDERS.md)** for a full list of providers and known issues.
@@ -78,7 +81,7 @@ Bare `relay-ai` prints help and migration guidance. Use `relay-ai claude` for th
78
81
  | Claude Desktop (Cowork + Code) | `relay-ai claude-app` | ✅ Supported macOS + Windows ([guide](docs/CLAUDE_DESKTOP_SETUP.md)) |
79
82
  | Codex CLI | `relay-ai codex` | ✅ Supported ([guide](docs/CODEX.md)) |
80
83
  | Codex desktop app | `relay-ai codex-app` | ✅ Supported macOS + Windows ([guide](docs/CODEX.md)) |
81
- | Google Gemini CLI | `relay-ai gemini` | Supported |
84
+ | Google Gemini CLI | `relay-ai gemini` | ⚠️ Experimental, model switching is done via .model prompt |
82
85
 
83
86
  ## Prerequisites
84
87
 
package/dist/cli.js CHANGED
@@ -35,7 +35,7 @@ import { join } from "path";
35
35
  // package.json
36
36
  var package_default = {
37
37
  name: "@jacobbd/relay-ai",
38
- version: "0.3.3",
38
+ version: "0.3.5",
39
39
  publishConfig: {
40
40
  access: "public"
41
41
  },
@@ -1154,7 +1154,8 @@ function findClaudeBinary() {
1154
1154
  encoding: "utf8",
1155
1155
  stdio: ["pipe", "pipe", "pipe"]
1156
1156
  });
1157
- const path = result.trim().split("\n")[0].trim();
1157
+ const lines = result.trim().split("\n").map((l) => l.trim()).filter(Boolean);
1158
+ const path = (isWindows ? lines.find((l) => l.toLowerCase().endsWith(".cmd")) : null) ?? lines[0];
1158
1159
  if (path) return path;
1159
1160
  } catch {
1160
1161
  }
@@ -3137,7 +3138,8 @@ function readModelsFromCache(backendId) {
3137
3138
  for (const entry of Object.values(providerData.models)) {
3138
3139
  if (!entry.id || entry.status === "deprecated") continue;
3139
3140
  const isFree = entry.cost !== void 0 && entry.cost.input === 0 && entry.cost.output === 0;
3140
- const modelFormat = classifyModelFormat(entry.id, entry.provider?.npm);
3141
+ const rawFormat = classifyModelFormat(entry.id, entry.provider?.npm);
3142
+ const modelFormat = backendId === "go" && rawFormat === "anthropic" ? "openai" : rawFormat;
3141
3143
  result.set(entry.id, {
3142
3144
  id: entry.id,
3143
3145
  name: entry.name ?? entry.id,
@@ -3169,7 +3171,10 @@ async function fetchModelsFromApi(backend) {
3169
3171
  function mergeModels(apiIds, cache, backendId) {
3170
3172
  return apiIds.filter((id) => !shouldHideModel({ providerId: backendId, modelId: id, agent: "claude" })).map((id) => {
3171
3173
  const cached = cache?.get(id);
3172
- if (cached) return { ...cached, sourceBackend: backendId };
3174
+ if (cached) {
3175
+ const modelFormat2 = backendId === "go" && cached.modelFormat === "anthropic" ? "openai" : cached.modelFormat;
3176
+ return { ...cached, sourceBackend: backendId, modelFormat: modelFormat2 };
3177
+ }
3173
3178
  const modelFormat = classifyModelFormat(id, void 0);
3174
3179
  return {
3175
3180
  id,
@@ -3277,7 +3282,8 @@ function findOpencodeBinary() {
3277
3282
  encoding: "utf8",
3278
3283
  stdio: ["pipe", "pipe", "pipe"]
3279
3284
  });
3280
- const path = result.trim().split("\n")[0].trim();
3285
+ const lines = result.trim().split("\n").map((l) => l.trim()).filter(Boolean);
3286
+ const path = (isWindows2 ? lines.find((l) => l.toLowerCase().endsWith(".cmd")) : null) ?? lines[0];
3281
3287
  if (path) return path;
3282
3288
  } catch {
3283
3289
  }
@@ -3307,9 +3313,7 @@ async function fetchRawOpencodeProviders() {
3307
3313
  finish(null);
3308
3314
  }, TIMEOUT_MS);
3309
3315
  try {
3310
- child = spawn2(binary, ["serve", "--port", "0"], {
3311
- stdio: ["pipe", "pipe", "pipe"]
3312
- });
3316
+ child = isWindows2 ? spawn2("cmd.exe", ["/c", binary, "serve", "--port", "0"], { stdio: ["pipe", "pipe", "pipe"] }) : spawn2(binary, ["serve", "--port", "0"], { stdio: ["pipe", "pipe", "pipe"] });
3313
3317
  } catch {
3314
3318
  finish(null);
3315
3319
  return;
@@ -10554,7 +10558,8 @@ function findCodexBinary() {
10554
10558
  encoding: "utf8",
10555
10559
  stdio: ["pipe", "pipe", "pipe"]
10556
10560
  });
10557
- const path = result.trim().split("\n")[0]?.trim();
10561
+ const lines = result.trim().split("\n").map((l) => l.trim()).filter(Boolean);
10562
+ const path = (isWindows3 ? lines.find((l) => l.toLowerCase().endsWith(".cmd")) : null) ?? lines[0];
10558
10563
  if (path) return path;
10559
10564
  } catch {
10560
10565
  }
@@ -11562,7 +11567,8 @@ function findGeminiBinary() {
11562
11567
  encoding: "utf8",
11563
11568
  stdio: ["pipe", "pipe", "pipe"]
11564
11569
  });
11565
- const path = result.trim().split("\n")[0]?.trim();
11570
+ const lines = result.trim().split("\n").map((l) => l.trim()).filter(Boolean);
11571
+ const path = (isWindows4 ? lines.find((l) => l.toLowerCase().endsWith(".cmd")) : null) ?? lines[0];
11566
11572
  if (path) return path;
11567
11573
  } catch {
11568
11574
  }
@@ -12926,7 +12932,7 @@ function waitForShutdown2() {
12926
12932
  }
12927
12933
 
12928
12934
  // src/codex/app-launch.ts
12929
- import { execSync as execSync5 } from "child_process";
12935
+ import { execSync as execSync5, spawn as spawn6 } from "child_process";
12930
12936
  import { existsSync as existsSync16, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
12931
12937
  import { homedir as homedir11 } from "os";
12932
12938
  import { join as join17 } from "path";
@@ -13069,7 +13075,7 @@ function openCodexAppAt(path) {
13069
13075
  }
13070
13076
  if (process.platform === "win32") {
13071
13077
  if (path.startsWith("shell:AppsFolder\\")) {
13072
- runPowerShell(`Start-Process '${path.replace(/'/g, "''")}'`);
13078
+ spawn6("cmd.exe", ["/c", "start", "", path], { stdio: "ignore", detached: true }).unref();
13073
13079
  } else {
13074
13080
  runPowerShell(`Start-Process -FilePath '${path.replace(/'/g, "''")}'`);
13075
13081
  }
@@ -13624,7 +13630,7 @@ import { join as join18, dirname as dirname7 } from "path";
13624
13630
  import { randomUUID as randomUUID3 } from "crypto";
13625
13631
  function getClaudeDesktopHome() {
13626
13632
  if (process.platform === "win32") {
13627
- return join18(process.env.APPDATA || join18(homedir12(), "AppData", "Roaming"), "Claude-3p");
13633
+ return join18(process.env.LOCALAPPDATA || join18(homedir12(), "AppData", "Local"), "Claude-3p");
13628
13634
  }
13629
13635
  return join18(homedir12(), "Library", "Application Support", "Claude-3p");
13630
13636
  }
@@ -13785,7 +13791,7 @@ function setupExitCleanup(uuid) {
13785
13791
  }
13786
13792
 
13787
13793
  // src/claude-desktop/app-launch.ts
13788
- import { execSync as execSync6 } from "child_process";
13794
+ import { execSync as execSync6, spawn as spawn7 } from "child_process";
13789
13795
  import { existsSync as existsSync19, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
13790
13796
  import { homedir as homedir13 } from "os";
13791
13797
  import { join as join20 } from "path";
@@ -13924,7 +13930,7 @@ function openClaudeAppAt(path) {
13924
13930
  }
13925
13931
  if (process.platform === "win32") {
13926
13932
  if (path.startsWith("shell:AppsFolder\\")) {
13927
- runPowerShell2(`Start-Process '${path.replace(/'/g, "''")}'`);
13933
+ spawn7("cmd.exe", ["/c", "start", "", path], { stdio: "ignore", detached: true }).unref();
13928
13934
  } else {
13929
13935
  runPowerShell2(`Start-Process -FilePath '${path.replace(/'/g, "''")}'`);
13930
13936
  }
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacobbd/relay-ai",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,165 +0,0 @@
1
- {
2
- "data": [
3
- {
4
- "id": "grok-4.20-0309-non-reasoning",
5
- "aliases": [
6
- "grok-4.20-non-reasoning",
7
- "grok-4.20-non-reasoning-latest",
8
- "grok-4.20-beta-non-reasoning",
9
- "grok-4.20-beta-latest-non-reasoning",
10
- "grok-4.20-experimental-beta-0304-non-reasoning",
11
- "grok-4.20-experimental-beta-non-reasoning-latest",
12
- "grok-4.20-beta-0309-non-reasoning",
13
- "grok-4.20-non-reasoning-gv2"
14
- ],
15
- "context_length": 1000000,
16
- "created": 1773014400,
17
- "object": "model",
18
- "owned_by": "xai",
19
- "prompt_text_token_price": 12500,
20
- "cached_prompt_text_token_price": 2000,
21
- "prompt_image_token_price": 12500,
22
- "completion_text_token_price": 25000,
23
- "prompt_text_token_price_long_context": 25000,
24
- "cached_prompt_text_token_price_long_context": 4000,
25
- "completion_text_token_price_long_context": 50000,
26
- "long_context_threshold": 200000
27
- },
28
- {
29
- "id": "grok-4.20-0309-reasoning",
30
- "aliases": [
31
- "grok-4.20-reasoning-latest",
32
- "grok-4.20",
33
- "grok-4.20-reasoning",
34
- "grok-4.20-0309",
35
- "grok-4.20-beta-0309-reasoning",
36
- "grok-4.20-beta",
37
- "grok-4.20-beta-0309",
38
- "grok-4.20-beta-latest",
39
- "grok-4.20-beta-latest-reasoning",
40
- "grok-4.20-beta-reasoning",
41
- "grok-4.20-experimental-beta-0304-reasoning",
42
- "grok-4.20-experimental-beta-0304",
43
- "grok-4.20-experimental-beta-reasoning-latest",
44
- "grok-4.20-experimental-beta-latest",
45
- "grok-4.20-reasoning-gv2"
46
- ],
47
- "context_length": 1000000,
48
- "created": 1773014400,
49
- "object": "model",
50
- "owned_by": "xai",
51
- "prompt_text_token_price": 12500,
52
- "cached_prompt_text_token_price": 2000,
53
- "prompt_image_token_price": 12500,
54
- "completion_text_token_price": 25000,
55
- "prompt_text_token_price_long_context": 25000,
56
- "cached_prompt_text_token_price_long_context": 4000,
57
- "completion_text_token_price_long_context": 50000,
58
- "long_context_threshold": 200000
59
- },
60
- {
61
- "id": "grok-4.20-multi-agent-0309",
62
- "aliases": [
63
- "grok-4.20-multi-agent",
64
- "grok-4.20-multi-agent-latest",
65
- "grok-4.20-multi-agent-beta-latest",
66
- "grok-4.20-multi-agent-experimental-beta-0304",
67
- "grok-4.20-multi-agent-experimental-beta-latest",
68
- "grok-4.20-multi-agent-beta-0309"
69
- ],
70
- "context_length": 1000000,
71
- "created": 1773014400,
72
- "object": "model",
73
- "owned_by": "xai",
74
- "prompt_text_token_price": 12500,
75
- "cached_prompt_text_token_price": 2000,
76
- "prompt_image_token_price": 12500,
77
- "completion_text_token_price": 25000,
78
- "prompt_text_token_price_long_context": 25000,
79
- "cached_prompt_text_token_price_long_context": 4000,
80
- "completion_text_token_price_long_context": 50000,
81
- "long_context_threshold": 200000
82
- },
83
- {
84
- "id": "grok-4.3",
85
- "aliases": [
86
- "grok-4.3-latest",
87
- "grok-latest"
88
- ],
89
- "context_length": 1000000,
90
- "created": 1776384000,
91
- "object": "model",
92
- "owned_by": "xai",
93
- "prompt_text_token_price": 12500,
94
- "cached_prompt_text_token_price": 2000,
95
- "prompt_image_token_price": 12500,
96
- "completion_text_token_price": 25000,
97
- "prompt_text_token_price_long_context": 25000,
98
- "cached_prompt_text_token_price_long_context": 4000,
99
- "completion_text_token_price_long_context": 50000,
100
- "long_context_threshold": 200000
101
- },
102
- {
103
- "id": "grok-build-0.1",
104
- "aliases": [
105
- "grok-code-fast-1",
106
- "grok-code-fast",
107
- "grok-code-fast-1-0825"
108
- ],
109
- "context_length": 256000,
110
- "created": 1776297600,
111
- "object": "model",
112
- "owned_by": "xai",
113
- "prompt_text_token_price": 10000,
114
- "cached_prompt_text_token_price": 2000,
115
- "prompt_image_token_price": 10000,
116
- "completion_text_token_price": 20000,
117
- "prompt_text_token_price_long_context": 20000,
118
- "cached_prompt_text_token_price_long_context": 4000,
119
- "completion_text_token_price_long_context": 40000,
120
- "long_context_threshold": 200000
121
- },
122
- {
123
- "id": "grok-imagine-image",
124
- "aliases": [
125
- "grok-imagine-image-2026-03-02"
126
- ],
127
- "context_length": 8000,
128
- "created": 1769558400,
129
- "object": "model",
130
- "owned_by": "xai",
131
- "image_price": 200000000
132
- },
133
- {
134
- "id": "grok-imagine-image-quality",
135
- "aliases": [
136
- "grok-imagine-image-quality-20260403",
137
- "grok-imagine-image-quality-latest",
138
- "grok-imagine-image-pro"
139
- ],
140
- "context_length": 8000,
141
- "created": 1775174400,
142
- "object": "model",
143
- "owned_by": "xai",
144
- "image_price": 500000000
145
- },
146
- {
147
- "id": "grok-imagine-video",
148
- "aliases": [],
149
- "created": 1769558400,
150
- "object": "model",
151
- "owned_by": "xai"
152
- },
153
- {
154
- "id": "grok-imagine-video-1.5",
155
- "aliases": [
156
- "grok-imagine-video-1.5-preview",
157
- "grok-imagine-video-1.5-2026-05-30"
158
- ],
159
- "created": 1779840000,
160
- "object": "model",
161
- "owned_by": "xai"
162
- }
163
- ],
164
- "object": "list"
165
- }
Binary file
package/coverage/base.css DELETED
@@ -1,224 +0,0 @@
1
- body, html {
2
- margin:0; padding: 0;
3
- height: 100%;
4
- }
5
- body {
6
- font-family: Helvetica Neue, Helvetica, Arial;
7
- font-size: 14px;
8
- color:#333;
9
- }
10
- .small { font-size: 12px; }
11
- *, *:after, *:before {
12
- -webkit-box-sizing:border-box;
13
- -moz-box-sizing:border-box;
14
- box-sizing:border-box;
15
- }
16
- h1 { font-size: 20px; margin: 0;}
17
- h2 { font-size: 14px; }
18
- pre {
19
- font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
20
- margin: 0;
21
- padding: 0;
22
- -moz-tab-size: 2;
23
- -o-tab-size: 2;
24
- tab-size: 2;
25
- }
26
- a { color:#0074D9; text-decoration:none; }
27
- a:hover { text-decoration:underline; }
28
- .strong { font-weight: bold; }
29
- .space-top1 { padding: 10px 0 0 0; }
30
- .pad2y { padding: 20px 0; }
31
- .pad1y { padding: 10px 0; }
32
- .pad2x { padding: 0 20px; }
33
- .pad2 { padding: 20px; }
34
- .pad1 { padding: 10px; }
35
- .space-left2 { padding-left:55px; }
36
- .space-right2 { padding-right:20px; }
37
- .center { text-align:center; }
38
- .clearfix { display:block; }
39
- .clearfix:after {
40
- content:'';
41
- display:block;
42
- height:0;
43
- clear:both;
44
- visibility:hidden;
45
- }
46
- .fl { float: left; }
47
- @media only screen and (max-width:640px) {
48
- .col3 { width:100%; max-width:100%; }
49
- .hide-mobile { display:none!important; }
50
- }
51
-
52
- .quiet {
53
- color: #7f7f7f;
54
- color: rgba(0,0,0,0.5);
55
- }
56
- .quiet a { opacity: 0.7; }
57
-
58
- .fraction {
59
- font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
60
- font-size: 10px;
61
- color: #555;
62
- background: #E8E8E8;
63
- padding: 4px 5px;
64
- border-radius: 3px;
65
- vertical-align: middle;
66
- }
67
-
68
- div.path a:link, div.path a:visited { color: #333; }
69
- table.coverage {
70
- border-collapse: collapse;
71
- margin: 10px 0 0 0;
72
- padding: 0;
73
- }
74
-
75
- table.coverage td {
76
- margin: 0;
77
- padding: 0;
78
- vertical-align: top;
79
- }
80
- table.coverage td.line-count {
81
- text-align: right;
82
- padding: 0 5px 0 20px;
83
- }
84
- table.coverage td.line-coverage {
85
- text-align: right;
86
- padding-right: 10px;
87
- min-width:20px;
88
- }
89
-
90
- table.coverage td span.cline-any {
91
- display: inline-block;
92
- padding: 0 5px;
93
- width: 100%;
94
- }
95
- .missing-if-branch {
96
- display: inline-block;
97
- margin-right: 5px;
98
- border-radius: 3px;
99
- position: relative;
100
- padding: 0 4px;
101
- background: #333;
102
- color: yellow;
103
- }
104
-
105
- .skip-if-branch {
106
- display: none;
107
- margin-right: 10px;
108
- position: relative;
109
- padding: 0 4px;
110
- background: #ccc;
111
- color: white;
112
- }
113
- .missing-if-branch .typ, .skip-if-branch .typ {
114
- color: inherit !important;
115
- }
116
- .coverage-summary {
117
- border-collapse: collapse;
118
- width: 100%;
119
- }
120
- .coverage-summary tr { border-bottom: 1px solid #bbb; }
121
- .keyline-all { border: 1px solid #ddd; }
122
- .coverage-summary td, .coverage-summary th { padding: 10px; }
123
- .coverage-summary tbody { border: 1px solid #bbb; }
124
- .coverage-summary td { border-right: 1px solid #bbb; }
125
- .coverage-summary td:last-child { border-right: none; }
126
- .coverage-summary th {
127
- text-align: left;
128
- font-weight: normal;
129
- white-space: nowrap;
130
- }
131
- .coverage-summary th.file { border-right: none !important; }
132
- .coverage-summary th.pct { }
133
- .coverage-summary th.pic,
134
- .coverage-summary th.abs,
135
- .coverage-summary td.pct,
136
- .coverage-summary td.abs { text-align: right; }
137
- .coverage-summary td.file { white-space: nowrap; }
138
- .coverage-summary td.pic { min-width: 120px !important; }
139
- .coverage-summary tfoot td { }
140
-
141
- .coverage-summary .sorter {
142
- height: 10px;
143
- width: 7px;
144
- display: inline-block;
145
- margin-left: 0.5em;
146
- background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
147
- }
148
- .coverage-summary .sorted .sorter {
149
- background-position: 0 -20px;
150
- }
151
- .coverage-summary .sorted-desc .sorter {
152
- background-position: 0 -10px;
153
- }
154
- .status-line { height: 10px; }
155
- /* yellow */
156
- .cbranch-no { background: yellow !important; color: #111; }
157
- /* dark red */
158
- .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
159
- .low .chart { border:1px solid #C21F39 }
160
- .highlighted,
161
- .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
162
- background: #C21F39 !important;
163
- }
164
- /* medium red */
165
- .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
166
- /* light red */
167
- .low, .cline-no { background:#FCE1E5 }
168
- /* light green */
169
- .high, .cline-yes { background:rgb(230,245,208) }
170
- /* medium green */
171
- .cstat-yes { background:rgb(161,215,106) }
172
- /* dark green */
173
- .status-line.high, .high .cover-fill { background:rgb(77,146,33) }
174
- .high .chart { border:1px solid rgb(77,146,33) }
175
- /* dark yellow (gold) */
176
- .status-line.medium, .medium .cover-fill { background: #f9cd0b; }
177
- .medium .chart { border:1px solid #f9cd0b; }
178
- /* light yellow */
179
- .medium { background: #fff4c2; }
180
-
181
- .cstat-skip { background: #ddd; color: #111; }
182
- .fstat-skip { background: #ddd; color: #111 !important; }
183
- .cbranch-skip { background: #ddd !important; color: #111; }
184
-
185
- span.cline-neutral { background: #eaeaea; }
186
-
187
- .coverage-summary td.empty {
188
- opacity: .5;
189
- padding-top: 4px;
190
- padding-bottom: 4px;
191
- line-height: 1;
192
- color: #888;
193
- }
194
-
195
- .cover-fill, .cover-empty {
196
- display:inline-block;
197
- height: 12px;
198
- }
199
- .chart {
200
- line-height: 0;
201
- }
202
- .cover-empty {
203
- background: white;
204
- }
205
- .cover-full {
206
- border-right: none !important;
207
- }
208
- pre.prettyprint {
209
- border: none !important;
210
- padding: 0 !important;
211
- margin: 0 !important;
212
- }
213
- .com { color: #999 !important; }
214
- .ignore-none { color: #999; font-weight: normal; }
215
-
216
- .wrapper {
217
- min-height: 100%;
218
- height: auto !important;
219
- height: 100%;
220
- margin: 0 auto -48px;
221
- }
222
- .footer, .push {
223
- height: 48px;
224
- }
@@ -1,87 +0,0 @@
1
- /* eslint-disable */
2
- var jumpToCode = (function init() {
3
- // Classes of code we would like to highlight in the file view
4
- var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
5
-
6
- // Elements to highlight in the file listing view
7
- var fileListingElements = ['td.pct.low'];
8
-
9
- // We don't want to select elements that are direct descendants of another match
10
- var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
11
-
12
- // Selector that finds elements on the page to which we can jump
13
- var selector =
14
- fileListingElements.join(', ') +
15
- ', ' +
16
- notSelector +
17
- missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
18
-
19
- // The NodeList of matching elements
20
- var missingCoverageElements = document.querySelectorAll(selector);
21
-
22
- var currentIndex;
23
-
24
- function toggleClass(index) {
25
- missingCoverageElements
26
- .item(currentIndex)
27
- .classList.remove('highlighted');
28
- missingCoverageElements.item(index).classList.add('highlighted');
29
- }
30
-
31
- function makeCurrent(index) {
32
- toggleClass(index);
33
- currentIndex = index;
34
- missingCoverageElements.item(index).scrollIntoView({
35
- behavior: 'smooth',
36
- block: 'center',
37
- inline: 'center'
38
- });
39
- }
40
-
41
- function goToPrevious() {
42
- var nextIndex = 0;
43
- if (typeof currentIndex !== 'number' || currentIndex === 0) {
44
- nextIndex = missingCoverageElements.length - 1;
45
- } else if (missingCoverageElements.length > 1) {
46
- nextIndex = currentIndex - 1;
47
- }
48
-
49
- makeCurrent(nextIndex);
50
- }
51
-
52
- function goToNext() {
53
- var nextIndex = 0;
54
-
55
- if (
56
- typeof currentIndex === 'number' &&
57
- currentIndex < missingCoverageElements.length - 1
58
- ) {
59
- nextIndex = currentIndex + 1;
60
- }
61
-
62
- makeCurrent(nextIndex);
63
- }
64
-
65
- return function jump(event) {
66
- if (
67
- document.getElementById('fileSearch') === document.activeElement &&
68
- document.activeElement != null
69
- ) {
70
- // if we're currently focused on the search input, we don't want to navigate
71
- return;
72
- }
73
-
74
- switch (event.which) {
75
- case 78: // n
76
- case 74: // j
77
- goToNext();
78
- break;
79
- case 66: // b
80
- case 75: // k
81
- case 80: // p
82
- goToPrevious();
83
- break;
84
- }
85
- };
86
- })();
87
- window.addEventListener('keydown', jumpToCode);