@genrtl/grtl 0.1.1 → 0.3.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.
- package/README.md +70 -106
- package/dist/index.js +456 -232
- package/package.json +34 -34
package/README.md
CHANGED
|
@@ -1,106 +1,70 @@
|
|
|
1
|
-
# grtl
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
grtl setup --codex --project
|
|
72
|
-
grtl setup --claude --opencode
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Supported agent flags are `--claude`, `--cursor`, `--codex`, `--opencode`,
|
|
76
|
-
`--gemini`, and `--antigravity`. Without a flag, `grtl setup` presents an
|
|
77
|
-
interactive selection.
|
|
78
|
-
|
|
79
|
-
The API key is written to the selected agent's MCP configuration because the
|
|
80
|
-
agent must send it as a Bearer token. Protect that configuration file and do
|
|
81
|
-
not commit project-level MCP configuration containing a real key.
|
|
82
|
-
|
|
83
|
-
The default endpoint is:
|
|
84
|
-
|
|
85
|
-
```text
|
|
86
|
-
https://genrtl.com/api/mcp
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Use `--base-url` for another deployment:
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
grtl --base-url http://localhost:3005 setup --cursor --project
|
|
93
|
-
grtl --base-url http://localhost:3005 debug-search "compile error"
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Development
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
pnpm install
|
|
100
|
-
pnpm --filter @genrtl/grtl lint:check
|
|
101
|
-
pnpm --filter @genrtl/grtl typecheck
|
|
102
|
-
pnpm --filter @genrtl/grtl test
|
|
103
|
-
pnpm --filter @genrtl/grtl build
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
This project is derived from Upstash Context7 and retains its MIT license.
|
|
1
|
+
# @genrtl/grtl
|
|
2
|
+
|
|
3
|
+
CLI and coding-agent integration for the GenRTL RTL engineering knowledge
|
|
4
|
+
service.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install --global @genrtl/grtl
|
|
10
|
+
export GRTL_API_KEY="gtr_live_your_api_key"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
PowerShell:
|
|
14
|
+
|
|
15
|
+
```powershell
|
|
16
|
+
$env:GRTL_API_KEY = "gtr_live_your_api_key"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Agent Setup
|
|
20
|
+
|
|
21
|
+
Install a Skill that tells the agent to call the `grtl` CLI:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
grtl setup --cli --codex
|
|
25
|
+
grtl setup --cli --cursor --project
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Configure hosted MCP and install a Skill for the four MCP tools:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
grtl setup --mcp --codex
|
|
32
|
+
grtl setup --mcp --cursor --project
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Without `--cli` or `--mcp`, setup asks which mode to install.
|
|
36
|
+
|
|
37
|
+
For Codex, Skills are installed under `.agents/skills` for project setup or
|
|
38
|
+
`~/.agents/skills` for global setup. MCP mode also updates
|
|
39
|
+
`.codex/config.toml` or `~/.codex/config.toml`.
|
|
40
|
+
|
|
41
|
+
The hosted MCP endpoint is:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
https://genrtl.com/api/mcp
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Knowledge Commands
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
grtl knowledge-search "AXI stream backpressure design"
|
|
51
|
+
grtl spec2rtl-search "Design an APB register block"
|
|
52
|
+
grtl spec2plan-search "Plan an APB register block implementation"
|
|
53
|
+
grtl verification-search "Verify an async FIFO"
|
|
54
|
+
grtl debug-search "Explain this Vivado CDC warning"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Use `--json` for structured output. `--type` accepts `spec2rtl`, `spec2plan`, `verification`, or `debug`; other filters include
|
|
58
|
+
`--domain`, `--tool`, `--tool-version`, `--error-type`, `--severity`,
|
|
59
|
+
`--interface`, `--target`, `--tag`, `--top-k`, `--min-score`, and
|
|
60
|
+
`--workspace-id`.
|
|
61
|
+
|
|
62
|
+
## Development
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pnpm install
|
|
66
|
+
pnpm --filter @genrtl/grtl lint:check
|
|
67
|
+
pnpm --filter @genrtl/grtl typecheck
|
|
68
|
+
pnpm --filter @genrtl/grtl test
|
|
69
|
+
pnpm --filter @genrtl/grtl build
|
|
70
|
+
```
|
package/dist/index.js
CHANGED
|
@@ -6,106 +6,16 @@ import pc6 from "picocolors";
|
|
|
6
6
|
import figlet from "figlet";
|
|
7
7
|
|
|
8
8
|
// src/commands/setup.ts
|
|
9
|
-
import
|
|
9
|
+
import { select } from "@inquirer/prompts";
|
|
10
|
+
import { dirname as dirname4, join as join2 } from "path";
|
|
11
|
+
import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
10
12
|
import ora from "ora";
|
|
11
|
-
import
|
|
12
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
13
|
-
|
|
14
|
-
// src/utils/logger.ts
|
|
15
|
-
import pc from "picocolors";
|
|
16
|
-
var ANSI_PATTERN = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
17
|
-
function visibleLength(text) {
|
|
18
|
-
return text.replace(ANSI_PATTERN, "").length;
|
|
19
|
-
}
|
|
20
|
-
function padVisible(text, width) {
|
|
21
|
-
const padding = Math.max(0, width - visibleLength(text));
|
|
22
|
-
return text + " ".repeat(padding);
|
|
23
|
-
}
|
|
24
|
-
function box(lines, color = pc.green) {
|
|
25
|
-
const contentWidth = Math.max(...lines.map((line) => visibleLength(line)), 0);
|
|
26
|
-
const top = color(`\u250C${"\u2500".repeat(contentWidth + 2)}\u2510`);
|
|
27
|
-
const bottom = color(`\u2514${"\u2500".repeat(contentWidth + 2)}\u2518`);
|
|
28
|
-
console.log(top);
|
|
29
|
-
for (const line of lines) {
|
|
30
|
-
console.log(color("\u2502 ") + padVisible(line, contentWidth) + color(" \u2502"));
|
|
31
|
-
}
|
|
32
|
-
console.log(bottom);
|
|
33
|
-
}
|
|
34
|
-
var log = {
|
|
35
|
-
info: (message) => console.log(pc.cyan(message)),
|
|
36
|
-
success: (message) => console.log(pc.green(`\u2714 ${message}`)),
|
|
37
|
-
warn: (message) => console.log(pc.yellow(`\u26A0 ${message}`)),
|
|
38
|
-
error: (message) => console.log(pc.red(`\u2716 ${message}`)),
|
|
39
|
-
dim: (message) => console.log(pc.dim(message)),
|
|
40
|
-
item: (message) => console.log(pc.green(` ${message}`)),
|
|
41
|
-
itemAdd: (message) => console.log(` ${pc.green("+")} ${message}`),
|
|
42
|
-
plain: (message) => console.log(message),
|
|
43
|
-
blank: () => console.log(""),
|
|
44
|
-
box
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// src/utils/prompts.ts
|
|
48
|
-
import pc2 from "picocolors";
|
|
49
|
-
import { checkbox } from "@inquirer/prompts";
|
|
50
|
-
import readline from "readline";
|
|
51
|
-
async function checkboxWithHover(config, options) {
|
|
52
|
-
const choices = config.choices.filter(
|
|
53
|
-
(c) => typeof c === "object" && c !== null && !("type" in c && c.type === "separator")
|
|
54
|
-
);
|
|
55
|
-
const values = choices.map((c) => c.value);
|
|
56
|
-
const totalItems = values.length;
|
|
57
|
-
let cursorPosition = choices.findIndex((c) => !c.disabled);
|
|
58
|
-
if (cursorPosition < 0) cursorPosition = 0;
|
|
59
|
-
const getName = options?.getName ?? ((v) => v.name);
|
|
60
|
-
const keypressHandler = (_str, key) => {
|
|
61
|
-
if (key.name === "up") {
|
|
62
|
-
let next = cursorPosition - 1;
|
|
63
|
-
while (next >= 0 && choices[next].disabled) next--;
|
|
64
|
-
if (next >= 0) cursorPosition = next;
|
|
65
|
-
} else if (key.name === "down") {
|
|
66
|
-
let next = cursorPosition + 1;
|
|
67
|
-
while (next < totalItems && choices[next].disabled) next++;
|
|
68
|
-
if (next < totalItems) cursorPosition = next;
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
readline.emitKeypressEvents(process.stdin);
|
|
72
|
-
process.stdin.on("keypress", keypressHandler);
|
|
73
|
-
const customConfig = {
|
|
74
|
-
...config,
|
|
75
|
-
theme: {
|
|
76
|
-
...config.theme,
|
|
77
|
-
style: {
|
|
78
|
-
answer: (text) => pc2.green(text),
|
|
79
|
-
...config.theme?.style,
|
|
80
|
-
highlight: (text) => pc2.green(text),
|
|
81
|
-
renderSelectedChoices: (selected, _allChoices) => {
|
|
82
|
-
if (selected.length === 0) {
|
|
83
|
-
return pc2.dim(getName(values[cursorPosition]));
|
|
84
|
-
}
|
|
85
|
-
return selected.map((c) => getName(c.value)).join(", ");
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
try {
|
|
91
|
-
const selected = await checkbox(customConfig);
|
|
92
|
-
if (selected.length === 0) {
|
|
93
|
-
return [values[cursorPosition]];
|
|
94
|
-
}
|
|
95
|
-
return selected;
|
|
96
|
-
} finally {
|
|
97
|
-
process.stdin.removeListener("keypress", keypressHandler);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// src/utils/tracking.ts
|
|
102
|
-
function trackEvent(_event, _data) {
|
|
103
|
-
}
|
|
13
|
+
import pc3 from "picocolors";
|
|
104
14
|
|
|
105
|
-
// src/setup/
|
|
15
|
+
// src/setup/agents.ts
|
|
106
16
|
import { access } from "fs/promises";
|
|
107
|
-
import { homedir } from "os";
|
|
108
17
|
import { join } from "path";
|
|
18
|
+
import { homedir } from "os";
|
|
109
19
|
var SETUP_AGENT_NAMES = {
|
|
110
20
|
claude: "Claude Code",
|
|
111
21
|
cursor: "Cursor",
|
|
@@ -115,18 +25,38 @@ var SETUP_AGENT_NAMES = {
|
|
|
115
25
|
gemini: "Gemini CLI"
|
|
116
26
|
};
|
|
117
27
|
var mcpBaseUrl = "https://genrtl.com/api/mcp";
|
|
28
|
+
var STDIO_PACKAGE = "@upstash/genrtl-mcp";
|
|
118
29
|
function setMcpBaseUrl(url) {
|
|
119
30
|
const normalized = url.replace(/\/+$/, "");
|
|
120
31
|
mcpBaseUrl = normalized.endsWith("/api/mcp") ? normalized : `${normalized}/api/mcp`;
|
|
121
32
|
}
|
|
122
|
-
function
|
|
123
|
-
|
|
33
|
+
function stdioArgs(auth) {
|
|
34
|
+
const args = ["-y", STDIO_PACKAGE];
|
|
35
|
+
if (auth.mode === "api-key" && auth.apiKey) {
|
|
36
|
+
args.push("--api-key", auth.apiKey);
|
|
37
|
+
}
|
|
38
|
+
return args;
|
|
39
|
+
}
|
|
40
|
+
function stdioEntry(auth) {
|
|
41
|
+
return { command: "npx", args: stdioArgs(auth) };
|
|
124
42
|
}
|
|
125
43
|
function claudeConfigDir() {
|
|
126
44
|
return process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
|
|
127
45
|
}
|
|
128
46
|
function claudeGlobalMcpPath() {
|
|
129
|
-
|
|
47
|
+
if (process.env.CLAUDE_CONFIG_DIR) {
|
|
48
|
+
return join(claudeConfigDir(), ".claude.json");
|
|
49
|
+
}
|
|
50
|
+
return join(homedir(), ".claude.json");
|
|
51
|
+
}
|
|
52
|
+
function mcpUrl(_auth) {
|
|
53
|
+
return mcpBaseUrl;
|
|
54
|
+
}
|
|
55
|
+
function withHeaders(base, auth) {
|
|
56
|
+
if (auth.mode === "api-key" && auth.apiKey) {
|
|
57
|
+
return { ...base, headers: { Authorization: `Bearer ${auth.apiKey}` } };
|
|
58
|
+
}
|
|
59
|
+
return base;
|
|
130
60
|
}
|
|
131
61
|
var agents = {
|
|
132
62
|
claude: {
|
|
@@ -138,17 +68,17 @@ var agents = {
|
|
|
138
68
|
return [claudeGlobalMcpPath()];
|
|
139
69
|
},
|
|
140
70
|
configKey: "mcpServers",
|
|
141
|
-
buildEntry: (auth) => ({
|
|
142
|
-
type: "http",
|
|
143
|
-
url: mcpBaseUrl,
|
|
144
|
-
headers: headers(auth)
|
|
145
|
-
})
|
|
71
|
+
buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
|
|
146
72
|
},
|
|
147
73
|
rule: {
|
|
148
74
|
kind: "file",
|
|
149
75
|
dir: (scope) => scope === "global" ? join(claudeConfigDir(), "rules") : join(".claude", "rules"),
|
|
150
76
|
filename: "genrtl.md"
|
|
151
77
|
},
|
|
78
|
+
skill: {
|
|
79
|
+
name: "genrtl-mcp",
|
|
80
|
+
dir: (scope) => scope === "global" ? join(claudeConfigDir(), "skills") : join(".claude", "skills")
|
|
81
|
+
},
|
|
152
82
|
detect: {
|
|
153
83
|
projectPaths: [".mcp.json", ".claude"],
|
|
154
84
|
get globalPaths() {
|
|
@@ -163,13 +93,17 @@ var agents = {
|
|
|
163
93
|
projectPaths: [join(".cursor", "mcp.json")],
|
|
164
94
|
globalPaths: [join(homedir(), ".cursor", "mcp.json")],
|
|
165
95
|
configKey: "mcpServers",
|
|
166
|
-
buildEntry: (auth) => (
|
|
96
|
+
buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ url: mcpUrl(auth) }, auth)
|
|
167
97
|
},
|
|
168
98
|
rule: {
|
|
169
99
|
kind: "file",
|
|
170
100
|
dir: (scope) => scope === "global" ? join(homedir(), ".cursor", "rules") : join(".cursor", "rules"),
|
|
171
101
|
filename: "genrtl.mdc"
|
|
172
102
|
},
|
|
103
|
+
skill: {
|
|
104
|
+
name: "genrtl-mcp",
|
|
105
|
+
dir: (scope) => scope === "global" ? join(homedir(), ".cursor", "skills") : join(".cursor", "skills")
|
|
106
|
+
},
|
|
173
107
|
detect: {
|
|
174
108
|
projectPaths: [".cursor"],
|
|
175
109
|
globalPaths: [join(homedir(), ".cursor")]
|
|
@@ -187,18 +121,17 @@ var agents = {
|
|
|
187
121
|
join(homedir(), ".config", "opencode", ".opencode.jsonc")
|
|
188
122
|
],
|
|
189
123
|
configKey: "mcp",
|
|
190
|
-
buildEntry: (auth) => ({
|
|
191
|
-
type: "remote",
|
|
192
|
-
url: mcpBaseUrl,
|
|
193
|
-
enabled: true,
|
|
194
|
-
headers: headers(auth)
|
|
195
|
-
})
|
|
124
|
+
buildEntry: (auth, transport) => transport === "stdio" ? { type: "local", command: ["npx", ...stdioArgs(auth)], enabled: true } : withHeaders({ type: "remote", url: mcpUrl(auth), enabled: true }, auth)
|
|
196
125
|
},
|
|
197
126
|
rule: {
|
|
198
127
|
kind: "append",
|
|
199
128
|
file: (scope) => scope === "global" ? join(homedir(), ".config", "opencode", "AGENTS.md") : "AGENTS.md",
|
|
200
129
|
sectionMarker: "<!-- genrtl -->"
|
|
201
130
|
},
|
|
131
|
+
skill: {
|
|
132
|
+
name: "genrtl-mcp",
|
|
133
|
+
dir: (scope) => scope === "global" ? join(homedir(), ".agents", "skills") : join(".agents", "skills")
|
|
134
|
+
},
|
|
202
135
|
detect: {
|
|
203
136
|
projectPaths: ["opencode.json", "opencode.jsonc", ".opencode.json", ".opencode.jsonc"],
|
|
204
137
|
globalPaths: [join(homedir(), ".config", "opencode")]
|
|
@@ -211,14 +144,16 @@ var agents = {
|
|
|
211
144
|
projectPaths: [join(".codex", "config.toml")],
|
|
212
145
|
globalPaths: [join(homedir(), ".codex", "config.toml")],
|
|
213
146
|
configKey: "mcp_servers",
|
|
214
|
-
buildEntry: (auth) =>
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
147
|
+
buildEntry: (auth, transport) => {
|
|
148
|
+
if (transport === "stdio") return stdioEntry(auth);
|
|
149
|
+
if (auth.mode === "api-key" && auth.apiKeyEnvVar) {
|
|
150
|
+
return {
|
|
151
|
+
type: "http",
|
|
152
|
+
url: mcpUrl(auth),
|
|
153
|
+
bearer_token_env_var: auth.apiKeyEnvVar
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return withHeaders({ type: "http", url: mcpUrl(auth) }, auth);
|
|
222
157
|
}
|
|
223
158
|
},
|
|
224
159
|
rule: {
|
|
@@ -226,25 +161,37 @@ var agents = {
|
|
|
226
161
|
file: (scope) => scope === "global" ? join(homedir(), ".codex", "AGENTS.md") : "AGENTS.md",
|
|
227
162
|
sectionMarker: "<!-- genrtl -->"
|
|
228
163
|
},
|
|
164
|
+
skill: {
|
|
165
|
+
name: "genrtl-mcp",
|
|
166
|
+
dir: (scope) => scope === "global" ? join(homedir(), ".agents", "skills") : join(".agents", "skills")
|
|
167
|
+
},
|
|
229
168
|
detect: {
|
|
230
169
|
projectPaths: [".codex"],
|
|
231
170
|
globalPaths: [join(homedir(), ".codex")]
|
|
232
171
|
}
|
|
233
172
|
},
|
|
173
|
+
// Antigravity is built on Gemini infrastructure and shares ~/.gemini/. Per
|
|
174
|
+
// the official Codelabs guide, Antigravity 2.0/IDE/CLI read MCP servers from
|
|
175
|
+
// ~/.gemini/config/mcp_config.json globally; there is no project-level MCP
|
|
176
|
+
// config, so projectPaths is empty and setupAgent falls back to global.
|
|
234
177
|
antigravity: {
|
|
235
178
|
name: "antigravity",
|
|
236
179
|
displayName: "Antigravity",
|
|
237
180
|
mcp: {
|
|
238
181
|
projectPaths: [],
|
|
239
|
-
globalPaths: [join(homedir(), ".gemini", "
|
|
182
|
+
globalPaths: [join(homedir(), ".gemini", "config", "mcp_config.json")],
|
|
240
183
|
configKey: "mcpServers",
|
|
241
|
-
buildEntry: (auth) => (
|
|
184
|
+
buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ serverUrl: mcpUrl(auth) }, auth)
|
|
242
185
|
},
|
|
243
186
|
rule: {
|
|
244
187
|
kind: "append",
|
|
245
188
|
file: (scope) => scope === "global" ? join(homedir(), ".gemini", "GEMINI.md") : "GEMINI.md",
|
|
246
189
|
sectionMarker: "<!-- genrtl -->"
|
|
247
190
|
},
|
|
191
|
+
skill: {
|
|
192
|
+
name: "genrtl-mcp",
|
|
193
|
+
dir: (scope) => scope === "global" ? join(homedir(), ".agent", "skills") : join(".agent", "skills")
|
|
194
|
+
},
|
|
248
195
|
detect: {
|
|
249
196
|
projectPaths: [".agent"],
|
|
250
197
|
globalPaths: [join(homedir(), ".gemini", "antigravity"), join(homedir(), ".agent")]
|
|
@@ -257,13 +204,17 @@ var agents = {
|
|
|
257
204
|
projectPaths: [join(".gemini", "settings.json")],
|
|
258
205
|
globalPaths: [join(homedir(), ".gemini", "settings.json")],
|
|
259
206
|
configKey: "mcpServers",
|
|
260
|
-
buildEntry: (auth) => (
|
|
207
|
+
buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ httpUrl: mcpUrl(auth) }, auth)
|
|
261
208
|
},
|
|
262
209
|
rule: {
|
|
263
210
|
kind: "append",
|
|
264
211
|
file: (scope) => scope === "global" ? join(homedir(), ".gemini", "GEMINI.md") : "GEMINI.md",
|
|
265
212
|
sectionMarker: "<!-- genrtl -->"
|
|
266
213
|
},
|
|
214
|
+
skill: {
|
|
215
|
+
name: "genrtl-mcp",
|
|
216
|
+
dir: (scope) => scope === "global" ? join(homedir(), ".gemini", "skills") : join(".gemini", "skills")
|
|
217
|
+
},
|
|
267
218
|
detect: {
|
|
268
219
|
projectPaths: [".gemini"],
|
|
269
220
|
globalPaths: [join(homedir(), ".gemini")]
|
|
@@ -274,9 +225,9 @@ function getAgent(name) {
|
|
|
274
225
|
return agents[name];
|
|
275
226
|
}
|
|
276
227
|
var ALL_AGENT_NAMES = Object.keys(agents);
|
|
277
|
-
async function pathExists(
|
|
228
|
+
async function pathExists(p) {
|
|
278
229
|
try {
|
|
279
|
-
await access(
|
|
230
|
+
await access(p);
|
|
280
231
|
return true;
|
|
281
232
|
} catch {
|
|
282
233
|
return false;
|
|
@@ -286,8 +237,8 @@ async function detectAgents(scope) {
|
|
|
286
237
|
const detected = [];
|
|
287
238
|
for (const agent of Object.values(agents)) {
|
|
288
239
|
const paths = scope === "global" ? agent.detect.globalPaths : agent.detect.projectPaths;
|
|
289
|
-
for (const
|
|
290
|
-
const fullPath = scope === "global" ?
|
|
240
|
+
for (const p of paths) {
|
|
241
|
+
const fullPath = scope === "global" ? p : join(process.cwd(), p);
|
|
291
242
|
if (await pathExists(fullPath)) {
|
|
292
243
|
detected.push(agent.name);
|
|
293
244
|
break;
|
|
@@ -297,56 +248,6 @@ async function detectAgents(scope) {
|
|
|
297
248
|
return detected;
|
|
298
249
|
}
|
|
299
250
|
|
|
300
|
-
// src/setup/templates.ts
|
|
301
|
-
var GITHUB_RAW_URLS = ["https://raw.githubusercontent.com/xroting/grtl/main/rules"];
|
|
302
|
-
var FALLBACK_MCP = `Use GenRTL MCP tools for grounded RTL engineering knowledge.
|
|
303
|
-
|
|
304
|
-
Choose one tool:
|
|
305
|
-
- \`genrtl_knowledge_search\` for cross-domain RTL questions
|
|
306
|
-
- \`genrtl_spec2rtl_search\` for requirements and RTL design
|
|
307
|
-
- \`genrtl_verification_search\` for testbenches and verification
|
|
308
|
-
- \`genrtl_debug_search\` for lint, CDC, compile, synthesis, and RTL bugs
|
|
309
|
-
|
|
310
|
-
Pass the complete engineering question in \`query\`. Add filters only when useful.`;
|
|
311
|
-
var FALLBACK_CLI = `Use the \`grtl\` CLI for grounded RTL engineering knowledge.
|
|
312
|
-
|
|
313
|
-
Choose one command:
|
|
314
|
-
- \`npx @genrtl/grtl@latest knowledge-search "<query>"\`
|
|
315
|
-
- \`npx @genrtl/grtl@latest spec2rtl-search "<query>"\`
|
|
316
|
-
- \`npx @genrtl/grtl@latest verification-search "<query>"\`
|
|
317
|
-
- \`npx @genrtl/grtl@latest debug-search "<query>"\`
|
|
318
|
-
|
|
319
|
-
Pass the complete engineering question. Add filters such as \`--tool\`,
|
|
320
|
-
\`--tool-version\`, \`--target\`, \`--interface\`, or \`--tag\` when known.
|
|
321
|
-
If authentication fails, set \`GRTL_API_KEY\`.`;
|
|
322
|
-
var CURSOR_FRONTMATTER = `---
|
|
323
|
-
alwaysApply: true
|
|
324
|
-
---
|
|
325
|
-
|
|
326
|
-
`;
|
|
327
|
-
var CODEX_CLI_SANDBOX_GUIDANCE = `Run GenRTL CLI requests outside Codex's default sandbox. If a GenRTL CLI command fails with DNS or network errors such as ENOTFOUND, host resolution failures, or fetch failed, rerun it outside the sandbox instead of retrying inside the sandbox.`;
|
|
328
|
-
async function fetchRule(filename, fallback) {
|
|
329
|
-
for (const base of GITHUB_RAW_URLS) {
|
|
330
|
-
try {
|
|
331
|
-
const res = await fetch(`${base}/${filename}`);
|
|
332
|
-
if (res.ok) return await res.text();
|
|
333
|
-
} catch {
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return fallback;
|
|
338
|
-
}
|
|
339
|
-
async function getRuleContent(mode, agent) {
|
|
340
|
-
const [filename, fallback] = mode === "mcp" ? ["genrtl-mcp.md", FALLBACK_MCP] : ["genrtl-cli.md", FALLBACK_CLI];
|
|
341
|
-
let body = await fetchRule(filename, fallback);
|
|
342
|
-
if (mode === "cli" && agent === "codex" && !body.includes(CODEX_CLI_SANDBOX_GUIDANCE)) {
|
|
343
|
-
body = `${body.trimEnd()}
|
|
344
|
-
${CODEX_CLI_SANDBOX_GUIDANCE}
|
|
345
|
-
`;
|
|
346
|
-
}
|
|
347
|
-
return agent === "cursor" ? `${CURSOR_FRONTMATTER}${body}` : body;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
251
|
// src/setup/mcp-writer.ts
|
|
351
252
|
import { access as access2, readFile, writeFile, mkdir } from "fs/promises";
|
|
352
253
|
import { dirname } from "path";
|
|
@@ -415,15 +316,15 @@ async function writeJsonConfig(filePath, config) {
|
|
|
415
316
|
}
|
|
416
317
|
function buildTomlServerBlock(serverName, entry) {
|
|
417
318
|
const lines = [`[mcp_servers.${serverName}]`];
|
|
418
|
-
const
|
|
319
|
+
const headers = entry.headers;
|
|
419
320
|
for (const [key, value] of Object.entries(entry)) {
|
|
420
321
|
if (key === "headers") continue;
|
|
421
322
|
lines.push(`${key} = ${JSON.stringify(value)}`);
|
|
422
323
|
}
|
|
423
|
-
if (
|
|
324
|
+
if (headers && Object.keys(headers).length > 0) {
|
|
424
325
|
lines.push("");
|
|
425
326
|
lines.push(`[mcp_servers.${serverName}.http_headers]`);
|
|
426
|
-
for (const [key, value] of Object.entries(
|
|
327
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
427
328
|
lines.push(`${key} = ${JSON.stringify(value)}`);
|
|
428
329
|
}
|
|
429
330
|
}
|
|
@@ -468,6 +369,236 @@ async function appendTomlServer(filePath, serverName, entry) {
|
|
|
468
369
|
return { alreadyExists };
|
|
469
370
|
}
|
|
470
371
|
|
|
372
|
+
// src/setup/templates.ts
|
|
373
|
+
var GITHUB_RAW_URLS = ["https://raw.githubusercontent.com/xroting/grtl/main/rules"];
|
|
374
|
+
var FALLBACK_MCP = `Use GenRTL MCP tools for grounded RTL engineering knowledge.
|
|
375
|
+
|
|
376
|
+
Choose one tool:
|
|
377
|
+
- \`genrtl_knowledge_search\` for cross-domain RTL questions
|
|
378
|
+
- \`genrtl_spec2rtl_search\` for requirements and RTL design
|
|
379
|
+
- \`genrtl_spec2plan_search\` for implementation planning from a specification
|
|
380
|
+
- \`genrtl_verification_search\` for testbenches and verification
|
|
381
|
+
- \`genrtl_debug_search\` for lint, CDC, compile, synthesis, and RTL bugs
|
|
382
|
+
|
|
383
|
+
Pass the complete engineering question in \`query\`. Add filters only when useful.`;
|
|
384
|
+
var FALLBACK_CLI = `Use the \`grtl\` CLI for grounded RTL engineering knowledge.
|
|
385
|
+
|
|
386
|
+
Choose one command:
|
|
387
|
+
- \`npx @genrtl/grtl@latest knowledge-search "<query>"\`
|
|
388
|
+
- \`npx @genrtl/grtl@latest spec2rtl-search "<query>"\`
|
|
389
|
+
- \`npx @genrtl/grtl@latest spec2plan-search "<query>"\`
|
|
390
|
+
- \`npx @genrtl/grtl@latest verification-search "<query>"\`
|
|
391
|
+
- \`npx @genrtl/grtl@latest debug-search "<query>"\`
|
|
392
|
+
|
|
393
|
+
Pass the complete engineering question. Add filters such as \`--tool\`,
|
|
394
|
+
\`--tool-version\`, \`--target\`, \`--interface\`, or \`--tag\` when known.
|
|
395
|
+
If authentication fails, set \`GRTL_API_KEY\` or \`GENRTL_API_KEY\` in the
|
|
396
|
+
coding agent's environment.`;
|
|
397
|
+
var CURSOR_FRONTMATTER = `---
|
|
398
|
+
alwaysApply: true
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
`;
|
|
402
|
+
var CODEX_CLI_SANDBOX_GUIDANCE = `Run GenRTL CLI requests outside Codex's default sandbox. If a GenRTL CLI command fails with DNS or network errors such as ENOTFOUND, host resolution failures, or fetch failed, rerun it outside the sandbox instead of retrying inside the sandbox.`;
|
|
403
|
+
var MCP_SKILL = `---
|
|
404
|
+
name: genrtl-mcp
|
|
405
|
+
description: Use GenRTL MCP tools for grounded RTL design, verification, lint, CDC, synthesis, compile, and debugging knowledge.
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
# GenRTL MCP
|
|
409
|
+
|
|
410
|
+
Use this skill when an RTL engineering task needs grounded GenRTL knowledge.
|
|
411
|
+
|
|
412
|
+
Choose exactly one MCP tool:
|
|
413
|
+
|
|
414
|
+
- \`genrtl_knowledge_search\` for cross-domain RTL questions.
|
|
415
|
+
- \`genrtl_spec2rtl_search\` for requirements, protocols, control logic, or algorithm-to-RTL work.
|
|
416
|
+
- \`genrtl_spec2plan_search\` for turning a specification into an actionable implementation plan.
|
|
417
|
+
- \`genrtl_verification_search\` for testbenches and verification.
|
|
418
|
+
- \`genrtl_debug_search\` for lint, CDC, compile, synthesis, or RTL bugs.
|
|
419
|
+
|
|
420
|
+
Pass the complete engineering question in \`query\`. Add \`filters\`, \`top_k\`,
|
|
421
|
+
\`min_score\`, or \`workspace_id\` only when useful.
|
|
422
|
+
`;
|
|
423
|
+
var CLI_SKILL = `---
|
|
424
|
+
name: genrtl-cli
|
|
425
|
+
description: Use the grtl CLI for grounded RTL design, verification, lint, CDC, synthesis, compile, and debugging knowledge.
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
# GenRTL CLI
|
|
429
|
+
|
|
430
|
+
Use this skill when an RTL engineering task needs grounded GenRTL knowledge and
|
|
431
|
+
the GenRTL MCP server is not configured.
|
|
432
|
+
|
|
433
|
+
Choose exactly one command:
|
|
434
|
+
|
|
435
|
+
- \`grtl knowledge-search "<query>" --json\` for cross-domain RTL questions.
|
|
436
|
+
- \`grtl spec2rtl-search "<query>" --json\` for requirements, protocols, control logic, or algorithm-to-RTL work.
|
|
437
|
+
- \`grtl spec2plan-search "<query>" --json\` for turning a specification into an actionable implementation plan.
|
|
438
|
+
- \`grtl verification-search "<query>" --json\` for testbenches and verification.
|
|
439
|
+
- \`grtl debug-search "<query>" --json\` for lint, CDC, compile, synthesis, or RTL bugs.
|
|
440
|
+
|
|
441
|
+
Pass the complete engineering question. Add filters such as \`--tool\`,
|
|
442
|
+
\`--tool-version\`, \`--target\`, \`--interface\`, or \`--tag\` only when useful.
|
|
443
|
+
The CLI requires \`GRTL_API_KEY\` or \`GENRTL_API_KEY\` in its environment.
|
|
444
|
+
`;
|
|
445
|
+
function getSkillContent(mode) {
|
|
446
|
+
return mode === "mcp" ? MCP_SKILL : CLI_SKILL;
|
|
447
|
+
}
|
|
448
|
+
async function fetchRule(filename, fallback) {
|
|
449
|
+
for (const base of GITHUB_RAW_URLS) {
|
|
450
|
+
try {
|
|
451
|
+
const res = await fetch(`${base}/${filename}`);
|
|
452
|
+
if (res.ok) return await res.text();
|
|
453
|
+
} catch {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return fallback;
|
|
458
|
+
}
|
|
459
|
+
async function getRuleContent(mode, agent) {
|
|
460
|
+
const [filename, fallback] = mode === "mcp" ? ["genrtl-mcp.md", FALLBACK_MCP] : ["genrtl-cli.md", FALLBACK_CLI];
|
|
461
|
+
let body = await fetchRule(filename, fallback);
|
|
462
|
+
if (mode === "cli" && agent === "codex" && !body.includes(CODEX_CLI_SANDBOX_GUIDANCE)) {
|
|
463
|
+
body = `${body.trimEnd()}
|
|
464
|
+
${CODEX_CLI_SANDBOX_GUIDANCE}
|
|
465
|
+
`;
|
|
466
|
+
}
|
|
467
|
+
return agent === "cursor" ? `${CURSOR_FRONTMATTER}${body}` : body;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// src/utils/installer.ts
|
|
471
|
+
import { mkdir as mkdir2, writeFile as writeFile2, rm, symlink, lstat } from "fs/promises";
|
|
472
|
+
import { resolve as resolve2, dirname as dirname3 } from "path";
|
|
473
|
+
|
|
474
|
+
// src/utils/skill-name.ts
|
|
475
|
+
import { resolve, dirname as dirname2, basename } from "path";
|
|
476
|
+
var SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
477
|
+
function isSafeSkillName(name) {
|
|
478
|
+
if (typeof name !== "string") return false;
|
|
479
|
+
if (name.length === 0 || name.length > 128) return false;
|
|
480
|
+
if (name === "." || name === "..") return false;
|
|
481
|
+
if (name.includes("\0")) return false;
|
|
482
|
+
if (!SAFE_NAME.test(name)) return false;
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
function assertSkillNameInRoot(skillsRoot, skillName) {
|
|
486
|
+
if (!isSafeSkillName(skillName)) {
|
|
487
|
+
throw new Error(`Unsafe skill name: ${JSON.stringify(skillName)}`);
|
|
488
|
+
}
|
|
489
|
+
const root = resolve(skillsRoot);
|
|
490
|
+
const target = resolve(root, skillName);
|
|
491
|
+
if (dirname2(target) !== root || basename(target) !== skillName) {
|
|
492
|
+
throw new Error(`Skill name "${skillName}" escapes the skills root`);
|
|
493
|
+
}
|
|
494
|
+
return target;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/utils/installer.ts
|
|
498
|
+
async function installSkillFiles(skillName, files, skillsRoot) {
|
|
499
|
+
const skillDir = assertSkillNameInRoot(skillsRoot, skillName);
|
|
500
|
+
for (const file of files) {
|
|
501
|
+
const filePath = resolve2(skillDir, file.path);
|
|
502
|
+
if (!filePath.startsWith(skillDir + "/") && !filePath.startsWith(skillDir + "\\") && filePath !== skillDir) {
|
|
503
|
+
throw new Error(`Skill file path "${file.path}" resolves outside the target directory`);
|
|
504
|
+
}
|
|
505
|
+
const fileDir = dirname3(filePath);
|
|
506
|
+
await mkdir2(fileDir, { recursive: true });
|
|
507
|
+
await writeFile2(filePath, file.content);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/utils/logger.ts
|
|
512
|
+
import pc from "picocolors";
|
|
513
|
+
var ANSI_PATTERN = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
514
|
+
function visibleLength(text) {
|
|
515
|
+
return text.replace(ANSI_PATTERN, "").length;
|
|
516
|
+
}
|
|
517
|
+
function padVisible(text, width) {
|
|
518
|
+
const padding = Math.max(0, width - visibleLength(text));
|
|
519
|
+
return text + " ".repeat(padding);
|
|
520
|
+
}
|
|
521
|
+
function box(lines, color = pc.green) {
|
|
522
|
+
const contentWidth = Math.max(...lines.map((line) => visibleLength(line)), 0);
|
|
523
|
+
const top = color(`\u250C${"\u2500".repeat(contentWidth + 2)}\u2510`);
|
|
524
|
+
const bottom = color(`\u2514${"\u2500".repeat(contentWidth + 2)}\u2518`);
|
|
525
|
+
console.log(top);
|
|
526
|
+
for (const line of lines) {
|
|
527
|
+
console.log(color("\u2502 ") + padVisible(line, contentWidth) + color(" \u2502"));
|
|
528
|
+
}
|
|
529
|
+
console.log(bottom);
|
|
530
|
+
}
|
|
531
|
+
var log = {
|
|
532
|
+
info: (message) => console.log(pc.cyan(message)),
|
|
533
|
+
success: (message) => console.log(pc.green(`\u2714 ${message}`)),
|
|
534
|
+
warn: (message) => console.log(pc.yellow(`\u26A0 ${message}`)),
|
|
535
|
+
error: (message) => console.log(pc.red(`\u2716 ${message}`)),
|
|
536
|
+
dim: (message) => console.log(pc.dim(message)),
|
|
537
|
+
item: (message) => console.log(pc.green(` ${message}`)),
|
|
538
|
+
itemAdd: (message) => console.log(` ${pc.green("+")} ${message}`),
|
|
539
|
+
plain: (message) => console.log(message),
|
|
540
|
+
blank: () => console.log(""),
|
|
541
|
+
box
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// src/utils/prompts.ts
|
|
545
|
+
import pc2 from "picocolors";
|
|
546
|
+
import { checkbox } from "@inquirer/prompts";
|
|
547
|
+
import readline from "readline";
|
|
548
|
+
async function checkboxWithHover(config, options) {
|
|
549
|
+
const choices = config.choices.filter(
|
|
550
|
+
(c) => typeof c === "object" && c !== null && !("type" in c && c.type === "separator")
|
|
551
|
+
);
|
|
552
|
+
const values = choices.map((c) => c.value);
|
|
553
|
+
const totalItems = values.length;
|
|
554
|
+
let cursorPosition = choices.findIndex((c) => !c.disabled);
|
|
555
|
+
if (cursorPosition < 0) cursorPosition = 0;
|
|
556
|
+
const getName = options?.getName ?? ((v) => v.name);
|
|
557
|
+
const keypressHandler = (_str, key) => {
|
|
558
|
+
if (key.name === "up") {
|
|
559
|
+
let next = cursorPosition - 1;
|
|
560
|
+
while (next >= 0 && choices[next].disabled) next--;
|
|
561
|
+
if (next >= 0) cursorPosition = next;
|
|
562
|
+
} else if (key.name === "down") {
|
|
563
|
+
let next = cursorPosition + 1;
|
|
564
|
+
while (next < totalItems && choices[next].disabled) next++;
|
|
565
|
+
if (next < totalItems) cursorPosition = next;
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
readline.emitKeypressEvents(process.stdin);
|
|
569
|
+
process.stdin.on("keypress", keypressHandler);
|
|
570
|
+
const customConfig = {
|
|
571
|
+
...config,
|
|
572
|
+
theme: {
|
|
573
|
+
...config.theme,
|
|
574
|
+
style: {
|
|
575
|
+
answer: (text) => pc2.green(text),
|
|
576
|
+
...config.theme?.style,
|
|
577
|
+
highlight: (text) => pc2.green(text),
|
|
578
|
+
renderSelectedChoices: (selected, _allChoices) => {
|
|
579
|
+
if (selected.length === 0) {
|
|
580
|
+
return pc2.dim(getName(values[cursorPosition]));
|
|
581
|
+
}
|
|
582
|
+
return selected.map((c) => getName(c.value)).join(", ");
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
try {
|
|
588
|
+
const selected = await checkbox(customConfig);
|
|
589
|
+
if (selected.length === 0) {
|
|
590
|
+
return [values[cursorPosition]];
|
|
591
|
+
}
|
|
592
|
+
return selected;
|
|
593
|
+
} finally {
|
|
594
|
+
process.stdin.removeListener("keypress", keypressHandler);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// src/utils/tracking.ts
|
|
599
|
+
function trackEvent(_event, _data) {
|
|
600
|
+
}
|
|
601
|
+
|
|
471
602
|
// src/commands/setup.ts
|
|
472
603
|
var CHECKBOX_THEME = {
|
|
473
604
|
style: {
|
|
@@ -475,6 +606,10 @@ var CHECKBOX_THEME = {
|
|
|
475
606
|
disabledChoice: (text) => ` ${pc3.dim("-")} ${pc3.dim(text)}`
|
|
476
607
|
}
|
|
477
608
|
};
|
|
609
|
+
var SKILL_NAMES = {
|
|
610
|
+
cli: "genrtl-cli",
|
|
611
|
+
mcp: "genrtl-mcp"
|
|
612
|
+
};
|
|
478
613
|
function getSelectedAgents(options) {
|
|
479
614
|
const agents2 = [];
|
|
480
615
|
if (options.claude) agents2.push("claude");
|
|
@@ -486,20 +621,57 @@ function getSelectedAgents(options) {
|
|
|
486
621
|
return agents2;
|
|
487
622
|
}
|
|
488
623
|
function resolveSetupAuth(options) {
|
|
489
|
-
if (options.apiKey) return { apiKey: options.apiKey };
|
|
624
|
+
if (options.apiKey) return { mode: "api-key", apiKey: options.apiKey };
|
|
490
625
|
if (process.env.GRTL_API_KEY) {
|
|
491
|
-
return {
|
|
626
|
+
return {
|
|
627
|
+
mode: "api-key",
|
|
628
|
+
apiKey: process.env.GRTL_API_KEY,
|
|
629
|
+
apiKeyEnvVar: "GRTL_API_KEY"
|
|
630
|
+
};
|
|
492
631
|
}
|
|
493
632
|
if (process.env.GENRTL_API_KEY) {
|
|
494
|
-
return {
|
|
633
|
+
return {
|
|
634
|
+
mode: "api-key",
|
|
635
|
+
apiKey: process.env.GENRTL_API_KEY,
|
|
636
|
+
apiKeyEnvVar: "GENRTL_API_KEY"
|
|
637
|
+
};
|
|
495
638
|
}
|
|
496
639
|
return void 0;
|
|
497
640
|
}
|
|
498
641
|
function registerSetupCommand(program2) {
|
|
499
|
-
program2.command("setup").description("
|
|
642
|
+
program2.command("setup").description("Install GenRTL CLI or MCP integration for a coding agent").option("--cli", "Install a Skill that calls the grtl CLI").option("--mcp", "Configure GenRTL MCP and install its Skill").option("--claude", "Set up Claude Code").option("--cursor", "Set up Cursor").option("--antigravity", "Set up Antigravity").option("--opencode", "Set up OpenCode").option("--codex", "Set up Codex").option("--gemini", "Set up Gemini CLI").option("-p, --project", "Configure the current project instead of the user profile").option("-y, --yes", "Use MCP mode and all detected agents without prompting").option("--api-key <key>", "GenRTL API key for MCP mode").action(async (options) => {
|
|
500
643
|
await setupCommand(options);
|
|
501
644
|
});
|
|
502
645
|
}
|
|
646
|
+
async function resolveMode(options) {
|
|
647
|
+
if (options.cli && options.mcp) {
|
|
648
|
+
log.error("Choose either --cli or --mcp, not both.");
|
|
649
|
+
process.exitCode = 1;
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
if (options.cli) return "cli";
|
|
653
|
+
if (options.mcp || options.yes) return "mcp";
|
|
654
|
+
try {
|
|
655
|
+
return await select({
|
|
656
|
+
message: "How should your coding agent access GenRTL?",
|
|
657
|
+
choices: [
|
|
658
|
+
{
|
|
659
|
+
name: "CLI Skill",
|
|
660
|
+
value: "cli",
|
|
661
|
+
description: "The agent runs the installed grtl command."
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
name: "MCP Server + Skill",
|
|
665
|
+
value: "mcp",
|
|
666
|
+
description: "The agent calls the four hosted GenRTL MCP tools."
|
|
667
|
+
}
|
|
668
|
+
]
|
|
669
|
+
});
|
|
670
|
+
} catch {
|
|
671
|
+
log.warn("Setup cancelled");
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
503
675
|
async function promptAgents() {
|
|
504
676
|
try {
|
|
505
677
|
return await checkboxWithHover(
|
|
@@ -524,7 +696,7 @@ async function resolveAgents(options, scope) {
|
|
|
524
696
|
const detected = await detectAgents(scope);
|
|
525
697
|
if (detected.length > 0 && options.yes) return detected;
|
|
526
698
|
if (options.yes) {
|
|
527
|
-
log.error("No supported coding agents were detected. Pass an agent flag such as --
|
|
699
|
+
log.error("No supported coding agents were detected. Pass an agent flag such as --codex.");
|
|
528
700
|
return [];
|
|
529
701
|
}
|
|
530
702
|
log.blank();
|
|
@@ -532,14 +704,14 @@ async function resolveAgents(options, scope) {
|
|
|
532
704
|
if (!selected) log.warn("Setup cancelled");
|
|
533
705
|
return selected ?? [];
|
|
534
706
|
}
|
|
535
|
-
async function installRule(agentName, scope) {
|
|
707
|
+
async function installRule(agentName, scope, mode) {
|
|
536
708
|
const rule = getAgent(agentName).rule;
|
|
537
|
-
const content = await getRuleContent(
|
|
709
|
+
const content = await getRuleContent(mode, agentName);
|
|
538
710
|
if (rule.kind === "file") {
|
|
539
711
|
const ruleDir = scope === "global" ? rule.dir("global") : join2(process.cwd(), rule.dir("project"));
|
|
540
712
|
const rulePath = join2(ruleDir, rule.filename);
|
|
541
|
-
await
|
|
542
|
-
await
|
|
713
|
+
await mkdir3(dirname4(rulePath), { recursive: true });
|
|
714
|
+
await writeFile3(rulePath, content, "utf-8");
|
|
543
715
|
return { status: "installed", path: rulePath };
|
|
544
716
|
}
|
|
545
717
|
const filePath = scope === "global" ? rule.file("global") : join2(process.cwd(), rule.file("project"));
|
|
@@ -554,69 +726,108 @@ ${rule.sectionMarker}`;
|
|
|
554
726
|
}
|
|
555
727
|
if (existing.includes(rule.sectionMarker)) {
|
|
556
728
|
const regex = new RegExp(`${escapedMarker}\\n[\\s\\S]*?${escapedMarker}`);
|
|
557
|
-
await
|
|
729
|
+
await writeFile3(filePath, existing.replace(regex, section), "utf-8");
|
|
558
730
|
return { status: "updated", path: filePath };
|
|
559
731
|
}
|
|
560
732
|
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
|
|
561
|
-
await
|
|
562
|
-
await
|
|
733
|
+
await mkdir3(dirname4(filePath), { recursive: true });
|
|
734
|
+
await writeFile3(filePath, `${existing}${separator}${section}
|
|
563
735
|
`, "utf-8");
|
|
564
736
|
return { status: "installed", path: filePath };
|
|
565
737
|
}
|
|
566
|
-
async function
|
|
738
|
+
async function installSkill(agentName, scope, mode) {
|
|
739
|
+
const agent = getAgent(agentName);
|
|
740
|
+
const skillName = SKILL_NAMES[mode];
|
|
741
|
+
const skillsRoot = scope === "global" ? agent.skill.dir("global") : join2(process.cwd(), agent.skill.dir("project"));
|
|
742
|
+
const skillPath = join2(skillsRoot, skillName);
|
|
743
|
+
await installSkillFiles(
|
|
744
|
+
skillName,
|
|
745
|
+
[{ path: "SKILL.md", content: getSkillContent(mode) }],
|
|
746
|
+
skillsRoot
|
|
747
|
+
);
|
|
748
|
+
return { status: "installed", path: skillPath };
|
|
749
|
+
}
|
|
750
|
+
async function configureMcp(agentName, auth, scope) {
|
|
567
751
|
const agent = getAgent(agentName);
|
|
568
752
|
const candidates = scope === "global" || agent.mcp.projectPaths.length === 0 ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((path) => join2(process.cwd(), path));
|
|
569
753
|
const mcpPath = await resolveMcpPath(candidates);
|
|
570
|
-
const entry = agent.mcp.buildEntry(auth);
|
|
754
|
+
const entry = agent.mcp.buildEntry(auth, "http");
|
|
755
|
+
if (mcpPath.endsWith(".toml")) {
|
|
756
|
+
const { alreadyExists: alreadyExists2 } = await appendTomlServer(mcpPath, "genrtl", entry);
|
|
757
|
+
return { status: alreadyExists2 ? "reconfigured" : "configured", path: mcpPath };
|
|
758
|
+
}
|
|
759
|
+
const existing = await readJsonConfig(mcpPath);
|
|
760
|
+
const { config, alreadyExists } = mergeServerEntry(
|
|
761
|
+
existing,
|
|
762
|
+
agent.mcp.configKey,
|
|
763
|
+
"genrtl",
|
|
764
|
+
entry
|
|
765
|
+
);
|
|
766
|
+
await writeJsonConfig(mcpPath, config);
|
|
767
|
+
return { status: alreadyExists ? "reconfigured" : "configured", path: mcpPath };
|
|
768
|
+
}
|
|
769
|
+
async function setupAgent(agentName, mode, auth, scope) {
|
|
770
|
+
const agent = getAgent(agentName);
|
|
571
771
|
let mcpStatus;
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
agent.mcp.configKey,
|
|
581
|
-
"genrtl",
|
|
582
|
-
entry
|
|
583
|
-
);
|
|
584
|
-
await writeJsonConfig(mcpPath, config);
|
|
585
|
-
mcpStatus = alreadyExists ? "reconfigured" : "configured";
|
|
772
|
+
let mcpPath;
|
|
773
|
+
if (mode === "mcp" && auth) {
|
|
774
|
+
try {
|
|
775
|
+
const result = await configureMcp(agentName, auth, scope);
|
|
776
|
+
mcpStatus = result.status;
|
|
777
|
+
mcpPath = result.path;
|
|
778
|
+
} catch (error) {
|
|
779
|
+
mcpStatus = `failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
586
780
|
}
|
|
587
|
-
} catch (error) {
|
|
588
|
-
mcpStatus = `failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
589
781
|
}
|
|
590
782
|
let ruleStatus;
|
|
591
783
|
let rulePath = "";
|
|
592
784
|
try {
|
|
593
|
-
const result = await installRule(agentName, scope);
|
|
785
|
+
const result = await installRule(agentName, scope, mode);
|
|
594
786
|
ruleStatus = result.status;
|
|
595
787
|
rulePath = result.path;
|
|
596
788
|
} catch (error) {
|
|
597
789
|
ruleStatus = `failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
598
790
|
}
|
|
599
|
-
|
|
791
|
+
let skillStatus;
|
|
792
|
+
let skillPath = "";
|
|
793
|
+
try {
|
|
794
|
+
const result = await installSkill(agentName, scope, mode);
|
|
795
|
+
skillStatus = result.status;
|
|
796
|
+
skillPath = result.path;
|
|
797
|
+
} catch (error) {
|
|
798
|
+
skillStatus = `failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
799
|
+
}
|
|
800
|
+
return {
|
|
801
|
+
agent: agent.displayName,
|
|
802
|
+
mcpStatus,
|
|
803
|
+
mcpPath,
|
|
804
|
+
ruleStatus,
|
|
805
|
+
rulePath,
|
|
806
|
+
skillStatus,
|
|
807
|
+
skillPath
|
|
808
|
+
};
|
|
600
809
|
}
|
|
601
810
|
async function setupCommand(options) {
|
|
602
811
|
trackEvent("command", { name: "setup" });
|
|
812
|
+
const mode = await resolveMode(options);
|
|
813
|
+
if (!mode) return;
|
|
603
814
|
const auth = resolveSetupAuth(options);
|
|
604
|
-
if (!auth) {
|
|
605
|
-
log.error("
|
|
815
|
+
if (mode === "mcp" && !auth) {
|
|
816
|
+
log.error("MCP mode requires GRTL_API_KEY, GENRTL_API_KEY, or --api-key.");
|
|
606
817
|
process.exitCode = 1;
|
|
607
818
|
return;
|
|
608
819
|
}
|
|
609
820
|
const scope = options.project ? "project" : "global";
|
|
610
821
|
const agents2 = await resolveAgents(options, scope);
|
|
611
822
|
if (agents2.length === 0) return;
|
|
612
|
-
const spinner = ora(
|
|
823
|
+
const spinner = ora(`Installing GenRTL ${mode.toUpperCase()} integration...`).start();
|
|
613
824
|
const results = [];
|
|
614
825
|
for (const agentName of agents2) {
|
|
615
826
|
spinner.text = `Configuring ${getAgent(agentName).displayName}...`;
|
|
616
|
-
results.push(await setupAgent(agentName, auth, scope));
|
|
827
|
+
results.push(await setupAgent(agentName, mode, auth, scope));
|
|
617
828
|
}
|
|
618
829
|
const failed = results.some(
|
|
619
|
-
(result) => result.mcpStatus
|
|
830
|
+
(result) => result.mcpStatus?.startsWith("failed") || result.ruleStatus.startsWith("failed") || result.skillStatus.startsWith("failed")
|
|
620
831
|
);
|
|
621
832
|
if (failed) {
|
|
622
833
|
spinner.warn("GenRTL setup completed with errors");
|
|
@@ -627,13 +838,20 @@ async function setupCommand(options) {
|
|
|
627
838
|
log.blank();
|
|
628
839
|
for (const result of results) {
|
|
629
840
|
log.plain(` ${pc3.bold(result.agent)}`);
|
|
630
|
-
|
|
631
|
-
|
|
841
|
+
if (result.mcpStatus && result.mcpPath) {
|
|
842
|
+
log.plain(` ${pc3.green("+")} MCP server ${result.mcpStatus}`);
|
|
843
|
+
log.plain(` ${pc3.dim(result.mcpPath)}`);
|
|
844
|
+
}
|
|
845
|
+
log.plain(` ${pc3.green("+")} Skill ${result.skillStatus}`);
|
|
846
|
+
log.plain(` ${pc3.dim(result.skillPath)}`);
|
|
632
847
|
log.plain(` ${pc3.green("+")} Rule ${result.ruleStatus}`);
|
|
633
848
|
log.plain(` ${pc3.dim(result.rulePath)}`);
|
|
634
849
|
}
|
|
635
850
|
log.blank();
|
|
636
|
-
|
|
851
|
+
if (mode === "cli" && !process.env.GRTL_API_KEY && !process.env.GENRTL_API_KEY) {
|
|
852
|
+
log.warn("Set GRTL_API_KEY or GENRTL_API_KEY in the coding agent's environment before use.");
|
|
853
|
+
}
|
|
854
|
+
trackEvent("setup", { agents: agents2, scope, mode, authMode: auth?.mode ?? "environment" });
|
|
637
855
|
}
|
|
638
856
|
|
|
639
857
|
// src/commands/knowledge.ts
|
|
@@ -644,8 +862,8 @@ import ora2 from "ora";
|
|
|
644
862
|
// src/constants.ts
|
|
645
863
|
import { readFileSync } from "fs";
|
|
646
864
|
import { fileURLToPath } from "url";
|
|
647
|
-
import { dirname as
|
|
648
|
-
var __dirname =
|
|
865
|
+
import { dirname as dirname5, join as join3 } from "path";
|
|
866
|
+
var __dirname = dirname5(fileURLToPath(import.meta.url));
|
|
649
867
|
var pkg = JSON.parse(readFileSync(join3(__dirname, "../package.json"), "utf-8"));
|
|
650
868
|
var VERSION = pkg.version;
|
|
651
869
|
var NAME = pkg.name;
|
|
@@ -737,6 +955,11 @@ var TOOL_COMMANDS = [
|
|
|
737
955
|
alias: "spec2rtl-search",
|
|
738
956
|
description: "Search Spec2RTL knowledge cards"
|
|
739
957
|
},
|
|
958
|
+
{
|
|
959
|
+
name: "genrtl_spec2plan_search",
|
|
960
|
+
alias: "spec2plan-search",
|
|
961
|
+
description: "Search Spec2Plan knowledge cards"
|
|
962
|
+
},
|
|
740
963
|
{
|
|
741
964
|
name: "genrtl_verification_search",
|
|
742
965
|
alias: "verification-search",
|
|
@@ -774,7 +997,7 @@ function buildKnowledgeSearchInput(query, options) {
|
|
|
774
997
|
}
|
|
775
998
|
const filters = {};
|
|
776
999
|
if (options.type?.length) {
|
|
777
|
-
const allowed = /* @__PURE__ */ new Set(["spec2rtl", "verification", "debug"]);
|
|
1000
|
+
const allowed = /* @__PURE__ */ new Set(["spec2rtl", "spec2plan", "verification", "debug"]);
|
|
778
1001
|
const invalid = options.type.find((type) => !allowed.has(type));
|
|
779
1002
|
if (invalid) {
|
|
780
1003
|
throw new InvalidArgumentError(`Invalid knowledge type: ${invalid}`);
|
|
@@ -851,7 +1074,7 @@ function printKnowledgeResult(result) {
|
|
|
851
1074
|
}
|
|
852
1075
|
}
|
|
853
1076
|
function addSearchOptions(command) {
|
|
854
|
-
return command.argument("<query>", "Natural-language RTL engineering question or diagnostic").option("--type <type...>", "Knowledge types: spec2rtl, verification, debug").option("--domain <domain>", "Filter by engineering domain").option("--tool <tool>", "Filter by EDA tool").option("--tool-version <version>", "Filter by EDA tool version").option("--error-type <type>", "Filter by error type").option("--severity <severity>", "Filter by severity").option("--interface <interface>", "Filter by hardware interface").option("--target <target>", "Filter by target: fpga, asic, or both").option("--tag <tag...>", "Filter by one or more tags").option("--top-k <count>", "Maximum results (1-20)", parseInteger).option("--min-score <score>", "Minimum similarity score (0-1)", parseNumber).option("--workspace-id <id>", "GenRTL workspace ID").option("--json", "Output the structured MCP result as JSON");
|
|
1077
|
+
return command.argument("<query>", "Natural-language RTL engineering question or diagnostic").option("--type <type...>", "Knowledge types: spec2rtl, spec2plan, verification, debug").option("--domain <domain>", "Filter by engineering domain").option("--tool <tool>", "Filter by EDA tool").option("--tool-version <version>", "Filter by EDA tool version").option("--error-type <type>", "Filter by error type").option("--severity <severity>", "Filter by severity").option("--interface <interface>", "Filter by hardware interface").option("--target <target>", "Filter by target: fpga, asic, or both").option("--tag <tag...>", "Filter by one or more tags").option("--top-k <count>", "Maximum results (1-20)", parseInteger).option("--min-score <score>", "Minimum similarity score (0-1)", parseNumber).option("--workspace-id <id>", "GenRTL workspace ID").option("--json", "Output the structured MCP result as JSON");
|
|
855
1078
|
}
|
|
856
1079
|
function registerKnowledgeCommands(program2) {
|
|
857
1080
|
for (const tool of TOOL_COMMANDS) {
|
|
@@ -869,8 +1092,8 @@ import pc5 from "picocolors";
|
|
|
869
1092
|
|
|
870
1093
|
// src/utils/update-check.ts
|
|
871
1094
|
import { homedir as homedir2 } from "os";
|
|
872
|
-
import { dirname as
|
|
873
|
-
import { mkdir as
|
|
1095
|
+
import { dirname as dirname6, join as join4 } from "path";
|
|
1096
|
+
import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
874
1097
|
var DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
875
1098
|
var UPDATE_STATE_FILE = join4(homedir2(), ".genrtl", "cli-state.json");
|
|
876
1099
|
function getStateFilePath(stateFile) {
|
|
@@ -886,8 +1109,8 @@ async function readUpdateState(stateFile) {
|
|
|
886
1109
|
}
|
|
887
1110
|
async function writeUpdateState(state, stateFile) {
|
|
888
1111
|
const path = getStateFilePath(stateFile);
|
|
889
|
-
await
|
|
890
|
-
await
|
|
1112
|
+
await mkdir4(dirname6(path), { recursive: true });
|
|
1113
|
+
await writeFile4(path, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
891
1114
|
}
|
|
892
1115
|
function compareVersions(a, b) {
|
|
893
1116
|
const normalize = (version) => version.split("-", 1)[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -1063,13 +1286,13 @@ function registerUpgradeCommand(program2) {
|
|
|
1063
1286
|
});
|
|
1064
1287
|
}
|
|
1065
1288
|
function runCommand(command, args) {
|
|
1066
|
-
return new Promise((
|
|
1289
|
+
return new Promise((resolve3, reject) => {
|
|
1067
1290
|
const child = spawn(command, args, {
|
|
1068
1291
|
stdio: "inherit",
|
|
1069
1292
|
shell: process.platform === "win32"
|
|
1070
1293
|
});
|
|
1071
1294
|
child.on("error", reject);
|
|
1072
|
-
child.on("close", (code) =>
|
|
1295
|
+
child.on("close", (code) => resolve3(code));
|
|
1073
1296
|
});
|
|
1074
1297
|
}
|
|
1075
1298
|
async function runUpgradePlan(plan) {
|
|
@@ -1206,12 +1429,13 @@ program.name("grtl").description("GenRTL CLI - Search RTL engineering knowledge
|
|
|
1206
1429
|
`
|
|
1207
1430
|
Examples:
|
|
1208
1431
|
${brand.dim("# Configure GenRTL for your coding agent")}
|
|
1209
|
-
${brand.primary("
|
|
1210
|
-
${brand.primary("GRTL_API_KEY=your_key npx @genrtl/grtl setup --codex --project")}
|
|
1432
|
+
${brand.primary("npx @genrtl/grtl setup --cli --codex --project")}
|
|
1433
|
+
${brand.primary("GRTL_API_KEY=your_key npx @genrtl/grtl setup --mcp --codex --project")}
|
|
1211
1434
|
|
|
1212
|
-
${brand.dim("# Search the same
|
|
1435
|
+
${brand.dim("# Search the same five tools exposed by the GenRTL MCP server")}
|
|
1213
1436
|
${brand.primary('npx @genrtl/grtl knowledge-search "AXI stream backpressure design"')}
|
|
1214
1437
|
${brand.primary('npx @genrtl/grtl spec2rtl-search "Generate an APB register block"')}
|
|
1438
|
+
${brand.primary('npx @genrtl/grtl spec2plan-search "Plan an APB register block implementation"')}
|
|
1215
1439
|
${brand.primary('npx @genrtl/grtl verification-search "Verify an async FIFO"')}
|
|
1216
1440
|
${brand.primary('npx @genrtl/grtl debug-search "Explain this Vivado CDC warning"')}
|
|
1217
1441
|
`
|
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
2
|
"name": "@genrtl/grtl",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI for GenRTL RTL engineering knowledge
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "CLI, MCP, and coding-agent Skills for GenRTL RTL engineering knowledge",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"grtl": "dist/index.js"
|
|
8
8
|
},
|
|
9
|
-
"files": [
|
|
10
|
-
"dist"
|
|
11
|
-
],
|
|
12
|
-
"scripts": {
|
|
13
|
-
"build": "tsup",
|
|
14
|
-
"dev": "tsup --watch",
|
|
15
|
-
"typecheck": "tsc --noEmit",
|
|
16
|
-
"lint": "eslint src --fix",
|
|
17
|
-
"lint:check": "eslint src",
|
|
18
|
-
"format": "prettier --write src",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"dev": "tsup --watch",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"lint": "eslint src --fix",
|
|
17
|
+
"lint:check": "eslint src",
|
|
18
|
+
"format": "prettier --write src",
|
|
19
19
|
"format:check": "prettier --check src",
|
|
20
20
|
"clean": "rm -rf dist node_modules",
|
|
21
21
|
"test": "vitest run",
|
|
@@ -29,24 +29,24 @@
|
|
|
29
29
|
"figlet": "^1.9.4",
|
|
30
30
|
"ora": "^9.0.0",
|
|
31
31
|
"picocolors": "^1.1.1"
|
|
32
|
-
},
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@types/figlet": "^1.7.0",
|
|
35
|
-
"@types/node": "^22.19.1",
|
|
36
|
-
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
|
37
|
-
"@typescript-eslint/parser": "^8.28.0",
|
|
38
|
-
"eslint": "^9.34.0",
|
|
39
|
-
"eslint-plugin-prettier": "^5.2.5",
|
|
40
|
-
"prettier": "^3.6.2",
|
|
41
|
-
"tsup": "^8.5.0",
|
|
42
|
-
"typescript": "^5.8.2",
|
|
43
|
-
"typescript-eslint": "^8.28.0",
|
|
44
|
-
"vitest": "^4.0.13"
|
|
45
|
-
},
|
|
46
|
-
"keywords": [
|
|
47
|
-
"genrtl",
|
|
48
|
-
"cli",
|
|
49
|
-
"ai",
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/figlet": "^1.7.0",
|
|
35
|
+
"@types/node": "^22.19.1",
|
|
36
|
+
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
|
37
|
+
"@typescript-eslint/parser": "^8.28.0",
|
|
38
|
+
"eslint": "^9.34.0",
|
|
39
|
+
"eslint-plugin-prettier": "^5.2.5",
|
|
40
|
+
"prettier": "^3.6.2",
|
|
41
|
+
"tsup": "^8.5.0",
|
|
42
|
+
"typescript": "^5.8.2",
|
|
43
|
+
"typescript-eslint": "^8.28.0",
|
|
44
|
+
"vitest": "^4.0.13"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"genrtl",
|
|
48
|
+
"cli",
|
|
49
|
+
"ai",
|
|
50
50
|
"rtl",
|
|
51
51
|
"systemverilog",
|
|
52
52
|
"verilog",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"mcp"
|
|
55
55
|
],
|
|
56
56
|
"author": "xroting",
|
|
57
|
-
"license": "MIT",
|
|
58
|
-
"repository": {
|
|
59
|
-
"type": "git",
|
|
57
|
+
"license": "MIT",
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
60
|
"url": "git+https://github.com/xroting/grtl.git",
|
|
61
61
|
"directory": "packages/cli"
|
|
62
62
|
},
|