create-restforge-skills 0.1.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 +137 -0
- package/cli/index.js +235 -0
- package/cli/mcp.js +94 -0
- package/package.json +30 -0
- package/skills/restforge/SKILL.md +421 -0
- package/skills/restforge/references/config-schema.md +173 -0
- package/skills/restforge/references/dbschema-catalog.md +238 -0
- package/skills/restforge/references/design-to-sdf.md +618 -0
- package/skills/restforge/references/field-validation.md +173 -0
- package/skills/restforge/references/rdf-advanced.md +488 -0
- package/skills/restforge/references/udf-catalog.md +489 -0
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# RESTForge — Agent Skills (portable)
|
|
2
|
+
|
|
3
|
+
Vendor-neutral [Agent Skills](https://agentskills.io) for RESTForge. The same
|
|
4
|
+
end-to-end RESTForge workflow knowledge, packaged in the open `SKILL.md` format
|
|
5
|
+
so it works across any skills-compatible agent — **Claude Code, Cursor, Gemini
|
|
6
|
+
CLI, and OpenAI Codex** — not only Claude Code.
|
|
7
|
+
|
|
8
|
+
## What this package is (and is not)
|
|
9
|
+
|
|
10
|
+
This package is the **portable** distribution of RESTForge agent knowledge.
|
|
11
|
+
|
|
12
|
+
- It contains pure Agent Skills: a `skills/restforge/SKILL.md` plus `references/`.
|
|
13
|
+
- It has **no** client-specific packaging — no Claude Code `plugin.json`,
|
|
14
|
+
`marketplace.json`, or `.mcp.json`.
|
|
15
|
+
|
|
16
|
+
It is a **complement to**, not a replacement for, [`restforge-plugins`](../packages/restforge-plugins).
|
|
17
|
+
|
|
18
|
+
| | `restforge-plugins` | `restforge-skills` (this package) |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| Target | Claude Code only | Claude Code, Cursor, Gemini CLI, Codex |
|
|
21
|
+
| Distribution | Claude Code plugin + marketplace | Copy/symlink the skill folder into each client's skills directory |
|
|
22
|
+
| MCP registration | Bundled (`.mcp.json`) | Done per client by the user (see below) |
|
|
23
|
+
| Knowledge source | `skills/restforge-skills/` | Same knowledge, portable form |
|
|
24
|
+
|
|
25
|
+
Both packages wrap the **same** workflow knowledge and depend on the **same**
|
|
26
|
+
execution engine: the `@restforgejs/mcp-server` MCP server. A skill describes
|
|
27
|
+
*how* to drive RESTForge; the MCP server is *what* actually executes the
|
|
28
|
+
operations. The skill is useless without the MCP server registered in the client.
|
|
29
|
+
|
|
30
|
+
## Directory layout
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
restforge-skills/
|
|
34
|
+
├── README.md ← this file
|
|
35
|
+
├── package.json ← npm package: create-restforge-skills
|
|
36
|
+
├── bump-and-publish.bat ← version bump + npm publish
|
|
37
|
+
├── cli/
|
|
38
|
+
│ └── index.js ← installer that copies the skill into each client
|
|
39
|
+
├── docs/ ← internal docs, NOT published (excluded from npm files)
|
|
40
|
+
└── skills/
|
|
41
|
+
└── restforge/
|
|
42
|
+
├── SKILL.md ← the portable skill (name + description + workflow)
|
|
43
|
+
└── references/ ← progressive-disclosure reference material
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
One command. It detects your installed clients (Claude Code, Cursor), copies the
|
|
49
|
+
skill into each, and registers the RESTForge MCP server — because the skill is
|
|
50
|
+
inert without it:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx create-restforge-skills
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The MCP server is registered via `npx -y @restforgejs/mcp-server`, so there is no
|
|
57
|
+
separate global install. The MCP config is **merged, not overwritten**, and the
|
|
58
|
+
existing file is backed up to `<config>.bak` first.
|
|
59
|
+
|
|
60
|
+
### Where to run it (working directory)
|
|
61
|
+
|
|
62
|
+
This is an **installer**, not a project scaffolder. Unlike `create-restforge-app`,
|
|
63
|
+
it takes **no positional folder argument** (`myapp` / `.`). The install location
|
|
64
|
+
is chosen by `--scope`, not by where you stand or by a path you pass.
|
|
65
|
+
|
|
66
|
+
- **Default (`--scope=user`) — run it from anywhere.** The current directory is
|
|
67
|
+
ignored. The skill goes to your home (`~/.claude/skills/`, `~/.cursor/skills/`)
|
|
68
|
+
and is available across **all** your projects. This is the normal usage: a skill
|
|
69
|
+
is editor/agent tooling, not part of any one app's source.
|
|
70
|
+
|
|
71
|
+
- **`--scope=project` — run it inside the project folder.** Here the current
|
|
72
|
+
directory matters: the skill and MCP config are written relative to it
|
|
73
|
+
(`./.claude/...`, `./.cursor/...`), so you must `cd` into the target project
|
|
74
|
+
first. Use this to commit the skill with a specific repo so the team gets it on
|
|
75
|
+
clone.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# normal — anywhere, global for every project
|
|
79
|
+
npx create-restforge-skills
|
|
80
|
+
|
|
81
|
+
# attach to one project — cd in first
|
|
82
|
+
cd path/to/my-project
|
|
83
|
+
npx create-restforge-skills --scope=project
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Note: a bare path such as `npx create-restforge-skills .` is **not** recognized as
|
|
87
|
+
"install here" — it is treated as an unknown argument. Use `--scope=project`.
|
|
88
|
+
|
|
89
|
+
### Options
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx create-restforge-skills --client=cursor # one client only
|
|
93
|
+
npx create-restforge-skills --scope=project # into ./.claude or ./.cursor (commit for the team)
|
|
94
|
+
npx create-restforge-skills --mcp-command=global # use the installed restforge-mcp binary instead of npx
|
|
95
|
+
npx create-restforge-skills --no-mcp # skill only; you manage MCP yourself
|
|
96
|
+
npx create-restforge-skills --dry-run # preview, write nothing
|
|
97
|
+
npx create-restforge-skills --force # overwrite an existing skill (update)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
After install, set a valid RESTForge `LICENSE` for `codegen_*`/`runtime_*`
|
|
101
|
+
operations (Designer/frontend tools need no license). On Claude Code, if the
|
|
102
|
+
skills directory was just created, restart the client so it watches the new
|
|
103
|
+
directory.
|
|
104
|
+
|
|
105
|
+
### Where it installs
|
|
106
|
+
|
|
107
|
+
| Client | Skill (user scope) | MCP config |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| Claude Code | `~/.claude/skills/restforge/` | `~/.claude.json` |
|
|
110
|
+
| Cursor | `~/.cursor/skills/restforge/` | `~/.cursor/mcp.json` |
|
|
111
|
+
|
|
112
|
+
> For Claude Code, a turnkey alternative is the `restforge-plugins` package,
|
|
113
|
+
> which bundles the skill **and** MCP registration in one `/plugin install`.
|
|
114
|
+
|
|
115
|
+
### Manual install (no CLI)
|
|
116
|
+
|
|
117
|
+
The skill folder is self-contained — copy `skills/restforge/` (including
|
|
118
|
+
`references/`) into the client's skills directory and add
|
|
119
|
+
`{ "mcpServers": { "restforge": { "command": "npx", "args": ["-y", "@restforgejs/mcp-server"] } } }`
|
|
120
|
+
to the client's MCP config.
|
|
121
|
+
|
|
122
|
+
### Gemini CLI / OpenAI Codex
|
|
123
|
+
|
|
124
|
+
Planned. Both consume the same `skills/restforge/` folder; only the skills
|
|
125
|
+
directory path and MCP config file differ per client.
|
|
126
|
+
|
|
127
|
+
## Source of truth
|
|
128
|
+
|
|
129
|
+
The knowledge in this skill mirrors the RESTForge handbook
|
|
130
|
+
([`restforge-handbook`](../restforge-handbook)) and the MCP tool surface
|
|
131
|
+
([`mcp-server`](../packages/mcp-server)). When RESTForge behavior changes, update the
|
|
132
|
+
handbook first, then this skill — the skill is downstream documentation of real
|
|
133
|
+
tool behavior, never an independent source.
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT.
|
package/cli/index.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* create-restforge-skills
|
|
6
|
+
*
|
|
7
|
+
* Installs the bundled RESTForge Agent Skill (skills/restforge/) into the
|
|
8
|
+
* skills directory of each detected client (Claude Code, Cursor, ...) AND
|
|
9
|
+
* registers the RESTForge MCP server, because the skill is inert without it.
|
|
10
|
+
*
|
|
11
|
+
* The skill is the payload; this CLI is the delivery mechanism. It copies the
|
|
12
|
+
* whole skill folder — SKILL.md AND references/ — so reference links keep
|
|
13
|
+
* working, and merges (never overwrites) the MCP server into each client config.
|
|
14
|
+
*
|
|
15
|
+
* It does NOT set a license; that secret stays a manual step.
|
|
16
|
+
*
|
|
17
|
+
* Zero runtime dependencies: Node built-ins only, for fast `npx` and broad
|
|
18
|
+
* platform support (Windows included).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const os = require('os');
|
|
24
|
+
const { buildEntry, registerMcp } = require('./mcp');
|
|
25
|
+
|
|
26
|
+
const SKILL_NAME = 'restforge';
|
|
27
|
+
// Bundled skill source: <package>/skills/restforge, resolved relative to this file.
|
|
28
|
+
const SOURCE_DIR = path.join(__dirname, '..', 'skills', SKILL_NAME);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Client registry. Each client knows where its skills live (user vs project
|
|
32
|
+
* scope) and where its MCP config file is. Paths are computed lazily so we never
|
|
33
|
+
* touch the filesystem for a client the user did not select.
|
|
34
|
+
*/
|
|
35
|
+
const CLIENTS = {
|
|
36
|
+
claude: {
|
|
37
|
+
label: 'Claude Code',
|
|
38
|
+
homeMarker: () => path.join(os.homedir(), '.claude'),
|
|
39
|
+
skillsDir: (scope, cwd) =>
|
|
40
|
+
scope === 'project'
|
|
41
|
+
? path.join(cwd, '.claude', 'skills')
|
|
42
|
+
: path.join(os.homedir(), '.claude', 'skills'),
|
|
43
|
+
mcpConfig: (scope, cwd) =>
|
|
44
|
+
scope === 'project'
|
|
45
|
+
? path.join(cwd, '.mcp.json')
|
|
46
|
+
: path.join(os.homedir(), '.claude.json'),
|
|
47
|
+
},
|
|
48
|
+
cursor: {
|
|
49
|
+
label: 'Cursor',
|
|
50
|
+
homeMarker: () => path.join(os.homedir(), '.cursor'),
|
|
51
|
+
skillsDir: (scope, cwd) =>
|
|
52
|
+
scope === 'project'
|
|
53
|
+
? path.join(cwd, '.cursor', 'skills')
|
|
54
|
+
: path.join(os.homedir(), '.cursor', 'skills'),
|
|
55
|
+
mcpConfig: (scope, cwd) =>
|
|
56
|
+
scope === 'project'
|
|
57
|
+
? path.join(cwd, '.cursor', 'mcp.json')
|
|
58
|
+
: path.join(os.homedir(), '.cursor', 'mcp.json'),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function parseArgs(argv) {
|
|
63
|
+
const opts = {
|
|
64
|
+
clients: null, // null = auto-detect
|
|
65
|
+
scope: 'user', // 'user' | 'project'
|
|
66
|
+
dryRun: false,
|
|
67
|
+
force: false,
|
|
68
|
+
mcp: true, // register MCP by default — the skill is useless without it
|
|
69
|
+
mcpCommand: 'npx', // 'npx' (no global install) | 'global' (restforge-mcp)
|
|
70
|
+
yes: false,
|
|
71
|
+
help: false,
|
|
72
|
+
};
|
|
73
|
+
for (const arg of argv) {
|
|
74
|
+
if (arg === '--help' || arg === '-h') opts.help = true;
|
|
75
|
+
else if (arg === '--yes' || arg === '-y') opts.yes = true;
|
|
76
|
+
else if (arg === '--dry-run') opts.dryRun = true;
|
|
77
|
+
else if (arg === '--force') opts.force = true;
|
|
78
|
+
else if (arg === '--no-mcp') opts.mcp = false;
|
|
79
|
+
else if (arg === '--with-mcp') opts.mcp = true; // accepted; now the default
|
|
80
|
+
else if (arg.startsWith('--mcp-command=')) {
|
|
81
|
+
opts.mcpCommand = arg.slice('--mcp-command='.length).trim().toLowerCase();
|
|
82
|
+
} else if (arg.startsWith('--client=')) {
|
|
83
|
+
opts.clients = arg
|
|
84
|
+
.slice('--client='.length)
|
|
85
|
+
.split(',')
|
|
86
|
+
.map((c) => c.trim().toLowerCase())
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
} else if (arg.startsWith('--scope=')) {
|
|
89
|
+
opts.scope = arg.slice('--scope='.length).trim().toLowerCase();
|
|
90
|
+
} else {
|
|
91
|
+
console.error(`Unknown argument: ${arg}`);
|
|
92
|
+
opts.help = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return opts;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const HELP = `
|
|
99
|
+
create-restforge-skills — install the RESTForge Agent Skill into your clients.
|
|
100
|
+
|
|
101
|
+
Usage:
|
|
102
|
+
npx create-restforge-skills [options]
|
|
103
|
+
|
|
104
|
+
By default this copies the skill AND registers the RESTForge MCP server (via npx),
|
|
105
|
+
so it works out of the box. The MCP config is merged, not overwritten, and the
|
|
106
|
+
existing file is backed up to <config>.bak first.
|
|
107
|
+
|
|
108
|
+
Options:
|
|
109
|
+
--client=<list> Comma-separated clients: claude,cursor. Default: auto-detect.
|
|
110
|
+
--scope=<scope> user (default) installs globally for you; project installs
|
|
111
|
+
into ./.claude or ./.cursor in the current directory.
|
|
112
|
+
--no-mcp Skip MCP registration (only do this if you know why —
|
|
113
|
+
the skill cannot call anything without the MCP server).
|
|
114
|
+
--mcp-command=<c> npx (default, no global install needed) or
|
|
115
|
+
global (uses the installed restforge-mcp binary).
|
|
116
|
+
--force Overwrite an existing restforge skill folder.
|
|
117
|
+
--dry-run Show what would happen without writing anything.
|
|
118
|
+
--yes, -y Run non-interactively, accepting defaults.
|
|
119
|
+
--help, -h Show this help.
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
/** Decide which clients to target. */
|
|
123
|
+
function resolveClients(opts) {
|
|
124
|
+
if (opts.clients && opts.clients.length) {
|
|
125
|
+
const unknown = opts.clients.filter((c) => !CLIENTS[c]);
|
|
126
|
+
if (unknown.length) {
|
|
127
|
+
console.error(`Unknown client(s): ${unknown.join(', ')}`);
|
|
128
|
+
console.error(`Supported: ${Object.keys(CLIENTS).join(', ')}`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
return opts.clients;
|
|
132
|
+
}
|
|
133
|
+
// Auto-detect: a client counts as present if its home marker dir exists.
|
|
134
|
+
return Object.keys(CLIENTS).filter((key) => {
|
|
135
|
+
try {
|
|
136
|
+
return fs.statSync(CLIENTS[key].homeMarker()).isDirectory();
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Build the concrete install targets for the chosen clients and scope. */
|
|
144
|
+
function buildTargets(clientKeys, opts) {
|
|
145
|
+
const cwd = process.cwd();
|
|
146
|
+
return clientKeys.map((key) => {
|
|
147
|
+
const client = CLIENTS[key];
|
|
148
|
+
return {
|
|
149
|
+
key,
|
|
150
|
+
label: client.label,
|
|
151
|
+
dest: path.join(client.skillsDir(opts.scope, cwd), SKILL_NAME),
|
|
152
|
+
mcpConfig: client.mcpConfig(opts.scope, cwd),
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Copy the skill folder (SKILL.md + references/) into one target. */
|
|
158
|
+
function installSkill(target, opts) {
|
|
159
|
+
const exists = fs.existsSync(target.dest);
|
|
160
|
+
if (exists && !opts.force) {
|
|
161
|
+
return { status: 'skipped', reason: 'already exists (use --force to overwrite)' };
|
|
162
|
+
}
|
|
163
|
+
if (opts.dryRun) {
|
|
164
|
+
return { status: exists ? 'would-overwrite' : 'would-install' };
|
|
165
|
+
}
|
|
166
|
+
fs.mkdirSync(path.dirname(target.dest), { recursive: true });
|
|
167
|
+
if (exists) fs.rmSync(target.dest, { recursive: true, force: true });
|
|
168
|
+
fs.cpSync(SOURCE_DIR, target.dest, { recursive: true });
|
|
169
|
+
return { status: exists ? 'overwritten' : 'installed' };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const pad = (s) => String(s).padEnd(12);
|
|
173
|
+
|
|
174
|
+
function main() {
|
|
175
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
176
|
+
if (opts.help) {
|
|
177
|
+
process.stdout.write(HELP);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!['user', 'project'].includes(opts.scope)) {
|
|
182
|
+
console.error(`Invalid --scope: ${opts.scope} (expected "user" or "project")`);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
if (!['npx', 'global'].includes(opts.mcpCommand)) {
|
|
186
|
+
console.error(`Invalid --mcp-command: ${opts.mcpCommand} (expected "npx" or "global")`);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
if (!fs.existsSync(path.join(SOURCE_DIR, 'SKILL.md'))) {
|
|
190
|
+
console.error(`Bundled skill not found at ${SOURCE_DIR}. Broken package?`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const clientKeys = resolveClients(opts);
|
|
195
|
+
if (!clientKeys.length) {
|
|
196
|
+
console.error('No target client detected (looked for ~/.claude and ~/.cursor).');
|
|
197
|
+
console.error('Pass one explicitly, e.g. --client=claude,cursor');
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const targets = buildTargets(clientKeys, opts);
|
|
202
|
+
const mcpEntry = buildEntry(opts.mcpCommand);
|
|
203
|
+
|
|
204
|
+
console.log(
|
|
205
|
+
`\nRESTForge skill — ${opts.dryRun ? 'dry run' : 'install'} (scope: ${opts.scope}` +
|
|
206
|
+
`, mcp: ${opts.mcp ? opts.mcpCommand : 'off'})\n`
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
for (const target of targets) {
|
|
210
|
+
const skill = installSkill(target, opts);
|
|
211
|
+
console.log(` ${pad(target.label)} ${target.dest}`);
|
|
212
|
+
console.log(` ${pad('')} → skill: ${skill.status}${skill.reason ? ` (${skill.reason})` : ''}`);
|
|
213
|
+
|
|
214
|
+
if (opts.mcp) {
|
|
215
|
+
const mcp = registerMcp(target.mcpConfig, mcpEntry, opts);
|
|
216
|
+
console.log(` ${pad('')} → mcp: ${mcp.status}${mcp.error ? ` — ${mcp.error}` : ''}`);
|
|
217
|
+
console.log(` ${pad('')} ${target.mcpConfig}`);
|
|
218
|
+
if (mcp.backup) console.log(` ${pad('')} backup: ${mcp.backup}`);
|
|
219
|
+
if (mcp.status === 'error') {
|
|
220
|
+
console.log(` ${pad('')} add manually: "mcpServers": { "restforge": ${JSON.stringify(mcpEntry)} }`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log('\nNext step:');
|
|
226
|
+
console.log(' Set a valid RESTForge LICENSE for codegen_*/runtime_* operations.');
|
|
227
|
+
console.log(' (Designer/frontend tools work without a license.)');
|
|
228
|
+
if (clientKeys.includes('claude')) {
|
|
229
|
+
console.log(' Claude Code: if its skills dir was just created, restart the client so it');
|
|
230
|
+
console.log(' watches the new directory.');
|
|
231
|
+
}
|
|
232
|
+
console.log('');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
main();
|
package/cli/mcp.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP registration for create-restforge-skills.
|
|
5
|
+
*
|
|
6
|
+
* The skill is inert without the RESTForge MCP server, so registration is the
|
|
7
|
+
* default behavior. This module merges (never overwrites) a `restforge` entry
|
|
8
|
+
* into a client's MCP config and backs the file up first.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build the MCP server entry.
|
|
16
|
+
* - 'npx' (default): no separate global install — npx fetches on first run.
|
|
17
|
+
* - 'global': uses the globally-installed `restforge-mcp` binary (faster start).
|
|
18
|
+
*/
|
|
19
|
+
function buildEntry(mcpCommand) {
|
|
20
|
+
if (mcpCommand === 'global') {
|
|
21
|
+
return { command: 'restforge-mcp' };
|
|
22
|
+
}
|
|
23
|
+
return { command: 'npx', args: ['-y', '@restforgejs/mcp-server'] };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function readConfig(file) {
|
|
27
|
+
if (!fs.existsSync(file)) return { existed: false, data: {} };
|
|
28
|
+
const raw = fs.readFileSync(file, 'utf8').trim();
|
|
29
|
+
if (!raw) return { existed: true, data: {} };
|
|
30
|
+
let data;
|
|
31
|
+
try {
|
|
32
|
+
data = JSON.parse(raw);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
const e = new Error(`config is not valid JSON: ${err.message}`);
|
|
35
|
+
e.code = 'EBADJSON';
|
|
36
|
+
throw e;
|
|
37
|
+
}
|
|
38
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data)) {
|
|
39
|
+
const e = new Error('config root is not a JSON object');
|
|
40
|
+
e.code = 'EBADJSON';
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
return { existed: true, data };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Merge the `restforge` MCP server into `file`.
|
|
48
|
+
* Returns { status, backup?, error? } where status is one of:
|
|
49
|
+
* registered | updated | already-registered | would-register | would-update | error
|
|
50
|
+
* Never overwrites unrelated keys; backs up an existing file before writing.
|
|
51
|
+
*/
|
|
52
|
+
function registerMcp(file, entry, opts) {
|
|
53
|
+
let cfg;
|
|
54
|
+
try {
|
|
55
|
+
cfg = readConfig(file);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (err.code === 'EBADJSON') {
|
|
58
|
+
// Refuse to touch a config we cannot safely parse.
|
|
59
|
+
return { status: 'error', error: err.message };
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const data = cfg.data;
|
|
65
|
+
const servers =
|
|
66
|
+
data.mcpServers && typeof data.mcpServers === 'object' && !Array.isArray(data.mcpServers)
|
|
67
|
+
? data.mcpServers
|
|
68
|
+
: {};
|
|
69
|
+
|
|
70
|
+
const existing = servers.restforge;
|
|
71
|
+
if (existing && JSON.stringify(existing) === JSON.stringify(entry)) {
|
|
72
|
+
return { status: 'already-registered' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const willUpdate = Boolean(existing);
|
|
76
|
+
if (opts.dryRun) {
|
|
77
|
+
return { status: willUpdate ? 'would-update' : 'would-register' };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let backup;
|
|
81
|
+
if (cfg.existed) {
|
|
82
|
+
backup = file + '.bak';
|
|
83
|
+
fs.copyFileSync(file, backup);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
servers.restforge = entry;
|
|
87
|
+
data.mcpServers = servers;
|
|
88
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
89
|
+
fs.writeFileSync(file, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
90
|
+
|
|
91
|
+
return { status: willUpdate ? 'updated' : 'registered', backup };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { buildEntry, registerMcp };
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-restforge-skills",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Install the RESTForge Agent Skill into Claude Code, Cursor, and other skills-compatible clients.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-restforge-skills": "cli/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"cli",
|
|
11
|
+
"skills",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"restforge",
|
|
19
|
+
"agent-skills",
|
|
20
|
+
"skill",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"cursor",
|
|
23
|
+
"mcp"
|
|
24
|
+
],
|
|
25
|
+
"author": {
|
|
26
|
+
"name": "RESTForge",
|
|
27
|
+
"url": "https://restforge.dev"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT"
|
|
30
|
+
}
|