frontmcp 1.2.0 → 1.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/package.json +4 -4
- package/src/commands/build/exec/bin-meta.d.ts +49 -0
- package/src/commands/build/exec/bin-meta.js +68 -0
- package/src/commands/build/exec/bin-meta.js.map +1 -0
- package/src/commands/build/exec/cli-runtime/generate-cli-entry.js +195 -3
- package/src/commands/build/exec/cli-runtime/generate-cli-entry.js.map +1 -1
- package/src/commands/build/exec/cli-runtime/plugin-emitter.d.ts +160 -0
- package/src/commands/build/exec/cli-runtime/plugin-emitter.js +512 -0
- package/src/commands/build/exec/cli-runtime/plugin-emitter.js.map +1 -0
- package/src/commands/build/exec/cli-runtime/schema-extractor.d.ts +13 -1
- package/src/commands/build/exec/cli-runtime/schema-extractor.js +29 -3
- package/src/commands/build/exec/cli-runtime/schema-extractor.js.map +1 -1
- package/src/commands/build/exec/cli-runtime/skill-md-compose.d.ts +25 -0
- package/src/commands/build/exec/cli-runtime/skill-md-compose.js +63 -0
- package/src/commands/build/exec/cli-runtime/skill-md-compose.js.map +1 -0
- package/src/commands/build/exec/index.js +26 -0
- package/src/commands/build/exec/index.js.map +1 -1
- package/src/commands/dev/bridge/child-supervisor.d.ts +48 -0
- package/src/commands/dev/bridge/child-supervisor.js +228 -0
- package/src/commands/dev/bridge/child-supervisor.js.map +1 -0
- package/src/commands/dev/bridge/errors.d.ts +23 -0
- package/src/commands/dev/bridge/errors.js +34 -0
- package/src/commands/dev/bridge/errors.js.map +1 -0
- package/src/commands/dev/bridge/index.d.ts +30 -0
- package/src/commands/dev/bridge/index.js +220 -0
- package/src/commands/dev/bridge/index.js.map +1 -0
- package/src/commands/dev/bridge/log.d.ts +29 -0
- package/src/commands/dev/bridge/log.js +82 -0
- package/src/commands/dev/bridge/log.js.map +1 -0
- package/src/commands/dev/bridge/state-machine.d.ts +56 -0
- package/src/commands/dev/bridge/state-machine.js +245 -0
- package/src/commands/dev/bridge/state-machine.js.map +1 -0
- package/src/commands/dev/bridge/stdio-framer.d.ts +47 -0
- package/src/commands/dev/bridge/stdio-framer.js +128 -0
- package/src/commands/dev/bridge/stdio-framer.js.map +1 -0
- package/src/commands/dev/bridge/upstream-client.d.ts +49 -0
- package/src/commands/dev/bridge/upstream-client.js +159 -0
- package/src/commands/dev/bridge/upstream-client.js.map +1 -0
- package/src/commands/dev/bridge/watcher.d.ts +30 -0
- package/src/commands/dev/bridge/watcher.js +87 -0
- package/src/commands/dev/bridge/watcher.js.map +1 -0
- package/src/commands/dev/dev.d.ts +18 -1
- package/src/commands/dev/dev.js +134 -14
- package/src/commands/dev/dev.js.map +1 -1
- package/src/commands/dev/inspector.d.ts +13 -1
- package/src/commands/dev/inspector.js +77 -3
- package/src/commands/dev/inspector.js.map +1 -1
- package/src/commands/dev/port.d.ts +23 -0
- package/src/commands/dev/port.js +87 -0
- package/src/commands/dev/port.js.map +1 -0
- package/src/commands/dev/register.d.ts +1 -1
- package/src/commands/dev/register.js +28 -4
- package/src/commands/dev/register.js.map +1 -1
- package/src/commands/dev/test.d.ts +26 -1
- package/src/commands/dev/test.js +181 -64
- package/src/commands/dev/test.js.map +1 -1
- package/src/commands/eject/mcp-client.d.ts +25 -0
- package/src/commands/eject/mcp-client.js +74 -0
- package/src/commands/eject/mcp-client.js.map +1 -0
- package/src/commands/eject/register.d.ts +9 -0
- package/src/commands/eject/register.js +56 -0
- package/src/commands/eject/register.js.map +1 -0
- package/src/commands/install/install-claude-plugin.d.ts +13 -0
- package/src/commands/install/install-claude-plugin.js +327 -0
- package/src/commands/install/install-claude-plugin.js.map +1 -0
- package/src/commands/install/register.d.ts +16 -0
- package/src/commands/install/register.js +70 -0
- package/src/commands/install/register.js.map +1 -0
- package/src/commands/scaffold/create.js +44 -0
- package/src/commands/scaffold/create.js.map +1 -1
- package/src/commands/skills/from-entry.d.ts +31 -0
- package/src/commands/skills/from-entry.js +68 -0
- package/src/commands/skills/from-entry.js.map +1 -0
- package/src/commands/skills/install.d.ts +12 -0
- package/src/commands/skills/install.js +173 -8
- package/src/commands/skills/install.js.map +1 -1
- package/src/commands/skills/register.js +7 -3
- package/src/commands/skills/register.js.map +1 -1
- package/src/config/frontmcp-config.loader.d.ts +28 -0
- package/src/config/frontmcp-config.loader.js +146 -67
- package/src/config/frontmcp-config.loader.js.map +1 -1
- package/src/config/frontmcp-config.resolve.d.ts +67 -0
- package/src/config/frontmcp-config.resolve.js +118 -0
- package/src/config/frontmcp-config.resolve.js.map +1 -0
- package/src/config/frontmcp-config.schema.d.ts +207 -0
- package/src/config/frontmcp-config.schema.js +217 -1
- package/src/config/frontmcp-config.schema.js.map +1 -1
- package/src/config/frontmcp-config.types.d.ts +133 -0
- package/src/config/frontmcp-config.types.js.map +1 -1
- package/src/config/index.d.ts +2 -1
- package/src/config/index.js +3 -1
- package/src/config/index.js.map +1 -1
- package/src/core/args.d.ts +13 -0
- package/src/core/args.js.map +1 -1
- package/src/core/bridge.js +39 -0
- package/src/core/bridge.js.map +1 -1
- package/src/core/cli.d.ts +0 -6
- package/src/core/cli.js +23 -3
- package/src/core/cli.js.map +1 -1
- package/src/core/help.d.ts +1 -1
- package/src/core/help.js +27 -6
- package/src/core/help.js.map +1 -1
- package/src/core/program.d.ts +1 -1
- package/src/core/program.js +56 -12
- package/src/core/program.js.map +1 -1
- package/src/core/project-commands.d.ts +44 -0
- package/src/core/project-commands.js +216 -0
- package/src/core/project-commands.js.map +1 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Implementation of `frontmcp install --claude-plugin [--codex]` (issue #411).
|
|
4
|
+
*
|
|
5
|
+
* Flow:
|
|
6
|
+
* 1. Load frontmcp.config + package.json to determine name/version/description.
|
|
7
|
+
* 2. Spin up a transient direct-mode SDK instance to enumerate skills and
|
|
8
|
+
* prompts (the same surfaces a built bin would expose).
|
|
9
|
+
* 3. Delegate to the shared `plugin-emitter.ts` helper.
|
|
10
|
+
*
|
|
11
|
+
* Status / dry-run paths are short-circuited before the SDK is loaded.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.runInstallCurrentProject = runInstallCurrentProject;
|
|
15
|
+
exports.runUninstallCurrentProject = runUninstallCurrentProject;
|
|
16
|
+
const tslib_1 = require("tslib");
|
|
17
|
+
const os = tslib_1.__importStar(require("os"));
|
|
18
|
+
const path = tslib_1.__importStar(require("path"));
|
|
19
|
+
const frontmcp_config_loader_1 = require("../../config/frontmcp-config.loader");
|
|
20
|
+
const colors_1 = require("../../core/colors");
|
|
21
|
+
const plugin_emitter_1 = require("../build/exec/cli-runtime/plugin-emitter");
|
|
22
|
+
async function runInstallCurrentProject(rawOpts) {
|
|
23
|
+
const opts = normalizeOptions(rawOpts);
|
|
24
|
+
if (!opts.claudePlugin && !opts.codex) {
|
|
25
|
+
process.stderr.write((0, colors_1.c)('red', 'Specify --claude and/or --codex.\n'));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const projectMeta = await loadProjectMeta();
|
|
29
|
+
if (!projectMeta) {
|
|
30
|
+
process.stderr.write((0, colors_1.c)('red', 'No frontmcp.config found in this directory. Run `frontmcp install` from a FrontMCP project root.\n'));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
if (opts.status) {
|
|
34
|
+
await printStatus(projectMeta, opts);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const cliVersion = await getCliVersion();
|
|
38
|
+
const skills = opts.skills === false || opts.onlyMcp ? [] : await collectSkillsFromProject();
|
|
39
|
+
const commands = opts.commands === false || opts.onlyMcp ? [] : await collectPromptsFromProject();
|
|
40
|
+
if (opts.claudePlugin) {
|
|
41
|
+
await runClaudeInstall({ projectMeta, opts, cliVersion, skills, commands });
|
|
42
|
+
}
|
|
43
|
+
if (opts.codex) {
|
|
44
|
+
await runCodexInstall({ projectMeta, opts, cliVersion });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function loadProjectMeta() {
|
|
48
|
+
const cwd = process.cwd();
|
|
49
|
+
const cfg = await (0, frontmcp_config_loader_1.tryLoadFrontMcpConfig)(cwd).catch(() => undefined);
|
|
50
|
+
if (!cfg)
|
|
51
|
+
return undefined;
|
|
52
|
+
const { readJSON, fileExists } = await import('@frontmcp/utils');
|
|
53
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
54
|
+
const pkg = (await fileExists(pkgPath))
|
|
55
|
+
? (await readJSON(pkgPath))
|
|
56
|
+
: {};
|
|
57
|
+
return {
|
|
58
|
+
name: cfg.name ?? pkg.name ?? path.basename(cwd),
|
|
59
|
+
version: cfg.version ?? pkg.version ?? '0.0.0',
|
|
60
|
+
description: pkg.description ?? `${cfg.name ?? pkg.name ?? 'FrontMCP server'} (FrontMCP plugin)`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Best-effort: bundle the project's entry to a temp file, hand the bundle
|
|
65
|
+
* to the schema-extractor (the same routine the per-bin path uses at build
|
|
66
|
+
* time), and return the enumerated skills / prompts. Failures are logged
|
|
67
|
+
* and the empty-array fallback is returned so a partial setup never blocks
|
|
68
|
+
* an install — the per-bin path remains the authoritative source.
|
|
69
|
+
*
|
|
70
|
+
* Wrapped in a single bundle+extract pass so both collectors share work:
|
|
71
|
+
* spawning two extractor passes would double the SDK boot cost on every
|
|
72
|
+
* dev-tool install.
|
|
73
|
+
*/
|
|
74
|
+
// Keyed by absolute `cwd` so two installs invoked back-to-back from
|
|
75
|
+
// different project roots (e.g. an integration test, or a script that
|
|
76
|
+
// `cd`s between projects) don't reuse the first project's skill/prompt
|
|
77
|
+
// extraction. Each project gets its own cached promise — and within a
|
|
78
|
+
// single project both `collectSkillsFromProject` and
|
|
79
|
+
// `collectPromptsFromProject` share the one bundle+extract pass.
|
|
80
|
+
const cachedExtractionByCwd = new Map();
|
|
81
|
+
async function getProjectExtraction(cwd) {
|
|
82
|
+
const cached = cachedExtractionByCwd.get(cwd);
|
|
83
|
+
if (cached)
|
|
84
|
+
return cached;
|
|
85
|
+
const extractionPromise = (async () => {
|
|
86
|
+
try {
|
|
87
|
+
const { resolveEntry } = await import('../../shared/fs.js');
|
|
88
|
+
const { mkdtemp, rm } = await import('@frontmcp/utils');
|
|
89
|
+
const entry = await resolveEntry(cwd);
|
|
90
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), 'frontmcp-install-'));
|
|
91
|
+
const bundlePath = path.join(tempDir, 'entry.cjs');
|
|
92
|
+
try {
|
|
93
|
+
const esbuild = require('esbuild');
|
|
94
|
+
await esbuild.build({
|
|
95
|
+
entryPoints: [entry],
|
|
96
|
+
bundle: true,
|
|
97
|
+
write: true,
|
|
98
|
+
outfile: bundlePath,
|
|
99
|
+
platform: 'node',
|
|
100
|
+
format: 'cjs',
|
|
101
|
+
target: 'es2022',
|
|
102
|
+
packages: 'external',
|
|
103
|
+
sourcemap: false,
|
|
104
|
+
logLevel: 'silent',
|
|
105
|
+
absWorkingDir: cwd,
|
|
106
|
+
});
|
|
107
|
+
const { extractSchemas } = await import('../build/exec/cli-runtime/schema-extractor.js');
|
|
108
|
+
const schema = await extractSchemas(bundlePath);
|
|
109
|
+
const skills = (schema.skillAssets ?? []).map((entry) => ({
|
|
110
|
+
name: entry.skillName,
|
|
111
|
+
description: entry.description ?? `${entry.skillName} skill`,
|
|
112
|
+
tags: entry.tags,
|
|
113
|
+
license: entry.license,
|
|
114
|
+
instructionFile: entry.instructionFile,
|
|
115
|
+
resourceDirs: entry.resourceDirs,
|
|
116
|
+
}));
|
|
117
|
+
const commands = (schema.prompts ?? []).map((p) => ({
|
|
118
|
+
name: p.name,
|
|
119
|
+
description: p.description,
|
|
120
|
+
arguments: p.arguments,
|
|
121
|
+
}));
|
|
122
|
+
return { skills, commands };
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
// Drop the cache entry so the NEXT install from this cwd retries
|
|
130
|
+
// extraction — without this, a transient esbuild/SDK-boot failure
|
|
131
|
+
// would freeze the project at `{ skills: [], commands: [] }` for
|
|
132
|
+
// the rest of the process even after the user fixes the entry.
|
|
133
|
+
// The current caller still gets the empty fallback so the install
|
|
134
|
+
// itself doesn't crash.
|
|
135
|
+
cachedExtractionByCwd.delete(cwd);
|
|
136
|
+
process.stderr.write((0, colors_1.c)('yellow', `[install] could not enumerate skills/prompts from project (${err.message}); ` +
|
|
137
|
+
`emitting plugin without them. Use the per-bin install (\`<bin> install -p claude\`) ` +
|
|
138
|
+
`after \`frontmcp build:exec\` if you need the full set.\n`));
|
|
139
|
+
return { skills: [], commands: [] };
|
|
140
|
+
}
|
|
141
|
+
})();
|
|
142
|
+
// Set BEFORE awaiting so concurrent callers within the same install
|
|
143
|
+
// (collectSkillsFromProject + collectPromptsFromProject) share the
|
|
144
|
+
// single bundle+extract pass. The catch above un-sets on failure.
|
|
145
|
+
cachedExtractionByCwd.set(cwd, extractionPromise);
|
|
146
|
+
return extractionPromise;
|
|
147
|
+
}
|
|
148
|
+
async function collectSkillsFromProject() {
|
|
149
|
+
return (await getProjectExtraction(process.cwd())).skills;
|
|
150
|
+
}
|
|
151
|
+
async function collectPromptsFromProject() {
|
|
152
|
+
return (await getProjectExtraction(process.cwd())).commands;
|
|
153
|
+
}
|
|
154
|
+
async function getCliVersion() {
|
|
155
|
+
try {
|
|
156
|
+
const { readJSON } = await import('@frontmcp/utils');
|
|
157
|
+
const pkg = (await readJSON(path.join(__dirname, '..', '..', '..', 'package.json')));
|
|
158
|
+
return pkg.version ?? '0.0.0';
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return '0.0.0';
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function runClaudeInstall(args) {
|
|
165
|
+
const destRoot = resolveDestRoot(args.opts);
|
|
166
|
+
const emitOpts = {
|
|
167
|
+
destRoot,
|
|
168
|
+
name: args.projectMeta.name,
|
|
169
|
+
version: args.projectMeta.version,
|
|
170
|
+
description: args.projectMeta.description,
|
|
171
|
+
mcpCommand: args.opts.command ?? args.projectMeta.name,
|
|
172
|
+
mcpArgs: ['serve', '--stdio'],
|
|
173
|
+
envHints: args.opts.env ?? [],
|
|
174
|
+
skills: args.skills,
|
|
175
|
+
commands: args.commands,
|
|
176
|
+
cliVersion: args.cliVersion,
|
|
177
|
+
dryRun: args.opts.dryRun,
|
|
178
|
+
};
|
|
179
|
+
const result = await (0, plugin_emitter_1.emitClaudePlugin)(emitOpts);
|
|
180
|
+
if (args.opts.dryRun) {
|
|
181
|
+
process.stdout.write(`${(0, colors_1.c)('cyan', '[install:claude] dry-run plan')}\n`);
|
|
182
|
+
process.stdout.write(` pluginDir: ${result.pluginDir}\n`);
|
|
183
|
+
process.stdout.write(` manifest: ${JSON.stringify(result.manifest, null, 2)}\n`);
|
|
184
|
+
process.stdout.write(` filesWritten (planned):\n`);
|
|
185
|
+
for (const f of result.filesWritten)
|
|
186
|
+
process.stdout.write(` + ${f}\n`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
process.stdout.write((0, colors_1.c)('green', `✓ Wrote ${result.pluginDir}/ (${args.skills.length} skills, ${args.commands.length} commands, 1 MCP server)\n`));
|
|
190
|
+
if (result.filesRemoved.length > 0) {
|
|
191
|
+
process.stdout.write(` Cleaned up ${result.filesRemoved.length} stale file(s) from previous install.\n`);
|
|
192
|
+
}
|
|
193
|
+
// Surface the dev-tool limitation explicitly: today this command only
|
|
194
|
+
// emits the MCP server entry; per-project @Skill / @Prompt enumeration
|
|
195
|
+
// is wired through the per-bin path (`<bin> install -p claude`). Don't
|
|
196
|
+
// let users assume zero counts mean "your project has no skills".
|
|
197
|
+
if (args.skills.length === 0 &&
|
|
198
|
+
args.commands.length === 0 &&
|
|
199
|
+
args.opts.skills !== false &&
|
|
200
|
+
args.opts.commands !== false &&
|
|
201
|
+
!args.opts.onlyMcp) {
|
|
202
|
+
process.stdout.write((0, colors_1.c)('yellow', ' Note: the dev-tool path does not yet enumerate @Skill or @Prompt from project source.\n' +
|
|
203
|
+
' Build with `frontmcp build --target cli` and run `<bin> install -p claude` for full plugin coverage.\n'));
|
|
204
|
+
}
|
|
205
|
+
process.stdout.write((0, colors_1.c)('dim', ' Restart Claude Code (or run `/plugins reload`) to pick up the plugin.\n'));
|
|
206
|
+
}
|
|
207
|
+
async function runCodexInstall(args) {
|
|
208
|
+
void args.cliVersion;
|
|
209
|
+
const configPath = path.join(os.homedir(), '.codex', 'config.toml');
|
|
210
|
+
const env = {};
|
|
211
|
+
for (const name of args.opts.env ?? [])
|
|
212
|
+
env[name] = `\${${name}}`;
|
|
213
|
+
const result = await (0, plugin_emitter_1.emitCodexEntry)({
|
|
214
|
+
configPath,
|
|
215
|
+
name: args.projectMeta.name,
|
|
216
|
+
command: args.opts.command ?? args.projectMeta.name,
|
|
217
|
+
args: ['serve', '--stdio'],
|
|
218
|
+
env,
|
|
219
|
+
dryRun: args.opts.dryRun,
|
|
220
|
+
});
|
|
221
|
+
if (args.opts.dryRun) {
|
|
222
|
+
process.stdout.write(`${(0, colors_1.c)('cyan', '[install:codex] dry-run plan')}\n`);
|
|
223
|
+
process.stdout.write(` configPath: ${configPath}\n`);
|
|
224
|
+
process.stdout.write(` configContent:\n${indentBlock(result.configContent, 4)}\n`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
process.stdout.write((0, colors_1.c)('green', `✓ Updated ${configPath} with [[mcp_servers]] entry for ${args.projectMeta.name}\n`));
|
|
228
|
+
}
|
|
229
|
+
async function printStatus(projectMeta, opts) {
|
|
230
|
+
process.stdout.write(`${(0, colors_1.c)('bold', `${projectMeta.name} install --status`)}\n`);
|
|
231
|
+
if (opts.claudePlugin || !opts.codex) {
|
|
232
|
+
const destRoot = resolveDestRoot(opts);
|
|
233
|
+
const pluginDir = path.join(destRoot, projectMeta.name);
|
|
234
|
+
const installed = await (0, plugin_emitter_1.readInstalledPluginVersion)(pluginDir);
|
|
235
|
+
if (installed) {
|
|
236
|
+
const tag = installed === projectMeta.version ? 'installed' : 'outdated';
|
|
237
|
+
process.stdout.write(` claude: ${tag} v${installed}${tag === 'outdated' ? ` (project at v${projectMeta.version})` : ''} at ${pluginDir}\n`);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
process.stdout.write(` claude: not installed at ${pluginDir}\n`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (opts.codex) {
|
|
244
|
+
const configPath = path.join(os.homedir(), '.codex', 'config.toml');
|
|
245
|
+
const { fileExists, readFile } = await import('@frontmcp/utils');
|
|
246
|
+
if (!(await fileExists(configPath))) {
|
|
247
|
+
process.stdout.write(` codex: not installed (${configPath} does not exist)\n`);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
const content = await readFile(configPath);
|
|
251
|
+
const present = content.includes(`# frontmcp:codex-start:${projectMeta.name}`);
|
|
252
|
+
process.stdout.write(present
|
|
253
|
+
? ` codex: installed entry for ${projectMeta.name} in ${configPath}\n`
|
|
254
|
+
: ` codex: not installed in ${configPath}\n`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function runUninstallCurrentProject(rawOpts) {
|
|
259
|
+
const opts = normalizeOptions(rawOpts);
|
|
260
|
+
if (!opts.claudePlugin && !opts.codex) {
|
|
261
|
+
process.stderr.write((0, colors_1.c)('red', 'Specify --claude and/or --codex.\n'));
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
const projectMeta = await loadProjectMeta();
|
|
265
|
+
if (!projectMeta) {
|
|
266
|
+
process.stderr.write((0, colors_1.c)('red', 'No frontmcp.config found in this directory.\n'));
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
if (opts.claudePlugin) {
|
|
270
|
+
const destRoot = resolveDestRoot(opts);
|
|
271
|
+
const result = await (0, plugin_emitter_1.removeClaudePlugin)({ destRoot, name: projectMeta.name });
|
|
272
|
+
if (result.removed.length === 0) {
|
|
273
|
+
process.stdout.write((0, colors_1.c)('dim', ` claude: nothing to remove at ${result.pluginDir}\n`));
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
process.stdout.write((0, colors_1.c)('green', `✓ Removed ${result.removed.length} file(s) from ${result.pluginDir}\n`));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (opts.codex) {
|
|
280
|
+
const configPath = path.join(os.homedir(), '.codex', 'config.toml');
|
|
281
|
+
const result = await (0, plugin_emitter_1.removeCodexEntry)({ configPath, name: projectMeta.name });
|
|
282
|
+
if (result.removed) {
|
|
283
|
+
process.stdout.write((0, colors_1.c)('green', `✓ Removed [[mcp_servers]] entry for ${projectMeta.name} from ${configPath}\n`));
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
process.stdout.write((0, colors_1.c)('dim', ` codex: no entry for ${projectMeta.name} in ${configPath}\n`));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function resolveDestRoot(opts) {
|
|
291
|
+
if (opts.dir)
|
|
292
|
+
return path.resolve(opts.dir);
|
|
293
|
+
if (opts.scope === 'user')
|
|
294
|
+
return path.join(os.homedir(), '.claude', 'plugins');
|
|
295
|
+
return path.join(process.cwd(), '.claude', 'plugins');
|
|
296
|
+
}
|
|
297
|
+
function normalizeOptions(raw) {
|
|
298
|
+
// Fail fast on bogus `--scope` values rather than silently coercing them to
|
|
299
|
+
// 'project' — `--scope usr` would otherwise write files to the wrong root
|
|
300
|
+
// (cwd's `.claude/plugins/` instead of `~/.claude/plugins/`) with no signal.
|
|
301
|
+
const rawScope = raw['scope'];
|
|
302
|
+
if (rawScope !== undefined && rawScope !== 'project' && rawScope !== 'user') {
|
|
303
|
+
throw new Error(`Invalid --scope value: ${String(rawScope)}. Expected "project" or "user".`);
|
|
304
|
+
}
|
|
305
|
+
const scope = rawScope === 'user' ? 'user' : 'project';
|
|
306
|
+
return {
|
|
307
|
+
claudePlugin: raw['claudePlugin'] === true,
|
|
308
|
+
codex: raw['codex'] === true,
|
|
309
|
+
scope,
|
|
310
|
+
skills: raw['skills'] !== false,
|
|
311
|
+
commands: raw['commands'] !== false,
|
|
312
|
+
onlyMcp: raw['onlyMcp'] === true,
|
|
313
|
+
command: typeof raw['command'] === 'string' ? raw['command'] : undefined,
|
|
314
|
+
env: Array.isArray(raw['env']) ? raw['env'] : [],
|
|
315
|
+
dir: typeof raw['dir'] === 'string' ? raw['dir'] : undefined,
|
|
316
|
+
dryRun: raw['dryRun'] === true,
|
|
317
|
+
status: raw['status'] === true,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function indentBlock(text, n) {
|
|
321
|
+
const pad = ' '.repeat(n);
|
|
322
|
+
return text
|
|
323
|
+
.split('\n')
|
|
324
|
+
.map((line) => pad + line)
|
|
325
|
+
.join('\n');
|
|
326
|
+
}
|
|
327
|
+
//# sourceMappingURL=install-claude-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-claude-plugin.js","sourceRoot":"","sources":["../../../../src/commands/install/install-claude-plugin.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Implementation of `frontmcp install --claude-plugin [--codex]` (issue #411).\n *\n * Flow:\n * 1. Load frontmcp.config + package.json to determine name/version/description.\n * 2. Spin up a transient direct-mode SDK instance to enumerate skills and\n * prompts (the same surfaces a built bin would expose).\n * 3. Delegate to the shared `plugin-emitter.ts` helper.\n *\n * Status / dry-run paths are short-circuited before the SDK is loaded.\n */\n\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { tryLoadFrontMcpConfig } from '../../config/frontmcp-config.loader';\nimport { c } from '../../core/colors';\nimport {\n emitClaudePlugin,\n emitCodexEntry,\n readInstalledPluginVersion,\n removeClaudePlugin,\n removeCodexEntry,\n type EmitClaudePluginOptions,\n type PluginEmitterCommandInput,\n type PluginEmitterSkillInput,\n} from '../build/exec/cli-runtime/plugin-emitter';\n\ninterface InstallOptions {\n claudePlugin?: boolean;\n codex?: boolean;\n scope?: 'project' | 'user';\n skills?: boolean;\n commands?: boolean;\n onlyMcp?: boolean;\n command?: string;\n env?: string[];\n dir?: string;\n dryRun?: boolean;\n status?: boolean;\n}\n\nexport async function runInstallCurrentProject(rawOpts: Record<string, unknown>): Promise<void> {\n const opts = normalizeOptions(rawOpts);\n\n if (!opts.claudePlugin && !opts.codex) {\n process.stderr.write(c('red', 'Specify --claude and/or --codex.\\n'));\n process.exit(1);\n }\n\n const projectMeta = await loadProjectMeta();\n if (!projectMeta) {\n process.stderr.write(\n c('red', 'No frontmcp.config found in this directory. Run `frontmcp install` from a FrontMCP project root.\\n'),\n );\n process.exit(1);\n }\n\n if (opts.status) {\n await printStatus(projectMeta, opts);\n return;\n }\n\n const cliVersion = await getCliVersion();\n const skills = opts.skills === false || opts.onlyMcp ? [] : await collectSkillsFromProject();\n const commands = opts.commands === false || opts.onlyMcp ? [] : await collectPromptsFromProject();\n\n if (opts.claudePlugin) {\n await runClaudeInstall({ projectMeta, opts, cliVersion, skills, commands });\n }\n if (opts.codex) {\n await runCodexInstall({ projectMeta, opts, cliVersion });\n }\n}\n\ninterface ProjectMeta {\n name: string;\n version: string;\n description: string;\n}\n\nasync function loadProjectMeta(): Promise<ProjectMeta | undefined> {\n const cwd = process.cwd();\n const cfg = await tryLoadFrontMcpConfig(cwd).catch(() => undefined);\n if (!cfg) return undefined;\n const { readJSON, fileExists } = await import('@frontmcp/utils');\n const pkgPath = path.join(cwd, 'package.json');\n const pkg: { name?: string; version?: string; description?: string } = (await fileExists(pkgPath))\n ? ((await readJSON(pkgPath)) as { name?: string; version?: string; description?: string })\n : {};\n return {\n name: cfg.name ?? pkg.name ?? path.basename(cwd),\n version: cfg.version ?? pkg.version ?? '0.0.0',\n description: pkg.description ?? `${cfg.name ?? pkg.name ?? 'FrontMCP server'} (FrontMCP plugin)`,\n };\n}\n\n/**\n * Best-effort: bundle the project's entry to a temp file, hand the bundle\n * to the schema-extractor (the same routine the per-bin path uses at build\n * time), and return the enumerated skills / prompts. Failures are logged\n * and the empty-array fallback is returned so a partial setup never blocks\n * an install — the per-bin path remains the authoritative source.\n *\n * Wrapped in a single bundle+extract pass so both collectors share work:\n * spawning two extractor passes would double the SDK boot cost on every\n * dev-tool install.\n */\n// Keyed by absolute `cwd` so two installs invoked back-to-back from\n// different project roots (e.g. an integration test, or a script that\n// `cd`s between projects) don't reuse the first project's skill/prompt\n// extraction. Each project gets its own cached promise — and within a\n// single project both `collectSkillsFromProject` and\n// `collectPromptsFromProject` share the one bundle+extract pass.\nconst cachedExtractionByCwd = new Map<\n string,\n Promise<{ skills: PluginEmitterSkillInput[]; commands: PluginEmitterCommandInput[] }>\n>();\n\nasync function getProjectExtraction(cwd: string): Promise<{\n skills: PluginEmitterSkillInput[];\n commands: PluginEmitterCommandInput[];\n}> {\n const cached = cachedExtractionByCwd.get(cwd);\n if (cached) return cached;\n const extractionPromise = (async () => {\n try {\n const { resolveEntry } = await import('../../shared/fs.js');\n const { mkdtemp, rm } = await import('@frontmcp/utils');\n const entry = await resolveEntry(cwd);\n const tempDir = await mkdtemp(path.join(os.tmpdir(), 'frontmcp-install-'));\n const bundlePath = path.join(tempDir, 'entry.cjs');\n try {\n const esbuild = require('esbuild') as typeof import('esbuild');\n await esbuild.build({\n entryPoints: [entry],\n bundle: true,\n write: true,\n outfile: bundlePath,\n platform: 'node',\n format: 'cjs',\n target: 'es2022',\n packages: 'external',\n sourcemap: false,\n logLevel: 'silent',\n absWorkingDir: cwd,\n });\n const { extractSchemas } = await import('../build/exec/cli-runtime/schema-extractor.js');\n const schema = await extractSchemas(bundlePath);\n const skills: PluginEmitterSkillInput[] = (schema.skillAssets ?? []).map((entry) => ({\n name: entry.skillName,\n description: entry.description ?? `${entry.skillName} skill`,\n tags: entry.tags,\n license: entry.license,\n instructionFile: entry.instructionFile,\n resourceDirs: entry.resourceDirs,\n }));\n const commands: PluginEmitterCommandInput[] = (schema.prompts ?? []).map((p) => ({\n name: p.name,\n description: p.description,\n arguments: p.arguments,\n }));\n return { skills, commands };\n } finally {\n await rm(tempDir, { recursive: true, force: true }).catch(() => undefined);\n }\n } catch (err) {\n // Drop the cache entry so the NEXT install from this cwd retries\n // extraction — without this, a transient esbuild/SDK-boot failure\n // would freeze the project at `{ skills: [], commands: [] }` for\n // the rest of the process even after the user fixes the entry.\n // The current caller still gets the empty fallback so the install\n // itself doesn't crash.\n cachedExtractionByCwd.delete(cwd);\n process.stderr.write(\n c(\n 'yellow',\n `[install] could not enumerate skills/prompts from project (${(err as Error).message}); ` +\n `emitting plugin without them. Use the per-bin install (\\`<bin> install -p claude\\`) ` +\n `after \\`frontmcp build:exec\\` if you need the full set.\\n`,\n ),\n );\n return { skills: [], commands: [] };\n }\n })();\n // Set BEFORE awaiting so concurrent callers within the same install\n // (collectSkillsFromProject + collectPromptsFromProject) share the\n // single bundle+extract pass. The catch above un-sets on failure.\n cachedExtractionByCwd.set(cwd, extractionPromise);\n return extractionPromise;\n}\n\nasync function collectSkillsFromProject(): Promise<PluginEmitterSkillInput[]> {\n return (await getProjectExtraction(process.cwd())).skills;\n}\n\nasync function collectPromptsFromProject(): Promise<PluginEmitterCommandInput[]> {\n return (await getProjectExtraction(process.cwd())).commands;\n}\n\nasync function getCliVersion(): Promise<string> {\n try {\n const { readJSON } = await import('@frontmcp/utils');\n const pkg = (await readJSON(path.join(__dirname, '..', '..', '..', 'package.json'))) as { version?: string };\n return pkg.version ?? '0.0.0';\n } catch {\n return '0.0.0';\n }\n}\n\nasync function runClaudeInstall(args: {\n projectMeta: ProjectMeta;\n opts: InstallOptions;\n cliVersion: string;\n skills: PluginEmitterSkillInput[];\n commands: PluginEmitterCommandInput[];\n}): Promise<void> {\n const destRoot = resolveDestRoot(args.opts);\n const emitOpts: EmitClaudePluginOptions = {\n destRoot,\n name: args.projectMeta.name,\n version: args.projectMeta.version,\n description: args.projectMeta.description,\n mcpCommand: args.opts.command ?? args.projectMeta.name,\n mcpArgs: ['serve', '--stdio'],\n envHints: args.opts.env ?? [],\n skills: args.skills,\n commands: args.commands,\n cliVersion: args.cliVersion,\n dryRun: args.opts.dryRun,\n };\n\n const result = await emitClaudePlugin(emitOpts);\n\n if (args.opts.dryRun) {\n process.stdout.write(`${c('cyan', '[install:claude] dry-run plan')}\\n`);\n process.stdout.write(` pluginDir: ${result.pluginDir}\\n`);\n process.stdout.write(` manifest: ${JSON.stringify(result.manifest, null, 2)}\\n`);\n process.stdout.write(` filesWritten (planned):\\n`);\n for (const f of result.filesWritten) process.stdout.write(` + ${f}\\n`);\n return;\n }\n\n process.stdout.write(\n c(\n 'green',\n `✓ Wrote ${result.pluginDir}/ (${args.skills.length} skills, ${args.commands.length} commands, 1 MCP server)\\n`,\n ),\n );\n if (result.filesRemoved.length > 0) {\n process.stdout.write(` Cleaned up ${result.filesRemoved.length} stale file(s) from previous install.\\n`);\n }\n // Surface the dev-tool limitation explicitly: today this command only\n // emits the MCP server entry; per-project @Skill / @Prompt enumeration\n // is wired through the per-bin path (`<bin> install -p claude`). Don't\n // let users assume zero counts mean \"your project has no skills\".\n if (\n args.skills.length === 0 &&\n args.commands.length === 0 &&\n args.opts.skills !== false &&\n args.opts.commands !== false &&\n !args.opts.onlyMcp\n ) {\n process.stdout.write(\n c(\n 'yellow',\n ' Note: the dev-tool path does not yet enumerate @Skill or @Prompt from project source.\\n' +\n ' Build with `frontmcp build --target cli` and run `<bin> install -p claude` for full plugin coverage.\\n',\n ),\n );\n }\n process.stdout.write(c('dim', ' Restart Claude Code (or run `/plugins reload`) to pick up the plugin.\\n'));\n}\n\nasync function runCodexInstall(args: {\n projectMeta: ProjectMeta;\n opts: InstallOptions;\n cliVersion: string;\n}): Promise<void> {\n void args.cliVersion;\n const configPath = path.join(os.homedir(), '.codex', 'config.toml');\n const env: Record<string, string> = {};\n for (const name of args.opts.env ?? []) env[name] = `\\${${name}}`;\n const result = await emitCodexEntry({\n configPath,\n name: args.projectMeta.name,\n command: args.opts.command ?? args.projectMeta.name,\n args: ['serve', '--stdio'],\n env,\n dryRun: args.opts.dryRun,\n });\n\n if (args.opts.dryRun) {\n process.stdout.write(`${c('cyan', '[install:codex] dry-run plan')}\\n`);\n process.stdout.write(` configPath: ${configPath}\\n`);\n process.stdout.write(` configContent:\\n${indentBlock(result.configContent, 4)}\\n`);\n return;\n }\n\n process.stdout.write(c('green', `✓ Updated ${configPath} with [[mcp_servers]] entry for ${args.projectMeta.name}\\n`));\n}\n\nasync function printStatus(projectMeta: ProjectMeta, opts: InstallOptions): Promise<void> {\n process.stdout.write(`${c('bold', `${projectMeta.name} install --status`)}\\n`);\n if (opts.claudePlugin || !opts.codex) {\n const destRoot = resolveDestRoot(opts);\n const pluginDir = path.join(destRoot, projectMeta.name);\n const installed = await readInstalledPluginVersion(pluginDir);\n if (installed) {\n const tag = installed === projectMeta.version ? 'installed' : 'outdated';\n process.stdout.write(\n ` claude: ${tag} v${installed}${tag === 'outdated' ? ` (project at v${projectMeta.version})` : ''} at ${pluginDir}\\n`,\n );\n } else {\n process.stdout.write(` claude: not installed at ${pluginDir}\\n`);\n }\n }\n if (opts.codex) {\n const configPath = path.join(os.homedir(), '.codex', 'config.toml');\n const { fileExists, readFile } = await import('@frontmcp/utils');\n if (!(await fileExists(configPath))) {\n process.stdout.write(` codex: not installed (${configPath} does not exist)\\n`);\n } else {\n const content = await readFile(configPath);\n const present = content.includes(`# frontmcp:codex-start:${projectMeta.name}`);\n process.stdout.write(\n present\n ? ` codex: installed entry for ${projectMeta.name} in ${configPath}\\n`\n : ` codex: not installed in ${configPath}\\n`,\n );\n }\n }\n}\n\nexport async function runUninstallCurrentProject(rawOpts: Record<string, unknown>): Promise<void> {\n const opts = normalizeOptions(rawOpts);\n\n if (!opts.claudePlugin && !opts.codex) {\n process.stderr.write(c('red', 'Specify --claude and/or --codex.\\n'));\n process.exit(1);\n }\n\n const projectMeta = await loadProjectMeta();\n if (!projectMeta) {\n process.stderr.write(c('red', 'No frontmcp.config found in this directory.\\n'));\n process.exit(1);\n }\n\n if (opts.claudePlugin) {\n const destRoot = resolveDestRoot(opts);\n const result = await removeClaudePlugin({ destRoot, name: projectMeta.name });\n if (result.removed.length === 0) {\n process.stdout.write(c('dim', ` claude: nothing to remove at ${result.pluginDir}\\n`));\n } else {\n process.stdout.write(c('green', `✓ Removed ${result.removed.length} file(s) from ${result.pluginDir}\\n`));\n }\n }\n if (opts.codex) {\n const configPath = path.join(os.homedir(), '.codex', 'config.toml');\n const result = await removeCodexEntry({ configPath, name: projectMeta.name });\n if (result.removed) {\n process.stdout.write(c('green', `✓ Removed [[mcp_servers]] entry for ${projectMeta.name} from ${configPath}\\n`));\n } else {\n process.stdout.write(c('dim', ` codex: no entry for ${projectMeta.name} in ${configPath}\\n`));\n }\n }\n}\n\nfunction resolveDestRoot(opts: InstallOptions): string {\n if (opts.dir) return path.resolve(opts.dir);\n if (opts.scope === 'user') return path.join(os.homedir(), '.claude', 'plugins');\n return path.join(process.cwd(), '.claude', 'plugins');\n}\n\nfunction normalizeOptions(raw: Record<string, unknown>): InstallOptions {\n // Fail fast on bogus `--scope` values rather than silently coercing them to\n // 'project' — `--scope usr` would otherwise write files to the wrong root\n // (cwd's `.claude/plugins/` instead of `~/.claude/plugins/`) with no signal.\n const rawScope = raw['scope'];\n if (rawScope !== undefined && rawScope !== 'project' && rawScope !== 'user') {\n throw new Error(`Invalid --scope value: ${String(rawScope)}. Expected \"project\" or \"user\".`);\n }\n const scope: InstallOptions['scope'] = rawScope === 'user' ? 'user' : 'project';\n return {\n claudePlugin: raw['claudePlugin'] === true,\n codex: raw['codex'] === true,\n scope,\n skills: raw['skills'] !== false,\n commands: raw['commands'] !== false,\n onlyMcp: raw['onlyMcp'] === true,\n command: typeof raw['command'] === 'string' ? (raw['command'] as string) : undefined,\n env: Array.isArray(raw['env']) ? (raw['env'] as string[]) : [],\n dir: typeof raw['dir'] === 'string' ? (raw['dir'] as string) : undefined,\n dryRun: raw['dryRun'] === true,\n status: raw['status'] === true,\n };\n}\n\nfunction indentBlock(text: string, n: number): string {\n const pad = ' '.repeat(n);\n return text\n .split('\\n')\n .map((line) => pad + line)\n .join('\\n');\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #411 — registers the dev-tool side `frontmcp plugin` command tree
|
|
3
|
+
* (uses `plugin` as the noun because `install` is already taken by the
|
|
4
|
+
* npm-package install command at `package/register.ts`).
|
|
5
|
+
*
|
|
6
|
+
* Subcommands:
|
|
7
|
+
* frontmcp plugin install — emit a Claude Code plugin folder + Codex entry
|
|
8
|
+
* frontmcp plugin uninstall — remove what `install` wrote
|
|
9
|
+
* frontmcp plugin status — report install state per provider
|
|
10
|
+
*
|
|
11
|
+
* The per-bin equivalent (auto-included in every built CLI) extends the
|
|
12
|
+
* existing `<bin> install` with `-p claude|codex` and shares the same
|
|
13
|
+
* emitter under `cli-runtime/plugin-emitter.ts`.
|
|
14
|
+
*/
|
|
15
|
+
import { type Command } from 'commander';
|
|
16
|
+
export declare function registerInstallCommands(program: Command): void;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Issue #411 — registers the dev-tool side `frontmcp plugin` command tree
|
|
4
|
+
* (uses `plugin` as the noun because `install` is already taken by the
|
|
5
|
+
* npm-package install command at `package/register.ts`).
|
|
6
|
+
*
|
|
7
|
+
* Subcommands:
|
|
8
|
+
* frontmcp plugin install — emit a Claude Code plugin folder + Codex entry
|
|
9
|
+
* frontmcp plugin uninstall — remove what `install` wrote
|
|
10
|
+
* frontmcp plugin status — report install state per provider
|
|
11
|
+
*
|
|
12
|
+
* The per-bin equivalent (auto-included in every built CLI) extends the
|
|
13
|
+
* existing `<bin> install` with `-p claude|codex` and shares the same
|
|
14
|
+
* emitter under `cli-runtime/plugin-emitter.ts`.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.registerInstallCommands = registerInstallCommands;
|
|
18
|
+
/**
|
|
19
|
+
* Attach the shared plugin-flag surface to a subcommand. `install`,
|
|
20
|
+
* `uninstall`, and `status` all accept the same flag set so docs +
|
|
21
|
+
* tests can rely on a uniform invocation contract (issue #411). The
|
|
22
|
+
* dry-run / skip-tree / mcp-override flags are no-ops on `status` but
|
|
23
|
+
* keeping the surface uniform avoids `Unknown option` errors when the
|
|
24
|
+
* caller scripts the three subcommands together.
|
|
25
|
+
*/
|
|
26
|
+
function withSharedPluginFlags(cmd, scopeDescription) {
|
|
27
|
+
return cmd
|
|
28
|
+
.option('--scope <scope>', scopeDescription, 'project')
|
|
29
|
+
.option('--no-skills', 'Skip the skills/ subtree')
|
|
30
|
+
.option('--no-commands', 'Skip the commands/ subtree')
|
|
31
|
+
.option('--only-mcp', 'Skip the plugin folder; just register the MCP server')
|
|
32
|
+
.option('--command <cmd>', 'Override the MCP server invocation in the plugin manifest')
|
|
33
|
+
.option('--env <name>', 'Env-var placeholder to surface on the plugin (repeatable)', (v, acc) => [...acc, v], [])
|
|
34
|
+
.option('--dir <dir>', 'Override the plugin destination root')
|
|
35
|
+
.option('--dry-run', 'Print the planned tree and exit without writing');
|
|
36
|
+
}
|
|
37
|
+
function registerInstallCommands(program) {
|
|
38
|
+
const plugin = program
|
|
39
|
+
.command('plugin')
|
|
40
|
+
.description('Install the current FrontMCP server as a plugin for an AI tool');
|
|
41
|
+
withSharedPluginFlags(plugin
|
|
42
|
+
.command('install')
|
|
43
|
+
.description('Emit a Claude Code plugin folder and/or Codex mcp_servers entry from the current project')
|
|
44
|
+
.option('--claude', 'Emit a Claude Code plugin into <scope>/.claude/plugins/<name>/')
|
|
45
|
+
.option('--codex', 'Emit a Codex mcp_servers entry into ~/.codex/config.toml'), 'project | user (default: project)').action(async (opts) => {
|
|
46
|
+
// Map flag name to the legacy internal option name expected by the runner.
|
|
47
|
+
const mappedOpts = { ...opts, claudePlugin: opts['claude'] === true };
|
|
48
|
+
const { runInstallCurrentProject } = await import('./install-claude-plugin.js');
|
|
49
|
+
await runInstallCurrentProject(mappedOpts);
|
|
50
|
+
});
|
|
51
|
+
withSharedPluginFlags(plugin
|
|
52
|
+
.command('uninstall')
|
|
53
|
+
.description('Remove what `frontmcp plugin install` previously wrote')
|
|
54
|
+
.option('--claude', 'Remove the Claude Code plugin folder')
|
|
55
|
+
.option('--codex', 'Remove the Codex mcp_servers entry'), 'project | user (default: project)').action(async (opts) => {
|
|
56
|
+
const mappedOpts = { ...opts, claudePlugin: opts['claude'] === true };
|
|
57
|
+
const { runUninstallCurrentProject } = await import('./install-claude-plugin.js');
|
|
58
|
+
await runUninstallCurrentProject(mappedOpts);
|
|
59
|
+
});
|
|
60
|
+
withSharedPluginFlags(plugin
|
|
61
|
+
.command('status')
|
|
62
|
+
.description('Report install state per provider')
|
|
63
|
+
.option('--claude', 'Check Claude Code plugin state')
|
|
64
|
+
.option('--codex', 'Check Codex mcp_servers entry'), 'project | user (default: project)').action(async (opts) => {
|
|
65
|
+
const mappedOpts = { ...opts, claudePlugin: opts['claude'] === true, status: true };
|
|
66
|
+
const { runInstallCurrentProject } = await import('./install-claude-plugin.js');
|
|
67
|
+
await runInstallCurrentProject(mappedOpts);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../../../src/commands/install/register.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AA6BH,0DA4CC;AArED;;;;;;;GAOG;AACH,SAAS,qBAAqB,CAAC,GAAY,EAAE,gBAAwB;IACnE,OAAO,GAAG;SACP,MAAM,CAAC,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,CAAC;SACtD,MAAM,CAAC,aAAa,EAAE,0BAA0B,CAAC;SACjD,MAAM,CAAC,eAAe,EAAE,4BAA4B,CAAC;SACrD,MAAM,CAAC,YAAY,EAAE,sDAAsD,CAAC;SAC5E,MAAM,CAAC,iBAAiB,EAAE,2DAA2D,CAAC;SACtF,MAAM,CACL,cAAc,EACd,2DAA2D,EAC3D,CAAC,CAAS,EAAE,GAAa,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,EACzC,EAAc,CACf;SACA,MAAM,CAAC,aAAa,EAAE,sCAAsC,CAAC;SAC7D,MAAM,CAAC,WAAW,EAAE,iDAAiD,CAAC,CAAC;AAC5E,CAAC;AAED,SAAgB,uBAAuB,CAAC,OAAgB;IACtD,MAAM,MAAM,GAAG,OAAO;SACnB,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,gEAAgE,CAAC,CAAC;IAEjF,qBAAqB,CACnB,MAAM;SACH,OAAO,CAAC,SAAS,CAAC;SAClB,WAAW,CAAC,0FAA0F,CAAC;SACvG,MAAM,CAAC,UAAU,EAAE,gEAAgE,CAAC;SACpF,MAAM,CAAC,SAAS,EAAE,0DAA0D,CAAC,EAChF,mCAAmC,CACpC,CAAC,MAAM,CAAC,KAAK,EAAE,IAA6B,EAAE,EAAE;QAC/C,2EAA2E;QAC3E,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC;QACtE,MAAM,EAAE,wBAAwB,EAAE,GAAG,MAAM,MAAM,CAAC,4BAA4B,CAAC,CAAC;QAChF,MAAM,wBAAwB,CAAC,UAAU,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,qBAAqB,CACnB,MAAM;SACH,OAAO,CAAC,WAAW,CAAC;SACpB,WAAW,CAAC,wDAAwD,CAAC;SACrE,MAAM,CAAC,UAAU,EAAE,sCAAsC,CAAC;SAC1D,MAAM,CAAC,SAAS,EAAE,oCAAoC,CAAC,EAC1D,mCAAmC,CACpC,CAAC,MAAM,CAAC,KAAK,EAAE,IAA6B,EAAE,EAAE;QAC/C,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC;QACtE,MAAM,EAAE,0BAA0B,EAAE,GAAG,MAAM,MAAM,CAAC,4BAA4B,CAAC,CAAC;QAClF,MAAM,0BAA0B,CAAC,UAAU,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,qBAAqB,CACnB,MAAM;SACH,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,mCAAmC,CAAC;SAChD,MAAM,CAAC,UAAU,EAAE,gCAAgC,CAAC;SACpD,MAAM,CAAC,SAAS,EAAE,+BAA+B,CAAC,EACrD,mCAAmC,CACpC,CAAC,MAAM,CAAC,KAAK,EAAE,IAA6B,EAAE,EAAE;QAC/C,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QACpF,MAAM,EAAE,wBAAwB,EAAE,GAAG,MAAM,MAAM,CAAC,4BAA4B,CAAC,CAAC;QAChF,MAAM,wBAAwB,CAAC,UAAU,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Issue #411 — registers the dev-tool side `frontmcp plugin` command tree\n * (uses `plugin` as the noun because `install` is already taken by the\n * npm-package install command at `package/register.ts`).\n *\n * Subcommands:\n * frontmcp plugin install — emit a Claude Code plugin folder + Codex entry\n * frontmcp plugin uninstall — remove what `install` wrote\n * frontmcp plugin status — report install state per provider\n *\n * The per-bin equivalent (auto-included in every built CLI) extends the\n * existing `<bin> install` with `-p claude|codex` and shares the same\n * emitter under `cli-runtime/plugin-emitter.ts`.\n */\n\nimport { type Command } from 'commander';\n\n/**\n * Attach the shared plugin-flag surface to a subcommand. `install`,\n * `uninstall`, and `status` all accept the same flag set so docs +\n * tests can rely on a uniform invocation contract (issue #411). The\n * dry-run / skip-tree / mcp-override flags are no-ops on `status` but\n * keeping the surface uniform avoids `Unknown option` errors when the\n * caller scripts the three subcommands together.\n */\nfunction withSharedPluginFlags(cmd: Command, scopeDescription: string): Command {\n return cmd\n .option('--scope <scope>', scopeDescription, 'project')\n .option('--no-skills', 'Skip the skills/ subtree')\n .option('--no-commands', 'Skip the commands/ subtree')\n .option('--only-mcp', 'Skip the plugin folder; just register the MCP server')\n .option('--command <cmd>', 'Override the MCP server invocation in the plugin manifest')\n .option(\n '--env <name>',\n 'Env-var placeholder to surface on the plugin (repeatable)',\n (v: string, acc: string[]) => [...acc, v],\n [] as string[],\n )\n .option('--dir <dir>', 'Override the plugin destination root')\n .option('--dry-run', 'Print the planned tree and exit without writing');\n}\n\nexport function registerInstallCommands(program: Command): void {\n const plugin = program\n .command('plugin')\n .description('Install the current FrontMCP server as a plugin for an AI tool');\n\n withSharedPluginFlags(\n plugin\n .command('install')\n .description('Emit a Claude Code plugin folder and/or Codex mcp_servers entry from the current project')\n .option('--claude', 'Emit a Claude Code plugin into <scope>/.claude/plugins/<name>/')\n .option('--codex', 'Emit a Codex mcp_servers entry into ~/.codex/config.toml'),\n 'project | user (default: project)',\n ).action(async (opts: Record<string, unknown>) => {\n // Map flag name to the legacy internal option name expected by the runner.\n const mappedOpts = { ...opts, claudePlugin: opts['claude'] === true };\n const { runInstallCurrentProject } = await import('./install-claude-plugin.js');\n await runInstallCurrentProject(mappedOpts);\n });\n\n withSharedPluginFlags(\n plugin\n .command('uninstall')\n .description('Remove what `frontmcp plugin install` previously wrote')\n .option('--claude', 'Remove the Claude Code plugin folder')\n .option('--codex', 'Remove the Codex mcp_servers entry'),\n 'project | user (default: project)',\n ).action(async (opts: Record<string, unknown>) => {\n const mappedOpts = { ...opts, claudePlugin: opts['claude'] === true };\n const { runUninstallCurrentProject } = await import('./install-claude-plugin.js');\n await runUninstallCurrentProject(mappedOpts);\n });\n\n withSharedPluginFlags(\n plugin\n .command('status')\n .description('Report install state per provider')\n .option('--claude', 'Check Claude Code plugin state')\n .option('--codex', 'Check Codex mcp_servers entry'),\n 'project | user (default: project)',\n ).action(async (opts: Record<string, unknown>) => {\n const mappedOpts = { ...opts, claudePlugin: opts['claude'] === true, status: true };\n const { runInstallCurrentProject } = await import('./install-claude-plugin.js');\n await runInstallCurrentProject(mappedOpts);\n });\n}\n"]}
|
|
@@ -77,6 +77,47 @@ async function scaffoldFileIfMissing(baseDir, p, content) {
|
|
|
77
77
|
await (0, utils_1.writeFile)(p, content.replace(/^\n/, ''));
|
|
78
78
|
console.log((0, colors_1.c)('green', `✓ created ${path.relative(baseDir, p)}`));
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Render a starter `frontmcp.config.ts` for the chosen deployment target
|
|
82
|
+
* (issue #400). The emitted file is consumed by `dev`, `test`, `inspector`,
|
|
83
|
+
* `pm start/socket`, and `skills install/export` — see the matching
|
|
84
|
+
* `deployment/frontmcp-config` docs page for the full surface.
|
|
85
|
+
*/
|
|
86
|
+
function renderFrontmcpConfigTemplate(projectName, deploymentTarget) {
|
|
87
|
+
const safeName = sanitizeForFolder(projectName);
|
|
88
|
+
// Every value of `DeploymentTarget` ('node' | 'vercel' | 'lambda' |
|
|
89
|
+
// 'cloudflare') ships HTTP, so the scaffold only ever emits the HTTP
|
|
90
|
+
// client + transport blocks. The stdio variant lived here briefly but
|
|
91
|
+
// was unreachable — reintroduce it (and add a 'desktop'/'cli' target to
|
|
92
|
+
// `DeploymentTarget`) if scaffold should support stdio in the future.
|
|
93
|
+
const port = 3000;
|
|
94
|
+
const clientBlock = ` clients: {
|
|
95
|
+
'claude-code': {
|
|
96
|
+
name: '${safeName}',
|
|
97
|
+
transport: 'http',
|
|
98
|
+
url: 'http://127.0.0.1:${port}/mcp',
|
|
99
|
+
},
|
|
100
|
+
},`;
|
|
101
|
+
const transportBlock = ` transport: { default: 'http', http: { port: ${port}, path: '/mcp' } },`;
|
|
102
|
+
return `import { defineConfig } from 'frontmcp';
|
|
103
|
+
|
|
104
|
+
// Single source of truth for every \`frontmcp\` CLI command (dev / test /
|
|
105
|
+
// inspector / pm start / socket / skills install / export). Override any
|
|
106
|
+
// field with an explicit CLI flag or the matching \`FRONTMCP_<NAME>\` env
|
|
107
|
+
// var — precedence: CLI > env > config > built-in default.
|
|
108
|
+
export default defineConfig({
|
|
109
|
+
name: '${safeName}',
|
|
110
|
+
entry: './src/main.ts',
|
|
111
|
+
deployments: [{ target: '${deploymentTarget}' }],
|
|
112
|
+
${transportBlock}
|
|
113
|
+
env: {
|
|
114
|
+
shared: {},
|
|
115
|
+
dev: { NODE_ENV: 'development' },
|
|
116
|
+
},
|
|
117
|
+
${clientBlock}
|
|
118
|
+
});
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
80
121
|
const TEMPLATE_MAIN_TS = `
|
|
81
122
|
import 'reflect-metadata';
|
|
82
123
|
import { FrontMcp } from '@frontmcp/sdk';
|
|
@@ -1444,6 +1485,9 @@ async function scaffoldProject(options) {
|
|
|
1444
1485
|
await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'src', 'main.ts'), TEMPLATE_MAIN_TS);
|
|
1445
1486
|
await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'src', 'calc.app.ts'), TEMPLATE_CALC_APP_TS);
|
|
1446
1487
|
await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'src', 'tools', 'add.tool.ts'), TEMPLATE_ADD_TOOL_TS);
|
|
1488
|
+
// Issue #400 — emit a starter frontmcp.config.ts so subsequent CLI runs
|
|
1489
|
+
// pick up entry / port / env / client snippets without re-typing them.
|
|
1490
|
+
await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'frontmcp.config.ts'), renderFrontmcpConfigTemplate(projectName, deploymentTarget));
|
|
1447
1491
|
// E2E scaffolding (jest config auto-generated by `frontmcp test`)
|
|
1448
1492
|
await scaffoldFileIfMissing(targetDir, path.join(targetDir, 'e2e', 'server.e2e.spec.ts'), TEMPLATE_E2E_TEST_TS);
|
|
1449
1493
|
// Skills scaffolding
|