@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.
Files changed (3) hide show
  1. package/README.md +70 -106
  2. package/dist/index.js +456 -232
  3. package/package.json +34 -34
package/README.md CHANGED
@@ -1,106 +1,70 @@
1
- # grtl
2
-
3
- Command-line client for the GenRTL RTL engineering knowledge service.
4
-
5
- ## Requirements
6
-
7
- - Node.js 20.12 or newer
8
- - A GenRTL API key
9
-
10
- ## Installation
11
-
12
- ```bash
13
- npm install --global @genrtl/grtl
14
- ```
15
-
16
- Set the API key in your environment:
17
-
18
- ```bash
19
- export GRTL_API_KEY="gtr_live_your_api_key"
20
- ```
21
-
22
- PowerShell:
23
-
24
- ```powershell
25
- $env:GRTL_API_KEY = "gtr_live_your_api_key"
26
- ```
27
-
28
- `GENRTL_API_KEY` is also supported.
29
-
30
- ## Knowledge Tools
31
-
32
- The CLI exposes the same four tools as the hosted GenRTL MCP server:
33
-
34
- ```bash
35
- grtl genrtl_knowledge_search "How should this AXI stream handle backpressure?"
36
- grtl genrtl_spec2rtl_search "Implement an APB register block with byte enables"
37
- grtl genrtl_verification_search "Build a self-checking testbench for an async FIFO"
38
- grtl genrtl_debug_search "Explain this Vivado CDC warning and suggest a safe fix"
39
- ```
40
-
41
- Short aliases are available:
42
-
43
- ```bash
44
- grtl knowledge-search "..."
45
- grtl spec2rtl-search "..."
46
- grtl verification-search "..."
47
- grtl debug-search "..."
48
- ```
49
-
50
- Search options include `--type`, `--domain`, `--tool`, `--tool-version`,
51
- `--error-type`, `--severity`, `--interface`, `--target`, `--tag`, `--top-k`,
52
- `--min-score`, `--workspace-id`, and `--json`.
53
-
54
- Example:
55
-
56
- ```bash
57
- grtl debug-search "FSM hangs after reset" \
58
- --tool Vivado \
59
- --target fpga \
60
- --tag reset fsm \
61
- --top-k 8 \
62
- --json
63
- ```
64
-
65
- ## Agent Setup
66
-
67
- Configure the hosted HTTP MCP endpoint for a supported coding agent:
68
-
69
- ```bash
70
- grtl setup --cursor
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 pc3 from "picocolors";
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 { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
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/http-agents.ts
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 headers(auth) {
123
- return { Authorization: `Bearer ${auth.apiKey}` };
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
- return process.env.CLAUDE_CONFIG_DIR ? join(claudeConfigDir(), ".claude.json") : join(homedir(), ".claude.json");
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) => ({ url: mcpBaseUrl, headers: headers(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) => auth.apiKeyEnvVar ? {
215
- type: "http",
216
- url: mcpBaseUrl,
217
- bearer_token_env_var: auth.apiKeyEnvVar
218
- } : {
219
- type: "http",
220
- url: mcpBaseUrl,
221
- headers: headers(auth)
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", "antigravity", "mcp_config.json")],
182
+ globalPaths: [join(homedir(), ".gemini", "config", "mcp_config.json")],
240
183
  configKey: "mcpServers",
241
- buildEntry: (auth) => ({ serverUrl: mcpBaseUrl, headers: headers(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) => ({ httpUrl: mcpBaseUrl, headers: headers(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(path) {
228
+ async function pathExists(p) {
278
229
  try {
279
- await access(path);
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 path of paths) {
290
- const fullPath = scope === "global" ? path : join(process.cwd(), path);
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 headers2 = entry.headers;
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 (headers2 && Object.keys(headers2).length > 0) {
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(headers2)) {
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 { apiKey: process.env.GRTL_API_KEY, apiKeyEnvVar: "GRTL_API_KEY" };
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 { apiKey: process.env.GENRTL_API_KEY, apiKeyEnvVar: "GENRTL_API_KEY" };
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("Configure the hosted GenRTL HTTP MCP server for a coding agent").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 all detected agents without prompting").option("--api-key <key>", "GenRTL API key (prefer the GRTL_API_KEY environment variable)").action(async (options) => {
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 --cursor.");
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("mcp", agentName);
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 mkdir2(dirname2(rulePath), { recursive: true });
542
- await writeFile2(rulePath, content, "utf-8");
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 writeFile2(filePath, existing.replace(regex, section), "utf-8");
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 mkdir2(dirname2(filePath), { recursive: true });
562
- await writeFile2(filePath, `${existing}${separator}${section}
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 setupAgent(agentName, auth, scope) {
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
- try {
573
- if (mcpPath.endsWith(".toml")) {
574
- const { alreadyExists } = await appendTomlServer(mcpPath, "genrtl", entry);
575
- mcpStatus = alreadyExists ? "reconfigured" : "configured";
576
- } else {
577
- const existing = await readJsonConfig(mcpPath);
578
- const { config, alreadyExists } = mergeServerEntry(
579
- existing,
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
- return { agent: agent.displayName, mcpStatus, mcpPath, ruleStatus, rulePath };
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("Set GRTL_API_KEY or pass --api-key before running setup.");
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("Configuring GenRTL MCP...").start();
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.startsWith("failed") || result.ruleStatus.startsWith("failed")
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
- log.plain(` ${pc3.green("+")} MCP server ${result.mcpStatus}`);
631
- log.plain(` ${pc3.dim(result.mcpPath)}`);
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
- trackEvent("setup", { agents: agents2, scope, authMode: "api-key" });
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 dirname3, join as join3 } from "path";
648
- var __dirname = dirname3(fileURLToPath(import.meta.url));
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 dirname4, join as join4 } from "path";
873
- import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
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 mkdir3(dirname4(path), { recursive: true });
890
- await writeFile3(path, JSON.stringify(state, null, 2) + "\n", "utf-8");
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((resolve, reject) => {
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) => resolve(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("GRTL_API_KEY=your_key npx @genrtl/grtl setup --cursor")}
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 four tools exposed by the GenRTL MCP server")}
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.1.1",
4
- "description": "CLI for GenRTL RTL engineering knowledge and MCP setup",
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
  },